juniper-data-client 0.4.1__tar.gz → 0.4.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.
- juniper_data_client-0.4.2/PKG-INFO +194 -0
- juniper_data_client-0.4.2/README.md +149 -0
- {juniper_data_client-0.4.1 → juniper_data_client-0.4.2}/juniper_data_client/__init__.py +3 -1
- {juniper_data_client-0.4.1 → juniper_data_client-0.4.2}/juniper_data_client/client.py +38 -3
- {juniper_data_client-0.4.1 → juniper_data_client-0.4.2}/juniper_data_client/constants.py +60 -0
- juniper_data_client-0.4.2/juniper_data_client/contract.py +125 -0
- {juniper_data_client-0.4.1 → juniper_data_client-0.4.2}/juniper_data_client/testing/fake_client.py +17 -2
- juniper_data_client-0.4.2/juniper_data_client.egg-info/PKG-INFO +194 -0
- {juniper_data_client-0.4.1 → juniper_data_client-0.4.2}/juniper_data_client.egg-info/SOURCES.txt +2 -0
- {juniper_data_client-0.4.1 → juniper_data_client-0.4.2}/pyproject.toml +1 -1
- {juniper_data_client-0.4.1 → juniper_data_client-0.4.2}/tests/test_client.py +28 -1
- juniper_data_client-0.4.2/tests/test_contract.py +116 -0
- {juniper_data_client-0.4.1 → juniper_data_client-0.4.2}/tests/test_fake_client.py +22 -3
- juniper_data_client-0.4.1/PKG-INFO +0 -271
- juniper_data_client-0.4.1/README.md +0 -226
- juniper_data_client-0.4.1/juniper_data_client.egg-info/PKG-INFO +0 -271
- {juniper_data_client-0.4.1 → juniper_data_client-0.4.2}/LICENSE +0 -0
- {juniper_data_client-0.4.1 → juniper_data_client-0.4.2}/juniper_data_client/exceptions.py +0 -0
- {juniper_data_client-0.4.1 → juniper_data_client-0.4.2}/juniper_data_client/py.typed +0 -0
- {juniper_data_client-0.4.1 → juniper_data_client-0.4.2}/juniper_data_client/testing/__init__.py +0 -0
- {juniper_data_client-0.4.1 → juniper_data_client-0.4.2}/juniper_data_client/testing/generators.py +0 -0
- {juniper_data_client-0.4.1 → juniper_data_client-0.4.2}/juniper_data_client.egg-info/dependency_links.txt +0 -0
- {juniper_data_client-0.4.1 → juniper_data_client-0.4.2}/juniper_data_client.egg-info/requires.txt +0 -0
- {juniper_data_client-0.4.1 → juniper_data_client-0.4.2}/juniper_data_client.egg-info/top_level.txt +0 -0
- {juniper_data_client-0.4.1 → juniper_data_client-0.4.2}/setup.cfg +0 -0
- {juniper_data_client-0.4.1 → juniper_data_client-0.4.2}/tests/test_create_dataset_tags_ttl.py +0 -0
- {juniper_data_client-0.4.1 → juniper_data_client-0.4.2}/tests/test_fake_client_batch.py +0 -0
- {juniper_data_client-0.4.1 → juniper_data_client-0.4.2}/tests/test_generator_parity.py +0 -0
- {juniper_data_client-0.4.1 → juniper_data_client-0.4.2}/tests/test_malformed_json_response.py +0 -0
- {juniper_data_client-0.4.1 → juniper_data_client-0.4.2}/tests/test_npzfile_resource_lifecycle.py +0 -0
- {juniper_data_client-0.4.1 → juniper_data_client-0.4.2}/tests/test_on_request_hook.py +0 -0
- {juniper_data_client-0.4.1 → juniper_data_client-0.4.2}/tests/test_performance.py +0 -0
- {juniper_data_client-0.4.1 → juniper_data_client-0.4.2}/tests/test_retry_policy.py +0 -0
- {juniper_data_client-0.4.1 → juniper_data_client-0.4.2}/tests/test_versioning.py +0 -0
- {juniper_data_client-0.4.1 → juniper_data_client-0.4.2}/tests/test_x_request_id_propagation.py +0 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: juniper-data-client
|
|
3
|
+
Version: 0.4.2
|
|
4
|
+
Summary: HTTP client for the JuniperData dataset generation service
|
|
5
|
+
Author: Paul Calnon
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/pcalnon/juniper-data-client
|
|
8
|
+
Project-URL: Repository, https://github.com/pcalnon/juniper-data-client
|
|
9
|
+
Project-URL: Documentation, https://github.com/pcalnon/juniper-data-client#readme
|
|
10
|
+
Project-URL: Issues, https://github.com/pcalnon/juniper-data-client/issues
|
|
11
|
+
Keywords: juniper,juniper-ml,juniper-ai,artificial-intelligence,dataset,machine-learning,api-client
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.12
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: numpy>=1.24.0
|
|
27
|
+
Requires-Dist: requests>=2.28.0
|
|
28
|
+
Requires-Dist: urllib3>=2.0.0
|
|
29
|
+
Provides-Extra: test
|
|
30
|
+
Requires-Dist: pytest>=7.0.0; extra == "test"
|
|
31
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "test"
|
|
32
|
+
Requires-Dist: pytest-timeout>=2.2.0; extra == "test"
|
|
33
|
+
Requires-Dist: responses>=0.23.0; extra == "test"
|
|
34
|
+
Requires-Dist: juniper-observability>=0.1.1; extra == "test"
|
|
35
|
+
Provides-Extra: observability
|
|
36
|
+
Requires-Dist: juniper-observability>=0.1.1; extra == "observability"
|
|
37
|
+
Provides-Extra: dev
|
|
38
|
+
Requires-Dist: juniper-data-client[test]; extra == "dev"
|
|
39
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
40
|
+
Requires-Dist: isort>=5.12.0; extra == "dev"
|
|
41
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
42
|
+
Requires-Dist: flake8>=7.0.0; extra == "dev"
|
|
43
|
+
Requires-Dist: types-requests>=2.28.0; extra == "dev"
|
|
44
|
+
Dynamic: license-file
|
|
45
|
+
|
|
46
|
+
<!-- markdownlint-disable MD013 MD033 MD041 -->
|
|
47
|
+
<!--
|
|
48
|
+
MD013 (line-length): README contains prose paragraphs that intentionally
|
|
49
|
+
exceed the 512-char ecosystem limit (canonical
|
|
50
|
+
§4 layout). Wrapping harms PyPI rendering.
|
|
51
|
+
MD033 (no-inline-html): The right-aligned logo + spacing rely on HTML.
|
|
52
|
+
MD041 (first-line-heading): The HTML logo is the first line by design.
|
|
53
|
+
Mirrors juniper-ml #283, juniper-cascor #276, juniper-cascor-worker #71.
|
|
54
|
+
-->
|
|
55
|
+
<div align="right" width="150px" height="150px" align="right" valign="top"> <img src="images/Juniper_Logo_150px.png" alt="Juniper" align="right" valign="top" width="150px" /></div>
|
|
56
|
+
<br /> <br /> <br /> <br />
|
|
57
|
+
|
|
58
|
+
# Juniper: Dynamic Neural Network Research Platform
|
|
59
|
+
|
|
60
|
+
Juniper is an AI/ML research platform for investigating dynamic neural network architectures and novel learning paradigms. The project emphasizes ground-up implementations from primary literature, enabling a more transparent exploration of fundamental algorithms.
|
|
61
|
+
|
|
62
|
+
## Juniper Data Client
|
|
63
|
+
|
|
64
|
+
`juniper-data-client` is the **Python HTTP client library** for the `juniper-data` dataset-generation service. The package exposes a single `JuniperDataClient` class whose methods correspond to the REST surface of `juniper-data`: generator schemas, dataset creation, tag-based filtering, batch operations, the named-version registry, and download of the resulting NPZ artifacts as ready-to-train `numpy` arrays. It is the canonical dataset-fetch interface used by both `juniper-cascor` (for training) and `juniper-canopy` (for visualisation), and it serializes the platform's shared `X_train` / `y_train` / `X_test` / `y_test` / `X_full` / `y_full` NPZ schema (all `float32`).
|
|
65
|
+
|
|
66
|
+
## Distribution
|
|
67
|
+
|
|
68
|
+
`juniper-data-client` is published on PyPI as **[`juniper-data-client`](https://pypi.org/project/juniper-data-client/)**.
|
|
69
|
+
The package is also surfaced through the platform meta-distribution
|
|
70
|
+
**[`juniper-ml`](https://pypi.org/project/juniper-ml/)**, which installs
|
|
71
|
+
the full client stack via `pip install juniper-ml[all]`.
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install juniper-data-client
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Ecosystem Compatibility
|
|
78
|
+
|
|
79
|
+
This client library is part of the [Juniper](https://github.com/pcalnon/juniper-ml) ecosystem.
|
|
80
|
+
Verified compatible versions:
|
|
81
|
+
|
|
82
|
+
| juniper-data | juniper-cascor | juniper-canopy | data-client | cascor-client | cascor-worker |
|
|
83
|
+
|--------------|----------------|----------------|-------------|---------------|---------------|
|
|
84
|
+
| 0.6.x | 0.5.x | 0.5.x | >=0.4.1 | >=0.4.0 | >=0.4.0 |
|
|
85
|
+
|
|
86
|
+
For full-stack Docker deployment and integration tests, see [`juniper-deploy`](https://github.com/pcalnon/juniper-deploy).
|
|
87
|
+
|
|
88
|
+
## Architecture
|
|
89
|
+
|
|
90
|
+
`juniper-data-client` is a thin synchronous HTTP client. It does not embed any dataset-generation logic of its own: every call resolves to an HTTP request against a `juniper-data` instance, and every NPZ artifact returned has been produced server-side.
|
|
91
|
+
|
|
92
|
+
```text
|
|
93
|
+
┌────────────────────────┐ ┌──────────────────┐
|
|
94
|
+
│ Caller (e.g. │ HTTP (requests + │ juniper-data │
|
|
95
|
+
│ juniper-cascor / │ urllib3 Retry) │ REST service │
|
|
96
|
+
│ juniper-canopy / │ ──────────────────► │ Port 8100 │
|
|
97
|
+
│ research notebook) │ ◄────────────────── │ /v1/... │
|
|
98
|
+
└──────────┬─────────────┘ JSON + NPZ └──────────────────┘
|
|
99
|
+
│ uses
|
|
100
|
+
▼
|
|
101
|
+
┌────────────────────────┐
|
|
102
|
+
│ juniper-data-client │
|
|
103
|
+
│ JuniperDataClient │
|
|
104
|
+
│ (this package) │
|
|
105
|
+
└────────────────────────┘
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The client retries idempotent verbs (GET) on transient failures via `urllib3.Retry` with exponential backoff; mutating verbs (POST, PATCH, DELETE) are **not** auto-retried, to avoid duplicate-side-effect bugs against the dataset registry. Optional `X-Request-ID` propagation and a `RequestHook` instrumentation surface are available for callers that integrate with the platform's observability stack.
|
|
109
|
+
|
|
110
|
+
## Related Services
|
|
111
|
+
|
|
112
|
+
| Service | Relationship | Notes |
|
|
113
|
+
|---------|-------------|-------|
|
|
114
|
+
| [juniper-data](https://github.com/pcalnon/juniper-data) | The HTTP service this client targets | Set `base_url` to the service's URL (default `http://localhost:8100`) |
|
|
115
|
+
| [juniper-cascor](https://github.com/pcalnon/juniper-cascor) | Primary consumer; uses this client to fetch training datasets | Reads `JUNIPER_DATA_URL` |
|
|
116
|
+
| [juniper-canopy](https://github.com/pcalnon/juniper-canopy) | Secondary consumer; uses this client to fetch visualisation data | Reads `JUNIPER_DATA_URL` |
|
|
117
|
+
|
|
118
|
+
## Active Research Components
|
|
119
|
+
|
|
120
|
+
`juniper-data-client` does not host research components of its own; it is the surface through which other components of the Juniper platform reach the research components hosted by `juniper-data`. Through this client, callers access the **ARC-AGI dataset families** (ARC-AGI-1 and ARC-AGI-2), the **named-version dataset registry** (`list_versions`, `get_latest`), the **batch dataset operations** (`batch_create`, `batch_delete`, `batch_update_tags`, `batch_export`), and the **NPZ artifact contract** that the rest of the platform consumes. Treating these as research artifacts is appropriate: each is a stable interface around which comparative experiments are composed.
|
|
121
|
+
|
|
122
|
+
## Quick Start Guide
|
|
123
|
+
|
|
124
|
+
### Prerequisites
|
|
125
|
+
|
|
126
|
+
- Python ≥ 3.12
|
|
127
|
+
- A running `juniper-data` instance reachable at the URL passed as `base_url` (typically `http://localhost:8100`)
|
|
128
|
+
|
|
129
|
+
### Installation
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
pip install juniper-data-client
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Optional extras: `[observability]` enables `X-Request-ID` propagation through `juniper-observability`; `[test]` installs the testing dependencies; `[dev]` adds linting and type-checking tools.
|
|
136
|
+
|
|
137
|
+
### Verification
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
from juniper_data_client import JuniperDataClient
|
|
141
|
+
|
|
142
|
+
with JuniperDataClient("http://localhost:8100") as client:
|
|
143
|
+
health = client.health_check()
|
|
144
|
+
print(f"Service status: {health['status']}")
|
|
145
|
+
|
|
146
|
+
result = client.create_spiral_dataset(
|
|
147
|
+
n_spirals=2,
|
|
148
|
+
n_points_per_spiral=100,
|
|
149
|
+
noise=0.1,
|
|
150
|
+
seed=42,
|
|
151
|
+
)
|
|
152
|
+
arrays = client.download_artifact_npz(result["dataset_id"])
|
|
153
|
+
|
|
154
|
+
print(f"Training samples: {len(arrays['X_train'])}")
|
|
155
|
+
print(f"Test samples: {len(arrays['X_test'])}")
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
For PyTorch consumers, the returned arrays convert directly with `torch.from_numpy`:
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
import torch
|
|
162
|
+
X_train = torch.from_numpy(arrays["X_train"]) # torch.float32
|
|
163
|
+
y_train = torch.from_numpy(arrays["y_train"]) # torch.float32
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Next Steps
|
|
167
|
+
|
|
168
|
+
- [`docs/QUICK_START.md`](docs/QUICK_START.md) — complete installation and verification guide
|
|
169
|
+
- [`docs/REFERENCE.md`](docs/REFERENCE.md) — full API reference, configuration, and error model
|
|
170
|
+
- [`docs/DEVELOPER_CHEATSHEET.md`](docs/DEVELOPER_CHEATSHEET.md) — quick-reference card for development tasks
|
|
171
|
+
- [`juniper-data`](https://github.com/pcalnon/juniper-data) — the upstream dataset service
|
|
172
|
+
- [`juniper-ml`](https://pypi.org/project/juniper-ml/) — platform meta-package on PyPI
|
|
173
|
+
|
|
174
|
+
## Research Philosophy
|
|
175
|
+
|
|
176
|
+
The Juniper platform exists to study learning algorithms whose network architecture is not fixed in advance. Its initial anchor is the Cascade-Correlation algorithm of Fahlman and Lebiere (1990), implemented from the primary literature without recourse to higher-level abstractions that elide the algorithm's operational detail. The organising commitment is that algorithm implementations remain inspectable at the level at which they were originally specified: candidate units, correlation objectives, weight-freezing semantics, and the structural events that grow the network are first-class artifacts of the codebase rather than internal details of a library wrapper. This permits comparative work — across algorithms, datasets, and hyperparameter regimes — to be conducted on a known and reproducible substrate.
|
|
177
|
+
|
|
178
|
+
The current platform comprises a Cascade-Correlation training service exposing a REST and WebSocket interface, a dataset-generation service with a named-version registry that includes the ARC-AGI families, a real-time monitoring dashboard for inspecting training dynamics as they occur, and a distributed worker that parallelises candidate-unit training across hosts. Near-term work extends the architectural-growth catalogue beyond Cascade-Correlation, introduces multi-network orchestration for comparative experiments at the level of network populations rather than individual runs, and tightens the dataset–training–monitoring loop into a reproducible research workbench. The longer-term direction is the systematic empirical study of constructive and architecture-growing learning algorithms, with first-class infrastructure for the ablation, comparison, and replication that such a study requires.
|
|
179
|
+
|
|
180
|
+
Within this programme, `juniper-data-client` is the canonical integration boundary between the training and data services. Its contract is the dataset-fetch interface used by every training-side consumer; changes to its surface are therefore changes to the platform's shared dataset semantics.
|
|
181
|
+
|
|
182
|
+
## Documentation
|
|
183
|
+
|
|
184
|
+
| Document | Purpose |
|
|
185
|
+
|----------|---------|
|
|
186
|
+
| [`docs/DOCUMENTATION_OVERVIEW.md`](docs/DOCUMENTATION_OVERVIEW.md) | Navigation index for all `juniper-data-client` documentation |
|
|
187
|
+
| [`docs/QUICK_START.md`](docs/QUICK_START.md) | Complete installation and verification guide |
|
|
188
|
+
| [`docs/REFERENCE.md`](docs/REFERENCE.md) | Full API reference, configuration, error model, and NPZ schema |
|
|
189
|
+
| [`docs/DEVELOPER_CHEATSHEET.md`](docs/DEVELOPER_CHEATSHEET.md) | Quick-reference card for development tasks |
|
|
190
|
+
| [`CHANGELOG.md`](CHANGELOG.md) | Version history |
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
|
|
194
|
+
MIT License — see [`LICENSE`](LICENSE) for details.
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
<!-- markdownlint-disable MD013 MD033 MD041 -->
|
|
2
|
+
<!--
|
|
3
|
+
MD013 (line-length): README contains prose paragraphs that intentionally
|
|
4
|
+
exceed the 512-char ecosystem limit (canonical
|
|
5
|
+
§4 layout). Wrapping harms PyPI rendering.
|
|
6
|
+
MD033 (no-inline-html): The right-aligned logo + spacing rely on HTML.
|
|
7
|
+
MD041 (first-line-heading): The HTML logo is the first line by design.
|
|
8
|
+
Mirrors juniper-ml #283, juniper-cascor #276, juniper-cascor-worker #71.
|
|
9
|
+
-->
|
|
10
|
+
<div align="right" width="150px" height="150px" align="right" valign="top"> <img src="images/Juniper_Logo_150px.png" alt="Juniper" align="right" valign="top" width="150px" /></div>
|
|
11
|
+
<br /> <br /> <br /> <br />
|
|
12
|
+
|
|
13
|
+
# Juniper: Dynamic Neural Network Research Platform
|
|
14
|
+
|
|
15
|
+
Juniper is an AI/ML research platform for investigating dynamic neural network architectures and novel learning paradigms. The project emphasizes ground-up implementations from primary literature, enabling a more transparent exploration of fundamental algorithms.
|
|
16
|
+
|
|
17
|
+
## Juniper Data Client
|
|
18
|
+
|
|
19
|
+
`juniper-data-client` is the **Python HTTP client library** for the `juniper-data` dataset-generation service. The package exposes a single `JuniperDataClient` class whose methods correspond to the REST surface of `juniper-data`: generator schemas, dataset creation, tag-based filtering, batch operations, the named-version registry, and download of the resulting NPZ artifacts as ready-to-train `numpy` arrays. It is the canonical dataset-fetch interface used by both `juniper-cascor` (for training) and `juniper-canopy` (for visualisation), and it serializes the platform's shared `X_train` / `y_train` / `X_test` / `y_test` / `X_full` / `y_full` NPZ schema (all `float32`).
|
|
20
|
+
|
|
21
|
+
## Distribution
|
|
22
|
+
|
|
23
|
+
`juniper-data-client` is published on PyPI as **[`juniper-data-client`](https://pypi.org/project/juniper-data-client/)**.
|
|
24
|
+
The package is also surfaced through the platform meta-distribution
|
|
25
|
+
**[`juniper-ml`](https://pypi.org/project/juniper-ml/)**, which installs
|
|
26
|
+
the full client stack via `pip install juniper-ml[all]`.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install juniper-data-client
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Ecosystem Compatibility
|
|
33
|
+
|
|
34
|
+
This client library is part of the [Juniper](https://github.com/pcalnon/juniper-ml) ecosystem.
|
|
35
|
+
Verified compatible versions:
|
|
36
|
+
|
|
37
|
+
| juniper-data | juniper-cascor | juniper-canopy | data-client | cascor-client | cascor-worker |
|
|
38
|
+
|--------------|----------------|----------------|-------------|---------------|---------------|
|
|
39
|
+
| 0.6.x | 0.5.x | 0.5.x | >=0.4.1 | >=0.4.0 | >=0.4.0 |
|
|
40
|
+
|
|
41
|
+
For full-stack Docker deployment and integration tests, see [`juniper-deploy`](https://github.com/pcalnon/juniper-deploy).
|
|
42
|
+
|
|
43
|
+
## Architecture
|
|
44
|
+
|
|
45
|
+
`juniper-data-client` is a thin synchronous HTTP client. It does not embed any dataset-generation logic of its own: every call resolves to an HTTP request against a `juniper-data` instance, and every NPZ artifact returned has been produced server-side.
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
┌────────────────────────┐ ┌──────────────────┐
|
|
49
|
+
│ Caller (e.g. │ HTTP (requests + │ juniper-data │
|
|
50
|
+
│ juniper-cascor / │ urllib3 Retry) │ REST service │
|
|
51
|
+
│ juniper-canopy / │ ──────────────────► │ Port 8100 │
|
|
52
|
+
│ research notebook) │ ◄────────────────── │ /v1/... │
|
|
53
|
+
└──────────┬─────────────┘ JSON + NPZ └──────────────────┘
|
|
54
|
+
│ uses
|
|
55
|
+
▼
|
|
56
|
+
┌────────────────────────┐
|
|
57
|
+
│ juniper-data-client │
|
|
58
|
+
│ JuniperDataClient │
|
|
59
|
+
│ (this package) │
|
|
60
|
+
└────────────────────────┘
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The client retries idempotent verbs (GET) on transient failures via `urllib3.Retry` with exponential backoff; mutating verbs (POST, PATCH, DELETE) are **not** auto-retried, to avoid duplicate-side-effect bugs against the dataset registry. Optional `X-Request-ID` propagation and a `RequestHook` instrumentation surface are available for callers that integrate with the platform's observability stack.
|
|
64
|
+
|
|
65
|
+
## Related Services
|
|
66
|
+
|
|
67
|
+
| Service | Relationship | Notes |
|
|
68
|
+
|---------|-------------|-------|
|
|
69
|
+
| [juniper-data](https://github.com/pcalnon/juniper-data) | The HTTP service this client targets | Set `base_url` to the service's URL (default `http://localhost:8100`) |
|
|
70
|
+
| [juniper-cascor](https://github.com/pcalnon/juniper-cascor) | Primary consumer; uses this client to fetch training datasets | Reads `JUNIPER_DATA_URL` |
|
|
71
|
+
| [juniper-canopy](https://github.com/pcalnon/juniper-canopy) | Secondary consumer; uses this client to fetch visualisation data | Reads `JUNIPER_DATA_URL` |
|
|
72
|
+
|
|
73
|
+
## Active Research Components
|
|
74
|
+
|
|
75
|
+
`juniper-data-client` does not host research components of its own; it is the surface through which other components of the Juniper platform reach the research components hosted by `juniper-data`. Through this client, callers access the **ARC-AGI dataset families** (ARC-AGI-1 and ARC-AGI-2), the **named-version dataset registry** (`list_versions`, `get_latest`), the **batch dataset operations** (`batch_create`, `batch_delete`, `batch_update_tags`, `batch_export`), and the **NPZ artifact contract** that the rest of the platform consumes. Treating these as research artifacts is appropriate: each is a stable interface around which comparative experiments are composed.
|
|
76
|
+
|
|
77
|
+
## Quick Start Guide
|
|
78
|
+
|
|
79
|
+
### Prerequisites
|
|
80
|
+
|
|
81
|
+
- Python ≥ 3.12
|
|
82
|
+
- A running `juniper-data` instance reachable at the URL passed as `base_url` (typically `http://localhost:8100`)
|
|
83
|
+
|
|
84
|
+
### Installation
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pip install juniper-data-client
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Optional extras: `[observability]` enables `X-Request-ID` propagation through `juniper-observability`; `[test]` installs the testing dependencies; `[dev]` adds linting and type-checking tools.
|
|
91
|
+
|
|
92
|
+
### Verification
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from juniper_data_client import JuniperDataClient
|
|
96
|
+
|
|
97
|
+
with JuniperDataClient("http://localhost:8100") as client:
|
|
98
|
+
health = client.health_check()
|
|
99
|
+
print(f"Service status: {health['status']}")
|
|
100
|
+
|
|
101
|
+
result = client.create_spiral_dataset(
|
|
102
|
+
n_spirals=2,
|
|
103
|
+
n_points_per_spiral=100,
|
|
104
|
+
noise=0.1,
|
|
105
|
+
seed=42,
|
|
106
|
+
)
|
|
107
|
+
arrays = client.download_artifact_npz(result["dataset_id"])
|
|
108
|
+
|
|
109
|
+
print(f"Training samples: {len(arrays['X_train'])}")
|
|
110
|
+
print(f"Test samples: {len(arrays['X_test'])}")
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
For PyTorch consumers, the returned arrays convert directly with `torch.from_numpy`:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
import torch
|
|
117
|
+
X_train = torch.from_numpy(arrays["X_train"]) # torch.float32
|
|
118
|
+
y_train = torch.from_numpy(arrays["y_train"]) # torch.float32
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Next Steps
|
|
122
|
+
|
|
123
|
+
- [`docs/QUICK_START.md`](docs/QUICK_START.md) — complete installation and verification guide
|
|
124
|
+
- [`docs/REFERENCE.md`](docs/REFERENCE.md) — full API reference, configuration, and error model
|
|
125
|
+
- [`docs/DEVELOPER_CHEATSHEET.md`](docs/DEVELOPER_CHEATSHEET.md) — quick-reference card for development tasks
|
|
126
|
+
- [`juniper-data`](https://github.com/pcalnon/juniper-data) — the upstream dataset service
|
|
127
|
+
- [`juniper-ml`](https://pypi.org/project/juniper-ml/) — platform meta-package on PyPI
|
|
128
|
+
|
|
129
|
+
## Research Philosophy
|
|
130
|
+
|
|
131
|
+
The Juniper platform exists to study learning algorithms whose network architecture is not fixed in advance. Its initial anchor is the Cascade-Correlation algorithm of Fahlman and Lebiere (1990), implemented from the primary literature without recourse to higher-level abstractions that elide the algorithm's operational detail. The organising commitment is that algorithm implementations remain inspectable at the level at which they were originally specified: candidate units, correlation objectives, weight-freezing semantics, and the structural events that grow the network are first-class artifacts of the codebase rather than internal details of a library wrapper. This permits comparative work — across algorithms, datasets, and hyperparameter regimes — to be conducted on a known and reproducible substrate.
|
|
132
|
+
|
|
133
|
+
The current platform comprises a Cascade-Correlation training service exposing a REST and WebSocket interface, a dataset-generation service with a named-version registry that includes the ARC-AGI families, a real-time monitoring dashboard for inspecting training dynamics as they occur, and a distributed worker that parallelises candidate-unit training across hosts. Near-term work extends the architectural-growth catalogue beyond Cascade-Correlation, introduces multi-network orchestration for comparative experiments at the level of network populations rather than individual runs, and tightens the dataset–training–monitoring loop into a reproducible research workbench. The longer-term direction is the systematic empirical study of constructive and architecture-growing learning algorithms, with first-class infrastructure for the ablation, comparison, and replication that such a study requires.
|
|
134
|
+
|
|
135
|
+
Within this programme, `juniper-data-client` is the canonical integration boundary between the training and data services. Its contract is the dataset-fetch interface used by every training-side consumer; changes to its surface are therefore changes to the platform's shared dataset semantics.
|
|
136
|
+
|
|
137
|
+
## Documentation
|
|
138
|
+
|
|
139
|
+
| Document | Purpose |
|
|
140
|
+
|----------|---------|
|
|
141
|
+
| [`docs/DOCUMENTATION_OVERVIEW.md`](docs/DOCUMENTATION_OVERVIEW.md) | Navigation index for all `juniper-data-client` documentation |
|
|
142
|
+
| [`docs/QUICK_START.md`](docs/QUICK_START.md) | Complete installation and verification guide |
|
|
143
|
+
| [`docs/REFERENCE.md`](docs/REFERENCE.md) | Full API reference, configuration, error model, and NPZ schema |
|
|
144
|
+
| [`docs/DEVELOPER_CHEATSHEET.md`](docs/DEVELOPER_CHEATSHEET.md) | Quick-reference card for development tasks |
|
|
145
|
+
| [`CHANGELOG.md`](CHANGELOG.md) | Version history |
|
|
146
|
+
|
|
147
|
+
## License
|
|
148
|
+
|
|
149
|
+
MIT License — see [`LICENSE`](LICENSE) for details.
|
|
@@ -5,12 +5,14 @@ dataset generation service, used by both JuniperCascor and juniper-canopy.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from juniper_data_client.client import JuniperDataClient, RequestHook
|
|
8
|
+
from juniper_data_client.contract import validate_npz_contract
|
|
8
9
|
from juniper_data_client.exceptions import JuniperDataClientError, JuniperDataConfigurationError, JuniperDataConnectionError, JuniperDataNotFoundError, JuniperDataTimeoutError, JuniperDataValidationError
|
|
9
10
|
|
|
10
|
-
__version__ = "0.4.
|
|
11
|
+
__version__ = "0.4.2"
|
|
11
12
|
|
|
12
13
|
__all__ = [
|
|
13
14
|
"JuniperDataClient",
|
|
15
|
+
"validate_npz_contract",
|
|
14
16
|
"JuniperDataClientError",
|
|
15
17
|
"JuniperDataConfigurationError",
|
|
16
18
|
"JuniperDataConnectionError",
|
|
@@ -8,6 +8,7 @@ import io
|
|
|
8
8
|
import logging
|
|
9
9
|
import os
|
|
10
10
|
import time
|
|
11
|
+
from pathlib import Path
|
|
11
12
|
from typing import Any, Callable, Dict, List, Optional
|
|
12
13
|
from urllib.parse import urlparse
|
|
13
14
|
|
|
@@ -18,6 +19,7 @@ from urllib3.util.retry import Retry
|
|
|
18
19
|
|
|
19
20
|
from juniper_data_client.constants import (
|
|
20
21
|
API_KEY_ENV_VAR,
|
|
22
|
+
API_KEY_FILE_ENV_VAR,
|
|
21
23
|
API_KEY_HEADER_NAME,
|
|
22
24
|
API_VERSION_PATH_SUFFIX,
|
|
23
25
|
DEFAULT_BACKOFF_FACTOR,
|
|
@@ -65,6 +67,26 @@ from juniper_data_client.exceptions import JuniperDataClientError, JuniperDataCo
|
|
|
65
67
|
logger = logging.getLogger("juniper_data_client.client")
|
|
66
68
|
|
|
67
69
|
|
|
70
|
+
def _resolve_api_key_from_env() -> Optional[str]:
|
|
71
|
+
"""Resolve the juniper-data API key from the environment.
|
|
72
|
+
|
|
73
|
+
Honors the Docker-secret ``JUNIPER_DATA_API_KEY_FILE`` indirection (a file whose
|
|
74
|
+
stripped contents are the key, e.g. ``/run/secrets/juniper_data_api_keys``) before the
|
|
75
|
+
plain ``JUNIPER_DATA_API_KEY`` env var, so a consumer that mounts the key as a file and
|
|
76
|
+
leaves ``api_key`` unset still authenticates. Mirrors how the Juniper services resolve
|
|
77
|
+
their own secrets (e.g. cascor ``api.secrets.get_secret``).
|
|
78
|
+
"""
|
|
79
|
+
file_path = os.environ.get(API_KEY_FILE_ENV_VAR)
|
|
80
|
+
if file_path:
|
|
81
|
+
try:
|
|
82
|
+
content = Path(file_path).read_text(encoding="utf-8").strip()
|
|
83
|
+
except OSError:
|
|
84
|
+
content = ""
|
|
85
|
+
if content:
|
|
86
|
+
return content
|
|
87
|
+
return os.environ.get(API_KEY_ENV_VAR)
|
|
88
|
+
|
|
89
|
+
|
|
68
90
|
# METRICS-MON R4.3 / seed-13: type alias for the optional instrumentation
|
|
69
91
|
# hook. Consumers (canopy, cascor) pass a callable matching this shape;
|
|
70
92
|
# default in :class:`JuniperDataClient` is no-op so standalone users
|
|
@@ -151,7 +173,7 @@ class JuniperDataClient:
|
|
|
151
173
|
# guards — the no-op call is a single attribute load + return).
|
|
152
174
|
self._on_request: RequestHook = on_request or _noop_request_hook
|
|
153
175
|
|
|
154
|
-
resolved_api_key = api_key or
|
|
176
|
+
resolved_api_key = api_key or _resolve_api_key_from_env()
|
|
155
177
|
if resolved_api_key:
|
|
156
178
|
self.session.headers[API_KEY_HEADER_NAME] = resolved_api_key
|
|
157
179
|
|
|
@@ -525,12 +547,25 @@ class JuniperDataClient:
|
|
|
525
547
|
def download_artifact_npz(self, dataset_id: str) -> Dict[str, np.ndarray]:
|
|
526
548
|
"""Download and load an NPZ artifact for a dataset.
|
|
527
549
|
|
|
528
|
-
The returned dictionary contains
|
|
550
|
+
The returned dictionary contains every array in the NPZ. Classification
|
|
551
|
+
artifacts carry the standard keys:
|
|
529
552
|
- X_train, y_train: Training features and one-hot labels
|
|
530
553
|
- X_test, y_test: Test features and one-hot labels
|
|
531
554
|
- X_full, y_full: Full dataset features and one-hot labels
|
|
532
555
|
|
|
533
|
-
|
|
556
|
+
Additional keys may be present depending on the generator (WS-1 /
|
|
557
|
+
juniper-data#168):
|
|
558
|
+
- y_reg_{split}: regression target(s) (e.g. equities next-day close).
|
|
559
|
+
- 3-D sequence artifacts (X.ndim == 3, shape (W, L, F)) add a per-step
|
|
560
|
+
dt_{split} (or absolute t_{split}) elapsed-time channel plus
|
|
561
|
+
target_dt_{split} and optional observed_mask_{split} /
|
|
562
|
+
padding_mask_{split}. Pass the result to
|
|
563
|
+
:func:`juniper_data_client.validate_npz_contract` to classify
|
|
564
|
+
("tabular" / "sequence") and validate it. The matching ``task_type``
|
|
565
|
+
("classification" / "regression") and ``time_unit`` live in the
|
|
566
|
+
dataset metadata (see :meth:`get_dataset_metadata`).
|
|
567
|
+
|
|
568
|
+
Feature and label arrays are float32.
|
|
534
569
|
|
|
535
570
|
Args:
|
|
536
571
|
dataset_id: ID of the dataset whose artifact to download
|
|
@@ -15,6 +15,36 @@ License: MIT License
|
|
|
15
15
|
|
|
16
16
|
from typing import List, Tuple
|
|
17
17
|
|
|
18
|
+
__all__ = [
|
|
19
|
+
"DEFAULT_BASE_URL",
|
|
20
|
+
"FAKE_BASE_URL",
|
|
21
|
+
"DEFAULT_TIMEOUT",
|
|
22
|
+
"DEFAULT_RETRIES",
|
|
23
|
+
"DEFAULT_BACKOFF_FACTOR",
|
|
24
|
+
"RETRYABLE_STATUS_CODES",
|
|
25
|
+
"RETRY_ALLOWED_METHODS",
|
|
26
|
+
"HTTP_POOL_CONNECTIONS",
|
|
27
|
+
"HTTP_POOL_MAXSIZE",
|
|
28
|
+
"URL_SCHEME_PREFIXES",
|
|
29
|
+
"DEFAULT_URL_SCHEME_PREFIX",
|
|
30
|
+
"API_VERSION_PATH_SUFFIX",
|
|
31
|
+
"DEFAULT_READY_TIMEOUT",
|
|
32
|
+
"DEFAULT_READY_POLL_INTERVAL",
|
|
33
|
+
"HEALTH_READY_STATUS",
|
|
34
|
+
"API_KEY_HEADER_NAME",
|
|
35
|
+
"API_KEY_ENV_VAR",
|
|
36
|
+
"API_KEY_FILE_ENV_VAR",
|
|
37
|
+
"ENDPOINT_HEALTH",
|
|
38
|
+
"ENDPOINT_HEALTH_READY",
|
|
39
|
+
"ENDPOINT_GENERATORS",
|
|
40
|
+
"ENDPOINT_GENERATOR_SCHEMA_TEMPLATE",
|
|
41
|
+
"ENDPOINT_DATASETS",
|
|
42
|
+
"ENDPOINT_DATASETS_VERSIONS",
|
|
43
|
+
"ENDPOINT_DATASETS_LATEST",
|
|
44
|
+
"ENDPOINT_DATASET_BY_ID_TEMPLATE",
|
|
45
|
+
"ENDPOINT_DATASET_ARTIFACT_TEMPLATE",
|
|
46
|
+
]
|
|
47
|
+
|
|
18
48
|
# ─── Service Configuration ───────────────────────────────────────────────────
|
|
19
49
|
|
|
20
50
|
DEFAULT_BASE_URL: str = "http://localhost:8100"
|
|
@@ -53,6 +83,11 @@ HEALTH_READY_STATUS: str = "ready"
|
|
|
53
83
|
|
|
54
84
|
API_KEY_HEADER_NAME: str = "X-API-Key"
|
|
55
85
|
API_KEY_ENV_VAR: str = "JUNIPER_DATA_API_KEY"
|
|
86
|
+
# Docker-secret indirection: when set, points at a file whose stripped contents are the
|
|
87
|
+
# API key (e.g. /run/secrets/juniper_data_api_keys). The client honors this before the
|
|
88
|
+
# plain API_KEY_ENV_VAR, so a deployment that mounts the key as a file (and sets only the
|
|
89
|
+
# _FILE form) still authenticates -- mirrors how the Juniper services read their secrets.
|
|
90
|
+
API_KEY_FILE_ENV_VAR: str = f"{API_KEY_ENV_VAR}_FILE"
|
|
56
91
|
|
|
57
92
|
# ─── REST Endpoints ──────────────────────────────────────────────────────────
|
|
58
93
|
|
|
@@ -215,3 +250,28 @@ FAKE_SERVICE_STATUS: str = "ok"
|
|
|
215
250
|
FAKE_SERVICE_NAME: str = "juniper-data"
|
|
216
251
|
FAKE_SERVICE_VERSION: str = "fake"
|
|
217
252
|
FAKE_SERVICE_UPTIME_SECONDS: float = 0.0
|
|
253
|
+
|
|
254
|
+
# ─── NPZ Artifact Contract (WS-1 / juniper-data#168) ─────────────────────────
|
|
255
|
+
|
|
256
|
+
# Split suffixes used for every per-split NPZ key (suffix a stem with
|
|
257
|
+
# f"{stem}_{split}", e.g. f"{NPZ_KEY_X}_train").
|
|
258
|
+
NPZ_SPLITS: Tuple[str, ...] = ("train", "test", "full")
|
|
259
|
+
|
|
260
|
+
# Canonical per-split key stems.
|
|
261
|
+
NPZ_KEY_X: str = "X" # features: (N, F) tabular or (W, L, F) sequence
|
|
262
|
+
NPZ_KEY_Y: str = "y" # one-hot classification target
|
|
263
|
+
NPZ_KEY_Y_REG: str = "y_reg" # regression target (e.g. next-day close)
|
|
264
|
+
NPZ_KEY_T: str = "t" # absolute per-step time (sequence)
|
|
265
|
+
NPZ_KEY_DT: str = "dt" # per-step elapsed time / Δt (sequence)
|
|
266
|
+
NPZ_KEY_TARGET_DT: str = "target_dt" # irregular forecast horizon (sequence)
|
|
267
|
+
NPZ_KEY_OBSERVED_MASK: str = "observed_mask" # 1=real, 0=imputed (sequence)
|
|
268
|
+
NPZ_KEY_PADDING_MASK: str = "padding_mask" # 1=valid, 0=structural padding (sequence)
|
|
269
|
+
NPZ_KEY_SEQ_LENGTHS: str = "seq_lengths" # valid step count per window (sequence)
|
|
270
|
+
|
|
271
|
+
# Contract discriminators returned by ``validate_npz_contract``.
|
|
272
|
+
CONTRACT_KIND_TABULAR: str = "tabular"
|
|
273
|
+
CONTRACT_KIND_SEQUENCE: str = "sequence"
|
|
274
|
+
|
|
275
|
+
# ``task_type`` values carried in dataset metadata (meta.json, not the NPZ).
|
|
276
|
+
TASK_TYPE_CLASSIFICATION: str = "classification"
|
|
277
|
+
TASK_TYPE_REGRESSION: str = "regression"
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""NPZ contract validation for tabular and 3-D sequence dataset artifacts.
|
|
2
|
+
|
|
3
|
+
WS-1 (juniper-data#168) adds an additive, ``X.ndim``-dispatched NPZ contract: a
|
|
4
|
+
2-D ``X`` is the legacy tabular artifact (unchanged), while a 3-D ``X``
|
|
5
|
+
``(W, L, F)`` is a time-series / irregular-Δt sequence artifact carrying a
|
|
6
|
+
per-step ``dt`` (or absolute ``t``) channel plus optional ``observed_mask`` /
|
|
7
|
+
``padding_mask``.
|
|
8
|
+
|
|
9
|
+
``validate_npz_contract`` classifies a loaded artifact as ``"tabular"`` or
|
|
10
|
+
``"sequence"`` and enforces the sequence rules: at least one of ``t`` / ``dt``;
|
|
11
|
+
``dt >= 0`` with ``dt[:, 0] == 0``; consistent ``t`` / ``dt``; binary masks of the
|
|
12
|
+
right shape; and ``observed_mask`` only meaningful where ``padding_mask == 1``.
|
|
13
|
+
The 2-D path is untouched and returns immediately.
|
|
14
|
+
|
|
15
|
+
Reference: ``juniper-ml/notes/JUNIPER_RECURSE_DELTA_T_HANDLING_2026-06-05.md`` §6.
|
|
16
|
+
|
|
17
|
+
Project: Juniper
|
|
18
|
+
Sub-Project: juniper-data-client
|
|
19
|
+
Application: JuniperDataClient
|
|
20
|
+
Author: Paul Calnon
|
|
21
|
+
Version: 0.4.1
|
|
22
|
+
License: MIT License
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from typing import Dict
|
|
26
|
+
|
|
27
|
+
import numpy as np
|
|
28
|
+
|
|
29
|
+
from juniper_data_client.constants import (
|
|
30
|
+
CONTRACT_KIND_SEQUENCE,
|
|
31
|
+
CONTRACT_KIND_TABULAR,
|
|
32
|
+
NPZ_KEY_DT,
|
|
33
|
+
NPZ_KEY_OBSERVED_MASK,
|
|
34
|
+
NPZ_KEY_PADDING_MASK,
|
|
35
|
+
NPZ_KEY_T,
|
|
36
|
+
NPZ_KEY_X,
|
|
37
|
+
NPZ_SPLITS,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def validate_npz_contract(arrays: Dict[str, np.ndarray], *, dt_atol: float = 1e-6) -> str:
|
|
42
|
+
"""Classify and validate a loaded NPZ artifact's contract.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
arrays: NPZ array mapping (e.g. the dict returned by
|
|
46
|
+
:meth:`JuniperDataClient.download_artifact_npz`). An
|
|
47
|
+
``np.lib.npyio.NpzFile`` also works (it supports ``in`` / ``[]``).
|
|
48
|
+
dt_atol: absolute tolerance for the ``t`` / ``dt`` consistency check.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
``"tabular"`` for a 2-D ``X`` (legacy path, no further checks), or
|
|
52
|
+
``"sequence"`` for a validated 3-D artifact.
|
|
53
|
+
|
|
54
|
+
Raises:
|
|
55
|
+
ValueError: if ``X`` is neither 2-D nor 3-D, or any 3-D sequence rule is
|
|
56
|
+
violated (missing ``t`` / ``dt``; bad ``dt`` shape, sign, or
|
|
57
|
+
``dt[:, 0]``; inconsistent ``t`` / ``dt``; a non-binary or mis-shaped
|
|
58
|
+
mask; or ``observed_mask`` set on a padded step).
|
|
59
|
+
"""
|
|
60
|
+
x_full_key = f"{NPZ_KEY_X}_full"
|
|
61
|
+
x_train_key = f"{NPZ_KEY_X}_train"
|
|
62
|
+
x = arrays[x_full_key] if x_full_key in arrays else arrays[x_train_key]
|
|
63
|
+
if x.ndim == 2:
|
|
64
|
+
return CONTRACT_KIND_TABULAR
|
|
65
|
+
if x.ndim != 3:
|
|
66
|
+
raise ValueError(f"X must be 2-D (tabular) or 3-D (sequence), got {x.ndim}-D")
|
|
67
|
+
|
|
68
|
+
for split in NPZ_SPLITS:
|
|
69
|
+
x_key = f"{NPZ_KEY_X}_{split}"
|
|
70
|
+
if x_key in arrays:
|
|
71
|
+
xs = arrays[x_key]
|
|
72
|
+
_validate_sequence_split(arrays, split, int(xs.shape[0]), int(xs.shape[1]), dt_atol)
|
|
73
|
+
return CONTRACT_KIND_SEQUENCE
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _validate_sequence_split(arrays: Dict[str, np.ndarray], split: str, n_windows: int, lookback: int, dt_atol: float) -> None:
|
|
77
|
+
"""Enforce the 3-D sequence rules for one split's keys."""
|
|
78
|
+
t_key = f"{NPZ_KEY_T}_{split}"
|
|
79
|
+
dt_key = f"{NPZ_KEY_DT}_{split}"
|
|
80
|
+
has_t = t_key in arrays
|
|
81
|
+
has_dt = dt_key in arrays
|
|
82
|
+
if not (has_t or has_dt):
|
|
83
|
+
raise ValueError(f"{split}: a 3-D artifact needs at least one of {t_key!r} / {dt_key!r}")
|
|
84
|
+
if has_dt:
|
|
85
|
+
_validate_dt(arrays[dt_key], n_windows, lookback, dt_key)
|
|
86
|
+
if has_t and has_dt:
|
|
87
|
+
_validate_t_dt_consistency(arrays[t_key], arrays[dt_key], split, dt_atol)
|
|
88
|
+
_validate_masks(arrays, split, n_windows, lookback)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _validate_dt(dt: np.ndarray, n_windows: int, lookback: int, dt_key: str) -> None:
|
|
92
|
+
"""``dt`` must be ``(W, L)``, non-negative, with a zero first column."""
|
|
93
|
+
if dt.shape != (n_windows, lookback):
|
|
94
|
+
raise ValueError(f"{dt_key} shape {dt.shape} != {(n_windows, lookback)}")
|
|
95
|
+
if np.any(dt < 0):
|
|
96
|
+
raise ValueError(f"{dt_key} has negative gaps")
|
|
97
|
+
if n_windows and np.any(dt[:, 0] != 0):
|
|
98
|
+
raise ValueError(f"{dt_key}[:, 0] must be 0 by convention")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _validate_t_dt_consistency(t: np.ndarray, dt: np.ndarray, split: str, dt_atol: float) -> None:
|
|
102
|
+
"""When both ``t`` and ``dt`` are present they must agree to tolerance."""
|
|
103
|
+
recon = np.zeros_like(t)
|
|
104
|
+
recon[:, 1:] = np.diff(t, axis=1)
|
|
105
|
+
if not np.allclose(recon, dt, atol=dt_atol):
|
|
106
|
+
raise ValueError(f"{split}: t_ and dt_ are inconsistent")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _validate_masks(arrays: Dict[str, np.ndarray], split: str, n_windows: int, lookback: int) -> None:
|
|
110
|
+
"""Masks (if present) must be binary, correctly shaped, and consistent."""
|
|
111
|
+
for mask_key in (f"{NPZ_KEY_OBSERVED_MASK}_{split}", f"{NPZ_KEY_PADDING_MASK}_{split}"):
|
|
112
|
+
if mask_key in arrays:
|
|
113
|
+
mask = arrays[mask_key]
|
|
114
|
+
if mask.shape != (n_windows, lookback):
|
|
115
|
+
raise ValueError(f"{mask_key} shape {mask.shape} != {(n_windows, lookback)}")
|
|
116
|
+
if not np.isin(mask, (0, 1)).all():
|
|
117
|
+
raise ValueError(f"{mask_key} must be binary (0/1)")
|
|
118
|
+
|
|
119
|
+
observed_key = f"{NPZ_KEY_OBSERVED_MASK}_{split}"
|
|
120
|
+
padding_key = f"{NPZ_KEY_PADDING_MASK}_{split}"
|
|
121
|
+
if observed_key in arrays and padding_key in arrays:
|
|
122
|
+
observed = arrays[observed_key]
|
|
123
|
+
padding = arrays[padding_key]
|
|
124
|
+
if np.any((padding == 0) & (observed == 1)):
|
|
125
|
+
raise ValueError(f"{split}: observed_mask=1 on a padded (padding_mask=0) step")
|