rustima 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {rustima-0.1.0 → rustima-0.2.0}/Cargo.lock +1 -1
- {rustima-0.1.0 → rustima-0.2.0}/Cargo.toml +4 -2
- {rustima-0.1.0 → rustima-0.2.0}/PKG-INFO +2 -1
- {rustima-0.1.0 → rustima-0.2.0}/benches/bench_fit.rs +2 -2
- {rustima-0.1.0 → rustima-0.2.0}/benches/bench_kalman.rs +5 -5
- rustima-0.2.0/lbfgsb_c/License.txt +60 -0
- {rustima-0.1.0 → rustima-0.2.0}/lbfgsb_c/lbfgsb.h +6 -0
- {rustima-0.1.0 → rustima-0.2.0}/pyproject.toml +10 -2
- {rustima-0.1.0 → rustima-0.2.0}/python/rustima/__init__.py +7 -2
- {rustima-0.1.0 → rustima-0.2.0}/python/rustima/model.py +225 -0
- {rustima-0.1.0 → rustima-0.2.0}/python_tests/conftest.py +6 -2
- {rustima-0.1.0 → rustima-0.2.0}/python_tests/generate_fixtures.py +33 -7
- {rustima-0.1.0 → rustima-0.2.0}/python_tests/generate_matrix_fixtures.py +5 -1
- {rustima-0.1.0 → rustima-0.2.0}/python_tests/test_exog.py +19 -16
- rustima-0.2.0/python_tests/test_extend.py +286 -0
- {rustima-0.1.0 → rustima-0.2.0}/python_tests/test_fit.py +11 -4
- {rustima-0.1.0 → rustima-0.2.0}/python_tests/test_inference.py +10 -10
- {rustima-0.1.0 → rustima-0.2.0}/python_tests/test_input_validation.py +6 -6
- {rustima-0.1.0 → rustima-0.2.0}/python_tests/test_multi_order_accuracy.py +1 -1
- rustima-0.2.0/python_tests/test_rolling.py +229 -0
- {rustima-0.1.0 → rustima-0.2.0}/python_tests/test_safety_guards.py +10 -3
- {rustima-0.1.0 → rustima-0.2.0}/python_tests/test_simple_diff.py +1 -1
- {rustima-0.1.0 → rustima-0.2.0}/python_tests/test_trend.py +10 -10
- rustima-0.2.0/python_tests/test_variance_parity.py +137 -0
- {rustima-0.1.0 → rustima-0.2.0}/src/batch.rs +5 -2
- {rustima-0.1.0 → rustima-0.2.0}/src/forecast.rs +207 -14
- {rustima-0.1.0 → rustima-0.2.0}/src/kalman.rs +70 -2
- {rustima-0.1.0 → rustima-0.2.0}/src/lib.rs +70 -0
- {rustima-0.1.0 → rustima-0.2.0}/src/optimizer.rs +26 -10
- {rustima-0.1.0 → rustima-0.2.0}/src/params.rs +5 -4
- {rustima-0.1.0 → rustima-0.2.0}/src/score.rs +70 -11
- {rustima-0.1.0 → rustima-0.2.0}/src/start_params.rs +4 -4
- {rustima-0.1.0 → rustima-0.2.0}/src/types.rs +6 -1
- {rustima-0.1.0 → rustima-0.2.0}/tests/fixtures/matrix_tier_a.json +60 -30
- {rustima-0.1.0 → rustima-0.2.0}/tests/fixtures/matrix_tier_b.json +138 -69
- rustima-0.2.0/tests/fixtures/statsmodels_forecast_reference.json +1081 -0
- {rustima-0.1.0 → rustima-0.2.0}/tests/fixtures/statsmodels_reference.json +10 -5
- rustima-0.1.0/.github/workflows/ci.yml +0 -64
- rustima-0.1.0/.github/workflows/nightly.yml +0 -83
- rustima-0.1.0/.github/workflows/release.yml +0 -242
- rustima-0.1.0/BENCHMARK_SPEC.md +0 -219
- rustima-0.1.0/benchmark_comprehensive.py +0 -152
- rustima-0.1.0/benchmark_higher_order.py +0 -178
- rustima-0.1.0/compare_all_orders.py +0 -240
- rustima-0.1.0/debug_loglike.py +0 -672
- rustima-0.1.0/debug_mismatch.py +0 -268
- rustima-0.1.0/docs/param_compare_2019.md +0 -105
- rustima-0.1.0/docs/ver5.2/spec_numerical_hessian.md +0 -257
- rustima-0.1.0/docs/ver5.2/spec_opg.md +0 -299
- rustima-0.1.0/docs/ver5.2/spec_simple_differencing.md +0 -551
- rustima-0.1.0/fit_summary_report.md +0 -335
- rustima-0.1.0/python_tests/bench_batch_forecast.py +0 -223
- rustima-0.1.0/python_tests/bench_comparison.py +0 -201
- rustima-0.1.0/python_tests/bench_full_comparison.py +0 -377
- rustima-0.1.0/python_tests/bench_grid_5x5.py +0 -433
- rustima-0.1.0/python_tests/bench_matrix_6x6_s7_s12_s24.py +0 -206
- rustima-0.1.0/python_tests/bench_matrix_parent.py +0 -190
- rustima-0.1.0/python_tests/bench_matrix_worker.py +0 -119
- rustima-0.1.0/python_tests/bench_memory_speed.py +0 -342
- rustima-0.1.0/python_tests/bench_pmdarima_compare.py +0 -165
- rustima-0.1.0/python_tests/bench_power_2019_2023.py +0 -220
- rustima-0.1.0/python_tests/bench_readme.py +0 -338
- rustima-0.1.0/python_tests/bench_report_arima.py +0 -278
- rustima-0.1.0/python_tests/bench_report_arimax.py +0 -270
- rustima-0.1.0/python_tests/bench_report_sarima.py +0 -317
- rustima-0.1.0/python_tests/bench_report_sarimax.py +0 -317
- rustima-0.1.0/python_tests/bench_s24_highorder.py +0 -259
- rustima-0.1.0/python_tests/bench_sarima_plot.py +0 -156
- rustima-0.1.0/python_tests/bench_sarima_retry_pmdarima_1y.py +0 -127
- rustima-0.1.0/python_tests/bench_sarima_scaling.py +0 -251
- rustima-0.1.0/python_tests/bench_sarima_worker.py +0 -136
- rustima-0.1.0/python_tests/bench_v5_report.py +0 -604
- rustima-0.1.0/python_tests/benchmark_vs_statsmodels.py +0 -433
- rustima-0.1.0/python_tests/build_ppt_pmdarima.py +0 -234
- rustima-0.1.0/python_tests/compare_profile_methods_2019.py +0 -136
- rustima-0.1.0/python_tests/compare_profile_methods_R.R +0 -54
- rustima-0.1.0/python_tests/compare_with_r.R +0 -73
- rustima-0.1.0/python_tests/diagnose_profile_tr_plateau.py +0 -93
- rustima-0.1.0/python_tests/gen_tex_report.py +0 -499
- rustima-0.1.0/python_tests/phase_c_power_auto.py +0 -139
- rustima-0.1.0/python_tests/phase_c_robustness.py +0 -150
- rustima-0.1.0/python_tests/report_convergence_failures.py +0 -301
- rustima-0.1.0/python_tests/run_fit_summary.py +0 -331
- rustima-0.1.0/python_tests/show_matrix_coefs.py +0 -78
- rustima-0.1.0/python_tests/verify_ll_at_same_params.py +0 -91
- rustima-0.1.0/python_tests/verify_sarima_vs_sarimax.py +0 -86
- rustima-0.1.0/result_v1/00_SUMMARY.md +0 -105
- rustima-0.1.0/result_v1/01_ARIMA_comparison.md +0 -51
- rustima-0.1.0/result_v1/02_SARIMA_s12_comparison.md +0 -192
- rustima-0.1.0/result_v1/02_SARIMA_s24_comparison.md +0 -192
- rustima-0.1.0/result_v1/02_SARIMA_s7_comparison.md +0 -192
- rustima-0.1.0/result_v1/03_ARIMAX_comparison.md +0 -50
- rustima-0.1.0/result_v1/04_SARIMAX_s12_comparison.md +0 -192
- rustima-0.1.0/result_v1/04_SARIMAX_s24_comparison.md +0 -192
- rustima-0.1.0/result_v1/04_SARIMAX_s7_comparison.md +0 -192
- rustima-0.1.0/result_v1/05_auto_arima_benchmark.md +0 -129
- rustima-0.1.0/result_v1/bench_all_models.py +0 -397
- rustima-0.1.0/result_v1/bench_auto_arima.py +0 -435
- rustima-0.1.0/test.ipynb +0 -2108
- rustima-0.1.0/tests/fixtures/statsmodels_forecast_reference.json +0 -1077
- {rustima-0.1.0 → rustima-0.2.0}/.env.example +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/.gitignore +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/LICENSE +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/README.md +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/build.rs +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/docs/PERF_DIAGNOSIS.md +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/docs/SUPPORTED_COMBINATIONS.md +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/docs/api_reference.md +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/docs/error_codes.md +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/docs/migration_guide.md +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/docs/profiled_kalman_gls_plan.md +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/docs/statsmodels_compat.md +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/docs/ver5_convergence_improvement.md +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/examples/bench_comprehensive.py +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/examples/bench_vs_statsmodels.py +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/examples/high_order_bench.rs +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/examples/sarimax_exog_demo.py +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/examples/sarimax_hourly_s24.py +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/lbfgsb_c/lbfgsb.c +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/lbfgsb_c/linesearch.c +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/lbfgsb_c/linpack.c +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/lbfgsb_c/miniCBLAS.c +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/lbfgsb_c/print.c +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/lbfgsb_c/subalgorithms.c +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/lbfgsb_c/timer.c +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/python/rustima/auto.py +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/python_tests/test_auto.py +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/python_tests/test_batch.py +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/python_tests/test_forecast.py +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/python_tests/test_high_order_accuracy.py +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/python_tests/test_matrix_tier_a.py +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/python_tests/test_polars.py +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/python_tests/test_prediction_quality.py +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/python_tests/test_profile_trust_region.py +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/python_tests/test_smoke.py +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/src/css.rs +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/src/error.rs +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/src/inference.rs +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/src/initialization.rs +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/src/lbfgsb_ffi.rs +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/src/lbfgsb_wrapper.rs +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/src/pipeline.rs +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/src/polynomial.rs +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/src/state_space.rs +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/src/test_helpers.rs +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/tests/fixtures/.gitkeep +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/tests/fixtures/statsmodels_fit_reference.json +0 -0
- {rustima-0.1.0 → rustima-0.2.0}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "rustima"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
rust-version = "1.83"
|
|
6
6
|
description = "High-performance SARIMAX engine with PyO3 bindings"
|
|
@@ -27,7 +27,9 @@ statrs = "0.18"
|
|
|
27
27
|
rayon = "1.10"
|
|
28
28
|
|
|
29
29
|
# Python bindings
|
|
30
|
-
|
|
30
|
+
# auto-initialize 제거(2026-07-11): 크레이트 내 Python::with_gil 사용처 0 — 임베딩 불필요.
|
|
31
|
+
# 켜두면 manylinux(정적 python) 휠 빌드가 실패해 PyPI 릴리스를 막는다.
|
|
32
|
+
pyo3 = { version = "0.28" }
|
|
31
33
|
numpy = "0.28"
|
|
32
34
|
|
|
33
35
|
# Error handling
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rustima
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Intended Audience :: Science/Research
|
|
6
6
|
Classifier: Intended Audience :: Developers
|
|
@@ -13,6 +13,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
17
|
Classifier: Programming Language :: Rust
|
|
17
18
|
Classifier: Topic :: Scientific/Engineering
|
|
18
19
|
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
use criterion::{criterion_group, criterion_main, Criterion};
|
|
2
|
-
use
|
|
3
|
-
use
|
|
2
|
+
use rustima::optimizer;
|
|
3
|
+
use rustima::types::{SarimaxConfig, SarimaxOrder, Trend};
|
|
4
4
|
|
|
5
5
|
/// Deterministic LCG data generator (fixed seed → reproducible data).
|
|
6
6
|
fn generate_ar1_data(n: usize, phi: f64, seed: u64) -> Vec<f64> {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
use criterion::{criterion_group, criterion_main, Criterion};
|
|
2
|
-
use
|
|
3
|
-
use
|
|
4
|
-
use
|
|
5
|
-
use
|
|
6
|
-
use
|
|
2
|
+
use rustima::initialization::KalmanInit;
|
|
3
|
+
use rustima::kalman::kalman_loglike;
|
|
4
|
+
use rustima::params::SarimaxParams;
|
|
5
|
+
use rustima::state_space::StateSpace;
|
|
6
|
+
use rustima::types::{SarimaxConfig, SarimaxOrder, Trend};
|
|
7
7
|
|
|
8
8
|
/// Deterministic LCG data generator (fixed seed → reproducible data).
|
|
9
9
|
fn generate_ar1_data(n: usize, phi: f64, seed: u64) -> Vec<f64> {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
L-BFGS-B License
|
|
2
|
+
================
|
|
3
|
+
|
|
4
|
+
This directory contains a C translation of L-BFGS-B (version 3.0):
|
|
5
|
+
|
|
6
|
+
- Original FORTRAN L-BFGS-B: Ciyou Zhu, Richard Byrd, Jorge Nocedal,
|
|
7
|
+
Jose Luis Morales (Northwestern University).
|
|
8
|
+
- C translation (L-BFGS-B-C): Stephen Becker, 2015
|
|
9
|
+
(stephen.becker@colorado.edu), https://github.com/stephenbeckr/L-BFGS-B-C
|
|
10
|
+
|
|
11
|
+
L-BFGS-B is released under the "New BSD License" (aka "Modified BSD License"
|
|
12
|
+
or "3-clause license").
|
|
13
|
+
|
|
14
|
+
New BSD License
|
|
15
|
+
---------------
|
|
16
|
+
|
|
17
|
+
Redistribution and use in source and binary forms, with or without
|
|
18
|
+
modification, are permitted provided that the following conditions are met:
|
|
19
|
+
|
|
20
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
21
|
+
this list of conditions and the following disclaimer.
|
|
22
|
+
|
|
23
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
24
|
+
this list of conditions and the following disclaimer in the documentation
|
|
25
|
+
and/or other materials provided with the distribution.
|
|
26
|
+
|
|
27
|
+
3. Neither the name of the copyright holder nor the names of its contributors
|
|
28
|
+
may be used to endorse or promote products derived from this software
|
|
29
|
+
without specific prior written permission.
|
|
30
|
+
|
|
31
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
32
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
33
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
34
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
35
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
36
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
37
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
38
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
39
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
40
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
41
|
+
POSSIBILITY OF SUCH DAMAGE.
|
|
42
|
+
|
|
43
|
+
Citation request (from the original authors)
|
|
44
|
+
--------------------------------------------
|
|
45
|
+
|
|
46
|
+
The authors of L-BFGS-B expect that all publications describing work using
|
|
47
|
+
this software, or all commercial products using it, quote at least one of
|
|
48
|
+
the references below:
|
|
49
|
+
|
|
50
|
+
R. H. Byrd, P. Lu and J. Nocedal. A Limited Memory Algorithm for Bound
|
|
51
|
+
Constrained Optimization (1995), SIAM Journal on Scientific and
|
|
52
|
+
Statistical Computing, 16, 5, pp. 1190-1208.
|
|
53
|
+
|
|
54
|
+
C. Zhu, R. H. Byrd and J. Nocedal. Algorithm 778: L-BFGS-B, FORTRAN
|
|
55
|
+
routines for large scale bound constrained optimization (1997), ACM
|
|
56
|
+
Transactions on Mathematical Software, 23, 4, pp. 550-560.
|
|
57
|
+
|
|
58
|
+
J.L. Morales and J. Nocedal. Remark on "Algorithm 778: L-BFGS-B, FORTRAN
|
|
59
|
+
routines for large scale bound constrained optimization" (2011), ACM
|
|
60
|
+
Transactions on Mathematical Software, 38, 1.
|
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
#ifndef lbfgsb_h
|
|
6
6
|
#define lbfgsb_h
|
|
7
7
|
|
|
8
|
+
/* MSVC's C mode has no C11 _Thread_local keyword — map to its equivalent
|
|
9
|
+
* so Windows wheel builds compile. gcc/clang paths are unaffected. */
|
|
10
|
+
#if defined(_MSC_VER) && !defined(__clang__)
|
|
11
|
+
#define _Thread_local __declspec(thread)
|
|
12
|
+
#endif
|
|
13
|
+
|
|
8
14
|
|
|
9
15
|
|
|
10
16
|
/* Force 64-bit integers on all platforms (including Windows LLP64
|
|
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "rustima"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
requires-python = ">=3.10"
|
|
9
9
|
description = "High-performance SARIMAX (Seasonal ARIMA with eXogenous regressors) engine in Rust with statsmodels-compatible Python API"
|
|
10
10
|
readme = "README.md"
|
|
@@ -38,6 +38,7 @@ classifiers = [
|
|
|
38
38
|
"Programming Language :: Python :: 3.11",
|
|
39
39
|
"Programming Language :: Python :: 3.12",
|
|
40
40
|
"Programming Language :: Python :: 3.13",
|
|
41
|
+
"Programming Language :: Python :: 3.14",
|
|
41
42
|
"Programming Language :: Rust",
|
|
42
43
|
"Topic :: Scientific/Engineering",
|
|
43
44
|
"Topic :: Scientific/Engineering :: Mathematics",
|
|
@@ -72,7 +73,14 @@ notebook = [
|
|
|
72
73
|
features = ["pyo3/extension-module"]
|
|
73
74
|
module-name = "rustima"
|
|
74
75
|
python-source = "python"
|
|
75
|
-
include = ["LICENSE", "README.md"]
|
|
76
|
+
include = ["LICENSE", "README.md", "lbfgsb_c/License.txt"]
|
|
77
|
+
# sdist 위생: 벤치마크·논문 자산은 배포에서 제외 (개발용은 저장소에 유지)
|
|
78
|
+
exclude = [
|
|
79
|
+
"benchmarks/**",
|
|
80
|
+
"docs/ver5.2/**",
|
|
81
|
+
"*.ipynb",
|
|
82
|
+
"*.csv",
|
|
83
|
+
]
|
|
76
84
|
|
|
77
85
|
[tool.pytest.ini_options]
|
|
78
86
|
testpaths = ["python_tests"]
|
|
@@ -30,16 +30,21 @@ if "RAYON_NUM_THREADS" not in _os.environ:
|
|
|
30
30
|
from .rustima import * # noqa: F401,F403
|
|
31
31
|
|
|
32
32
|
# High-level Python API
|
|
33
|
-
from .model import
|
|
33
|
+
from .model import (
|
|
34
|
+
SARIMAXModel, SARIMAXResult, ForecastResult, PredictionResult,
|
|
35
|
+
RollingForecastResult,
|
|
36
|
+
)
|
|
34
37
|
from .auto import auto_arima, AutoARIMAResult
|
|
35
38
|
|
|
36
39
|
__all__ = [
|
|
37
40
|
# Rust low-level functions
|
|
38
41
|
"version",
|
|
39
|
-
"sarimax_fit", "sarimax_forecast", "
|
|
42
|
+
"sarimax_fit", "sarimax_forecast", "sarimax_rolling_forecast",
|
|
43
|
+
"sarimax_loglike", "sarimax_residuals",
|
|
40
44
|
"sarimax_batch_fit", "sarimax_batch_forecast", "sarimax_batch_loglike",
|
|
41
45
|
"sarimax_grid_search", "sarimax_inference", "sarimax_diagnostics",
|
|
42
46
|
# High-level Python API
|
|
43
47
|
"SARIMAXModel", "SARIMAXResult", "ForecastResult", "PredictionResult",
|
|
48
|
+
"RollingForecastResult",
|
|
44
49
|
"auto_arima", "AutoARIMAResult",
|
|
45
50
|
]
|
|
@@ -636,6 +636,67 @@ class SARIMAXModel:
|
|
|
636
636
|
self._fit_result = SARIMAXResult(self, result_dict)
|
|
637
637
|
return self._fit_result
|
|
638
638
|
|
|
639
|
+
def filter(self, params):
|
|
640
|
+
"""Construct a result at the given parameters WITHOUT fitting.
|
|
641
|
+
|
|
642
|
+
Runs a single Kalman-filter pass to evaluate the log-likelihood at
|
|
643
|
+
``params`` and returns a :class:`SARIMAXResult` bound to this model.
|
|
644
|
+
No optimization is performed (statsmodels ``.filter(params)``
|
|
645
|
+
semantics). This is the building block for walk-forward rolling
|
|
646
|
+
(:meth:`SARIMAXResult.extend`) and for reconstructing a result from
|
|
647
|
+
serialized parameters without re-estimation.
|
|
648
|
+
|
|
649
|
+
Parameters
|
|
650
|
+
----------
|
|
651
|
+
params : array_like
|
|
652
|
+
Full parameter vector in statsmodels layout
|
|
653
|
+
``[trend | exog | ar | ma | sar | sma | sigma2]``.
|
|
654
|
+
|
|
655
|
+
Returns
|
|
656
|
+
-------
|
|
657
|
+
SARIMAXResult
|
|
658
|
+
``result.method == "filter"``, ``converged == True``.
|
|
659
|
+
"""
|
|
660
|
+
params = np.ascontiguousarray(np.asarray(params, dtype=np.float64).ravel())
|
|
661
|
+
expected_names = _generate_param_names(
|
|
662
|
+
self.order, self.seasonal_order, self.n_exog, trend=self.trend
|
|
663
|
+
)
|
|
664
|
+
if len(params) != len(expected_names):
|
|
665
|
+
raise ValueError(
|
|
666
|
+
f"params length {len(params)} != expected {len(expected_names)} "
|
|
667
|
+
f"for this specification: {expected_names}"
|
|
668
|
+
)
|
|
669
|
+
|
|
670
|
+
llf = float(
|
|
671
|
+
rustima.sarimax_loglike(
|
|
672
|
+
self.endog,
|
|
673
|
+
self.order,
|
|
674
|
+
self.seasonal_order,
|
|
675
|
+
params,
|
|
676
|
+
**self._model_kwargs(),
|
|
677
|
+
)
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
k = len(params)
|
|
681
|
+
_p, d, _q = self.order
|
|
682
|
+
_P, D, _Q, s = self.seasonal_order
|
|
683
|
+
n_eff = self.nobs - ((d + s * D) if self.simple_differencing else 0)
|
|
684
|
+
sigma2_idx = expected_names.index("sigma2")
|
|
685
|
+
|
|
686
|
+
result_dict = {
|
|
687
|
+
"params": params,
|
|
688
|
+
"loglike": llf,
|
|
689
|
+
"scale": float(params[sigma2_idx]),
|
|
690
|
+
"aic": -2.0 * llf + 2.0 * k,
|
|
691
|
+
"bic": -2.0 * llf + k * np.log(n_eff),
|
|
692
|
+
"n_obs": n_eff,
|
|
693
|
+
"converged": True,
|
|
694
|
+
"method": "filter",
|
|
695
|
+
"n_iter": 0,
|
|
696
|
+
"n_params": k,
|
|
697
|
+
}
|
|
698
|
+
return SARIMAXResult(self, result_dict)
|
|
699
|
+
|
|
639
700
|
|
|
640
701
|
class SARIMAXResult:
|
|
641
702
|
"""Fit result wrapper (statsmodels ResultsWrapper compatible).
|
|
@@ -948,6 +1009,134 @@ class SARIMAXResult:
|
|
|
948
1009
|
"""Alias for forecast() (statsmodels compatibility)."""
|
|
949
1010
|
return self.forecast(steps=steps, alpha=alpha, exog=exog)
|
|
950
1011
|
|
|
1012
|
+
def rolling_forecast(self, start, step=1, horizon=1, alpha=0.05):
|
|
1013
|
+
"""Single-pass rolling-origin h-step forecasts (fixed parameters).
|
|
1014
|
+
|
|
1015
|
+
One Kalman-filter pass over the full sample captures the predicted
|
|
1016
|
+
state at every origin ``start, start+step, ...``; each origin's
|
|
1017
|
+
h-step forecast is propagated from its snapshot. Total cost
|
|
1018
|
+
O(T + N·horizon) — versus O(N·T) for an :meth:`extend` chain — while
|
|
1019
|
+
producing numerically identical forecasts (Markov property).
|
|
1020
|
+
|
|
1021
|
+
Origins run while ``origin <= nobs - 1``; models with exog are
|
|
1022
|
+
capped at ``nobs - horizon`` (in-sample exog must cover each
|
|
1023
|
+
forecast window). ``simple_differencing=True`` is not supported yet.
|
|
1024
|
+
|
|
1025
|
+
Parameters
|
|
1026
|
+
----------
|
|
1027
|
+
start : int
|
|
1028
|
+
First forecast origin (number of observations consumed).
|
|
1029
|
+
step : int
|
|
1030
|
+
Origin spacing (e.g. 24 for daily rolling on hourly data).
|
|
1031
|
+
horizon : int
|
|
1032
|
+
Forecast steps per origin.
|
|
1033
|
+
alpha : float
|
|
1034
|
+
CI significance level.
|
|
1035
|
+
|
|
1036
|
+
Returns
|
|
1037
|
+
-------
|
|
1038
|
+
RollingForecastResult
|
|
1039
|
+
``origins`` (N,), ``predicted_mean``/``variance``/``ci_lower``/
|
|
1040
|
+
``ci_upper`` (N, horizon).
|
|
1041
|
+
"""
|
|
1042
|
+
result = rustima.sarimax_rolling_forecast(
|
|
1043
|
+
self.model.endog,
|
|
1044
|
+
self.model.order,
|
|
1045
|
+
self.model.seasonal_order,
|
|
1046
|
+
self.params,
|
|
1047
|
+
start=start,
|
|
1048
|
+
step=step,
|
|
1049
|
+
horizon=horizon,
|
|
1050
|
+
alpha=alpha,
|
|
1051
|
+
**self._rs_kwargs(),
|
|
1052
|
+
)
|
|
1053
|
+
return RollingForecastResult(result, alpha=alpha)
|
|
1054
|
+
|
|
1055
|
+
def extend(self, endog, exog=None):
|
|
1056
|
+
"""Extend the sample with new observations, keeping parameters fixed.
|
|
1057
|
+
|
|
1058
|
+
Returns a new :class:`SARIMAXResult` whose sample is the original
|
|
1059
|
+
history plus ``endog``, filtered at the SAME parameters — no
|
|
1060
|
+
re-estimation. Subsequent :meth:`forecast` calls start after the new
|
|
1061
|
+
observations, enabling walk-forward rolling::
|
|
1062
|
+
|
|
1063
|
+
res = SARIMAXModel(train, order, seasonal_order).fit()
|
|
1064
|
+
for block in blocks:
|
|
1065
|
+
fc = res.get_forecast(steps=len(block)).predicted_mean
|
|
1066
|
+
res = res.extend(block)
|
|
1067
|
+
|
|
1068
|
+
Implementation note
|
|
1069
|
+
-------------------
|
|
1070
|
+
rustima refilters the FULL extended history with the fixed parameters
|
|
1071
|
+
(statsmodels ``append(refit=False)`` semantics). Because the Kalman
|
|
1072
|
+
filter is Markovian, post-extension forecasts are numerically
|
|
1073
|
+
equivalent to statsmodels' state-carry-over ``extend``. Unlike
|
|
1074
|
+
statsmodels ``extend``, ``llf``/``aic``/``bic`` here cover the full
|
|
1075
|
+
extended sample rather than only the new observations.
|
|
1076
|
+
|
|
1077
|
+
Parameters
|
|
1078
|
+
----------
|
|
1079
|
+
endog : array_like
|
|
1080
|
+
New observations that come AFTER the current sample.
|
|
1081
|
+
exog : array_like, optional
|
|
1082
|
+
Exogenous values for the new observations, shape
|
|
1083
|
+
``(len(endog), n_exog)``. Required iff the model has exog.
|
|
1084
|
+
|
|
1085
|
+
Returns
|
|
1086
|
+
-------
|
|
1087
|
+
SARIMAXResult
|
|
1088
|
+
"""
|
|
1089
|
+
new = np.asarray(endog, dtype=np.float64).ravel()
|
|
1090
|
+
if new.size == 0:
|
|
1091
|
+
raise ValueError("endog is empty: extend() requires at least one new observation")
|
|
1092
|
+
if not np.isfinite(new).all():
|
|
1093
|
+
raise ValueError("endog contains NaN or Inf values")
|
|
1094
|
+
|
|
1095
|
+
m = self.model
|
|
1096
|
+
if m.exog is not None:
|
|
1097
|
+
if exog is None:
|
|
1098
|
+
raise ValueError(
|
|
1099
|
+
"model was built with exog; extend() requires exog for the new observations"
|
|
1100
|
+
)
|
|
1101
|
+
ex = np.asarray(exog, dtype=np.float64)
|
|
1102
|
+
if ex.ndim == 1:
|
|
1103
|
+
ex = ex.reshape(-1, 1)
|
|
1104
|
+
if ex.shape != (new.size, m.n_exog):
|
|
1105
|
+
raise ValueError(
|
|
1106
|
+
f"exog shape {ex.shape} != expected ({new.size}, {m.n_exog})"
|
|
1107
|
+
)
|
|
1108
|
+
full_exog = np.vstack([m.exog, ex])
|
|
1109
|
+
else:
|
|
1110
|
+
if exog is not None:
|
|
1111
|
+
raise ValueError(
|
|
1112
|
+
"model was built without exog; unexpected exog passed to extend()"
|
|
1113
|
+
)
|
|
1114
|
+
full_exog = None
|
|
1115
|
+
|
|
1116
|
+
new_model = SARIMAXModel(
|
|
1117
|
+
np.concatenate([m.endog, new]),
|
|
1118
|
+
order=m.order,
|
|
1119
|
+
seasonal_order=m.seasonal_order,
|
|
1120
|
+
exog=full_exog,
|
|
1121
|
+
trend=m.trend,
|
|
1122
|
+
enforce_stationarity=m.enforce_stationarity,
|
|
1123
|
+
enforce_invertibility=m.enforce_invertibility,
|
|
1124
|
+
simple_differencing=m.simple_differencing,
|
|
1125
|
+
)
|
|
1126
|
+
return new_model.filter(self.params)
|
|
1127
|
+
|
|
1128
|
+
def append(self, endog, exog=None, refit=False, **fit_kwargs):
|
|
1129
|
+
"""Append new observations (statsmodels-compatible convenience).
|
|
1130
|
+
|
|
1131
|
+
``refit=False`` (default) is an alias for :meth:`extend` — parameters
|
|
1132
|
+
stay fixed. ``refit=True`` re-estimates parameters on the extended
|
|
1133
|
+
sample via :meth:`SARIMAXModel.fit`.
|
|
1134
|
+
"""
|
|
1135
|
+
extended = self.extend(endog, exog=exog)
|
|
1136
|
+
if refit:
|
|
1137
|
+
return extended.model.fit(**fit_kwargs)
|
|
1138
|
+
return extended
|
|
1139
|
+
|
|
951
1140
|
@property
|
|
952
1141
|
def resid(self):
|
|
953
1142
|
"""Standardized residuals."""
|
|
@@ -1249,6 +1438,42 @@ class ForecastResult:
|
|
|
1249
1438
|
})
|
|
1250
1439
|
|
|
1251
1440
|
|
|
1441
|
+
class RollingForecastResult:
|
|
1442
|
+
"""Rolling-origin forecast result (one row per origin).
|
|
1443
|
+
|
|
1444
|
+
Attributes
|
|
1445
|
+
----------
|
|
1446
|
+
origins : np.ndarray, shape (N,)
|
|
1447
|
+
Forecast origins (observations consumed before each forecast).
|
|
1448
|
+
predicted_mean : np.ndarray, shape (N, horizon)
|
|
1449
|
+
variance : np.ndarray, shape (N, horizon)
|
|
1450
|
+
ci_lower : np.ndarray, shape (N, horizon)
|
|
1451
|
+
ci_upper : np.ndarray, shape (N, horizon)
|
|
1452
|
+
"""
|
|
1453
|
+
|
|
1454
|
+
def __init__(self, result_dict, alpha=0.05):
|
|
1455
|
+
self.origins = np.asarray(result_dict["origins"], dtype=np.int64)
|
|
1456
|
+
self.predicted_mean = np.asarray(result_dict["mean"], dtype=np.float64)
|
|
1457
|
+
self.variance = np.asarray(result_dict["variance"], dtype=np.float64)
|
|
1458
|
+
self.ci_lower = np.asarray(result_dict["ci_lower"], dtype=np.float64)
|
|
1459
|
+
self.ci_upper = np.asarray(result_dict["ci_upper"], dtype=np.float64)
|
|
1460
|
+
self._alpha = alpha
|
|
1461
|
+
|
|
1462
|
+
def to_dataframe(self):
|
|
1463
|
+
"""Long-format Polars DataFrame: origin, step, mean, variance, ci."""
|
|
1464
|
+
import polars as pl
|
|
1465
|
+
|
|
1466
|
+
n_origins, horizon = self.predicted_mean.shape
|
|
1467
|
+
return pl.DataFrame({
|
|
1468
|
+
"origin": np.repeat(self.origins, horizon),
|
|
1469
|
+
"step": np.tile(np.arange(1, horizon + 1), n_origins),
|
|
1470
|
+
"mean": self.predicted_mean.ravel(),
|
|
1471
|
+
"variance": self.variance.ravel(),
|
|
1472
|
+
"ci_lower": self.ci_lower.ravel(),
|
|
1473
|
+
"ci_upper": self.ci_upper.ravel(),
|
|
1474
|
+
})
|
|
1475
|
+
|
|
1476
|
+
|
|
1252
1477
|
class PredictionResult:
|
|
1253
1478
|
"""In-sample (and optionally out-of-sample) prediction result.
|
|
1254
1479
|
|
|
@@ -58,10 +58,14 @@ def converged_models(models):
|
|
|
58
58
|
|
|
59
59
|
|
|
60
60
|
def expected_k_params(order, seasonal, n_exog=0):
|
|
61
|
-
"""Expected number of estimated params
|
|
61
|
+
"""Expected number of estimated params.
|
|
62
|
+
|
|
63
|
+
Non-concentrated layout (current default): [exog|ar|ma|sar|sma|sigma2],
|
|
64
|
+
so sigma2 adds +1.
|
|
65
|
+
"""
|
|
62
66
|
p, _d, q = order
|
|
63
67
|
P, _D, Q, _s = seasonal
|
|
64
|
-
return p + q + P + Q + n_exog
|
|
68
|
+
return p + q + P + Q + n_exog + 1
|
|
65
69
|
|
|
66
70
|
|
|
67
71
|
# ---------------------------------------------------------------------------
|
|
@@ -83,8 +83,14 @@ def fit_and_extract(y, order, seasonal_order=(0, 0, 0, 0)):
|
|
|
83
83
|
# Also compute loglike using model.loglike() to double-check
|
|
84
84
|
loglike_check = model.loglike(res.params)
|
|
85
85
|
|
|
86
|
+
# The fit above concentrates the scale out (concentrate_scale=True), so
|
|
87
|
+
# res.params omits sigma2. The current rustima engine / SARIMAXModel use
|
|
88
|
+
# the full non-concentrated layout [trend|exog|ar|ma|sar|sma|sigma2], so we
|
|
89
|
+
# append sigma2 (= res.scale, always the LAST parameter for trend='n').
|
|
90
|
+
params = res.params.tolist() + [float(res.scale)]
|
|
91
|
+
|
|
86
92
|
return {
|
|
87
|
-
"params":
|
|
93
|
+
"params": params,
|
|
88
94
|
"loglike": float(loglike),
|
|
89
95
|
"loglike_check": float(loglike_check),
|
|
90
96
|
"scale": float(res.scale),
|
|
@@ -200,17 +206,37 @@ def main():
|
|
|
200
206
|
concentrate_scale=True,
|
|
201
207
|
)
|
|
202
208
|
res = model.fit(disp=False)
|
|
203
|
-
|
|
209
|
+
|
|
210
|
+
# The fit concentrates the scale out for speed, so res.params omits
|
|
211
|
+
# sigma2. rustima (and statsmodels' default) use the full
|
|
212
|
+
# NON-concentrated layout [ar|ma|sar|sma|sigma2]. To make the fixture
|
|
213
|
+
# convention unambiguous, refilter a non-concentrated model at the
|
|
214
|
+
# full parameter vector and harvest the user-facing outputs from it —
|
|
215
|
+
# no post-hoc scale adjustments.
|
|
216
|
+
sigma2 = float(res.scale)
|
|
217
|
+
full_params = np.asarray(res.params.tolist() + [sigma2])
|
|
218
|
+
model_nc = sm.tsa.SARIMAX(
|
|
219
|
+
data,
|
|
220
|
+
order=order,
|
|
221
|
+
seasonal_order=seasonal_order,
|
|
222
|
+
trend="n",
|
|
223
|
+
enforce_stationarity=False,
|
|
224
|
+
enforce_invertibility=False,
|
|
225
|
+
concentrate_scale=False,
|
|
226
|
+
)
|
|
227
|
+
res_nc = model_nc.filter(full_params)
|
|
228
|
+
fcast = res_nc.get_forecast(steps=10)
|
|
204
229
|
ci = fcast.conf_int(alpha=0.05)
|
|
230
|
+
resid = res_nc.filter_results.standardized_forecasts_error[0]
|
|
205
231
|
|
|
206
|
-
|
|
207
|
-
|
|
232
|
+
ci_lo = ci[:, 0] if isinstance(ci, np.ndarray) else ci.iloc[:, 0].to_numpy()
|
|
233
|
+
ci_hi = ci[:, 1] if isinstance(ci, np.ndarray) else ci.iloc[:, 1].to_numpy()
|
|
208
234
|
|
|
209
235
|
forecast_fixtures[name] = {
|
|
210
|
-
"params":
|
|
236
|
+
"params": full_params.tolist(),
|
|
211
237
|
"forecast_mean": fcast.predicted_mean.tolist(),
|
|
212
|
-
"forecast_ci_lower":
|
|
213
|
-
"forecast_ci_upper":
|
|
238
|
+
"forecast_ci_lower": ci_lo.tolist(),
|
|
239
|
+
"forecast_ci_upper": ci_hi.tolist(),
|
|
214
240
|
"standardized_residuals": resid.tolist(),
|
|
215
241
|
}
|
|
216
242
|
|
|
@@ -244,8 +244,12 @@ def fit_oracle(y, order, seasonal, exog=None, future_exog=None):
|
|
|
244
244
|
else:
|
|
245
245
|
forecast_var = list(forecast_var)
|
|
246
246
|
|
|
247
|
+
# Fit concentrates the scale out (concentrate_scale=True), so
|
|
248
|
+
# res.params omits sigma2. The current rustima engine uses the full
|
|
249
|
+
# non-concentrated layout [exog|ar|ma|sar|sma|sigma2], so append
|
|
250
|
+
# sigma2 (= res.scale) as the trailing parameter.
|
|
247
251
|
return {
|
|
248
|
-
"params": res.params.tolist(),
|
|
252
|
+
"params": res.params.tolist() + [float(res.scale)],
|
|
249
253
|
"loglike": float(res.llf),
|
|
250
254
|
"aic": float(res.aic),
|
|
251
255
|
"bic": float(res.bic),
|
|
@@ -51,8 +51,8 @@ class TestExogLoglike:
|
|
|
51
51
|
def test_loglike_with_single_exog(self):
|
|
52
52
|
"""loglike with 1 exog variable returns finite value."""
|
|
53
53
|
y, exog = _generate_arx_data()
|
|
54
|
-
# params: [exog_beta(1), ar(1)]
|
|
55
|
-
params = np.array([2.0, 0.5], dtype=np.float64)
|
|
54
|
+
# params: [exog_beta(1), ar(1), sigma2]
|
|
55
|
+
params = np.array([2.0, 0.5, 0.25], dtype=np.float64)
|
|
56
56
|
ll = rustima.sarimax_loglike(
|
|
57
57
|
y, (1, 0, 0), (0, 0, 0, 0), params, exog=exog
|
|
58
58
|
)
|
|
@@ -61,8 +61,8 @@ class TestExogLoglike:
|
|
|
61
61
|
def test_loglike_with_multi_exog(self):
|
|
62
62
|
"""loglike with multiple exog variables returns finite value."""
|
|
63
63
|
y, X, _ = _generate_multi_exog_data(n_exog=3)
|
|
64
|
-
# params: [exog(3), ar(1)]
|
|
65
|
-
params = np.array([1.5, -0.8, 0.3, 0.5], dtype=np.float64)
|
|
64
|
+
# params: [exog(3), ar(1), sigma2]
|
|
65
|
+
params = np.array([1.5, -0.8, 0.3, 0.5, 0.25], dtype=np.float64)
|
|
66
66
|
ll = rustima.sarimax_loglike(
|
|
67
67
|
y, (1, 0, 0), (0, 0, 0, 0), params, exog=X
|
|
68
68
|
)
|
|
@@ -71,7 +71,7 @@ class TestExogLoglike:
|
|
|
71
71
|
def test_loglike_without_exog_unchanged(self):
|
|
72
72
|
"""loglike without exog is unchanged (backward compatible)."""
|
|
73
73
|
y, _ = _generate_arx_data()
|
|
74
|
-
params = np.array([0.5], dtype=np.float64)
|
|
74
|
+
params = np.array([0.5, 1.0], dtype=np.float64)
|
|
75
75
|
ll = rustima.sarimax_loglike(
|
|
76
76
|
y, (1, 0, 0), (0, 0, 0, 0), params
|
|
77
77
|
)
|
|
@@ -80,13 +80,15 @@ class TestExogLoglike:
|
|
|
80
80
|
def test_loglike_exog_improves_fit(self):
|
|
81
81
|
"""With true exog, loglike should be better than without."""
|
|
82
82
|
y, exog = _generate_arx_data(beta=2.0)
|
|
83
|
-
# With exog
|
|
84
|
-
|
|
83
|
+
# With exog; sigma2 ~ innovation variance of the SARIMAX
|
|
84
|
+
# regression-with-AR(1)-errors form (~1.25 for this ARX process)
|
|
85
|
+
params_with = np.array([2.0, 0.5, 1.25], dtype=np.float64)
|
|
85
86
|
ll_with = rustima.sarimax_loglike(
|
|
86
87
|
y, (1, 0, 0), (0, 0, 0, 0), params_with, exog=exog
|
|
87
88
|
)
|
|
88
|
-
# Without exog (misspecified)
|
|
89
|
-
|
|
89
|
+
# Without exog (misspecified); sigma2 ~ residual variance of the
|
|
90
|
+
# misspecified model (var(beta*x + eps) = 4 + 0.25)
|
|
91
|
+
params_without = np.array([0.5, 4.25], dtype=np.float64)
|
|
90
92
|
ll_without = rustima.sarimax_loglike(
|
|
91
93
|
y, (1, 0, 0), (0, 0, 0, 0), params_without
|
|
92
94
|
)
|
|
@@ -107,8 +109,8 @@ class TestExogFit:
|
|
|
107
109
|
y, (1, 0, 0), (0, 0, 0, 0), exog=exog
|
|
108
110
|
)
|
|
109
111
|
assert result["converged"], "fit with exog should converge"
|
|
110
|
-
# params: [exog(1), ar(1)]
|
|
111
|
-
assert len(result["params"]) ==
|
|
112
|
+
# params: [exog(1), ar(1), sigma2]
|
|
113
|
+
assert len(result["params"]) == 3
|
|
112
114
|
assert np.isfinite(result["loglike"])
|
|
113
115
|
|
|
114
116
|
def test_fit_recovers_exog_coeff(self):
|
|
@@ -132,8 +134,8 @@ class TestExogFit:
|
|
|
132
134
|
enforce_stationarity=False, enforce_invertibility=False,
|
|
133
135
|
)
|
|
134
136
|
assert result["converged"]
|
|
135
|
-
# params: [exog(3), ar(1)] =
|
|
136
|
-
assert len(result["params"]) ==
|
|
137
|
+
# params: [exog(3), ar(1), sigma2] = 5
|
|
138
|
+
assert len(result["params"]) == 5
|
|
137
139
|
assert np.isfinite(result["loglike"])
|
|
138
140
|
|
|
139
141
|
def test_fit_arma_with_exog(self):
|
|
@@ -144,8 +146,8 @@ class TestExogFit:
|
|
|
144
146
|
enforce_stationarity=False, enforce_invertibility=False,
|
|
145
147
|
)
|
|
146
148
|
assert result["converged"]
|
|
147
|
-
# params: [exog(1), ar(1), ma(1)] =
|
|
148
|
-
assert len(result["params"]) ==
|
|
149
|
+
# params: [exog(1), ar(1), ma(1), sigma2] = 4
|
|
150
|
+
assert len(result["params"]) == 4
|
|
149
151
|
|
|
150
152
|
|
|
151
153
|
# ---------------------------------------------------------------------------
|
|
@@ -246,7 +248,7 @@ class TestExogBatch:
|
|
|
246
248
|
for i, r in enumerate(results):
|
|
247
249
|
assert "error" not in r, f"series {i} failed: {r.get('error')}"
|
|
248
250
|
assert r["converged"], f"series {i} did not converge"
|
|
249
|
-
assert len(r["params"]) ==
|
|
251
|
+
assert len(r["params"]) == 3 # [exog(1), ar(1), sigma2]
|
|
250
252
|
|
|
251
253
|
|
|
252
254
|
# ---------------------------------------------------------------------------
|
|
@@ -281,6 +283,7 @@ class TestExogStatsmodelsComparison:
|
|
|
281
283
|
y, (1, 0, 0), (0, 0, 0, 0),
|
|
282
284
|
np.array(sm_params, dtype=np.float64),
|
|
283
285
|
exog=exog,
|
|
286
|
+
concentrate_scale=True,
|
|
284
287
|
)
|
|
285
288
|
|
|
286
289
|
# Should be close (not exact due to initialization differences;
|