tscp 0.1.0.dev0__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.
Files changed (128) hide show
  1. tscp-0.1.0.dev0/.gitignore +51 -0
  2. tscp-0.1.0.dev0/CHANGELOG.md +72 -0
  3. tscp-0.1.0.dev0/LICENSE +21 -0
  4. tscp-0.1.0.dev0/PKG-INFO +130 -0
  5. tscp-0.1.0.dev0/README.md +84 -0
  6. tscp-0.1.0.dev0/docs/source/api.md +91 -0
  7. tscp-0.1.0.dev0/docs/source/concepts.md +81 -0
  8. tscp-0.1.0.dev0/docs/source/conf.py +46 -0
  9. tscp-0.1.0.dev0/docs/source/index.md +28 -0
  10. tscp-0.1.0.dev0/docs/source/tutorials/component.md +46 -0
  11. tscp-0.1.0.dev0/docs/source/tutorials/compose.md +35 -0
  12. tscp-0.1.0.dev0/docs/source/tutorials/learned.md +26 -0
  13. tscp-0.1.0.dev0/docs/source/tutorials/plugin.md +40 -0
  14. tscp-0.1.0.dev0/docs/source/tutorials/production.md +27 -0
  15. tscp-0.1.0.dev0/examples/compose.py +42 -0
  16. tscp-0.1.0.dev0/examples/configs/compose.yaml +18 -0
  17. tscp-0.1.0.dev0/examples/configs/custom_component.yaml +6 -0
  18. tscp-0.1.0.dev0/examples/configs/learned_score.yaml +8 -0
  19. tscp-0.1.0.dev0/examples/configs/production.yaml +12 -0
  20. tscp-0.1.0.dev0/examples/configs/quickstart.yaml +12 -0
  21. tscp-0.1.0.dev0/examples/custom_component.py +94 -0
  22. tscp-0.1.0.dev0/examples/learned_score.py +118 -0
  23. tscp-0.1.0.dev0/examples/plugin/pyproject.toml +17 -0
  24. tscp-0.1.0.dev0/examples/plugin/spec.yaml +5 -0
  25. tscp-0.1.0.dev0/examples/plugin/src/tscp_example_plugin/__init__.py +66 -0
  26. tscp-0.1.0.dev0/examples/production.py +91 -0
  27. tscp-0.1.0.dev0/examples/quickstart.py +49 -0
  28. tscp-0.1.0.dev0/pyproject.toml +130 -0
  29. tscp-0.1.0.dev0/src/tscp/__init__.py +45 -0
  30. tscp-0.1.0.dev0/src/tscp/data/__init__.py +19 -0
  31. tscp-0.1.0.dev0/src/tscp/data/archive.py +488 -0
  32. tscp-0.1.0.dev0/src/tscp/data/availability.py +55 -0
  33. tscp-0.1.0.dev0/src/tscp/data/io.py +109 -0
  34. tscp-0.1.0.dev0/src/tscp/data/splits.py +169 -0
  35. tscp-0.1.0.dev0/src/tscp/datasets/__init__.py +6 -0
  36. tscp-0.1.0.dev0/src/tscp/datasets/synthetic.py +177 -0
  37. tscp-0.1.0.dev0/src/tscp/features/__init__.py +22 -0
  38. tscp-0.1.0.dev0/src/tscp/features/base.py +64 -0
  39. tscp-0.1.0.dev0/src/tscp/features/calendar.py +105 -0
  40. tscp-0.1.0.dev0/src/tscp/features/combine.py +69 -0
  41. tscp-0.1.0.dev0/src/tscp/features/context.py +42 -0
  42. tscp-0.1.0.dev0/src/tscp/features/history.py +58 -0
  43. tscp-0.1.0.dev0/src/tscp/features/phase.py +41 -0
  44. tscp-0.1.0.dev0/src/tscp/imports.py +32 -0
  45. tscp-0.1.0.dev0/src/tscp/methods/__init__.py +48 -0
  46. tscp-0.1.0.dev0/src/tscp/methods/aci.py +44 -0
  47. tscp-0.1.0.dev0/src/tscp/methods/base.py +228 -0
  48. tscp-0.1.0.dev0/src/tscp/methods/compose.py +90 -0
  49. tscp-0.1.0.dev0/src/tscp/methods/engine.py +128 -0
  50. tscp-0.1.0.dev0/src/tscp/methods/io.py +106 -0
  51. tscp-0.1.0.dev0/src/tscp/methods/nexcp.py +42 -0
  52. tscp-0.1.0.dev0/src/tscp/methods/split.py +30 -0
  53. tscp-0.1.0.dev0/src/tscp/metrics/__init__.py +30 -0
  54. tscp-0.1.0.dev0/src/tscp/metrics/conditional.py +66 -0
  55. tscp-0.1.0.dev0/src/tscp/metrics/interval.py +75 -0
  56. tscp-0.1.0.dev0/src/tscp/metrics/report.py +135 -0
  57. tscp-0.1.0.dev0/src/tscp/nn/__init__.py +13 -0
  58. tscp-0.1.0.dev0/src/tscp/nn/data.py +85 -0
  59. tscp-0.1.0.dev0/src/tscp/nn/losses.py +63 -0
  60. tscp-0.1.0.dev0/src/tscp/nn/training.py +125 -0
  61. tscp-0.1.0.dev0/src/tscp/online/__init__.py +6 -0
  62. tscp-0.1.0.dev0/src/tscp/online/aci.py +53 -0
  63. tscp-0.1.0.dev0/src/tscp/online/base.py +67 -0
  64. tscp-0.1.0.dev0/src/tscp/py.typed +0 -0
  65. tscp-0.1.0.dev0/src/tscp/quantile/__init__.py +16 -0
  66. tscp-0.1.0.dev0/src/tscp/quantile/base.py +73 -0
  67. tscp-0.1.0.dev0/src/tscp/quantile/distribution.py +170 -0
  68. tscp-0.1.0.dev0/src/tscp/quantile/weighted.py +103 -0
  69. tscp-0.1.0.dev0/src/tscp/regions/__init__.py +15 -0
  70. tscp-0.1.0.dev0/src/tscp/regions/base.py +139 -0
  71. tscp-0.1.0.dev0/src/tscp/regions/box.py +82 -0
  72. tscp-0.1.0.dev0/src/tscp/regions/interval.py +62 -0
  73. tscp-0.1.0.dev0/src/tscp/registry.py +179 -0
  74. tscp-0.1.0.dev0/src/tscp/scores/__init__.py +9 -0
  75. tscp-0.1.0.dev0/src/tscp/scores/absolute.py +22 -0
  76. tscp-0.1.0.dev0/src/tscp/scores/base.py +87 -0
  77. tscp-0.1.0.dev0/src/tscp/scores/cqr.py +71 -0
  78. tscp-0.1.0.dev0/src/tscp/scores/normalized.py +74 -0
  79. tscp-0.1.0.dev0/src/tscp/scores/signed.py +46 -0
  80. tscp-0.1.0.dev0/src/tscp/sources/__init__.py +7 -0
  81. tscp-0.1.0.dev0/src/tscp/sources/base.py +32 -0
  82. tscp-0.1.0.dev0/src/tscp/sources/expanding.py +25 -0
  83. tscp-0.1.0.dev0/src/tscp/sources/holdout.py +18 -0
  84. tscp-0.1.0.dev0/src/tscp/testing/__init__.py +40 -0
  85. tscp-0.1.0.dev0/src/tscp/testing/archives.py +80 -0
  86. tscp-0.1.0.dev0/src/tscp/testing/checks.py +832 -0
  87. tscp-0.1.0.dev0/src/tscp/testing/components.py +28 -0
  88. tscp-0.1.0.dev0/src/tscp/typing.py +24 -0
  89. tscp-0.1.0.dev0/src/tscp/utils/__init__.py +14 -0
  90. tscp-0.1.0.dev0/src/tscp/utils/component.py +76 -0
  91. tscp-0.1.0.dev0/src/tscp/utils/config.py +135 -0
  92. tscp-0.1.0.dev0/src/tscp/utils/hashing.py +35 -0
  93. tscp-0.1.0.dev0/src/tscp/weights/__init__.py +7 -0
  94. tscp-0.1.0.dev0/src/tscp/weights/base.py +22 -0
  95. tscp-0.1.0.dev0/src/tscp/weights/exponential.py +37 -0
  96. tscp-0.1.0.dev0/src/tscp/weights/uniform.py +23 -0
  97. tscp-0.1.0.dev0/tests/conftest.py +31 -0
  98. tscp-0.1.0.dev0/tests/data/test_archive.py +176 -0
  99. tscp-0.1.0.dev0/tests/data/test_from_forecasts.py +61 -0
  100. tscp-0.1.0.dev0/tests/data/test_io.py +131 -0
  101. tscp-0.1.0.dev0/tests/data/test_splits.py +95 -0
  102. tscp-0.1.0.dev0/tests/datasets/test_synthetic.py +46 -0
  103. tscp-0.1.0.dev0/tests/features/test_features.py +113 -0
  104. tscp-0.1.0.dev0/tests/fixtures/golden_regions.npz +0 -0
  105. tscp-0.1.0.dev0/tests/methods/test_compose.py +75 -0
  106. tscp-0.1.0.dev0/tests/methods/test_engine.py +76 -0
  107. tscp-0.1.0.dev0/tests/methods/test_golden.py +102 -0
  108. tscp-0.1.0.dev0/tests/methods/test_io.py +99 -0
  109. tscp-0.1.0.dev0/tests/methods/test_presets.py +104 -0
  110. tscp-0.1.0.dev0/tests/methods/test_release.py +48 -0
  111. tscp-0.1.0.dev0/tests/methods/test_scale.py +61 -0
  112. tscp-0.1.0.dev0/tests/methods/test_split.py +57 -0
  113. tscp-0.1.0.dev0/tests/metrics/test_conditional.py +34 -0
  114. tscp-0.1.0.dev0/tests/metrics/test_interval.py +54 -0
  115. tscp-0.1.0.dev0/tests/metrics/test_report.py +66 -0
  116. tscp-0.1.0.dev0/tests/nn/test_nn.py +152 -0
  117. tscp-0.1.0.dev0/tests/online/test_aci.py +60 -0
  118. tscp-0.1.0.dev0/tests/quantile/test_distribution.py +68 -0
  119. tscp-0.1.0.dev0/tests/quantile/test_weighted.py +117 -0
  120. tscp-0.1.0.dev0/tests/regions/test_contract.py +93 -0
  121. tscp-0.1.0.dev0/tests/scores/test_scores.py +155 -0
  122. tscp-0.1.0.dev0/tests/test_examples.py +35 -0
  123. tscp-0.1.0.dev0/tests/test_imports.py +34 -0
  124. tscp-0.1.0.dev0/tests/test_plugins.py +84 -0
  125. tscp-0.1.0.dev0/tests/test_registry.py +42 -0
  126. tscp-0.1.0.dev0/tests/testing/test_checks.py +230 -0
  127. tscp-0.1.0.dev0/tests/utils/test_config.py +79 -0
  128. tscp-0.1.0.dev0/tests/weights/test_exponential.py +43 -0
