classy-szlite 0.2.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 Boris Bolliet and the CLASS-SZ 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.
@@ -0,0 +1,148 @@
1
+ Metadata-Version: 2.4
2
+ Name: classy_szlite
3
+ Version: 0.2.0
4
+ Summary: Fast, differentiable cosmology in pure JAX. CMB Cls, matter Pk (linear and nonlinear), distances, derived parameters, and halo-model tSZ Cl^yy via high-accuracy ede-v2 CosmoPower emulators.
5
+ Author-email: Boris Bolliet <boris.bolliet@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/CLASS-SZ/classy_szlite
8
+ Project-URL: Source, https://github.com/CLASS-SZ/classy_szlite
9
+ Keywords: cosmology,cmb,tsz,halo-model,jax,ede
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Scientific/Engineering :: Astronomy
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: jax>=0.4
20
+ Requires-Dist: numpy>=1.24
21
+ Requires-Dist: mcfit>=0.0.21
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest>=7; extra == "dev"
24
+ Requires-Dist: pytest-xdist; extra == "dev"
25
+ Provides-Extra: plots
26
+ Requires-Dist: matplotlib>=3.6; extra == "plots"
27
+ Dynamic: license-file
28
+
29
+ # classy_szlite
30
+
31
+ [![tests](https://github.com/CLASS-SZ/classy_szlite/actions/workflows/test.yml/badge.svg)](https://github.com/CLASS-SZ/classy_szlite/actions/workflows/test.yml)
32
+ [![PyPI](https://img.shields.io/pypi/v/classy_szlite.svg)](https://pypi.org/project/classy_szlite/)
33
+ [![Documentation Status](https://readthedocs.org/projects/classy-szlite/badge/?version=latest)](https://classy-szlite.readthedocs.io/en/latest/)
34
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
35
+
36
+ **Fast, differentiable cosmology in pure JAX.**
37
+
38
+ `classy_szlite` provides JIT-compiled, `jax.grad`-friendly access to:
39
+
40
+ - **CMB angular power spectra** — TT, TE, EE
41
+ - **Linear and nonlinear matter Pk** — P(k, z), Pnl(k, z)
42
+ - **Distances** — H(z), comoving χ(z), angular-diameter D_A(z)
43
+ - **Derived parameters** — σ8, Ω_m, S8
44
+ - **Halo-model tSZ Cl^yy** — Arnaud 2010 GNFW pressure profile
45
+
46
+ Backed by the high-accuracy **ede-v2 CosmoPower emulators**. LCDM-equivalent
47
+ cosmology is the default — `fEDE = 0.001` (the package's default) reproduces
48
+ standard ΛCDM to emulator precision, and the same emulator covers the full
49
+ early-dark-energy parameter space if you want to explore it.
50
+
51
+ Runtime dependencies: `jax`, `numpy`, `mcfit`.
52
+
53
+ ## Install
54
+
55
+ ```bash
56
+ pip install classy_szlite
57
+ ```
58
+
59
+ Or from source:
60
+
61
+ ```bash
62
+ git clone https://github.com/CLASS-SZ/classy_szlite
63
+ cd classy_szlite
64
+ pip install -e .
65
+ ```
66
+
67
+ You also need the CosmoPower emulator `.npz` files at `~/class_sz_data/`
68
+ (or the path in `$CLASSY_SZLITE_DATA_DIR`). See
69
+ [Installation](https://classy-szlite.readthedocs.io/en/latest/installation.html).
70
+
71
+ ## Quick start
72
+
73
+ ```python
74
+ import jax.numpy as jnp
75
+ import classy_szlite as csl
76
+
77
+ cosmo = csl.CosmoParams() # Planck-18 + ede-v2 LCDM-equivalent defaults
78
+
79
+ # Derived parameters
80
+ csl.derived(cosmo)
81
+ # → {'sigma_8': 0.812, 'Omega_m': 0.311, 'S8': 0.827, 'der_full': ...}
82
+
83
+ # CMB Cls (dimensionless D_ℓ; × Tcmb² for μK²)
84
+ csl.cl_TTTEEE(cosmo)
85
+ # → {'ell', 'tt', 'te', 'ee'}
86
+
87
+ # Matter Pk at multiple z
88
+ k, pk = csl.Pk(cosmo, [0., 0.5, 1., 2.])
89
+ k, pnl = csl.Pnl(cosmo, [0., 0.5, 1., 2.])
90
+
91
+ # Distances
92
+ Hz, chi, Da = csl.distances(cosmo, [0.1, 0.5, 1.0])
93
+
94
+ # Halo-model tSZ Cl^yy
95
+ profile = csl.ProfileParamsA10(P0=8.13, beta=5.48, B=1.25)
96
+ ell = jnp.geomspace(2, 9000, 80)
97
+ cl_1h, cl_2h = csl.cl_yy(cosmo, profile, ell)
98
+
99
+ # MCMC fast path: precompute cosmology + halo grids once → ~5 ms/call
100
+ ev = csl.cl_yy_factory(cosmo, ell)
101
+ cl_1h, cl_2h = ev(profile)
102
+ ```
103
+
104
+ ## Throughput
105
+
106
+ Warm-call timing, n = 100 calls per benchmark, freshly randomised inputs:
107
+
108
+ | Function | mean ± std (ms) | calls/s |
109
+ | --- | --- | --- |
110
+ | `derived` | 0.54 ± 0.04 | 1850 ± 150 |
111
+ | `cl_TTTEEE` | 2.52 ± 0.14 | 400 ± 25 |
112
+ | `Pk` | 1.49 ± 0.12 | 670 ± 55 |
113
+ | `distances` | 1.29 ± 0.09 | 770 ± 60 |
114
+ | `cl_yy` (full pipeline) | 17.84 ± 0.58 | 56 ± 2 |
115
+ | `cl_yy_factory` (fixed-cosmo) | 5.38 ± 0.42 | 185 ± 15 |
116
+ | `cl_yy_factory + jax.grad` | 17.12 ± 1.01 | 58 ± 3 |
117
+
118
+ Reference platform: macOS arm64 (M-series CPU), single-thread JAX. See
119
+ [Throughput](https://classy-szlite.readthedocs.io/en/latest/throughput.html)
120
+ for a more detailed table + reproduction script.
121
+
122
+ ## JAX gradients
123
+
124
+ All public functions are JAX-traceable. The factory closure is the
125
+ recommended path for gradient-based inference at fixed cosmology:
126
+
127
+ ```python
128
+ import jax
129
+ ev = csl.cl_yy_factory(cosmo, ell)
130
+
131
+ def loss(P0, beta):
132
+ cl_1h, cl_2h = ev(csl.ProfileParamsA10(P0=P0, beta=beta, B=1.25))
133
+ return jnp.sum(cl_1h + cl_2h)
134
+
135
+ d_loss = jax.grad(loss, argnums=(0, 1))(8.13, 5.48)
136
+ ```
137
+
138
+ Gradients also work through the full pipeline (cosmology + profile) and
139
+ w.r.t. the whole `CosmoParams` pytree — see
140
+ [Gradients](https://classy-szlite.readthedocs.io/en/latest/gradients.html).
141
+
142
+ ## Documentation
143
+
144
+ Full docs at **https://classy-szlite.readthedocs.io**.
145
+
146
+ ## License
147
+
148
+ MIT.
@@ -0,0 +1,120 @@
1
+ # classy_szlite
2
+
3
+ [![tests](https://github.com/CLASS-SZ/classy_szlite/actions/workflows/test.yml/badge.svg)](https://github.com/CLASS-SZ/classy_szlite/actions/workflows/test.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/classy_szlite.svg)](https://pypi.org/project/classy_szlite/)
5
+ [![Documentation Status](https://readthedocs.org/projects/classy-szlite/badge/?version=latest)](https://classy-szlite.readthedocs.io/en/latest/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
+
8
+ **Fast, differentiable cosmology in pure JAX.**
9
+
10
+ `classy_szlite` provides JIT-compiled, `jax.grad`-friendly access to:
11
+
12
+ - **CMB angular power spectra** — TT, TE, EE
13
+ - **Linear and nonlinear matter Pk** — P(k, z), Pnl(k, z)
14
+ - **Distances** — H(z), comoving χ(z), angular-diameter D_A(z)
15
+ - **Derived parameters** — σ8, Ω_m, S8
16
+ - **Halo-model tSZ Cl^yy** — Arnaud 2010 GNFW pressure profile
17
+
18
+ Backed by the high-accuracy **ede-v2 CosmoPower emulators**. LCDM-equivalent
19
+ cosmology is the default — `fEDE = 0.001` (the package's default) reproduces
20
+ standard ΛCDM to emulator precision, and the same emulator covers the full
21
+ early-dark-energy parameter space if you want to explore it.
22
+
23
+ Runtime dependencies: `jax`, `numpy`, `mcfit`.
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ pip install classy_szlite
29
+ ```
30
+
31
+ Or from source:
32
+
33
+ ```bash
34
+ git clone https://github.com/CLASS-SZ/classy_szlite
35
+ cd classy_szlite
36
+ pip install -e .
37
+ ```
38
+
39
+ You also need the CosmoPower emulator `.npz` files at `~/class_sz_data/`
40
+ (or the path in `$CLASSY_SZLITE_DATA_DIR`). See
41
+ [Installation](https://classy-szlite.readthedocs.io/en/latest/installation.html).
42
+
43
+ ## Quick start
44
+
45
+ ```python
46
+ import jax.numpy as jnp
47
+ import classy_szlite as csl
48
+
49
+ cosmo = csl.CosmoParams() # Planck-18 + ede-v2 LCDM-equivalent defaults
50
+
51
+ # Derived parameters
52
+ csl.derived(cosmo)
53
+ # → {'sigma_8': 0.812, 'Omega_m': 0.311, 'S8': 0.827, 'der_full': ...}
54
+
55
+ # CMB Cls (dimensionless D_ℓ; × Tcmb² for μK²)
56
+ csl.cl_TTTEEE(cosmo)
57
+ # → {'ell', 'tt', 'te', 'ee'}
58
+
59
+ # Matter Pk at multiple z
60
+ k, pk = csl.Pk(cosmo, [0., 0.5, 1., 2.])
61
+ k, pnl = csl.Pnl(cosmo, [0., 0.5, 1., 2.])
62
+
63
+ # Distances
64
+ Hz, chi, Da = csl.distances(cosmo, [0.1, 0.5, 1.0])
65
+
66
+ # Halo-model tSZ Cl^yy
67
+ profile = csl.ProfileParamsA10(P0=8.13, beta=5.48, B=1.25)
68
+ ell = jnp.geomspace(2, 9000, 80)
69
+ cl_1h, cl_2h = csl.cl_yy(cosmo, profile, ell)
70
+
71
+ # MCMC fast path: precompute cosmology + halo grids once → ~5 ms/call
72
+ ev = csl.cl_yy_factory(cosmo, ell)
73
+ cl_1h, cl_2h = ev(profile)
74
+ ```
75
+
76
+ ## Throughput
77
+
78
+ Warm-call timing, n = 100 calls per benchmark, freshly randomised inputs:
79
+
80
+ | Function | mean ± std (ms) | calls/s |
81
+ | --- | --- | --- |
82
+ | `derived` | 0.54 ± 0.04 | 1850 ± 150 |
83
+ | `cl_TTTEEE` | 2.52 ± 0.14 | 400 ± 25 |
84
+ | `Pk` | 1.49 ± 0.12 | 670 ± 55 |
85
+ | `distances` | 1.29 ± 0.09 | 770 ± 60 |
86
+ | `cl_yy` (full pipeline) | 17.84 ± 0.58 | 56 ± 2 |
87
+ | `cl_yy_factory` (fixed-cosmo) | 5.38 ± 0.42 | 185 ± 15 |
88
+ | `cl_yy_factory + jax.grad` | 17.12 ± 1.01 | 58 ± 3 |
89
+
90
+ Reference platform: macOS arm64 (M-series CPU), single-thread JAX. See
91
+ [Throughput](https://classy-szlite.readthedocs.io/en/latest/throughput.html)
92
+ for a more detailed table + reproduction script.
93
+
94
+ ## JAX gradients
95
+
96
+ All public functions are JAX-traceable. The factory closure is the
97
+ recommended path for gradient-based inference at fixed cosmology:
98
+
99
+ ```python
100
+ import jax
101
+ ev = csl.cl_yy_factory(cosmo, ell)
102
+
103
+ def loss(P0, beta):
104
+ cl_1h, cl_2h = ev(csl.ProfileParamsA10(P0=P0, beta=beta, B=1.25))
105
+ return jnp.sum(cl_1h + cl_2h)
106
+
107
+ d_loss = jax.grad(loss, argnums=(0, 1))(8.13, 5.48)
108
+ ```
109
+
110
+ Gradients also work through the full pipeline (cosmology + profile) and
111
+ w.r.t. the whole `CosmoParams` pytree — see
112
+ [Gradients](https://classy-szlite.readthedocs.io/en/latest/gradients.html).
113
+
114
+ ## Documentation
115
+
116
+ Full docs at **https://classy-szlite.readthedocs.io**.
117
+
118
+ ## License
119
+
120
+ MIT.
@@ -0,0 +1,50 @@
1
+ """classy_szlite — fast, differentiable cosmology in pure JAX.
2
+
3
+ A standalone Python package providing JIT-compiled, ``jax.grad``-friendly
4
+ access to:
5
+
6
+ * CMB angular power spectra (TT, TE, EE)
7
+ * Linear and non-linear matter Pk
8
+ * Distances (H(z), chi, Da)
9
+ * Derived parameters (σ8, Ω_m, S8)
10
+ * Halo-model tSZ Cl^yy (Arnaud 2010 GNFW)
11
+
12
+ Backed by the high-accuracy ede-v2 CosmoPower emulators (Bolliet et al.).
13
+ LCDM-equivalent cosmology is obtained by leaving ``fEDE = 0.001`` (the
14
+ default) — no separate emulator path needed.
15
+
16
+ Runtime deps: ``jax``, ``numpy``, ``mcfit``.
17
+
18
+ Quick start:
19
+
20
+ >>> import classy_szlite as csl
21
+ >>> cosmo = csl.CosmoParams()
22
+ >>> d = csl.derived(cosmo)
23
+ >>> ell_cls = csl.cl_TTTEEE(cosmo)
24
+ >>> k, pk = csl.Pk(cosmo, [0., 0.5, 1., 2.])
25
+ >>> Hz, chi, Da = csl.distances(cosmo, [0.1, 0.5, 1.0])
26
+ >>> profile = csl.ProfileParamsA10(P0=8.13, beta=5.48, B=1.25)
27
+ >>> import jax.numpy as jnp
28
+ >>> cl_1h, cl_2h = csl.cl_yy(cosmo, profile, ell=jnp.geomspace(2, 9000, 80))
29
+ >>> ev = csl.cl_yy_factory(cosmo, ell=jnp.geomspace(2, 9000, 80)) # fast path
30
+ >>> cl_1h, cl_2h = ev(profile)
31
+ """
32
+ __version__ = "0.2.0"
33
+
34
+ from .params import CosmoParams, ProfileParamsA10
35
+ from .api import (
36
+ derived,
37
+ cl_TTTEEE,
38
+ Pk, Pnl,
39
+ distances,
40
+ cl_yy,
41
+ cl_yy_factory,
42
+ cosmo_to_dict,
43
+ )
44
+
45
+ __all__ = [
46
+ "__version__",
47
+ "CosmoParams", "ProfileParamsA10",
48
+ "derived", "cl_TTTEEE", "Pk", "Pnl", "distances", "cl_yy", "cl_yy_factory",
49
+ "cosmo_to_dict",
50
+ ]
@@ -0,0 +1,180 @@
1
+ """Pure-JAX CosmoPower emulator forward pass.
2
+
3
+ Loads ``.npz`` files produced by the CosmoPower training pipeline and
4
+ exposes a JAX-traceable ``predict`` function. Supports two architectures:
5
+
6
+ 1. **Plain NN** (TT, DER, HZ, DAZ, PKL, PKNL, S8Z, etc.):
7
+ output = feature_train_std * NN(p_in) + feature_train_mean
8
+
9
+ 2. **NN + PCA** (TE and similar that decompress PCA coefficients):
10
+ output = ((NN(p_in) @ pca_transform_matrix) + pca_mean) * pca_std
11
+ * feature_train_std + feature_train_mean
12
+ (exact ordering inferred from cosmopower/classy_szfast convention)
13
+
14
+ The NN itself uses the "α-β sigmoid" activation introduced in
15
+ Spurio Mancini, Piras, Alsing et al. (2022, MNRAS 511, 1771) — a
16
+ trainable smooth interpolation between identity and sigmoid:
17
+
18
+ h_out = (β + sigmoid(α * z) * (1 - β)) * z
19
+
20
+ No keras / tensorflow dependency: the .npz files are read with numpy,
21
+ and the forward pass is a handful of jnp ops.
22
+ """
23
+ from __future__ import annotations
24
+
25
+ import os
26
+ from dataclasses import dataclass, field
27
+ from typing import Any
28
+
29
+ import numpy as np
30
+ import jax
31
+ import jax.numpy as jnp
32
+
33
+ jax.config.update("jax_enable_x64", True)
34
+
35
+
36
+ def _sigmoid(x):
37
+ return 1.0 / (1.0 + jnp.exp(-x))
38
+
39
+
40
+ @dataclass(frozen=True)
41
+ class Emulator:
42
+ """JAX-traceable CosmoPower emulator.
43
+
44
+ Hold the static weights as jnp arrays; ``predict(params_dict)`` runs
45
+ the forward pass and returns the output (already de-standardised, and
46
+ PCA-decompressed if applicable).
47
+
48
+ Attributes
49
+ ----------
50
+ parameters : list[str]
51
+ Input parameter names, in training order.
52
+ modes : np.ndarray
53
+ Output mode labels (ell, k-index, etc. — interpretation is
54
+ emulator-specific). Length = output dimension AFTER PCA decompression.
55
+ is_log : bool
56
+ If True, the emulator output is log10 of the physical quantity.
57
+ Caller can apply ``10**`` if needed; we leave that to the caller
58
+ because the convention varies (e.g. TE doesn't apply log10 since it
59
+ can go negative).
60
+ """
61
+
62
+ # JAX-side state
63
+ weights: tuple = field(repr=False) # tuple of (W, b) per layer
64
+ alphas: tuple = field(repr=False) # α per hidden layer
65
+ betas: tuple = field(repr=False) # β per hidden layer
66
+ param_mean: jnp.ndarray = field(repr=False)
67
+ param_std: jnp.ndarray = field(repr=False)
68
+ feature_mean: jnp.ndarray = field(repr=False)
69
+ feature_std: jnp.ndarray = field(repr=False)
70
+ pca_matrix: jnp.ndarray | None = field(repr=False, default=None)
71
+ pca_mean: jnp.ndarray | None = field(repr=False, default=None)
72
+ pca_std: jnp.ndarray | None = field(repr=False, default=None)
73
+
74
+ # Metadata
75
+ parameters: tuple[str, ...] = ()
76
+ modes: np.ndarray = field(default=None, repr=False)
77
+
78
+ @classmethod
79
+ def from_npz(cls, path: str) -> "Emulator":
80
+ """Load an emulator from a CosmoPower-style ``.npz`` file."""
81
+ raw = np.load(path, allow_pickle=True)
82
+ # CosmoPower saves a single object array containing a dict-like
83
+ d = raw["arr_0"].item()
84
+
85
+ # weights_ and biases_ are SEPARATE lists (one entry per layer);
86
+ # pair them up so the forward pass can iterate over (W, b) tuples.
87
+ weights = tuple(
88
+ (jnp.asarray(W, dtype=jnp.float64),
89
+ jnp.asarray(b, dtype=jnp.float64))
90
+ for W, b in zip(d["weights_"], d["biases_"])
91
+ )
92
+ # alphas_ / betas_: per hidden layer (one entry per hidden layer,
93
+ # i.e. len = n_layers - 1; final linear layer has no α-β activation)
94
+ alphas = tuple(jnp.asarray(a, dtype=jnp.float64) for a in d["alphas_"])
95
+ betas = tuple(jnp.asarray(b, dtype=jnp.float64) for b in d["betas_"])
96
+
97
+ param_mean = jnp.asarray(d["param_train_mean"], dtype=jnp.float64)
98
+ param_std = jnp.asarray(d["param_train_std"], dtype=jnp.float64)
99
+ feature_mean = jnp.asarray(d["feature_train_mean"], dtype=jnp.float64)
100
+ feature_std = jnp.asarray(d["feature_train_std"], dtype=jnp.float64)
101
+
102
+ # PCA fields are present only for PCA emulators (e.g. TE)
103
+ has_pca = "pca_transform_matrix" in d
104
+ pca_matrix = jnp.asarray(d["pca_transform_matrix"], dtype=jnp.float64) if has_pca else None
105
+ pca_mean = jnp.asarray(d["pca_mean"], dtype=jnp.float64) if has_pca else None
106
+ pca_std = jnp.asarray(d["pca_std"], dtype=jnp.float64) if has_pca else None
107
+
108
+ parameters = tuple(d["parameters"])
109
+ modes = np.asarray(d["modes"])
110
+
111
+ return cls(
112
+ weights=weights, alphas=alphas, betas=betas,
113
+ param_mean=param_mean, param_std=param_std,
114
+ feature_mean=feature_mean, feature_std=feature_std,
115
+ pca_matrix=pca_matrix, pca_mean=pca_mean, pca_std=pca_std,
116
+ parameters=parameters, modes=modes,
117
+ )
118
+
119
+ def predict(self, params_dict: dict) -> jnp.ndarray:
120
+ """Forward pass on a single or batched input.
121
+
122
+ ``params_dict``: maps each ``self.parameters`` name to a 1-D array-like
123
+ of length B (batch size); the output has shape ``(B, n_modes)`` (or
124
+ ``(n_modes,)`` if B=1).
125
+ """
126
+ # Stack inputs in the canonical order
127
+ p = jnp.stack([jnp.asarray(params_dict[name], dtype=jnp.float64)
128
+ for name in self.parameters], axis=-1) # (B, n_params)
129
+ # Standardise
130
+ h = (p - self.param_mean) / self.param_std
131
+
132
+ # Hidden layers with α-β sigmoid activation.
133
+ # CosmoPower .npz weights are stored as (in_dim, out_dim) so the
134
+ # matmul is `h @ W` (no transpose).
135
+ for (W, b), a, beta in zip(self.weights[:-1], self.alphas, self.betas):
136
+ z = h @ W + b
137
+ h = (beta + _sigmoid(a * z) * (1.0 - beta)) * z
138
+
139
+ # Final linear layer (no activation)
140
+ W, b = self.weights[-1]
141
+ preds = h @ W + b # (B, n_out)
142
+
143
+ # PCA decompression (if applicable)
144
+ if self.pca_matrix is not None:
145
+ # PCA-encoded: undo PCA standardisation, project back, then
146
+ # undo feature standardisation
147
+ preds = preds * self.pca_std + self.pca_mean # (B, n_pca)
148
+ preds = preds @ self.pca_matrix # (B, n_modes)
149
+
150
+ # Undo feature standardisation
151
+ preds = preds * self.feature_std + self.feature_mean
152
+ return preds.squeeze()
153
+
154
+
155
+ # ---------------------------------------------------------------------------
156
+ # Default data-directory resolution
157
+ # ---------------------------------------------------------------------------
158
+
159
+ def default_data_dir() -> str:
160
+ """Resolve the CosmoPower emulator data directory.
161
+
162
+ Resolution order:
163
+ 1. ``$CLASSY_SZLITE_DATA_DIR`` environment variable
164
+ 2. ``~/class_sz_data`` (the recommended new location)
165
+ 3. ``~/class_sz_data_directory`` (legacy classy_szfast path)
166
+ """
167
+ env = os.environ.get("CLASSY_SZLITE_DATA_DIR")
168
+ if env:
169
+ return os.path.expanduser(env)
170
+ for candidate in ("~/class_sz_data", "~/class_sz_data_directory"):
171
+ path = os.path.expanduser(candidate)
172
+ if os.path.isdir(path):
173
+ return path
174
+ raise RuntimeError(
175
+ "classy_szlite: cannot find CosmoPower emulator data. Set the "
176
+ "$CLASSY_SZLITE_DATA_DIR environment variable, or place the emulator "
177
+ "directory tree at ~/class_sz_data (or the legacy "
178
+ "~/class_sz_data_directory). Required subdirs per cosmo_model: "
179
+ "PK/, growth-and-distances/, TTTEEE/, derived-parameters/."
180
+ )
@@ -0,0 +1,94 @@
1
+ """Emulator file IDs, grid configs, and lazy loading.
2
+
3
+ Single emulator suite: **ede-v2**. The ede-v2 CosmoPower emulators are
4
+ extremely accurate and cover the LCDM-equivalent point at ``fEDE = 0.001``;
5
+ they replace any need for a separate LCDM emulator path.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ from functools import lru_cache
11
+
12
+ from ._emulator import Emulator, default_data_dir
13
+
14
+
15
+ # File IDs (paths relative to <data_root>/ede/)
16
+ _EMULATOR_FILES = {
17
+ "tt": "TTTEEE/TT_v2.npz",
18
+ "te": "TTTEEE/TE_v2.npz",
19
+ "ee": "TTTEEE/EE_v2.npz",
20
+ "pp": "PP/PP_v2.npz",
21
+ "pkl": "PK/PKL_v2.npz",
22
+ "pknl": "PK/PKNL_v2.npz",
23
+ "hz": "growth-and-distances/HZ_v2.npz",
24
+ "daz": "growth-and-distances/DAZ_v2.npz",
25
+ "s8z": "growth-and-distances/S8Z_v2.npz",
26
+ "der": "derived-parameters/DER_v2.npz",
27
+ }
28
+
29
+ # Subdir under the data root
30
+ _DATA_SUBDIR = "ede"
31
+
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # Pk emulator grid + extrapolation configuration
35
+ # ---------------------------------------------------------------------------
36
+ # Final n_k = nk // ndspl. Final k = geomspace(kmin, kmax, nk)[::ndspl].
37
+ # prefac:
38
+ # 'k3' — emulator output is log10[ k³ Pk ]; recover Pk via × k^-3
39
+ # extrap_kmin: extend the Pk grid down to this k via P(k) ∝ k^n_s
40
+ PK_GRID = dict(kmin=5e-4, kmax=10.0, nk=1000, ndspl=1,
41
+ prefac="k3", extrap_kmin=1e-4)
42
+
43
+ # Distance emulator z-grid: HZ trained on linspace(0, 20, 5000); DAZ omits
44
+ # z=0 (returns 4999 values) — we prepend chi(0)=0 in cosmology._predict_distances.
45
+ DIST_ZMAX = 20.0
46
+
47
+
48
+ # ---------------------------------------------------------------------------
49
+ # Per-emulator log10 convention
50
+ # ---------------------------------------------------------------------------
51
+ # Whether the raw NN output is log10(physical) vs physical directly.
52
+ # True → caller must apply 10^ to recover the physical quantity.
53
+ # False → raw output IS the physical quantity (or the prefactored-Pk form).
54
+ LOG10_OUTPUT = {
55
+ "tt": True, "ee": True, "pp": True, "te": False, # CMB
56
+ "pkl": True, "pknl": True, # Pk (handled in cosmology)
57
+ "hz": True, "daz": True, "s8z": True, # distances + sigma8(z)
58
+ "der": True, # derived (sigma_8 = der[1])
59
+ }
60
+
61
+
62
+ # ---------------------------------------------------------------------------
63
+ # Default cosmology parameters (matches ede-v2 emulator training default)
64
+ # ---------------------------------------------------------------------------
65
+ DEFAULT_COSMO = {
66
+ "ln10^{10}A_s": 3.047, "n_s": 0.9665,
67
+ "H0": 67.66, "omega_b": 0.02242, "omega_cdm": 0.11933,
68
+ "tau_reio": 0.054,
69
+ # EDE-specific defaults (LCDM-equivalent point)
70
+ "fEDE": 0.001, "log10z_c": 3.562, "thetai_scf": 2.83, "r": 0.0,
71
+ # ν convention: 3 degenerate ncdm of 0.02 eV → Σmν = 0.06 eV
72
+ "m_ncdm": 0.02, "N_ur": 0.00441,
73
+ }
74
+
75
+
76
+ # ---------------------------------------------------------------------------
77
+ # Lazy emulator loading + cache
78
+ # ---------------------------------------------------------------------------
79
+
80
+ @lru_cache(maxsize=16)
81
+ def get_emulator(name: str) -> Emulator:
82
+ """Load (and cache) the named emulator."""
83
+ if name not in _EMULATOR_FILES:
84
+ raise ValueError(f"Unknown emulator name {name!r}. "
85
+ f"Supported: {list(_EMULATOR_FILES)}")
86
+ root = default_data_dir()
87
+ path = os.path.join(root, _DATA_SUBDIR, _EMULATOR_FILES[name])
88
+ if not os.path.isfile(path):
89
+ raise FileNotFoundError(f"classy_szlite: emulator file not found: {path}")
90
+ return Emulator.from_npz(path)
91
+
92
+
93
+ def output_is_log10(name: str) -> bool:
94
+ return LOG10_OUTPUT[name]