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.
- classy_szlite-0.2.0/LICENSE +21 -0
- classy_szlite-0.2.0/PKG-INFO +148 -0
- classy_szlite-0.2.0/README.md +120 -0
- classy_szlite-0.2.0/classy_szlite/__init__.py +50 -0
- classy_szlite-0.2.0/classy_szlite/_emulator.py +180 -0
- classy_szlite-0.2.0/classy_szlite/_registry.py +94 -0
- classy_szlite-0.2.0/classy_szlite/api.py +194 -0
- classy_szlite-0.2.0/classy_szlite/cosmology.py +205 -0
- classy_szlite-0.2.0/classy_szlite/hmf.py +289 -0
- classy_szlite-0.2.0/classy_szlite/params.py +56 -0
- classy_szlite-0.2.0/classy_szlite/power_spectrum.py +423 -0
- classy_szlite-0.2.0/classy_szlite.egg-info/PKG-INFO +148 -0
- classy_szlite-0.2.0/classy_szlite.egg-info/SOURCES.txt +17 -0
- classy_szlite-0.2.0/classy_szlite.egg-info/dependency_links.txt +1 -0
- classy_szlite-0.2.0/classy_szlite.egg-info/requires.txt +10 -0
- classy_szlite-0.2.0/classy_szlite.egg-info/top_level.txt +1 -0
- classy_szlite-0.2.0/pyproject.toml +40 -0
- classy_szlite-0.2.0/setup.cfg +4 -0
- classy_szlite-0.2.0/tests/test_smoke.py +71 -0
|
@@ -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
|
+
[](https://github.com/CLASS-SZ/classy_szlite/actions/workflows/test.yml)
|
|
32
|
+
[](https://pypi.org/project/classy_szlite/)
|
|
33
|
+
[](https://classy-szlite.readthedocs.io/en/latest/)
|
|
34
|
+
[](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
|
+
[](https://github.com/CLASS-SZ/classy_szlite/actions/workflows/test.yml)
|
|
4
|
+
[](https://pypi.org/project/classy_szlite/)
|
|
5
|
+
[](https://classy-szlite.readthedocs.io/en/latest/)
|
|
6
|
+
[](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]
|