@@ -0,0 +1,51 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ build/
6
+ dist/
7
+ .venv/
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+ .coverage
11
+ .coverage.*
12
+ coverage.xml
13
+ htmlcov/
14
+
15
+ # Extra packages installed with pip --target on remote hosts
16
+ .pydeps/
17
+
18
+ # Private, local-only files: notes, AI-assistant instructions and settings, editor settings
19
+ /ignore/
20
+ AGENTS.md
21
+ CLAUDE.md
22
+ CLAUDE.local.md
23
+ GEMINI.md
24
+ .claude/
25
+ .cursor/
26
+ .cursorrules
27
+ .windsurfrules
28
+ .aider*
29
+ .continue/
30
+ .codex/
31
+ .mcp.json
32
+ .github/copilot-instructions.md
33
+ .vscode/
34
+ .idea/
35
+ *.code-workspace
36
+
37
+ # Sphinx build output and autosummary pages
38
+ docs/build/
39
+ docs/source/generated/
40
+
41
+ # Notebooks
42
+ .ipynb_checkpoints/
43
+
44
+ # Data and run outputs stay out of git. Anchored to the repo root: the package has its own
45
+ # data/ subpackage (src/tscp/data, tests/data), which must stay tracked.
46
+ /data/
47
+ /artifacts/
48
+ /logs/
49
+
50
+ # macOS
51
+ .DS_Store
@@ -0,0 +1,72 @@
1
+ # Changelog
2
+
3
+ All notable changes to tscp are listed here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ### Added
10
+
11
+ - Package layout under `src/tscp/`: one subpackage per design axis (`sources`, `scores`,
12
+ `weights`, `quantile`, `online`, `regions`), plus `data`, `methods`, `metrics`,
13
+ `features`, `datasets` and `utils`.
14
+ - `ResidualArchive`, `SplitPlan` with `chronological_split`, the availability rule, and
15
+ self-contained `.npz` archives with fingerprints.
16
+ - Row subsets that keep the archive they came from as their `timeline`, with
17
+ `known_counts` for reading the history before a row causally. `split()` cuts the
18
+ timeline after the split's last row; `head()` returns the first rows.
19
+ - The `ConformalPredictor` composer, and the presets `SplitCP`, `NexCP` and `ACI`,
20
+ registered in `method_map` and built with `get_method`. Trainable components are fitted
21
+ on the `fit` split, with the `validation` split when there is one.
22
+ - Scores that read whole rows (forecasts, outputs, context), with channels and
23
+ per-element miscoverage levels: `AbsoluteScore`, `SignedScore`, `NormalizedScore`
24
+ (forecaster scale or fitted scale) and `CQRScore` (symmetric or per side). Scores over
25
+ the horizon or across series are calibrated on rows whose targets are all observed.
26
+ - Quantile models return a `ScoreDistribution`: thresholds at any number of levels from
27
+ one sort, and the conformal p-value of any score. `WeightedQuantile` returns an
28
+ `EmpiricalDistribution`.
29
+ - Online updates answer queries keyed by their position and observe outcomes as their
30
+ targets arrive, with the realized levels on request: `ACIUpdate`, run by the online
31
+ engine in `tscp.methods.engine`.
32
+ - Other components: `HoldoutSource`, `ExpandingSource`, `UniformWeights`,
33
+ `ExponentialWeights`.
34
+ - Regions: `Interval`, and `Box` over steps, series or both. Every region exposes its
35
+ tensors, so indexing and device moves work for every type.
36
+ - Metrics: coverage, log-width, Winkler score, rolling, worst-window and grouped coverage,
37
+ and `evaluate`, which returns a `Report` with per-step and joint coverage for any
38
+ region type.
39
+ - Synthetic archives: a Gaussian control and a variance shift.
40
+ - Feature maps, computed causally from a row's timeline: `ResidualHistory` (the last
41
+ residuals known at each origin, per step), `Phase` (position in a cycle of base
42
+ steps), `Calendar` (minute to day of year, from timestamps), `ContextColumns` and
43
+ `Concat`.
44
+ - Optional archive `timestamps` (int64 nanoseconds, hashed). Archive files are schema
45
+ version 2; version 1 files still load.
46
+ - `tscp.nn` for learned components: `pinball_loss`, `interval_score`, `RowBatches` of
47
+ archive rows with their features, `train` (seeded, with early stopping on the
48
+ validation split) and `seeded`. `examples/learned_score.py` fits a score whose scale is
49
+ a small network on the context.
50
+ - `context_scale`: a synthetic control whose error scale follows a known covariate.
51
+ - Batch production: `ResidualArchive.from_forecasts` builds an archive from a backtest's
52
+ arrays (NaN truth means unobserved; timestamps as int64 nanoseconds or `datetime64`),
53
+ and `targets()` gives the truth back. `save_method` and `load_method` keep a fitted
54
+ method in one `.npz` file, without pickle; components whose fitted state lives outside
55
+ torch set `refit_on_load`. Fresh components load fitted state whose shape depends on
56
+ the data. `examples/production.py` runs the whole workflow.
57
+ - A `py.typed` marker, for type checkers.
58
+ - A release workflow that publishes to PyPI through trusted publishing when a version
59
+ tag is pushed.
60
+ - `tscp.testing`: checks that a method or component keeps tscp's contracts
61
+ (`check_method`, `check_score`, `check_weights`, `check_quantile_model`,
62
+ `check_online_update`, `check_region`), with `synthetic_archive` and `MaxAbsScore`.
63
+ tscp checks its own presets and components with them.
64
+ - `tscp.registry`: components and presets registered by kind and short name. `register`
65
+ adds new ones; a taken name raises unless `replace=True`. Plugins register through
66
+ the `tscp.plugins` entry-point group and are loaded when a name or a config's class is
67
+ not found. `compose` builds a method from a spec of short names, such as one read from
68
+ YAML; `examples/plugin` is an installable example plugin.
69
+ - Recorded constructor configs (`to_config`, `from_config`) and config hashes.
70
+ - Sphinx docs with a concepts page and tutorials (composing a method, writing a component,
71
+ a learned component and a plugin, batch production) whose code lives in `examples/`
72
+ and runs in the tests; CI that builds them with warnings as errors; pre-commit.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Roberto Neglia
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,130 @@
1
+ Metadata-Version: 2.5
2
+ Name: tscp
3
+ Version: 0.1.0.dev0
4
+ Summary: Conformal prediction for time series, in PyTorch
5
+ Project-URL: Documentation, https://tscp.readthedocs.io
6
+ Project-URL: Source, https://github.com/RobertoNeglia/tscp
7
+ Project-URL: Issues, https://github.com/RobertoNeglia/tscp/issues
8
+ Project-URL: Changelog, https://github.com/RobertoNeglia/tscp/blob/main/CHANGELOG.md
9
+ Author: Roberto Neglia
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: conformal-prediction,forecasting,prediction-intervals,pytorch,time-series,uncertainty-quantification
13
+ Classifier: Development Status :: 2 - Pre-Alpha
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Requires-Python: >=3.11
22
+ Requires-Dist: numpy>=1.26
23
+ Requires-Dist: torch>=2.2
24
+ Provides-Extra: dev
25
+ Requires-Dist: furo; extra == 'dev'
26
+ Requires-Dist: myst-parser>=3; extra == 'dev'
27
+ Requires-Dist: pre-commit; extra == 'dev'
28
+ Requires-Dist: pytest-cov>=5; extra == 'dev'
29
+ Requires-Dist: pytest>=8; extra == 'dev'
30
+ Requires-Dist: pyyaml>=6; extra == 'dev'
31
+ Requires-Dist: ruff==0.17.4; extra == 'dev'
32
+ Requires-Dist: sphinx-copybutton; extra == 'dev'
33
+ Requires-Dist: sphinx<9,>=7; extra == 'dev'
34
+ Provides-Extra: doc
35
+ Requires-Dist: furo; extra == 'doc'
36
+ Requires-Dist: myst-parser>=3; extra == 'doc'
37
+ Requires-Dist: sphinx-copybutton; extra == 'doc'
38
+ Requires-Dist: sphinx<9,>=7; extra == 'doc'
39
+ Provides-Extra: examples
40
+ Requires-Dist: pyyaml>=6; extra == 'examples'
41
+ Provides-Extra: test
42
+ Requires-Dist: pytest-cov>=5; extra == 'test'
43
+ Requires-Dist: pytest>=8; extra == 'test'
44
+ Requires-Dist: pyyaml>=6; extra == 'test'
45
+ Description-Content-Type: text/markdown
46
+
47
+ # tscp
48
+
49
+ [![CI](https://github.com/RobertoNeglia/tscp/actions/workflows/ci.yaml/badge.svg)](https://github.com/RobertoNeglia/tscp/actions/workflows/ci.yaml)
50
+ [![Documentation](https://readthedocs.org/projects/tscp/badge/?version=latest)](https://tscp.readthedocs.io/en/latest/)
51
+
52
+ Conformal prediction for time series, in PyTorch: the published methods behind one interface,
53
+ from one-step intervals to joint regions over a forecast horizon and across series, with one
54
+ evaluation protocol.
55
+
56
+ **Status:** early development. The data contract, the component interfaces, contract
57
+ checks for new components, a registry with plugins, feature maps, helpers for learned
58
+ components and saved methods are in place, with split CP, NexCP and ACI and the absolute,
59
+ signed, normalized and CQR scores; more methods will follow.
60
+
61
+ ## Install
62
+
63
+ tscp is in early development; its pre-releases install with
64
+
65
+ ```bash
66
+ pip install --pre tscp
67
+ ```
68
+
69
+ For development, clone the repository and install it with `pip install -e ".[dev]"`.
70
+ Documentation: https://tscp.readthedocs.io
71
+
72
+ ## Quickstart
73
+
74
+ ```python
75
+ from tscp.datasets import variance_shift
76
+ from tscp.methods import get_method
77
+ from tscp.metrics import evaluate
78
+
79
+ archive = variance_shift(horizon=1) # a synthetic residual archive
80
+ method = get_method("nexcp", decay=0.99, alpha=0.1)
81
+ region = method.fit(archive).calibrate(archive).predict(archive)
82
+ print(evaluate(region, archive.split("test"), alpha=0.1, window=200))
83
+ ```
84
+
85
+ `examples/quickstart.py` compares the three methods on two synthetic archives.
86
+
87
+ ## Build your own method
88
+
89
+ A method is one component per axis: residual source, score, calibration weights, quantile
90
+ model, online update. `compose` builds one from short names, such as a spec read from YAML:
91
+
92
+ ```python
93
+ from tscp.methods import compose
94
+
95
+ method = compose(
96
+ {
97
+ "score": "signed",
98
+ "source": "expanding",
99
+ "weights": {"name": "exponential", "decay": 0.99},
100
+ "update": {"name": "aci", "gamma": 0.005},
101
+ }
102
+ )
103
+ ```
104
+
105
+ A new component subclasses its axis's base class, and `tscp.testing` checks that it keeps
106
+ the contracts every method relies on, such as causality and split discipline:
107
+
108
+ ```python
109
+ from tscp.testing import check_method
110
+
111
+ check_method(lambda: compose({"score": "signed", "update": "aci"}))
112
+ ```
113
+
114
+ Packages can register components under short names as plugins. The tutorials in `docs/`
115
+ cover composing methods, writing components, learned components, plugins and batch
116
+ production; the code they show is in `examples/` and runs in the tests.
117
+
118
+ ## Batch production
119
+
120
+ ```python
121
+ from tscp.data import ResidualArchive
122
+ from tscp.methods import load_method, save_method
123
+
124
+ archive = ResidualArchive.from_forecasts(
125
+ truth, forecasts, sizes={"fit": 0.2, "calibration": 0.3, "test": None}
126
+ )
127
+ save_method(method.fit(archive), "method.npz") # one file, no pickle
128
+ region = load_method("method.npz").calibrate(archive).predict(archive)
129
+ intervals = region.to_target(archive.split("test").forecasts)
130
+ ```
@@ -0,0 +1,84 @@
1
+ # tscp
2
+
3
+ [![CI](https://github.com/RobertoNeglia/tscp/actions/workflows/ci.yaml/badge.svg)](https://github.com/RobertoNeglia/tscp/actions/workflows/ci.yaml)
4
+ [![Documentation](https://readthedocs.org/projects/tscp/badge/?version=latest)](https://tscp.readthedocs.io/en/latest/)
5
+
6
+ Conformal prediction for time series, in PyTorch: the published methods behind one interface,
7
+ from one-step intervals to joint regions over a forecast horizon and across series, with one
8
+ evaluation protocol.
9
+
10
+ **Status:** early development. The data contract, the component interfaces, contract
11
+ checks for new components, a registry with plugins, feature maps, helpers for learned
12
+ components and saved methods are in place, with split CP, NexCP and ACI and the absolute,
13
+ signed, normalized and CQR scores; more methods will follow.
14
+
15
+ ## Install
16
+
17
+ tscp is in early development; its pre-releases install with
18
+
19
+ ```bash
20
+ pip install --pre tscp
21
+ ```
22
+
23
+ For development, clone the repository and install it with `pip install -e ".[dev]"`.
24
+ Documentation: https://tscp.readthedocs.io
25
+
26
+ ## Quickstart
27
+
28
+ ```python
29
+ from tscp.datasets import variance_shift
30
+ from tscp.methods import get_method
31
+ from tscp.metrics import evaluate
32
+
33
+ archive = variance_shift(horizon=1) # a synthetic residual archive
34
+ method = get_method("nexcp", decay=0.99, alpha=0.1)
35
+ region = method.fit(archive).calibrate(archive).predict(archive)
36
+ print(evaluate(region, archive.split("test"), alpha=0.1, window=200))
37
+ ```
38
+
39
+ `examples/quickstart.py` compares the three methods on two synthetic archives.
40
+
41
+ ## Build your own method
42
+
43
+ A method is one component per axis: residual source, score, calibration weights, quantile
44
+ model, online update. `compose` builds one from short names, such as a spec read from YAML:
45
+
46
+ ```python
47
+ from tscp.methods import compose
48
+
49
+ method = compose(
50
+ {
51
+ "score": "signed",
52
+ "source": "expanding",
53
+ "weights": {"name": "exponential", "decay": 0.99},
54
+ "update": {"name": "aci", "gamma": 0.005},
55
+ }
56
+ )
57
+ ```
58
+
59
+ A new component subclasses its axis's base class, and `tscp.testing` checks that it keeps
60
+ the contracts every method relies on, such as causality and split discipline:
61
+
62
+ ```python
63
+ from tscp.testing import check_method
64
+
65
+ check_method(lambda: compose({"score": "signed", "update": "aci"}))
66
+ ```
67
+
68
+ Packages can register components under short names as plugins. The tutorials in `docs/`
69
+ cover composing methods, writing components, learned components, plugins and batch
70
+ production; the code they show is in `examples/` and runs in the tests.
71
+
72
+ ## Batch production
73
+
74
+ ```python
75
+ from tscp.data import ResidualArchive
76
+ from tscp.methods import load_method, save_method
77
+
78
+ archive = ResidualArchive.from_forecasts(
79
+ truth, forecasts, sizes={"fit": 0.2, "calibration": 0.3, "test": None}
80
+ )
81
+ save_method(method.fit(archive), "method.npz") # one file, no pickle
82
+ region = load_method("method.npz").calibrate(archive).predict(archive)
83
+ intervals = region.to_target(archive.split("test").forecasts)
84
+ ```
@@ -0,0 +1,91 @@
1
+ # API reference
2
+
3
+ ## Data contract
4
+
5
+ ```{eval-rst}
6
+ .. automodule:: tscp.data
7
+ ```
8
+
9
+ ## Axis 1: residual sources
10
+
11
+ ```{eval-rst}
12
+ .. automodule:: tscp.sources
13
+ ```
14
+
15
+ ## Axis 2: scores
16
+
17
+ ```{eval-rst}
18
+ .. automodule:: tscp.scores
19
+ ```
20
+
21
+ ## Axis 3: calibration weights
22
+
23
+ ```{eval-rst}
24
+ .. automodule:: tscp.weights
25
+ ```
26
+
27
+ ## Axis 4: quantile models
28
+
29
+ ```{eval-rst}
30
+ .. automodule:: tscp.quantile
31
+ ```
32
+
33
+ ## Axis 5: online updates
34
+
35
+ ```{eval-rst}
36
+ .. automodule:: tscp.online
37
+ ```
38
+
39
+ ## Axis 6: regions
40
+
41
+ ```{eval-rst}
42
+ .. automodule:: tscp.regions
43
+ ```
44
+
45
+ ## Methods
46
+
47
+ ```{eval-rst}
48
+ .. automodule:: tscp.methods
49
+ ```
50
+
51
+ ## Metrics
52
+
53
+ ```{eval-rst}
54
+ .. automodule:: tscp.metrics
55
+ ```
56
+
57
+ ## Features
58
+
59
+ ```{eval-rst}
60
+ .. automodule:: tscp.features
61
+ ```
62
+
63
+ ## Registry and plugins
64
+
65
+ ```{eval-rst}
66
+ .. automodule:: tscp.registry
67
+ ```
68
+
69
+ ## Learned components
70
+
71
+ ```{eval-rst}
72
+ .. automodule:: tscp.nn
73
+ ```
74
+
75
+ ## Checks
76
+
77
+ ```{eval-rst}
78
+ .. automodule:: tscp.testing
79
+ ```
80
+
81
+ ## Datasets
82
+
83
+ ```{eval-rst}
84
+ .. automodule:: tscp.datasets.synthetic
85
+ ```
86
+
87
+ ## Utilities
88
+
89
+ ```{eval-rst}
90
+ .. automodule:: tscp.utils
91
+ ```
@@ -0,0 +1,81 @@
1
+ # Concepts
2
+
3
+ tscp is post-hoc: it starts from the forecasts of a model that is already trained, and it
4
+ never trains the forecaster. A conformal method in tscp is one choice per *axis*, run
5
+ through a fixed lifecycle on a *residual archive*.
6
+
7
+ ## The residual archive
8
+
9
+ A {class}`~tscp.data.ResidualArchive` holds the residuals of one forecaster on one
10
+ dataset, with what methods need beside them.
11
+
12
+ - **Residuals** are truth minus forecast, in the target's original units, with shape
13
+ `[T, H, N]`: forecast origins, horizon steps and series. tscp never standardizes them.
14
+ - **Origins and steps** count base time steps. The target of origin `s` at step `h` is at
15
+ time `s + h`.
16
+ - **Splits** are named row sets in time order: `fit` and an optional `validation` for
17
+ components that are trained, `calibration` for conformal scores, and `test`. No two
18
+ splits share a target time.
19
+ - **Optional fields:** point forecasts, other forecaster outputs (such as predicted
20
+ quantiles, in target units), context known at each origin, an observed mask for missing
21
+ targets, timestamps, series names and a graph over the series.
22
+
23
+ {meth}`~tscp.data.ResidualArchive.from_forecasts` builds an archive from a backtest's
24
+ arrays, and {func}`~tscp.data.save_archive` writes it to a self-contained `.npz` file,
25
+ checked against a fingerprint when it is read back.
26
+
27
+ ## Knowing only the past
28
+
29
+ The residual of origin `s` at step `h` is known once its target time has passed: at origin
30
+ `t` it is usable only if `s + h <= t`. Everything that grows or adapts during the test
31
+ period follows this rule: expanding calibration pools, online updates and features.
32
+
33
+ A row subset, made by `select` or `split`, keeps the archive it came from as its
34
+ `timeline`. Components read the history before a row from there, through
35
+ {meth}`~tscp.data.ResidualArchive.known_counts`. `split` cuts the timeline after the
36
+ split's last row, so a component fitted on the fit split cannot reach anything later.
37
+ {func}`tscp.testing.check_method` checks both properties for any method.
38
+
39
+ ## One choice per axis
40
+
41
+ | Axis | Base class | What it decides |
42
+ |---|---|---|
43
+ | Residual source | {class}`~tscp.sources.Source` | which rows calibrate, and whether test residuals join them once known |
44
+ | Score | {class}`~tscp.scores.Score` | how an error becomes a number, and the region a threshold on it defines |
45
+ | Weights | {class}`~tscp.weights.Weights` | which past errors count, and how much |
46
+ | Quantile model | {class}`~tscp.quantile.QuantileModel` | the conformal distribution of the score, read at any level |
47
+ | Online update | {class}`~tscp.online.OnlineUpdate` | how the threshold reacts to its own misses |
48
+ | Region | {class}`~tscp.regions.Region` | what the user gets: a membership test and a size |
49
+
50
+ A score keeps the dimensions of `[H, N]` it does not reduce: an interval per step and
51
+ series, or one joint region over the horizon. A score may also split an element into
52
+ channels, such as the two sides of an interval, and share the miscoverage among them.
53
+ Feature maps ({class}`~tscp.features.FeatureMap`) turn what is known at each origin
54
+ into vectors for weights and quantile models.
55
+
56
+ ## The lifecycle
57
+
58
+ ```python
59
+ method.fit(archive) # trainable components see the fit and validation splits only
60
+ method.calibrate(archive) # scores of the calibration pool
61
+ region = method.predict(archive) # one region per test origin
62
+ report = evaluate(region, archive.split("test"), alpha)
63
+ ```
64
+
65
+ Without an online update, `predict` reads every threshold at once. With one, it answers the
66
+ test origins in time order and hands the update each outcome once its target time has
67
+ passed. {func}`~tscp.metrics.evaluate` returns a {class}`~tscp.metrics.Report`: coverage,
68
+ coverage per step and over the horizon, region size as log-volume per dimension, and
69
+ conditional coverage on request.
70
+
71
+ A fitted method is saved with {func}`~tscp.methods.save_method` and loaded with
72
+ {func}`~tscp.methods.load_method`; calibration is cheap, so a loaded method is
73
+ calibrated again before it predicts.
74
+
75
+ ## Regions
76
+
77
+ Regions live in residual coordinates: a target `y` with forecast `f` is covered when
78
+ `y - f` lies in the region. Coverage and volume do not change under this shift, so
79
+ evaluation never needs the forecasts, and `region.to_target(forecasts)` maps regions back
80
+ to target units. Region size is reported as log-volume per dimension, never as the mean of
81
+ raw volumes, which the widest regions would dominate.
@@ -0,0 +1,46 @@
1
+ """Sphinx configuration for the tscp documentation."""
2
+
3
+ import os
4
+ import sys
5
+
6
+ sys.path.insert(0, os.path.abspath("../../src"))
7
+
8
+ import tscp # noqa: E402
9
+
10
+ project = "tscp"
11
+ author = "Roberto Neglia"
12
+ copyright = "2026, Roberto Neglia"
13
+ release = tscp.__version__
14
+ version = release
15
+
16
+ extensions = [
17
+ "myst_parser",
18
+ "sphinx.ext.autodoc",
19
+ "sphinx.ext.intersphinx",
20
+ "sphinx.ext.napoleon",
21
+ "sphinx.ext.viewcode",
22
+ "sphinx_copybutton",
23
+ ]
24
+
25
+ source_suffix = {".md": "markdown", ".rst": "restructuredtext"}
26
+ exclude_patterns = ["build", "generated"]
27
+
28
+ autodoc_default_options = {"members": True, "imported-members": True, "member-order": "bysource"}
29
+ autodoc_typehints = "description"
30
+ napoleon_google_docstring = True
31
+ napoleon_numpy_docstring = False
32
+ myst_heading_anchors = 3
33
+
34
+ # Set TSCP_DOCS_OFFLINE=1 to build without fetching the inventories of other projects.
35
+ intersphinx_mapping = (
36
+ {}
37
+ if os.environ.get("TSCP_DOCS_OFFLINE")
38
+ else {
39
+ "python": ("https://docs.python.org/3", None),
40
+ "numpy": ("https://numpy.org/doc/stable", None),
41
+ "torch": ("https://docs.pytorch.org/docs/stable", None),
42
+ }
43
+ )
44
+
45
+ html_theme = "furo"
46
+ html_title = f"tscp {release}"
@@ -0,0 +1,28 @@
1
+ # tscp
2
+
3
+ Conformal prediction for time series, in PyTorch: the published methods behind one
4
+ interface, from one-step intervals to joint regions over a forecast horizon and across
5
+ series, with one evaluation protocol.
6
+
7
+ ```python
8
+ from tscp.datasets import variance_shift
9
+ from tscp.methods import get_method
10
+ from tscp.metrics import evaluate
11
+
12
+ archive = variance_shift(horizon=1)
13
+ method = get_method("nexcp", decay=0.99, alpha=0.1)
14
+ region = method.fit(archive).calibrate(archive).predict(archive)
15
+ print(evaluate(region, archive.split("test"), alpha=0.1, window=200))
16
+ ```
17
+
18
+ ```{toctree}
19
+ :maxdepth: 2
20
+
21
+ concepts
22
+ tutorials/compose
23
+ tutorials/component
24
+ tutorials/learned
25
+ tutorials/plugin
26
+ tutorials/production
27
+ api
28
+ ```
@@ -0,0 +1,46 @@
1
+ # Write a component
2
+
3
+ A component is one choice on one axis. Writing one means subclassing the axis's base
4
+ class and implementing a method or two; everything else (fitting, masks for unavailable
5
+ and missing data, online release, devices, configs) is the composer's job.
6
+
7
+ This weight scheme counts only the past errors at the same point of a cycle, such as the
8
+ same hour of the day, and lets older cycles count less:
9
+
10
+ ```{literalinclude} ../../../examples/custom_component.py
11
+ :language: python
12
+ :pyobject: PeriodicWeights
13
+ ```
14
+
15
+ Its constructor takes only JSON-friendly arguments, so its config records and rebuilds
16
+ it. `forward` returns nonnegative, unnormalized weights of each pool row for each query;
17
+ the composer multiplies them by the availability and observed masks, so the component
18
+ never handles either.
19
+
20
+ ## Check it
21
+
22
+ {mod}`tscp.testing` checks that a component keeps its axis's contract, and that a method
23
+ keeps tscp's: causality, split discipline, config round trips, saving and loading, and
24
+ devices. A check raises an `AssertionError` that names the first property that fails.
25
+
26
+ ```{literalinclude} ../../../examples/custom_component.py
27
+ :language: python
28
+ :pyobject: main
29
+ :start-at: def periodic_method
30
+ :end-at: print("PeriodicWeights
31
+ ```
32
+
33
+ There is a check per axis: {func}`~tscp.testing.check_score`,
34
+ {func}`~tscp.testing.check_weights`, {func}`~tscp.testing.check_quantile_model`,
35
+ {func}`~tscp.testing.check_online_update`, {func}`~tscp.testing.check_region` and
36
+ {func}`~tscp.testing.check_feature_map`.
37
+
38
+ ## Use it
39
+
40
+ ```bash
41
+ python examples/custom_component.py --config examples/configs/custom_component.yaml
42
+ ```
43
+
44
+ On errors whose size depends on the hour, split conformal over-covers some hours and
45
+ under-covers others; the periodic weights keep every hour near the target. To make the
46
+ component buildable by name in specs, register it ({doc}`plugin`).