emu_pk.ratio

The CLASS-distilled correction to a massless-LambdaCDM linear spectrum.

A network trained on massless LambdaCDM cannot be asked for a spectrum with neutrinos in it, and gets no response at all to dark energy. This module supplies both corrections, as ratios measured from CLASS on a grid and interpolated:

\[r_m (k,z) = \frac{P_m (k,z;\,\Sigma m_\nu, w_0, w_a)}{P_m(k,z;\,0,-1,0)}, \qquad r_{cb}(k,z) = \frac{P_{cb}(k,z;\,\Sigma m_\nu, w_0, w_a)}{P_m(k,z;\,0,-1,0)}\]

The neutrino part is indexed by \(f_\nu = \Omega_\nu/\Omega_m\) rather than by \(\Sigma m_\nu\), so one table serves every \(h\) and \(\Omega_m\) instead of being tied to the cosmology it was built at.

Both ratios are exactly 1 at the LambdaCDM massless corner – the stored log-ratio is exactly zero at \(f_\nu = 0\), \(w_0 = -1\), \(w_a = 0\) – so the correction is applied unconditionally, with no if sum_mnu > 0 branch. That is not tidiness: a Python branch on \(\Sigma m_\nu\) is a branch on a value that is a tracer under jax.grad, and would break the gradient the whole differentiable path exists to provide.

Factorisation

The correction ships as two factors, neutrinos and dark energy,

\[r(k,z;\,f_\nu,w_0,w_a) \simeq r^{\nu}(k,z;f_\nu)\,r^{\rm DE}(k,z;w_0,w_a)\]

because the full five-axis cube needs 16 derivative arrays for a tensor-product Hermite and the factors need 4 and 8 over much smaller cubes – megabytes against hundreds. Whether that is allowed is not assumed: build_ratio() runs the full grid regardless, measures the largest residual of the factorisation against it, and stores the number in the table as resid_max. If it is not comfortably below the emulator’s own shape error the factorisation is the wrong call, and the number is there to say so rather than to be trusted.

Validity

Outside the grid the interpolation would clamp, silently understating the correction, so suppression_m() raises instead. A plausible wrong number is worse than an exception. The check calls float(), which raises on a tracer, so it is skipped under tracing rather than attempted – a jitted forward model is checked once when it is built, outside the trace.

The table is regenerated in two steps, with classy installed – solve the grid, then assemble it:

python -m emu_pk.generate --mode ratio --shard 0 --n-per-shard 300 --out shards_ratio
python -m emu_pk.assemble --mode ratio --shards shards_ratio

The shipped .npz means ordinary use never needs either.

emu_pk.ratio.load(path=None)[source]

Resolve the path first, then hit the cache.

lru_cache keys on the call signature, so load() and load(None) are two different keys and would build – and hold – two independent copies of a table that is over a hundred megabytes once its derivative arrays exist. Resolving to a single canonical string before the cache is what makes them the same call.

Return type:

dict

emu_pk.ratio.suppression_m(k, f_nu, z=0.0, w0=-1.0, wa=0.0, table=None)[source]

\(P_m(k,z;\Sigma m_\nu,w_0,w_a)/P_m(k,z;0,-1,0)\).

Exactly 1 at the LambdaCDM massless corner.

emu_pk.ratio.suppression_cb(k, f_nu, z=0.0, w0=-1.0, wa=0.0, table=None)[source]

\(P_{cb}(k,z;\Sigma m_\nu,w_0,w_a)/P_m(k,z;0,-1,0)\).

Note the denominator is the massless total, not the massive total: this converts a massless-trained spectrum straight to the cold one, in a single multiplication, without ever forming the massive total in between.

emu_pk.ratio.MNU_MAX = 0.6

Largest summed neutrino mass the table covers, in eV.

emu_pk.ratio.Z_MAX = 5.0

Largest redshift the table covers.

emu_pk.ratio.W0_RANGE = (-1.3, -0.7)

Range of w0 the table covers, as (low, high). Narrower than emu_pk.box’s own bounds: the table was built for a different purpose and its grid was sized for that.

emu_pk.ratio.WA_RANGE = (-0.7, 0.5)

Range of wa the table covers, as (low, high).