exponet 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
exponet-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ExpoNet Contributors
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.
exponet-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,146 @@
1
+ Metadata-Version: 2.4
2
+ Name: exponet
3
+ Version: 0.1.0
4
+ Summary: Trainable blend of ReLU and squared ReLU activations for PyTorch
5
+ Author: ExpoNet Contributors
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.11
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: torch>=2.0.0
11
+ Requires-Dist: numpy>=1.24.0
12
+ Requires-Dist: scikit-learn>=1.3.0
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
15
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
16
+ Dynamic: license-file
17
+
18
+ # ExpoNet
19
+
20
+ ExpoNet is a compact PyTorch library for dense numeric models with a trainable
21
+ blend of ReLU and squared ReLU:
22
+
23
+ ```text
24
+ u = max(x, 0)
25
+ f(x; a) = (1 - a) * u + a * u * u
26
+ 0 <= a <= 1
27
+ ```
28
+
29
+ The primary activation uses one learned coefficient per hidden neuron. It uses
30
+ multiplication and addition on activation values, not a general power. ExpoNet
31
+ also provides scikit-learn-compatible dense regression and classification
32
+ estimators with CPU or a single CUDA device.
33
+
34
+ ## Install
35
+
36
+ ExpoNet requires Python 3.11 or later, PyTorch, NumPy, and scikit-learn.
37
+ Install the PyTorch build appropriate for your platform and GPU first, using
38
+ the [official PyTorch selector](https://pytorch.org/get-started/locally/), then
39
+ install this checkout:
40
+
41
+ ```powershell
42
+ python -m pip install .
43
+ ```
44
+
45
+ For a checkout, install the project and its development tools with:
46
+
47
+ ```powershell
48
+ python -m pip install -e ".[dev]"
49
+ ```
50
+
51
+ Package publication is not part of this release-readiness milestone; install a
52
+ built wheel locally with `python -m pip install dist/exponet-0.1.0-py3-none-any.whl`.
53
+
54
+ `device="auto"` uses CUDA when `torch.cuda.is_available()` is true, otherwise
55
+ CPU. `device="cuda"` or `device="cuda:N"` requires that device and raises if
56
+ it is unavailable; ExpoNet never silently falls back to CPU. A CUDA toolkit
57
+ compiler (`nvcc`) is not required for the prebuilt-PyTorch path.
58
+
59
+ Verified release-candidate coverage is Windows 11 CPU and CUDA on an NVIDIA
60
+ GeForce RTX 5060 (PyTorch 2.11.0+cu128, CUDA runtime 12.8, Python 3.12.10,
61
+ NumPy 2.5.2, scikit-learn 1.9.0). Linux has not yet been validated, so it is
62
+ not claimed as supported coverage.
63
+
64
+ ## Quick start
65
+
66
+ ```python
67
+ import numpy as np
68
+ from exponet import ExpoRegressor
69
+
70
+ rng = np.random.default_rng(0)
71
+ X = rng.normal(size=(80, 2)).astype(np.float32)
72
+ y = (1.5 * X[:, 0] - 0.75 * X[:, 1] + 0.2).astype(np.float32)
73
+
74
+ model = ExpoRegressor(
75
+ hidden_dims=(8,),
76
+ normalization="none",
77
+ trainable_blend=False,
78
+ blend_init=0.0, # ReLU control
79
+ epochs=80,
80
+ lr=0.03,
81
+ device="auto",
82
+ random_state=0,
83
+ ).fit(X, y)
84
+
85
+ predictions = model.predict(X[:3]) # shape: (3,)
86
+ print(model.device_, predictions)
87
+ ```
88
+
89
+ The same verified examples are available for [direct PyTorch use](examples/direct_torch.py),
90
+ [regression](examples/regression.py), and [multiclass classification](examples/classification.py).
91
+
92
+ ## Public API
93
+
94
+ | Export | Purpose | Output shape |
95
+ | --- | --- | --- |
96
+ | `ExpoActivation` | Reusable PyTorch activation. | Same as input tensor. |
97
+ | `ExpoMLP` | Dense `Linear -> optional LayerNorm -> ExpoActivation` blocks and a linear readout. | `(batch, out_features)` |
98
+ | `ExpoRegressor` | Dense numeric regression estimator. | `predict`: `(N,)` for 1-D targets, otherwise `(N, K)` |
99
+ | `ExpoClassifier` | Binary/multiclass integer or string-label estimator. | `predict`: `(N,)`; `predict_proba`: `(N, K)` in `classes_` order |
100
+
101
+ Both estimators accept dense finite numeric features shaped `(N, F)`, train in
102
+ float32, provide `fit`, `predict`, `score`, `get_blend_weights`, and restricted
103
+ inference `save`/`load` methods. See the [API contract](docs/API_CONTRACT.md)
104
+ for constructor parameters, validation, scaling, early stopping, persistence,
105
+ and reproducibility semantics.
106
+
107
+ ## Scope and limitations
108
+
109
+ - This is a dense numeric-data library. Sparse inputs, missing values,
110
+ categorical encoding, image/sequence architectures, sample/class weights,
111
+ streaming, warm starts, AMP, compilation, distributed execution, and custom
112
+ training hooks are outside this release.
113
+ - Classification accepts homogeneous integer or string labels only; continuous,
114
+ mixed, multilabel, and multioutput labels are rejected.
115
+ - Float32 is the primary runtime dtype. CPU float64 is supported for low-level
116
+ activation mathematics tests. Very large positive activations can overflow,
117
+ particularly toward the squared-ReLU endpoint.
118
+ - Saved snapshots restore inference state only, not optimizer or RNG state; do
119
+ not treat a loaded estimator as an exact training-resume checkpoint.
120
+ - The initial controlled evaluation did not show a consistent predictive
121
+ advantage over native ReLU, so no superiority or universal-normalization
122
+ claim is made. See the [initial evaluation](docs/INITIAL_EVALUATION.md).
123
+
124
+ ## Development validation
125
+
126
+ ```powershell
127
+ python -B -m ruff format --check --no-cache --no-respect-gitignore src tests benchmarks examples
128
+ python -B -m ruff check --no-cache --no-respect-gitignore src tests benchmarks examples
129
+ python -B -m pytest -q -p no:cacheprovider
130
+ ```
131
+
132
+ The [roadmap](docs/ROADMAP.md) records exact validation evidence and release
133
+ scope. ExpoNet is available under the [MIT License](LICENSE).
134
+
135
+ ## Design and evidence
136
+
137
+ | Document | Purpose |
138
+ | --- | --- |
139
+ | [API contract](docs/API_CONTRACT.md) | Implemented interfaces and exact behavior. |
140
+ | [Design](docs/DESIGN.md) | Mathematics, numerical behavior, normalization, and architecture. |
141
+ | [Decisions](docs/DECISIONS.md) | Accepted scope and defaults. |
142
+ | [Validation](docs/VALIDATION.md) | Correctness checks and experimental criteria. |
143
+ | [Initial evaluation](docs/INITIAL_EVALUATION.md) | P6 five-seed activation comparison. |
144
+ | [Activation timing](docs/ACTIVATION_BENCHMARK.md) | P1.04 CPU timing evidence and limitations. |
145
+ | [PSANN reuse assessment](docs/PSANN_REUSE.md) | Selective adaptation provenance; no PSANN runtime dependency. |
146
+ | [Roadmap](docs/ROADMAP.md) | Completed work, release evidence, and deferred scope. |
@@ -0,0 +1,129 @@
1
+ # ExpoNet
2
+
3
+ ExpoNet is a compact PyTorch library for dense numeric models with a trainable
4
+ blend of ReLU and squared ReLU:
5
+
6
+ ```text
7
+ u = max(x, 0)
8
+ f(x; a) = (1 - a) * u + a * u * u
9
+ 0 <= a <= 1
10
+ ```
11
+
12
+ The primary activation uses one learned coefficient per hidden neuron. It uses
13
+ multiplication and addition on activation values, not a general power. ExpoNet
14
+ also provides scikit-learn-compatible dense regression and classification
15
+ estimators with CPU or a single CUDA device.
16
+
17
+ ## Install
18
+
19
+ ExpoNet requires Python 3.11 or later, PyTorch, NumPy, and scikit-learn.
20
+ Install the PyTorch build appropriate for your platform and GPU first, using
21
+ the [official PyTorch selector](https://pytorch.org/get-started/locally/), then
22
+ install this checkout:
23
+
24
+ ```powershell
25
+ python -m pip install .
26
+ ```
27
+
28
+ For a checkout, install the project and its development tools with:
29
+
30
+ ```powershell
31
+ python -m pip install -e ".[dev]"
32
+ ```
33
+
34
+ Package publication is not part of this release-readiness milestone; install a
35
+ built wheel locally with `python -m pip install dist/exponet-0.1.0-py3-none-any.whl`.
36
+
37
+ `device="auto"` uses CUDA when `torch.cuda.is_available()` is true, otherwise
38
+ CPU. `device="cuda"` or `device="cuda:N"` requires that device and raises if
39
+ it is unavailable; ExpoNet never silently falls back to CPU. A CUDA toolkit
40
+ compiler (`nvcc`) is not required for the prebuilt-PyTorch path.
41
+
42
+ Verified release-candidate coverage is Windows 11 CPU and CUDA on an NVIDIA
43
+ GeForce RTX 5060 (PyTorch 2.11.0+cu128, CUDA runtime 12.8, Python 3.12.10,
44
+ NumPy 2.5.2, scikit-learn 1.9.0). Linux has not yet been validated, so it is
45
+ not claimed as supported coverage.
46
+
47
+ ## Quick start
48
+
49
+ ```python
50
+ import numpy as np
51
+ from exponet import ExpoRegressor
52
+
53
+ rng = np.random.default_rng(0)
54
+ X = rng.normal(size=(80, 2)).astype(np.float32)
55
+ y = (1.5 * X[:, 0] - 0.75 * X[:, 1] + 0.2).astype(np.float32)
56
+
57
+ model = ExpoRegressor(
58
+ hidden_dims=(8,),
59
+ normalization="none",
60
+ trainable_blend=False,
61
+ blend_init=0.0, # ReLU control
62
+ epochs=80,
63
+ lr=0.03,
64
+ device="auto",
65
+ random_state=0,
66
+ ).fit(X, y)
67
+
68
+ predictions = model.predict(X[:3]) # shape: (3,)
69
+ print(model.device_, predictions)
70
+ ```
71
+
72
+ The same verified examples are available for [direct PyTorch use](examples/direct_torch.py),
73
+ [regression](examples/regression.py), and [multiclass classification](examples/classification.py).
74
+
75
+ ## Public API
76
+
77
+ | Export | Purpose | Output shape |
78
+ | --- | --- | --- |
79
+ | `ExpoActivation` | Reusable PyTorch activation. | Same as input tensor. |
80
+ | `ExpoMLP` | Dense `Linear -> optional LayerNorm -> ExpoActivation` blocks and a linear readout. | `(batch, out_features)` |
81
+ | `ExpoRegressor` | Dense numeric regression estimator. | `predict`: `(N,)` for 1-D targets, otherwise `(N, K)` |
82
+ | `ExpoClassifier` | Binary/multiclass integer or string-label estimator. | `predict`: `(N,)`; `predict_proba`: `(N, K)` in `classes_` order |
83
+
84
+ Both estimators accept dense finite numeric features shaped `(N, F)`, train in
85
+ float32, provide `fit`, `predict`, `score`, `get_blend_weights`, and restricted
86
+ inference `save`/`load` methods. See the [API contract](docs/API_CONTRACT.md)
87
+ for constructor parameters, validation, scaling, early stopping, persistence,
88
+ and reproducibility semantics.
89
+
90
+ ## Scope and limitations
91
+
92
+ - This is a dense numeric-data library. Sparse inputs, missing values,
93
+ categorical encoding, image/sequence architectures, sample/class weights,
94
+ streaming, warm starts, AMP, compilation, distributed execution, and custom
95
+ training hooks are outside this release.
96
+ - Classification accepts homogeneous integer or string labels only; continuous,
97
+ mixed, multilabel, and multioutput labels are rejected.
98
+ - Float32 is the primary runtime dtype. CPU float64 is supported for low-level
99
+ activation mathematics tests. Very large positive activations can overflow,
100
+ particularly toward the squared-ReLU endpoint.
101
+ - Saved snapshots restore inference state only, not optimizer or RNG state; do
102
+ not treat a loaded estimator as an exact training-resume checkpoint.
103
+ - The initial controlled evaluation did not show a consistent predictive
104
+ advantage over native ReLU, so no superiority or universal-normalization
105
+ claim is made. See the [initial evaluation](docs/INITIAL_EVALUATION.md).
106
+
107
+ ## Development validation
108
+
109
+ ```powershell
110
+ python -B -m ruff format --check --no-cache --no-respect-gitignore src tests benchmarks examples
111
+ python -B -m ruff check --no-cache --no-respect-gitignore src tests benchmarks examples
112
+ python -B -m pytest -q -p no:cacheprovider
113
+ ```
114
+
115
+ The [roadmap](docs/ROADMAP.md) records exact validation evidence and release
116
+ scope. ExpoNet is available under the [MIT License](LICENSE).
117
+
118
+ ## Design and evidence
119
+
120
+ | Document | Purpose |
121
+ | --- | --- |
122
+ | [API contract](docs/API_CONTRACT.md) | Implemented interfaces and exact behavior. |
123
+ | [Design](docs/DESIGN.md) | Mathematics, numerical behavior, normalization, and architecture. |
124
+ | [Decisions](docs/DECISIONS.md) | Accepted scope and defaults. |
125
+ | [Validation](docs/VALIDATION.md) | Correctness checks and experimental criteria. |
126
+ | [Initial evaluation](docs/INITIAL_EVALUATION.md) | P6 five-seed activation comparison. |
127
+ | [Activation timing](docs/ACTIVATION_BENCHMARK.md) | P1.04 CPU timing evidence and limitations. |
128
+ | [PSANN reuse assessment](docs/PSANN_REUSE.md) | Selective adaptation provenance; no PSANN runtime dependency. |
129
+ | [Roadmap](docs/ROADMAP.md) | Completed work, release evidence, and deferred scope. |
@@ -0,0 +1,33 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77.0.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "exponet"
7
+ version = "0.1.0"
8
+ description = "Trainable blend of ReLU and squared ReLU activations for PyTorch"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ authors = [{name = "ExpoNet Contributors"}]
13
+ requires-python = ">=3.11"
14
+ dependencies = ["torch>=2.0.0", "numpy>=1.24.0", "scikit-learn>=1.3.0"]
15
+
16
+ [project.optional-dependencies]
17
+ dev = ["pytest>=7.0.0", "ruff>=0.1.0"]
18
+
19
+ [tool.setuptools]
20
+ packages = ["exponet"]
21
+ package-dir = {"" = "src"}
22
+
23
+ [tool.pytest.ini_options]
24
+ testpaths = ["tests"]
25
+ python_files = ["test_*.py"]
26
+ python_functions = ["test_*"]
27
+
28
+ [tool.ruff]
29
+ line-length = 88
30
+ target-version = "py311"
31
+
32
+ [tool.ruff.lint]
33
+ select = ["E", "F", "W", "I"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,8 @@
1
+ """ExpoNet: Trainable blend of ReLU and squared ReLU activations."""
2
+
3
+ from exponet.activations import ExpoActivation as ExpoActivation
4
+ from exponet.estimators import ExpoClassifier as ExpoClassifier
5
+ from exponet.estimators import ExpoRegressor as ExpoRegressor
6
+ from exponet.nn import ExpoMLP as ExpoMLP
7
+
8
+ __version__ = "0.1.0"
@@ -0,0 +1,172 @@
1
+ """Restricted, versioned inference snapshots for ExpoNet estimators."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import tempfile
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ import numpy as np
11
+ import sklearn
12
+ import torch
13
+ from sklearn.preprocessing import StandardScaler
14
+
15
+ FORMAT_VERSION = 1
16
+
17
+
18
+ def _cpu_state_dict(model: torch.nn.Module) -> dict[str, torch.Tensor]:
19
+ """Copy state tensors to CPU without changing the live model's device."""
20
+ return {
21
+ name: value.detach().cpu().clone() for name, value in model.state_dict().items()
22
+ }
23
+
24
+
25
+ def _scaler_payload(scaler: StandardScaler | None) -> dict[str, object] | None:
26
+ if scaler is None:
27
+ return None
28
+ return {
29
+ "mean": torch.as_tensor(scaler.mean_, dtype=torch.float64).clone(),
30
+ "scale": torch.as_tensor(scaler.scale_, dtype=torch.float64).clone(),
31
+ "var": torch.as_tensor(scaler.var_, dtype=torch.float64).clone(),
32
+ "n_features_in": int(scaler.n_features_in_),
33
+ "n_samples_seen": int(np.asarray(scaler.n_samples_seen_).item()),
34
+ }
35
+
36
+
37
+ def _primitive_constructor(params: dict[str, object]) -> dict[str, object]:
38
+ result = dict(params)
39
+ result["hidden_dims"] = list(result["hidden_dims"])
40
+ return result
41
+
42
+
43
+ def save_snapshot(estimator: Any, *, kind: str, path: str | Path) -> None:
44
+ """Atomically write a restricted inference snapshot for a fitted estimator."""
45
+ destination = Path(path)
46
+ if not destination.parent.exists():
47
+ raise ValueError(f"snapshot directory does not exist: {destination.parent}")
48
+ if not destination.parent.is_dir():
49
+ raise ValueError(f"snapshot parent is not a directory: {destination.parent}")
50
+ payload: dict[str, object] = {
51
+ "format_version": FORMAT_VERSION,
52
+ "estimator_kind": kind,
53
+ "constructor": _primitive_constructor(estimator.get_params(deep=False)),
54
+ "model_state": _cpu_state_dict(estimator.model_),
55
+ "fitted": {
56
+ "n_features_in": int(estimator.n_features_in_),
57
+ "n_iter": int(estimator.n_iter_),
58
+ "best_epoch": estimator.best_epoch_,
59
+ "history": estimator.history_,
60
+ "feature_scaler": _scaler_payload(estimator.feature_scaler_),
61
+ },
62
+ "producer": {
63
+ "exponet": "0.1.0",
64
+ "torch": str(torch.__version__),
65
+ "numpy": np.__version__,
66
+ "scikit_learn": sklearn.__version__,
67
+ },
68
+ }
69
+ fitted = payload["fitted"]
70
+ assert isinstance(fitted, dict)
71
+ if kind == "regressor":
72
+ fitted.update(
73
+ {
74
+ "n_outputs": int(estimator.n_outputs_),
75
+ "target_was_1d": bool(estimator._target_was_1d),
76
+ "target_scaler": _scaler_payload(estimator.target_scaler_),
77
+ }
78
+ )
79
+ elif kind == "classifier":
80
+ fitted.update(
81
+ {
82
+ "n_classes": int(estimator.n_classes_),
83
+ "classes": estimator.classes_.tolist(),
84
+ }
85
+ )
86
+ else:
87
+ raise ValueError(f"unsupported estimator kind: {kind!r}")
88
+
89
+ temporary_name: str | None = None
90
+ try:
91
+ with tempfile.NamedTemporaryFile(
92
+ mode="wb",
93
+ dir=destination.parent,
94
+ prefix=f".{destination.name}.",
95
+ delete=False,
96
+ ) as temporary:
97
+ temporary_name = temporary.name
98
+ torch.save(payload, temporary_name)
99
+ os.replace(temporary_name, destination)
100
+ temporary_name = None
101
+ finally:
102
+ if temporary_name is not None:
103
+ Path(temporary_name).unlink(missing_ok=True)
104
+
105
+
106
+ def load_snapshot(path: str | Path, *, expected_kind: str) -> dict[str, object]:
107
+ """Read and validate only the outer restricted snapshot envelope."""
108
+ try:
109
+ payload = torch.load(path, map_location="cpu", weights_only=True)
110
+ except Exception as error:
111
+ raise ValueError("invalid or unreadable ExpoNet snapshot") from error
112
+ if not isinstance(payload, dict):
113
+ raise ValueError("snapshot must contain a dictionary payload")
114
+ if payload.get("format_version") != FORMAT_VERSION:
115
+ raise ValueError("unsupported ExpoNet snapshot format version")
116
+ if payload.get("estimator_kind") != expected_kind:
117
+ raise ValueError(
118
+ f"snapshot is for {payload.get('estimator_kind')!r}, not {expected_kind!r}"
119
+ )
120
+ if not isinstance(payload.get("constructor"), dict):
121
+ raise ValueError("snapshot constructor metadata is invalid")
122
+ if not isinstance(payload.get("fitted"), dict):
123
+ raise ValueError("snapshot fitted metadata is invalid")
124
+ if not isinstance(payload.get("model_state"), dict) or not all(
125
+ isinstance(name, str) and isinstance(value, torch.Tensor)
126
+ for name, value in payload["model_state"].items()
127
+ ):
128
+ raise ValueError("snapshot model state is invalid")
129
+ return payload
130
+
131
+
132
+ def restore_scaler(
133
+ payload: object, *, expected_features: int, name: str
134
+ ) -> StandardScaler | None:
135
+ """Rebuild a fitted StandardScaler from finite tensor-only metadata."""
136
+ if payload is None:
137
+ return None
138
+ if not isinstance(payload, dict):
139
+ raise ValueError(f"snapshot {name} metadata is invalid")
140
+ try:
141
+ count = payload["n_features_in"]
142
+ seen = payload["n_samples_seen"]
143
+ mean = payload["mean"]
144
+ scale = payload["scale"]
145
+ var = payload["var"]
146
+ except KeyError as error:
147
+ raise ValueError(f"snapshot {name} metadata is incomplete") from error
148
+ if (
149
+ not isinstance(count, int)
150
+ or isinstance(count, bool)
151
+ or count != expected_features
152
+ or not isinstance(seen, int)
153
+ or isinstance(seen, bool)
154
+ or seen <= 0
155
+ or not all(isinstance(value, torch.Tensor) for value in (mean, scale, var))
156
+ ):
157
+ raise ValueError(f"snapshot {name} metadata is invalid")
158
+ arrays = [
159
+ value.detach().cpu().numpy().astype(np.float64, copy=True)
160
+ for value in (mean, scale, var)
161
+ ]
162
+ if any(array.shape != (expected_features,) for array in arrays) or not all(
163
+ np.isfinite(array).all() for array in arrays
164
+ ):
165
+ raise ValueError(f"snapshot {name} arrays are invalid")
166
+ if np.any(arrays[1] <= 0) or np.any(arrays[2] < 0):
167
+ raise ValueError(f"snapshot {name} arrays are invalid")
168
+ scaler = StandardScaler()
169
+ scaler.mean_, scaler.scale_, scaler.var_ = arrays
170
+ scaler.n_features_in_ = expected_features
171
+ scaler.n_samples_seen_ = seen
172
+ return scaler