tsbootstrap 0.7.0__tar.gz → 0.7.2__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.
- {tsbootstrap-0.7.0/src/tsbootstrap.egg-info → tsbootstrap-0.7.2}/PKG-INFO +17 -6
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/README.md +13 -1
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/pyproject.toml +32 -11
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/api.py +8 -1
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/model/arima.py +78 -3
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2/src/tsbootstrap.egg-info}/PKG-INFO +17 -6
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap.egg-info/requires.txt +1 -3
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/LICENSE +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/setup.cfg +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/__init__.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/adapters/__init__.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/adapters/estimators.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/block/__init__.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/block/_compiled.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/block/indices.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/block/pwsd.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/block/stationary.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/block/tapered.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/diagnostics.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/dispatch.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/engines/__init__.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/engines/arma_scipy.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/engines/var.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/errors.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/mcp.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/metadata.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/methods.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/model/__init__.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/model/fit.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/model/recursive.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/model/stability.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/prng_keys.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/py.typed +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/results.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/rng.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/uq/__init__.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/uq/adaptive.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/uq/calibration.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/uq/calibrators.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/uq/classical.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/uq/conformal.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/uq/forecast.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap/validation.py +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap.egg-info/SOURCES.txt +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap.egg-info/dependency_links.txt +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap.egg-info/entry_points.txt +0 -0
- {tsbootstrap-0.7.0 → tsbootstrap-0.7.2}/src/tsbootstrap.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tsbootstrap
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
4
4
|
Summary: A Python package to generate bootstrapped time series
|
|
5
5
|
Author-email: Sankalp Gilda <sankalp.gilda@gmail.com>
|
|
6
6
|
Maintainer-email: Sankalp Gilda <sankalp.gilda@gmail.com>, Franz Kiraly <franz.kiraly@sktime.net>, Benedikt Heidrich <benedikt.heidrich@sktime.net>
|
|
@@ -38,7 +38,8 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
38
38
|
Classifier: Programming Language :: Python :: 3.11
|
|
39
39
|
Classifier: Programming Language :: Python :: 3.12
|
|
40
40
|
Classifier: Programming Language :: Python :: 3.13
|
|
41
|
-
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
42
|
+
Requires-Python: <3.15,>=3.10
|
|
42
43
|
Description-Content-Type: text/markdown
|
|
43
44
|
License-File: LICENSE
|
|
44
45
|
Requires-Dist: numpy
|
|
@@ -51,7 +52,7 @@ Requires-Dist: statsmodels; extra == "models"
|
|
|
51
52
|
Provides-Extra: uq
|
|
52
53
|
Requires-Dist: scikit-learn; extra == "uq"
|
|
53
54
|
Provides-Extra: mcp
|
|
54
|
-
Requires-Dist: mcp<2,>=1.28; extra == "mcp"
|
|
55
|
+
Requires-Dist: mcp<2,>=1.28.1; extra == "mcp"
|
|
55
56
|
Provides-Extra: accel
|
|
56
57
|
Requires-Dist: numba; extra == "accel"
|
|
57
58
|
Provides-Extra: examples
|
|
@@ -108,8 +109,6 @@ Requires-Dist: pyright; extra == "dev"
|
|
|
108
109
|
Requires-Dist: ruff; extra == "dev"
|
|
109
110
|
Requires-Dist: autoflake; extra == "dev"
|
|
110
111
|
Requires-Dist: typos; extra == "dev"
|
|
111
|
-
Requires-Dist: tox; extra == "dev"
|
|
112
|
-
Requires-Dist: tox-gh-actions; extra == "dev"
|
|
113
112
|
Requires-Dist: pycobertura; extra == "dev"
|
|
114
113
|
Requires-Dist: tomlkit; extra == "dev"
|
|
115
114
|
Requires-Dist: memory-profiler>=0.60.0; extra == "dev"
|
|
@@ -318,7 +317,12 @@ reduce holds about 20 MB at B=50000 where materializing every replicate takes
|
|
|
318
317
|
about 1.94 GB (roughly 96x lighter), from
|
|
319
318
|
[`benchmarks/results/membench_2026-07-04.json`](benchmarks/results/membench_2026-07-04.json).
|
|
320
319
|
The multivariate and ragged-panel reduce
|
|
321
|
-
paths have no equivalent in `arch`.
|
|
320
|
+
paths have no equivalent in `arch`. The panel reduce
|
|
321
|
+
(`bootstrap_reduce_panel`) returns the full per-series bootstrap distribution
|
|
322
|
+
of the statistic (`n_bootstraps x num_series`), so quantile and tail workflows
|
|
323
|
+
on an estimator are served directly with no replicate tensor. Use the
|
|
324
|
+
materializing path only when the workflow consumes the resampled paths
|
|
325
|
+
themselves. Full methodology,
|
|
322
326
|
single-threaded numbers, and the reproduction script are in
|
|
323
327
|
[benchmarks/README.md](benchmarks/README.md).
|
|
324
328
|
|
|
@@ -436,6 +440,13 @@ To run all tests, in your developer environment, run:
|
|
|
436
440
|
uv run pytest tests/
|
|
437
441
|
```
|
|
438
442
|
|
|
443
|
+
That runs in a single process. Add the pytest-xdist flags CI uses to run the
|
|
444
|
+
suite in parallel, which is several times faster on a multi-core machine:
|
|
445
|
+
|
|
446
|
+
```sh
|
|
447
|
+
uv run pytest tests/ -n auto --dist loadscope --max-worker-restart 3
|
|
448
|
+
```
|
|
449
|
+
|
|
439
450
|
The sktime adapter classes can be validated with sktime's estimator checks:
|
|
440
451
|
|
|
441
452
|
```python
|
|
@@ -201,7 +201,12 @@ reduce holds about 20 MB at B=50000 where materializing every replicate takes
|
|
|
201
201
|
about 1.94 GB (roughly 96x lighter), from
|
|
202
202
|
[`benchmarks/results/membench_2026-07-04.json`](benchmarks/results/membench_2026-07-04.json).
|
|
203
203
|
The multivariate and ragged-panel reduce
|
|
204
|
-
paths have no equivalent in `arch`.
|
|
204
|
+
paths have no equivalent in `arch`. The panel reduce
|
|
205
|
+
(`bootstrap_reduce_panel`) returns the full per-series bootstrap distribution
|
|
206
|
+
of the statistic (`n_bootstraps x num_series`), so quantile and tail workflows
|
|
207
|
+
on an estimator are served directly with no replicate tensor. Use the
|
|
208
|
+
materializing path only when the workflow consumes the resampled paths
|
|
209
|
+
themselves. Full methodology,
|
|
205
210
|
single-threaded numbers, and the reproduction script are in
|
|
206
211
|
[benchmarks/README.md](benchmarks/README.md).
|
|
207
212
|
|
|
@@ -319,6 +324,13 @@ To run all tests, in your developer environment, run:
|
|
|
319
324
|
uv run pytest tests/
|
|
320
325
|
```
|
|
321
326
|
|
|
327
|
+
That runs in a single process. Add the pytest-xdist flags CI uses to run the
|
|
328
|
+
suite in parallel, which is several times faster on a multi-core machine:
|
|
329
|
+
|
|
330
|
+
```sh
|
|
331
|
+
uv run pytest tests/ -n auto --dist loadscope --max-worker-restart 3
|
|
332
|
+
```
|
|
333
|
+
|
|
322
334
|
The sktime adapter classes can be validated with sktime's estimator checks:
|
|
323
335
|
|
|
324
336
|
```python
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
|
|
2
|
+
# Floor is 83, not 61: setuptools below 83.0.0 lets a MANIFEST.in exclusion be
|
|
3
|
+
# bypassed in an sdist through a Unicode NFC/NFD normalization collision on the
|
|
4
|
+
# case-folding macOS filesystems (CVE-2026-59890), so a file the manifest excludes
|
|
5
|
+
# can still be published in the source distribution. This is the bound that governs
|
|
6
|
+
# our own sdist build. 83.0.0 requires Python >= 3.10, which matches requires-python
|
|
7
|
+
# below, so the floor costs no supported interpreter.
|
|
8
|
+
requires = ["setuptools>=83", "wheel"]
|
|
3
9
|
build-backend = "setuptools.build_meta"
|
|
4
10
|
|
|
5
11
|
[tool.setuptools.packages.find]
|
|
@@ -7,7 +13,7 @@ where = ["src"]
|
|
|
7
13
|
|
|
8
14
|
[project]
|
|
9
15
|
name = "tsbootstrap"
|
|
10
|
-
version = "0.7.
|
|
16
|
+
version = "0.7.2"
|
|
11
17
|
description = "A Python package to generate bootstrapped time series"
|
|
12
18
|
maintainers = [
|
|
13
19
|
{ name = "Sankalp Gilda", email = "sankalp.gilda@gmail.com" },
|
|
@@ -19,7 +25,7 @@ authors = [
|
|
|
19
25
|
]
|
|
20
26
|
license = { file = "LICENSE" }
|
|
21
27
|
readme = "README.md"
|
|
22
|
-
requires-python = ">=3.10,<3.
|
|
28
|
+
requires-python = ">=3.10,<3.15"
|
|
23
29
|
classifiers = [
|
|
24
30
|
"Development Status :: 4 - Beta",
|
|
25
31
|
"Intended Audience :: Science/Research",
|
|
@@ -33,6 +39,7 @@ classifiers = [
|
|
|
33
39
|
"Programming Language :: Python :: 3.11",
|
|
34
40
|
"Programming Language :: Python :: 3.12",
|
|
35
41
|
"Programming Language :: Python :: 3.13",
|
|
42
|
+
"Programming Language :: Python :: 3.14",
|
|
36
43
|
]
|
|
37
44
|
|
|
38
45
|
dependencies = [
|
|
@@ -60,7 +67,11 @@ uq = [
|
|
|
60
67
|
# on top of CORE tsbootstrap only (it does NOT pull the models or uq extras): the two
|
|
61
68
|
# tools are observation-resampling bootstraps, which are pure-numpy.
|
|
62
69
|
mcp = [
|
|
63
|
-
|
|
70
|
+
# Lower bound is 1.28.1, not 1.28: the WebSocket server transport in <= 1.28.0
|
|
71
|
+
# performs no Host/Origin validation (CVE-2026-59950), so a browser page can drive
|
|
72
|
+
# a locally bound server. tsbootstrap-mcp speaks stdio, but the bound keeps a
|
|
73
|
+
# consumer that imports the SDK for its own transport off the vulnerable release.
|
|
74
|
+
"mcp>=1.28.1,<2",
|
|
64
75
|
]
|
|
65
76
|
|
|
66
77
|
# Compiled acceleration: a numba kernel for the VAR recurrence (replicate-parallel).
|
|
@@ -133,8 +144,6 @@ dev = [
|
|
|
133
144
|
"ruff",
|
|
134
145
|
"autoflake",
|
|
135
146
|
"typos",
|
|
136
|
-
"tox",
|
|
137
|
-
"tox-gh-actions",
|
|
138
147
|
"pycobertura",
|
|
139
148
|
"tomlkit",
|
|
140
149
|
"memory-profiler>=0.60.0", # For performance testing
|
|
@@ -161,7 +170,15 @@ required-version = "==0.11.28"
|
|
|
161
170
|
[tool.pytest.ini_options]
|
|
162
171
|
minversion = "6.0"
|
|
163
172
|
pythonpath = ["."] # repo root on sys.path so tests can import the shared tests._helpers package
|
|
164
|
-
|
|
173
|
+
# Reporting only. Parallelism is deliberately NOT here: addopts applies to every pytest
|
|
174
|
+
# invocation in this repository, so putting "-n auto" in it made xdist the default for
|
|
175
|
+
# callers that must not have it, and each of those then had to remember to undo it. The
|
|
176
|
+
# notebook job forgot, started one Jupyter kernel per worker, and the kernels raced to
|
|
177
|
+
# bind their ZMQ ports. The obvious escape hatch does not work either: "-p no:xdist"
|
|
178
|
+
# unregisters the -n / --dist / --max-worker-restart options that addopts still passes,
|
|
179
|
+
# so pytest exits 4 on unrecognized arguments. Callers that want parallelism now ask for
|
|
180
|
+
# it: see the Run tests steps in .github/workflows/CI.yml and DEVELOPER_NOTES.md.
|
|
181
|
+
addopts = "-ra -q --durations=20"
|
|
165
182
|
testpaths = [
|
|
166
183
|
"tests",
|
|
167
184
|
]
|
|
@@ -482,8 +499,12 @@ do_not_mutate_patterns = [
|
|
|
482
499
|
]
|
|
483
500
|
|
|
484
501
|
pytest_add_cli_args_test_selection = ["-m", "not slow", "tests/"]
|
|
485
|
-
# -o addopts= drops the project's default addopts
|
|
486
|
-
#
|
|
487
|
-
# non-
|
|
488
|
-
#
|
|
502
|
+
# -o addopts= drops the project's default addopts, which is now reporting only: -ra, -q and
|
|
503
|
+
# --durations=20. mutmut runs pytest once per mutant, so a per-run durations table and a summary
|
|
504
|
+
# of every non-passing outcome are pure noise across thousands of runs. It has nothing to do
|
|
505
|
+
# with xdist any more:
|
|
506
|
+
# parallelism has left addopts and lives on the callers that want it.
|
|
507
|
+
# The per-test timeout is mutmut's hang guard: a mutant that sends an iterative solver or a
|
|
508
|
+
# recurrence into a non-terminating loop is killed at 60s (thread method, so it interrupts even
|
|
509
|
+
# C-extension calls) instead of stalling the whole run.
|
|
489
510
|
pytest_add_cli_args = ["-o", "addopts=", "--timeout=60", "--timeout-method=thread"]
|
|
@@ -694,7 +694,9 @@ def bootstrap_reduce_panel(
|
|
|
694
694
|
representation is a list of per-series arrays (or a flat array plus a CSR ``indptr``),
|
|
695
695
|
not the rectangular ``(n, d)`` that :func:`bootstrap_reduce` assumes. The output gains
|
|
696
696
|
a series axis, and ``.values()`` is mathematically incoherent across unequal lengths,
|
|
697
|
-
so the reduce IS the panel API.
|
|
697
|
+
so the reduce IS the panel API. The compiled kernels consume the
|
|
698
|
+
flat-values-plus-offsets layout directly, so access is contiguous within each series
|
|
699
|
+
rather than per-element pointer chasing.
|
|
698
700
|
|
|
699
701
|
Parameters
|
|
700
702
|
----------
|
|
@@ -737,6 +739,11 @@ def bootstrap_reduce_panel(
|
|
|
737
739
|
``.statistics`` of shape ``(n_bootstraps, num_series, |theta|)``, collapsed to
|
|
738
740
|
``(n_bootstraps, num_series)`` when the series are univariate and the statistic is
|
|
739
741
|
scalar (mirroring the ``(B,)`` collapse of the rectangular reduce).
|
|
742
|
+
|
|
743
|
+
The reduce returns the full per-series bootstrap distribution of the statistic
|
|
744
|
+
(``n_bootstraps x num_series``), so quantile and tail workflows on an estimator
|
|
745
|
+
are served directly with no replicate tensor. Use the materializing path only
|
|
746
|
+
when the workflow consumes the resampled paths themselves.
|
|
740
747
|
"""
|
|
741
748
|
if backend not in ("numpy", "compiled"):
|
|
742
749
|
raise MethodConfigError(
|
|
@@ -8,14 +8,20 @@ back using the original initial levels. statsmodels is imported lazily.
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
from dataclasses import dataclass
|
|
11
|
+
from typing import Any
|
|
11
12
|
|
|
12
13
|
import numpy as np
|
|
14
|
+
from numpy.linalg import LinAlgError
|
|
13
15
|
from numpy.typing import NDArray
|
|
14
16
|
from scipy.signal import lfilter, lfiltic
|
|
15
17
|
|
|
16
|
-
from tsbootstrap.errors import Codes, MethodConfigError
|
|
18
|
+
from tsbootstrap.errors import Codes, MethodConfigError, ModelStabilityError
|
|
17
19
|
from tsbootstrap.model.fit import _require_statsmodels
|
|
18
20
|
|
|
21
|
+
# Floor for the innovation variance in the fallback starting values: a constant (or
|
|
22
|
+
# near-constant) series has zero sample variance, which is not a usable sigma2 start.
|
|
23
|
+
_MIN_START_SIGMA2 = 1e-10
|
|
24
|
+
|
|
19
25
|
|
|
20
26
|
@dataclass(frozen=True, slots=True)
|
|
21
27
|
class ARMAFit:
|
|
@@ -66,10 +72,79 @@ def integrate_batched(w: NDArray[np.float64], levels: list[float]) -> NDArray[np
|
|
|
66
72
|
return cur
|
|
67
73
|
|
|
68
74
|
|
|
75
|
+
def _interior_start_params(
|
|
76
|
+
model: Any, demeaned: NDArray[np.float64], p: int, q: int
|
|
77
|
+
) -> NDArray[np.float64] | None:
|
|
78
|
+
"""Return statsmodels' starting values, or a white-noise start when they are unusable.
|
|
79
|
+
|
|
80
|
+
Returns ``None`` when the model's Hannan-Rissanen starting values survive the
|
|
81
|
+
stationarity transform, so the refit keeps statsmodels' default start. Returns an
|
|
82
|
+
explicit interior start (zero AR/MA, sigma2 at the sample variance) otherwise.
|
|
83
|
+
|
|
84
|
+
statsmodels already replaces starting AR parameters it judges non-stationary with
|
|
85
|
+
zeros, but that check accepts values sitting exactly ON the unit circle. Their
|
|
86
|
+
inverse stationarity transform divides by ``sqrt(1 - r**2) == 0`` and yields
|
|
87
|
+
``+/-inf``, whose forward transform is NaN, so an optimization started there returns
|
|
88
|
+
NaN parameters rather than raising.
|
|
89
|
+
"""
|
|
90
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
91
|
+
unconstrained = np.asarray(model.untransform_params(model.start_params), dtype=np.float64)
|
|
92
|
+
if np.isfinite(unconstrained).all():
|
|
93
|
+
return None
|
|
94
|
+
sigma2 = max(float(np.var(demeaned)), _MIN_START_SIGMA2)
|
|
95
|
+
return np.concatenate([np.zeros(p + q, dtype=np.float64), [sigma2]])
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _fit_arma_mle(demeaned: NDArray[np.float64], p: int, q: int) -> Any:
|
|
99
|
+
"""Maximum-likelihood ARMA(p, q) fit of ``demeaned``, robust at the unit circle.
|
|
100
|
+
|
|
101
|
+
statsmodels enforces stationarity with ``r = u / sqrt(1 + u**2)``. Once ``|u|``
|
|
102
|
+
passes roughly 1e8 that expression rounds to exactly ``+/-1.0`` in float64, so the
|
|
103
|
+
AR polynomial acquires a root exactly ON the unit circle. The stationary state-space
|
|
104
|
+
initialization must then solve a discrete Lyapunov equation whose transition matrix
|
|
105
|
+
is singular, and LAPACK fails inside the Kalman machinery -- reaching the caller as a
|
|
106
|
+
raw ``numpy.linalg.LinAlgError`` ("LU decomposition error", or "Schur decomposition
|
|
107
|
+
solver error" depending on which factorization is reached first). This happens either
|
|
108
|
+
at the Hannan-Rissanen starting values or at an intermediate optimizer step, so
|
|
109
|
+
conditioning the starting values alone does not close it.
|
|
110
|
+
|
|
111
|
+
The recovery refits the identical model under an approximate-diffuse initialization,
|
|
112
|
+
which solves no Lyapunov equation and so stays well defined for a transition matrix
|
|
113
|
+
with unit-modulus eigenvalues, starting from the interior when the default starting
|
|
114
|
+
values are themselves unusable. The recovered fit is returned as-is: it may well be
|
|
115
|
+
non-stationary, and rejecting it is the stability layer's job, not this one's. If the
|
|
116
|
+
recovery cannot produce finite parameters either, the failure is reported as a typed
|
|
117
|
+
:class:`~tsbootstrap.errors.ModelStabilityError`.
|
|
118
|
+
"""
|
|
119
|
+
from statsmodels.tsa.arima.model import ARIMA as _SMARIMA
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
return _SMARIMA(demeaned, order=(p, 0, q), trend="n").fit()
|
|
123
|
+
except LinAlgError as exc:
|
|
124
|
+
recovery = _SMARIMA(demeaned, order=(p, 0, q), trend="n")
|
|
125
|
+
recovery.ssm.initialize_approximate_diffuse()
|
|
126
|
+
start_params = _interior_start_params(recovery, demeaned, p, q)
|
|
127
|
+
try:
|
|
128
|
+
res = recovery.fit(start_params=start_params)
|
|
129
|
+
if np.isfinite(res.arparams).all() and np.isfinite(res.maparams).all():
|
|
130
|
+
return res
|
|
131
|
+
except LinAlgError:
|
|
132
|
+
# The diffuse re-fit reached the same singularity. Fall through to the typed
|
|
133
|
+
# error rather than let a linear-algebra internal reach the caller.
|
|
134
|
+
pass
|
|
135
|
+
raise ModelStabilityError(
|
|
136
|
+
f"ARMA(p={p}, q={q}) maximum likelihood reached an autoregressive root on the "
|
|
137
|
+
f"unit circle and could not be re-fit under a diffuse initialization; the series "
|
|
138
|
+
f"is probably under-differenced",
|
|
139
|
+
code=Codes.NEAR_UNIT_ROOT,
|
|
140
|
+
context={"p": p, "q": q, "n": int(demeaned.shape[0])},
|
|
141
|
+
hint="Increase the differencing order d, or reduce the ARMA order.",
|
|
142
|
+
) from exc
|
|
143
|
+
|
|
144
|
+
|
|
69
145
|
def fit_arma(w: NDArray[np.float64], p: int, q: int) -> ARMAFit:
|
|
70
146
|
"""Fit a demeaned ARMA(p, q) to the (already differenced) series ``w``."""
|
|
71
147
|
_require_statsmodels()
|
|
72
|
-
from statsmodels.tsa.arima.model import ARIMA as _SMARIMA
|
|
73
148
|
|
|
74
149
|
series = np.ascontiguousarray(np.asarray(w, dtype=np.float64).ravel())
|
|
75
150
|
n = series.shape[0]
|
|
@@ -80,7 +155,7 @@ def fit_arma(w: NDArray[np.float64], p: int, q: int) -> ARMAFit:
|
|
|
80
155
|
context={"p": p, "q": q, "n": n},
|
|
81
156
|
)
|
|
82
157
|
mean = float(series.mean())
|
|
83
|
-
res =
|
|
158
|
+
res = _fit_arma_mle(series - mean, p, q)
|
|
84
159
|
ar_coefs = np.ascontiguousarray(np.asarray(res.arparams, dtype=np.float64))
|
|
85
160
|
ma_coefs = np.ascontiguousarray(np.asarray(res.maparams, dtype=np.float64))
|
|
86
161
|
# Derive the innovations in OUR engine's convention (scipy lfilter), not statsmodels'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tsbootstrap
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
4
4
|
Summary: A Python package to generate bootstrapped time series
|
|
5
5
|
Author-email: Sankalp Gilda <sankalp.gilda@gmail.com>
|
|
6
6
|
Maintainer-email: Sankalp Gilda <sankalp.gilda@gmail.com>, Franz Kiraly <franz.kiraly@sktime.net>, Benedikt Heidrich <benedikt.heidrich@sktime.net>
|
|
@@ -38,7 +38,8 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
38
38
|
Classifier: Programming Language :: Python :: 3.11
|
|
39
39
|
Classifier: Programming Language :: Python :: 3.12
|
|
40
40
|
Classifier: Programming Language :: Python :: 3.13
|
|
41
|
-
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
42
|
+
Requires-Python: <3.15,>=3.10
|
|
42
43
|
Description-Content-Type: text/markdown
|
|
43
44
|
License-File: LICENSE
|
|
44
45
|
Requires-Dist: numpy
|
|
@@ -51,7 +52,7 @@ Requires-Dist: statsmodels; extra == "models"
|
|
|
51
52
|
Provides-Extra: uq
|
|
52
53
|
Requires-Dist: scikit-learn; extra == "uq"
|
|
53
54
|
Provides-Extra: mcp
|
|
54
|
-
Requires-Dist: mcp<2,>=1.28; extra == "mcp"
|
|
55
|
+
Requires-Dist: mcp<2,>=1.28.1; extra == "mcp"
|
|
55
56
|
Provides-Extra: accel
|
|
56
57
|
Requires-Dist: numba; extra == "accel"
|
|
57
58
|
Provides-Extra: examples
|
|
@@ -108,8 +109,6 @@ Requires-Dist: pyright; extra == "dev"
|
|
|
108
109
|
Requires-Dist: ruff; extra == "dev"
|
|
109
110
|
Requires-Dist: autoflake; extra == "dev"
|
|
110
111
|
Requires-Dist: typos; extra == "dev"
|
|
111
|
-
Requires-Dist: tox; extra == "dev"
|
|
112
|
-
Requires-Dist: tox-gh-actions; extra == "dev"
|
|
113
112
|
Requires-Dist: pycobertura; extra == "dev"
|
|
114
113
|
Requires-Dist: tomlkit; extra == "dev"
|
|
115
114
|
Requires-Dist: memory-profiler>=0.60.0; extra == "dev"
|
|
@@ -318,7 +317,12 @@ reduce holds about 20 MB at B=50000 where materializing every replicate takes
|
|
|
318
317
|
about 1.94 GB (roughly 96x lighter), from
|
|
319
318
|
[`benchmarks/results/membench_2026-07-04.json`](benchmarks/results/membench_2026-07-04.json).
|
|
320
319
|
The multivariate and ragged-panel reduce
|
|
321
|
-
paths have no equivalent in `arch`.
|
|
320
|
+
paths have no equivalent in `arch`. The panel reduce
|
|
321
|
+
(`bootstrap_reduce_panel`) returns the full per-series bootstrap distribution
|
|
322
|
+
of the statistic (`n_bootstraps x num_series`), so quantile and tail workflows
|
|
323
|
+
on an estimator are served directly with no replicate tensor. Use the
|
|
324
|
+
materializing path only when the workflow consumes the resampled paths
|
|
325
|
+
themselves. Full methodology,
|
|
322
326
|
single-threaded numbers, and the reproduction script are in
|
|
323
327
|
[benchmarks/README.md](benchmarks/README.md).
|
|
324
328
|
|
|
@@ -436,6 +440,13 @@ To run all tests, in your developer environment, run:
|
|
|
436
440
|
uv run pytest tests/
|
|
437
441
|
```
|
|
438
442
|
|
|
443
|
+
That runs in a single process. Add the pytest-xdist flags CI uses to run the
|
|
444
|
+
suite in parallel, which is several times faster on a multi-core machine:
|
|
445
|
+
|
|
446
|
+
```sh
|
|
447
|
+
uv run pytest tests/ -n auto --dist loadscope --max-worker-restart 3
|
|
448
|
+
```
|
|
449
|
+
|
|
439
450
|
The sktime adapter classes can be validated with sktime's estimator checks:
|
|
440
451
|
|
|
441
452
|
```python
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|