phasmanet 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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ioannis Tsioulis, Anastasios Tefas, Nikolaos Stergioulas
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,151 @@
1
+ Metadata-Version: 2.4
2
+ Name: phasmanet
3
+ Version: 0.1.0
4
+ Summary: PhasmaNet: a staged spectral token-refinement architecture for tabular regression and classification, with a pretrained gravitational-wave SNR surrogate.
5
+ Author: Ioannis Tsioulis, Anastasios Tefas, Nikolaos Stergioulas
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/giannis00/PhasmaNet-SNR
8
+ Project-URL: Source, https://github.com/giannis00/PhasmaNet-SNR
9
+ Project-URL: Issues, https://github.com/giannis00/PhasmaNet-SNR/issues
10
+ Keywords: deep learning,tabular data,neural surrogate,pytorch,gravitational waves,signal-to-noise ratio,selection effects
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
17
+ Classifier: Topic :: Scientific/Engineering :: Astronomy
18
+ Classifier: Topic :: Scientific/Engineering :: Physics
19
+ Requires-Python: >=3.9
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: numpy>=1.21
23
+ Requires-Dist: scipy>=1.7
24
+ Requires-Dist: torch>=1.13
25
+ Provides-Extra: sklearn
26
+ Requires-Dist: scikit-learn>=1.0; extra == "sklearn"
27
+ Provides-Extra: test
28
+ Requires-Dist: pytest>=7; extra == "test"
29
+ Requires-Dist: scikit-learn>=1.0; extra == "test"
30
+ Requires-Dist: pandas>=1.3; extra == "test"
31
+ Dynamic: license-file
32
+
33
+ <p align="center">
34
+ <img src="https://raw.githubusercontent.com/giannis00/PhasmaNet-SNR/main/assets/phasmanet_symbol.png" alt="PhasmaNet symbol" width="140">
35
+ </p>
36
+
37
+ <h1 align="center">PhasmaNet</h1>
38
+
39
+ PhasmaNet (from Greek *φάσμα*, spectrum) is a compact neural architecture for structured
40
+ tabular data. Learned input tokens are refined by an ordered cascade of attention-based
41
+ **Φ-Stages**, each followed by a **Φ-Refractor** subspace-mixing module, and then revisited
42
+ through gated top-down **Φ-Feedback** passes.
43
+
44
+ This package provides:
45
+
46
+ * `PhasmaNetRegressor` and `PhasmaNetClassifier`: estimators with the familiar
47
+ `fit` / `predict` interface, usable on any table of numerical features and compatible
48
+ with scikit-learn tools (`cross_val_score`, `Pipeline`, `clone`).
49
+ * `phasmanet.gw.optimal_snr`: a pretrained surrogate for the phase-averaged optimal
50
+ signal-to-noise ratio of precessing, higher-mode binary black hole signals.
51
+ * `PhasmaNet`, the PyTorch module itself, and `MonotoneCalibrator`, the rank-preserving
52
+ PCHIP calibration used in the paper.
53
+
54
+ ## Installation
55
+
56
+ ```bash
57
+ pip install phasmanet
58
+ ```
59
+
60
+ PhasmaNet needs PyTorch. For GPU support, install the PyTorch build that matches your
61
+ system first, following https://pytorch.org/get-started/locally/, and then install
62
+ `phasmanet`. scikit-learn is optional (`pip install "phasmanet[sklearn]"`).
63
+
64
+ ## Regression and classification on your own data
65
+
66
+ ```python
67
+ from phasmanet import PhasmaNetRegressor
68
+
69
+ reg = PhasmaNetRegressor(random_state=0) # architecture of the paper by default
70
+ reg.fit(X_train, y_train) # early stopping on a 15% validation split
71
+ y_pred = reg.predict(X_test)
72
+ print(reg.score(X_test, y_test)) # R^2
73
+
74
+ reg.save("model.pt")
75
+ reg = PhasmaNetRegressor.load("model.pt")
76
+ ```
77
+
78
+ ```python
79
+ from phasmanet import PhasmaNetClassifier
80
+
81
+ clf = PhasmaNetClassifier(random_state=0).fit(X_train, y_train)
82
+ proba = clf.predict_proba(X_test)
83
+ ```
84
+
85
+ Inputs are standardised internally, so raw features can be passed directly.
86
+
87
+ ### Hyperparameters
88
+
89
+ Every hyperparameter is a constructor argument; the defaults are the configuration used in
90
+ the paper (about 0.73 M parameters).
91
+
92
+ | Group | Argument (default) |
93
+ |---|---|
94
+ | Architecture | `num_stages` (3), `num_feedback` (2), `num_tokens` (8), `hidden_dim` (128), `heads` (4), `num_refractor_subspaces` (4), `use_refractor` (True), `ffn_mult` (4), `activation` ("gelu"; also "relu", "silu", "tanh", "elu", "leaky_relu"), `dropout` (0.1), `attn_dropout` (0.0) |
95
+ | Optimisation | `optimizer` ("adamw", "adam", "sgd"), `lr` (1e-3), `weight_decay` (1e-4), `momentum` (0.9, SGD), `batch_size` (128), `grad_clip` (None) |
96
+ | Schedule and stopping | `max_epochs` (150), `patience` (20), `min_delta` (1e-6), `scheduler` ("plateau", "cosine", "none"), `lr_patience` (5), `lr_factor` (0.5), `validation_fraction` (0.15) |
97
+ | Regression only | `loss` ("mse" or "huber"), `huber_delta` (1.0), `weighting` ("none", "by_hist", "inverse_freq"), `hist_bins` (20), `wmin` (0.2), `wmax` (5.0), `calibrate` (False), `calibration_bins` (60), `calibration_min_bin` (20) |
98
+ | Classification only | `class_weight` (None or "balanced") |
99
+ | Other | `device` ("auto"), `random_state` (None), `verbose` (0) |
100
+
101
+ `fit` also accepts `sample_weight` and `validation_data=(X_val, y_val)`. After fitting,
102
+ `history_` holds the training and validation loss and the learning rate per epoch.
103
+ Because the estimators follow the scikit-learn conventions, hyperparameters can be
104
+ searched with `GridSearchCV`, `RandomizedSearchCV` or Optuna.
105
+
106
+ The defaults were chosen for smooth, low-noise targets such as simulator outputs; for
107
+ other data, tune at least `hidden_dim`, `num_tokens`, `lr` and `max_epochs`.
108
+
109
+ ## Optimal SNR of binary black hole signals
110
+
111
+ ```python
112
+ import numpy as np
113
+ from phasmanet.gw import optimal_snr, chirp_mass, mass_ratio, spins_from_spherical
114
+
115
+ m1, m2 = np.array([36.0, 10.0]), np.array([29.0, 8.0]) # detector-frame masses, Msun
116
+ rho = optimal_snr(
117
+ mchirp=chirp_mass(m1, m2),
118
+ q=mass_ratio(m1, m2), # m_heavy / m_light >= 1
119
+ cos_inclination=np.array([0.3, -0.8]),
120
+ spin1=spins_from_spherical([0.5, 0.1], [0.4, 2.0], [1.0, 3.0]),
121
+ spin2=spins_from_spherical([0.2, 0.0], [1.0, 0.0], [0.0, 0.0]),
122
+ f_plus=np.array([0.4, -0.2]),
123
+ f_cross=np.array([0.1, 0.6]),
124
+ distance=np.array([410.0, 800.0]), # luminosity distance, Mpc
125
+ )
126
+ ```
127
+
128
+ Configuration of the pretrained model: single LIGO-Hanford detector, Advanced LIGO
129
+ zero-detuned high-power PSD, IMRPhenomXPHM waveforms, f_low = 10 Hz, SNR averaged over
130
+ the coalescence phase. Training covered component masses 3 to 170 Msun (detector frame),
131
+ spin magnitudes up to 0.99 and SNR below 200 at 1000 Mpc. The optimal SNR scales exactly
132
+ as 1/d_L, which `optimal_snr` applies for the given distance. Inputs outside the training
133
+ domain produce a warning. Throughput depends strongly on the hardware: a GPU is
134
+ recommended for catalogue-scale evaluations (millions of injections). Antenna patterns `f_plus`, `f_cross` can be computed with
135
+ PyCBC, Bilby or LALSuite for any sky position, polarisation and time.
136
+
137
+ ## Reproducing the paper
138
+
139
+ The training scripts, baselines and diagnostics used in the paper are in the
140
+ repository: https://github.com/giannis00/PhasmaNet-SNR
141
+
142
+ ## Citation
143
+
144
+ If you use PhasmaNet, please cite the paper (see `CITATION.cff`):
145
+
146
+ > I. Tsioulis, A. Tefas, N. Stergioulas, *ΦasmaNet: A Staged Spectral Token-Refinement
147
+ > Architecture for Fast Gravitational-Wave Signal-to-Noise Ratio Prediction*, arXiv (2026).
148
+
149
+ ## License
150
+
151
+ MIT. See `LICENSE`.
@@ -0,0 +1,119 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/giannis00/PhasmaNet-SNR/main/assets/phasmanet_symbol.png" alt="PhasmaNet symbol" width="140">
3
+ </p>
4
+
5
+ <h1 align="center">PhasmaNet</h1>
6
+
7
+ PhasmaNet (from Greek *φάσμα*, spectrum) is a compact neural architecture for structured
8
+ tabular data. Learned input tokens are refined by an ordered cascade of attention-based
9
+ **Φ-Stages**, each followed by a **Φ-Refractor** subspace-mixing module, and then revisited
10
+ through gated top-down **Φ-Feedback** passes.
11
+
12
+ This package provides:
13
+
14
+ * `PhasmaNetRegressor` and `PhasmaNetClassifier`: estimators with the familiar
15
+ `fit` / `predict` interface, usable on any table of numerical features and compatible
16
+ with scikit-learn tools (`cross_val_score`, `Pipeline`, `clone`).
17
+ * `phasmanet.gw.optimal_snr`: a pretrained surrogate for the phase-averaged optimal
18
+ signal-to-noise ratio of precessing, higher-mode binary black hole signals.
19
+ * `PhasmaNet`, the PyTorch module itself, and `MonotoneCalibrator`, the rank-preserving
20
+ PCHIP calibration used in the paper.
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ pip install phasmanet
26
+ ```
27
+
28
+ PhasmaNet needs PyTorch. For GPU support, install the PyTorch build that matches your
29
+ system first, following https://pytorch.org/get-started/locally/, and then install
30
+ `phasmanet`. scikit-learn is optional (`pip install "phasmanet[sklearn]"`).
31
+
32
+ ## Regression and classification on your own data
33
+
34
+ ```python
35
+ from phasmanet import PhasmaNetRegressor
36
+
37
+ reg = PhasmaNetRegressor(random_state=0) # architecture of the paper by default
38
+ reg.fit(X_train, y_train) # early stopping on a 15% validation split
39
+ y_pred = reg.predict(X_test)
40
+ print(reg.score(X_test, y_test)) # R^2
41
+
42
+ reg.save("model.pt")
43
+ reg = PhasmaNetRegressor.load("model.pt")
44
+ ```
45
+
46
+ ```python
47
+ from phasmanet import PhasmaNetClassifier
48
+
49
+ clf = PhasmaNetClassifier(random_state=0).fit(X_train, y_train)
50
+ proba = clf.predict_proba(X_test)
51
+ ```
52
+
53
+ Inputs are standardised internally, so raw features can be passed directly.
54
+
55
+ ### Hyperparameters
56
+
57
+ Every hyperparameter is a constructor argument; the defaults are the configuration used in
58
+ the paper (about 0.73 M parameters).
59
+
60
+ | Group | Argument (default) |
61
+ |---|---|
62
+ | Architecture | `num_stages` (3), `num_feedback` (2), `num_tokens` (8), `hidden_dim` (128), `heads` (4), `num_refractor_subspaces` (4), `use_refractor` (True), `ffn_mult` (4), `activation` ("gelu"; also "relu", "silu", "tanh", "elu", "leaky_relu"), `dropout` (0.1), `attn_dropout` (0.0) |
63
+ | Optimisation | `optimizer` ("adamw", "adam", "sgd"), `lr` (1e-3), `weight_decay` (1e-4), `momentum` (0.9, SGD), `batch_size` (128), `grad_clip` (None) |
64
+ | Schedule and stopping | `max_epochs` (150), `patience` (20), `min_delta` (1e-6), `scheduler` ("plateau", "cosine", "none"), `lr_patience` (5), `lr_factor` (0.5), `validation_fraction` (0.15) |
65
+ | Regression only | `loss` ("mse" or "huber"), `huber_delta` (1.0), `weighting` ("none", "by_hist", "inverse_freq"), `hist_bins` (20), `wmin` (0.2), `wmax` (5.0), `calibrate` (False), `calibration_bins` (60), `calibration_min_bin` (20) |
66
+ | Classification only | `class_weight` (None or "balanced") |
67
+ | Other | `device` ("auto"), `random_state` (None), `verbose` (0) |
68
+
69
+ `fit` also accepts `sample_weight` and `validation_data=(X_val, y_val)`. After fitting,
70
+ `history_` holds the training and validation loss and the learning rate per epoch.
71
+ Because the estimators follow the scikit-learn conventions, hyperparameters can be
72
+ searched with `GridSearchCV`, `RandomizedSearchCV` or Optuna.
73
+
74
+ The defaults were chosen for smooth, low-noise targets such as simulator outputs; for
75
+ other data, tune at least `hidden_dim`, `num_tokens`, `lr` and `max_epochs`.
76
+
77
+ ## Optimal SNR of binary black hole signals
78
+
79
+ ```python
80
+ import numpy as np
81
+ from phasmanet.gw import optimal_snr, chirp_mass, mass_ratio, spins_from_spherical
82
+
83
+ m1, m2 = np.array([36.0, 10.0]), np.array([29.0, 8.0]) # detector-frame masses, Msun
84
+ rho = optimal_snr(
85
+ mchirp=chirp_mass(m1, m2),
86
+ q=mass_ratio(m1, m2), # m_heavy / m_light >= 1
87
+ cos_inclination=np.array([0.3, -0.8]),
88
+ spin1=spins_from_spherical([0.5, 0.1], [0.4, 2.0], [1.0, 3.0]),
89
+ spin2=spins_from_spherical([0.2, 0.0], [1.0, 0.0], [0.0, 0.0]),
90
+ f_plus=np.array([0.4, -0.2]),
91
+ f_cross=np.array([0.1, 0.6]),
92
+ distance=np.array([410.0, 800.0]), # luminosity distance, Mpc
93
+ )
94
+ ```
95
+
96
+ Configuration of the pretrained model: single LIGO-Hanford detector, Advanced LIGO
97
+ zero-detuned high-power PSD, IMRPhenomXPHM waveforms, f_low = 10 Hz, SNR averaged over
98
+ the coalescence phase. Training covered component masses 3 to 170 Msun (detector frame),
99
+ spin magnitudes up to 0.99 and SNR below 200 at 1000 Mpc. The optimal SNR scales exactly
100
+ as 1/d_L, which `optimal_snr` applies for the given distance. Inputs outside the training
101
+ domain produce a warning. Throughput depends strongly on the hardware: a GPU is
102
+ recommended for catalogue-scale evaluations (millions of injections). Antenna patterns `f_plus`, `f_cross` can be computed with
103
+ PyCBC, Bilby or LALSuite for any sky position, polarisation and time.
104
+
105
+ ## Reproducing the paper
106
+
107
+ The training scripts, baselines and diagnostics used in the paper are in the
108
+ repository: https://github.com/giannis00/PhasmaNet-SNR
109
+
110
+ ## Citation
111
+
112
+ If you use PhasmaNet, please cite the paper (see `CITATION.cff`):
113
+
114
+ > I. Tsioulis, A. Tefas, N. Stergioulas, *ΦasmaNet: A Staged Spectral Token-Refinement
115
+ > Architecture for Fast Gravitational-Wave Signal-to-Noise Ratio Prediction*, arXiv (2026).
116
+
117
+ ## License
118
+
119
+ MIT. See `LICENSE`.
@@ -0,0 +1,58 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "phasmanet"
7
+ dynamic = ["version"]
8
+ description = "PhasmaNet: a staged spectral token-refinement architecture for tabular regression and classification, with a pretrained gravitational-wave SNR surrogate."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [
14
+ { name = "Ioannis Tsioulis" },
15
+ { name = "Anastasios Tefas" },
16
+ { name = "Nikolaos Stergioulas" },
17
+ ]
18
+ keywords = [
19
+ "deep learning", "tabular data", "neural surrogate", "pytorch",
20
+ "gravitational waves", "signal-to-noise ratio", "selection effects",
21
+ ]
22
+ classifiers = [
23
+ "Development Status :: 4 - Beta",
24
+ "Intended Audience :: Science/Research",
25
+ "Operating System :: OS Independent",
26
+ "Programming Language :: Python :: 3",
27
+ "Programming Language :: Python :: 3 :: Only",
28
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
29
+ "Topic :: Scientific/Engineering :: Astronomy",
30
+ "Topic :: Scientific/Engineering :: Physics",
31
+ ]
32
+ dependencies = [
33
+ "numpy>=1.21",
34
+ "scipy>=1.7",
35
+ "torch>=1.13",
36
+ ]
37
+
38
+ [project.optional-dependencies]
39
+ sklearn = ["scikit-learn>=1.0"]
40
+ test = ["pytest>=7", "scikit-learn>=1.0", "pandas>=1.3"]
41
+
42
+ [project.urls]
43
+ Homepage = "https://github.com/giannis00/PhasmaNet-SNR"
44
+ Source = "https://github.com/giannis00/PhasmaNet-SNR"
45
+ Issues = "https://github.com/giannis00/PhasmaNet-SNR/issues"
46
+
47
+ [tool.setuptools.dynamic]
48
+ version = { attr = "phasmanet._version.__version__" }
49
+
50
+ [tool.setuptools.packages.find]
51
+ where = ["src"]
52
+
53
+ [tool.setuptools.package-data]
54
+ phasmanet = ["py.typed"]
55
+ "phasmanet.gw" = ["data/*.pt"]
56
+
57
+ [tool.pytest.ini_options]
58
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,23 @@
1
+ """PhasmaNet: a staged spectral token-refinement architecture for tabular data.
2
+
3
+ Quick start::
4
+
5
+ from phasmanet import PhasmaNetRegressor
6
+ reg = PhasmaNetRegressor(random_state=0).fit(X_train, y_train)
7
+ y_pred = reg.predict(X_test)
8
+ """
9
+ from ._version import __version__
10
+ from .calibration import MonotoneCalibrator
11
+ from .estimators import PhasmaNetClassifier, PhasmaNetRegressor
12
+ from .model import PhasmaNet, PhiRefractor, PhiStage, convert_legacy_state_dict
13
+
14
+ __all__ = [
15
+ "__version__",
16
+ "PhasmaNet",
17
+ "PhiStage",
18
+ "PhiRefractor",
19
+ "PhasmaNetRegressor",
20
+ "PhasmaNetClassifier",
21
+ "MonotoneCalibrator",
22
+ "convert_legacy_state_dict",
23
+ ]
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,96 @@
1
+ """Rank-preserving monotonic (PCHIP) recalibration of point predictions.
2
+
3
+ Knots are the medians of (prediction, target) in quantile bins of the predictions,
4
+ made non-decreasing and extended to the observed range, then joined by a monotone
5
+ piecewise cubic Hermite interpolant. The calibrator is fitted on a validation split and
6
+ applied unchanged to new predictions.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from typing import Dict, Optional
11
+
12
+ import numpy as np
13
+
14
+ __all__ = ["MonotoneCalibrator"]
15
+
16
+
17
+ class MonotoneCalibrator:
18
+ """Smooth monotonic calibrator mapping raw predictions to calibrated predictions."""
19
+
20
+ def __init__(self, xk: Optional[np.ndarray] = None, yk: Optional[np.ndarray] = None):
21
+ self.xk = None if xk is None else np.asarray(xk, dtype=np.float64)
22
+ self.yk = None if yk is None else np.asarray(yk, dtype=np.float64)
23
+ self._pchip = None
24
+ if self.xk is not None:
25
+ self._build()
26
+
27
+ @property
28
+ def is_identity(self) -> bool:
29
+ return self.xk is None
30
+
31
+ def _build(self) -> None:
32
+ from scipy.interpolate import PchipInterpolator
33
+
34
+ if self.xk.size < 3 or self.yk.size != self.xk.size:
35
+ raise ValueError("need at least 3 knots with matching xk and yk")
36
+ if np.any(np.diff(self.xk) <= 0):
37
+ raise ValueError("xk must be strictly increasing")
38
+ self._pchip = PchipInterpolator(self.xk, self.yk, extrapolate=True)
39
+
40
+ def fit(self, pred, true, bins: int = 60, min_bin: int = 20) -> "MonotoneCalibrator":
41
+ """Fit knots from validation predictions ``pred`` and targets ``true``.
42
+
43
+ If there are too few distinct bins the calibrator stays the identity map.
44
+ """
45
+ pred = np.asarray(pred, dtype=np.float64).ravel()
46
+ true = np.asarray(true, dtype=np.float64).ravel()
47
+ if pred.shape != true.shape:
48
+ raise ValueError("pred and true must have the same length")
49
+ self.xk = self.yk = self._pchip = None
50
+
51
+ edges = np.unique(np.quantile(pred, np.linspace(0.0, 1.0, bins + 1)))
52
+ if len(edges) < 4:
53
+ return self
54
+
55
+ xk, yk = [], []
56
+ for lo, hi in zip(edges[:-1], edges[1:]):
57
+ m = (pred >= lo) & (pred < hi)
58
+ if m.sum() >= min_bin:
59
+ xk.append(np.median(pred[m]))
60
+ yk.append(np.median(true[m]))
61
+ xk = np.asarray(xk, dtype=np.float64)
62
+ yk = np.asarray(yk, dtype=np.float64)
63
+ if xk.size < 3:
64
+ return self
65
+
66
+ yk = np.maximum.accumulate(yk)
67
+ xk, keep = np.unique(xk, return_index=True)
68
+ yk = yk[keep]
69
+
70
+ x_min, x_max = pred.min(), pred.max()
71
+ y_min, y_max = true.min(), true.max()
72
+ if x_min < xk[0]:
73
+ xk = np.r_[x_min, xk]
74
+ yk = np.r_[min(y_min, yk[0]), yk]
75
+ if x_max > xk[-1]:
76
+ xk = np.r_[xk, x_max]
77
+ yk = np.r_[yk, max(y_max, yk[-1])]
78
+ self.xk, self.yk = xk, np.maximum.accumulate(yk)
79
+ self._build()
80
+ return self
81
+
82
+ def __call__(self, x) -> np.ndarray:
83
+ x = np.asarray(x, dtype=np.float64)
84
+ return x.copy() if self._pchip is None else self._pchip(x)
85
+
86
+ transform = __call__
87
+
88
+ def to_dict(self) -> Dict[str, list]:
89
+ if self.is_identity:
90
+ return {"xk": [], "yk": []}
91
+ return {"xk": self.xk.tolist(), "yk": self.yk.tolist()}
92
+
93
+ @classmethod
94
+ def from_dict(cls, d: Dict[str, list]) -> "MonotoneCalibrator":
95
+ xk, yk = d.get("xk") or [], d.get("yk") or []
96
+ return cls() if len(xk) == 0 else cls(xk, yk)