classy-szlite 0.2.0__py3-none-any.whl
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/__init__.py +50 -0
- classy_szlite/_emulator.py +180 -0
- classy_szlite/_registry.py +94 -0
- classy_szlite/api.py +194 -0
- classy_szlite/cosmology.py +205 -0
- classy_szlite/hmf.py +289 -0
- classy_szlite/params.py +56 -0
- classy_szlite/power_spectrum.py +423 -0
- classy_szlite-0.2.0.dist-info/METADATA +148 -0
- classy_szlite-0.2.0.dist-info/RECORD +13 -0
- classy_szlite-0.2.0.dist-info/WHEEL +5 -0
- classy_szlite-0.2.0.dist-info/licenses/LICENSE +21 -0
- classy_szlite-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -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]
|
classy_szlite/api.py
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""High-level public API: derived params, CMB Cls, matter Pk, distances,
|
|
2
|
+
tSZ Cl^yy. All functions take a :class:`classy_szlite.params.CosmoParams`.
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import jax
|
|
8
|
+
import jax.numpy as jnp
|
|
9
|
+
|
|
10
|
+
from ._registry import get_emulator, DEFAULT_COSMO
|
|
11
|
+
from .params import CosmoParams, ProfileParamsA10
|
|
12
|
+
from .cosmology import build as build_cosmo_grids, get_pk, get_pknl, get_distances
|
|
13
|
+
from .hmf import build_halo_grids
|
|
14
|
+
from .power_spectrum import cl_yy_1h_2h
|
|
15
|
+
|
|
16
|
+
jax.config.update("jax_enable_x64", True)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def cosmo_to_dict(cosmo: CosmoParams) -> dict:
|
|
20
|
+
"""Convert ``CosmoParams`` to the emulator-style dict (with curly-brace key).
|
|
21
|
+
|
|
22
|
+
Values pass through as-is (float or jax.Array) so the returned dict is
|
|
23
|
+
JAX-traceable — pass a ``CosmoParams`` of tracers to ``jax.grad``.
|
|
24
|
+
"""
|
|
25
|
+
return {
|
|
26
|
+
"omega_b": cosmo.omega_b,
|
|
27
|
+
"omega_cdm": cosmo.omega_cdm,
|
|
28
|
+
"H0": cosmo.H0,
|
|
29
|
+
"tau_reio": cosmo.tau_reio,
|
|
30
|
+
"ln10^{10}A_s": cosmo.ln10_10_As,
|
|
31
|
+
"n_s": cosmo.n_s,
|
|
32
|
+
"m_ncdm": cosmo.m_ncdm,
|
|
33
|
+
"N_ur": cosmo.N_ur,
|
|
34
|
+
"fEDE": cosmo.fEDE,
|
|
35
|
+
"log10z_c": cosmo.log10z_c,
|
|
36
|
+
"thetai_scf": cosmo.thetai_scf,
|
|
37
|
+
"r": cosmo.r,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# Derived parameters (σ8, Ω_m, S8)
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
def derived(cosmo: CosmoParams) -> dict:
|
|
46
|
+
"""Derived parameters: ``sigma_8``, ``Omega_m``, ``S8``.
|
|
47
|
+
|
|
48
|
+
Also returns the full 17-element DER emulator output as ``'der_full'``
|
|
49
|
+
(σ8 is at index 1; consult the CosmoPower DER training script for the
|
|
50
|
+
full list).
|
|
51
|
+
"""
|
|
52
|
+
em = get_emulator('der')
|
|
53
|
+
full = dict(DEFAULT_COSMO); full.update(cosmo_to_dict(cosmo))
|
|
54
|
+
p_in = {k: [full[k]] for k in em.parameters}
|
|
55
|
+
der = 10.0 ** np.asarray(em.predict(p_in)).flatten()
|
|
56
|
+
|
|
57
|
+
sigma_8 = float(der[1])
|
|
58
|
+
h = float(cosmo.H0) / 100.0
|
|
59
|
+
# Σmν = 3 × m_ncdm for ede-v2 ν convention (3 degenerate ν)
|
|
60
|
+
sum_mnu = 3.0 * float(cosmo.m_ncdm)
|
|
61
|
+
Omega_m = (float(cosmo.omega_b) + float(cosmo.omega_cdm)
|
|
62
|
+
+ sum_mnu / 93.14) / h ** 2
|
|
63
|
+
S8 = sigma_8 * (Omega_m / 0.3) ** 0.5
|
|
64
|
+
return {"sigma_8": sigma_8, "Omega_m": Omega_m, "S8": S8, "der_full": der}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
# CMB angular power spectra
|
|
69
|
+
# ---------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
# TT, EE, PP emulators output log10(prefactored Cl); TE outputs Cl directly
|
|
72
|
+
# (it can be negative). Recovery factor: ede-v2 uses 1/ell² to get raw Cl.
|
|
73
|
+
_CMB_LOG_CONVENTION = {"tt": True, "ee": True, "pp": True, "te": False}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def cl_TTTEEE(cosmo: CosmoParams,
|
|
77
|
+
spectra: tuple[str, ...] = ("tt", "te", "ee"),
|
|
78
|
+
ell_factor: bool = True) -> dict:
|
|
79
|
+
"""CMB angular power spectra.
|
|
80
|
+
|
|
81
|
+
Returns a dict with keys ``'ell'`` and the requested spectra
|
|
82
|
+
(``'tt','te','ee'``). Values are **dimensionless** — multiply by
|
|
83
|
+
``Tcmb_uK² = (2.7255e6)²`` to convert to μK².
|
|
84
|
+
|
|
85
|
+
``ell_factor`` (default ``True``) — return ``D_ell = ell(ell+1) Cl / (2π)``;
|
|
86
|
+
``False`` returns raw Cl.
|
|
87
|
+
"""
|
|
88
|
+
full = dict(DEFAULT_COSMO); full.update(cosmo_to_dict(cosmo))
|
|
89
|
+
|
|
90
|
+
out = {}
|
|
91
|
+
ell = None
|
|
92
|
+
for spec in spectra:
|
|
93
|
+
if spec not in _CMB_LOG_CONVENTION:
|
|
94
|
+
raise ValueError(f"Unknown spectrum {spec!r}. Pick from {tuple(_CMB_LOG_CONVENTION)}.")
|
|
95
|
+
em = get_emulator(spec)
|
|
96
|
+
p_in = {k: [full[k]] for k in em.parameters}
|
|
97
|
+
pred = np.asarray(em.predict(p_in)).flatten()
|
|
98
|
+
if _CMB_LOG_CONVENTION[spec]:
|
|
99
|
+
pred = 10.0 ** pred
|
|
100
|
+
out[spec] = pred
|
|
101
|
+
if ell is None:
|
|
102
|
+
ell = np.asarray(em.modes)
|
|
103
|
+
|
|
104
|
+
# ede-v2 recovery factor: raw → Cl
|
|
105
|
+
factor_to_Cl = 1.0 / (ell ** 2)
|
|
106
|
+
for s in spectra:
|
|
107
|
+
out[s] = out[s] * factor_to_Cl
|
|
108
|
+
|
|
109
|
+
out["ell"] = ell
|
|
110
|
+
if ell_factor:
|
|
111
|
+
fac_dl = ell * (ell + 1) / (2.0 * np.pi)
|
|
112
|
+
for s in spectra:
|
|
113
|
+
out[s] = out[s] * fac_dl
|
|
114
|
+
return out
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# ---------------------------------------------------------------------------
|
|
118
|
+
# Pk, Pnl, distances
|
|
119
|
+
# ---------------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
def Pk(cosmo: CosmoParams, z_arr):
|
|
122
|
+
"""Linear P(k, z) — returns ``(k, pk(z, k))``."""
|
|
123
|
+
return get_pk(cosmo_to_dict(cosmo), z_arr)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def Pnl(cosmo: CosmoParams, z_arr):
|
|
127
|
+
"""Non-linear P(k, z) (HMcode) — returns ``(k, pk(z, k))``."""
|
|
128
|
+
return get_pknl(cosmo_to_dict(cosmo), z_arr)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def distances(cosmo: CosmoParams, z_arr):
|
|
132
|
+
"""Returns ``(Hz, chi, Da)``. ``Hz`` is H(z)/c in 1/Mpc; distances in Mpc."""
|
|
133
|
+
return get_distances(cosmo_to_dict(cosmo), z_arr)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# ---------------------------------------------------------------------------
|
|
137
|
+
# tSZ Cl^yy (halo-model, Arnaud 2010 profile)
|
|
138
|
+
# ---------------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
def cl_yy(cosmo: CosmoParams, profile: ProfileParamsA10, ell,
|
|
141
|
+
z_grid: jax.Array | None = None,
|
|
142
|
+
n_z: int = 100, m_min: float = 1e10, m_max: float = 3.5e15,
|
|
143
|
+
n_m: int = 200, delta_crit: float = 500.0):
|
|
144
|
+
"""Halo-model tSZ angular power spectrum (full pipeline per call).
|
|
145
|
+
|
|
146
|
+
Returns ``(cl_1h, cl_2h)`` — dimensionless C_ell. Multiply by
|
|
147
|
+
``ell*(ell+1)/(2π)*1e12`` to get ``D_ell × 1e12``.
|
|
148
|
+
|
|
149
|
+
For MCMC sampling only profile parameters at fixed cosmology, use
|
|
150
|
+
:func:`cl_yy_factory` instead — ~3× faster.
|
|
151
|
+
"""
|
|
152
|
+
if z_grid is None:
|
|
153
|
+
z_grid = jnp.geomspace(0.005, 3.0, n_z)
|
|
154
|
+
cosmo_dict = cosmo_to_dict(cosmo)
|
|
155
|
+
cg = build_cosmo_grids(cosmo_dict, z_grid=z_grid)
|
|
156
|
+
hg = build_halo_grids(cg, cosmo_dict, delta_crit=delta_crit,
|
|
157
|
+
m_min=m_min, m_max=m_max, n_m=n_m)
|
|
158
|
+
pp_dict = profile._asdict()
|
|
159
|
+
cl_1h, cl_2h = cl_yy_1h_2h(jnp.asarray(ell), cg, hg, cosmo_dict,
|
|
160
|
+
profile='arnaud10', profile_params=pp_dict)
|
|
161
|
+
return cl_1h, cl_2h
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def cl_yy_factory(cosmo: CosmoParams, ell,
|
|
165
|
+
z_grid: jax.Array | None = None,
|
|
166
|
+
n_z: int = 100, m_min: float = 1e10, m_max: float = 3.5e15,
|
|
167
|
+
n_m: int = 200, delta_crit: float = 500.0):
|
|
168
|
+
"""Fixed-cosmology fast-path: precompute the heavy bits, get a closure.
|
|
169
|
+
|
|
170
|
+
Builds ``CosmoGrids`` (emulators → P_lin, distances, σ(R)) and
|
|
171
|
+
``HaloGrids`` (Tinker 08 HMF, bias) **once**, then returns:
|
|
172
|
+
|
|
173
|
+
ev(profile) -> (cl_1h, cl_2h)
|
|
174
|
+
|
|
175
|
+
A subsequent ``ev(profile)`` call only runs the ``cl_yy_1h_2h``
|
|
176
|
+
halo-model integration — typically ~5 ms per call. Intended for MCMC
|
|
177
|
+
over profile / nuisance parameters with fixed cosmology.
|
|
178
|
+
"""
|
|
179
|
+
if z_grid is None:
|
|
180
|
+
z_grid = jnp.geomspace(0.005, 3.0, n_z)
|
|
181
|
+
cosmo_dict = cosmo_to_dict(cosmo)
|
|
182
|
+
cg = build_cosmo_grids(cosmo_dict, z_grid=z_grid)
|
|
183
|
+
hg = build_halo_grids(cg, cosmo_dict, delta_crit=delta_crit,
|
|
184
|
+
m_min=m_min, m_max=m_max, n_m=n_m)
|
|
185
|
+
ell_jax = jnp.asarray(ell)
|
|
186
|
+
|
|
187
|
+
def evaluate(profile: ProfileParamsA10):
|
|
188
|
+
cl_1h, cl_2h = cl_yy_1h_2h(
|
|
189
|
+
ell_jax, cg, hg, cosmo_dict,
|
|
190
|
+
profile='arnaud10', profile_params=profile._asdict(),
|
|
191
|
+
)
|
|
192
|
+
return cl_1h, cl_2h
|
|
193
|
+
|
|
194
|
+
return evaluate
|