Skip to main content

emu_pk

PyPI Python Tests Docs Licence: BSD-3-Clause

Differentiable emulation of the linear matter power spectrum, over an eleven-parameter cosmology that includes three separate neutrino masses, CPL dark energy and spatial curvature, out to $k = 200\ h,\mathrm{Mpc}^{-1}$ and $z = 5$.

It is written in JAX, so derivatives with respect to the cosmological parameters come from automatic differentiation.

import numpy as np
from emu_pk import PkEmulator

emu = PkEmulator()
k = np.logspace(-3, 1, 200)                      # h/Mpc
theta = np.array([
    0.02237,     # omega_b
    0.1200,      # omega_cdm
    0.6736,      # h
    0.9649,      # n_s
    3.044,       # ln10A_s
    0.06,        # sum_mnu [eV] -- still the sum, unchanged in meaning
    -1.0,        # w0
    0.0,         # wa
    0.0,         # Omega_k      -- positive is open
    1 / 3,       # nu_r1  \  how that sum is divided over three species,
    1 / 3,       # nu_r2  /  m_i = r_i * sum_mnu, with r_3 = 1 - r_1 - r_2.
])               #            (1/3, 1/3) is the degenerate convention.

pk = emu.pk(k, z=0.5, params=theta)              # P_m(k, z) in (Mpc/h)^3
pk_cb = emu.pk_cb(k, z=0.5, params=theta)        # cdm + baryons, without neutrinos

Accuracy

We score the network against held-out CLASS solves, on a Latin hypercube drawn from a different seed from the training design. The record is emu_pk/data/validation.json, written by python -m emu_pk.validate.

Three quantities describe $P(k)$: its amplitude at $k = 0.05\ h,\mathrm{Mpc}^{-1}$, its shape once both spectra are renormalised there, and the two combined. Shape error is the largest fractional departure from CLASS over $k \in [10^{-3}, 10]\ h,\mathrm{Mpc}^{-1}$. Medians are over the held-out cosmologies, at $z = 0$.

at $z = 0$ median 90th max
amplitude at $k = 0.05$ 0.010 % 0.030 % 0.062 %
shape, renormalised 0.064 % 0.152 % 0.225 %
total, absolute 0.066 % 0.144 % 0.238 %
the metric's own floor 0.020 % 0.031 % 0.032 %

The floor row is the metric's own. The network predicts on a 400-node grid and the comparison asks CLASS at 300 other wavenumbers, so the interpolation between the nodes is scored as network error; that row is a CLASS spectrum pushed through the same path.

A Fisher forecast consumes derivatives rather than spectra. Against central differences of CLASS, at $z = 0$:

parameter error floor parameter error floor
ln10A_s exact — sum_mnu 0.303 % 0.010 %
n_s exact — wa 0.353 % 0.032 %
omega_cdm 0.046 % 0.038 % Omega_k 0.141 % 0.007 %
h 0.094 % 0.010 % nu_r1 5.58 % 0.261 %
w0 0.097 % 0.036 % nu_r2 5.05 % 0.202 %
omega_b 0.159 % 0.013 %

The two mass ratios are the weakest axes. Their effect on $P(k)$ is 0.32 % at $\Sigma m_\nu = 0.10$ eV and under 0.012 % above 0.25 eV, so there is little signal to fit; an axis the network ignored would score near 100 %.

And with respect to redshift, which $f\sigma_8$ is built from:

z = 0 z = 0.5 z = 1 z = 2
$\partial\ln P/\partial z$ 0.060 % 0.023 % 0.012 % 0.012 %
the measurement's own floor 0.038 % 0.009 % 0.007 % 0.006 %

These sit within a factor of two or three of what the comparison can resolve.

The box

Outside these bounds the network extrapolates, returning a finite and unwarranted number, so PkEmulator checks the box on every call it can.

parameter range
omega_b 0.0170 – 0.0280
omega_cdm 0.0500 – 0.3000
h 0.5500 – 0.8500
n_s 0.8400 – 1.1000
ln10A_s 1.6100 – 4.0000
sum_mnu [eV] 0.0000 – 0.6000
w0 −1.5000 – −0.5000
wa −1.0000 – 0.6000
Omega_k −0.1500 – 0.1500
nu_r1 0.0000 – 0.3333
nu_r2 0.0000 – 0.5000
$k$ [h/Mpc] $10^{-4}$ – 200
$z$ 0 – 5

Points with w0 + wa >= 0 are excluded. CPL dark energy then grows without bound towards early times and dominates before recombination.

The curvature bound is measured. CLASS solves the whole box at $|\Omega_k| \le 0.15$; on the closed side it begins refusing at $\Omega_k = -0.275$, at omega_cdm = 0.05, h = 0.85. On the open side it never refuses, including where the closure $\Omega_{\rm de} = 1 - \Omega_k - \Omega_m - \Omega_r$ turns negative, so that bound is stated rather than discovered.

Negative $\Omega_{\rm de}$ is kept. It covers 0.4 % of the flat box and 0.9 % of this one, CLASS solves it, and the spectrum is smooth in the parameters; validate reports it as a stratum.

The neutrino ratios divide $\Sigma m_\nu$ over three species, $m_i = r_i \Sigma m_\nu$ with $r_3 = 1 - r_1 - r_2$, on the ordered simplex $0 \le r_1 \le r_2 \le (1-r_1)/2$. CLASS sums the species and cannot distinguish them, so the six permutations of a mass vector are one cosmology and the ordering removes five of them. Both mass orderings and the degenerate limit lie inside; the degenerate point $(1/3, 1/3)$ is a vertex, and is scored as its own stratum.

Against CLASS, the degenerate approximation is wrong by 0.32 % in $P(k)$ at $\Sigma m_\nu = 0.10$ eV in an inverted ordering, and by under 0.012 % above 0.25 eV. This emulator's own shape error is 0.064 %.

Curvature costs the fit little above $k \approx 10^{-2}$, where its effect is a $k$-independent growth rescaling flat in $k$ to 0.5 %. Below $k \approx 10^{-3}$ it does not: the curvature scale $\sqrt{|\Omega_k|}H_0/c$ is $1.3\times10^{-4}\ h,\mathrm{Mpc}^{-1}$ at the edge of the box, inside the $k$ grid. That decade is scored separately; see docs/design_notes.md.

Install

pip install emu_pk                 # inference: numpy + jax, nothing else
pip install 'emu_pk[gen]'          # + classy, to generate data or validate
pip install 'emu_pk[train]'        # + optax, to train

import emu_pk in an environment with no classy and no optax must work, so that a package depending on this one does not inherit a Boltzmann solver or a training stack. The test suite asserts it.

[gen] compiles CLASS from source and needs a C compiler.

A dedicated environment

environment.yml is a minimal conda environment — python, numpy and JAX, and nothing else:

mamba env create -f environment.yml     # or: conda env create -f environment.yml
mamba activate emu_pk
pip install -e .

It pins the CPU build of jaxlib: 64 MB against 199 MB for the CUDA one, and left unpinned the build depends on whether the machine that solved the environment happened to have a driver. The file says how to swap it for a GPU.

The extras are commented blocks in the same file — uncomment the one you need. classy is not on conda-forge, so [gen] comes from pip and compiles CLASS from source.

What is here

Module Needs What it does
emu_pk.model numpy, jax evaluate the network, in pure JAX
emu_pk.ratio numpy, jax the CLASS-distilled massive-ν and CPL correction
emu_pk.box, .grid, .cosmo, .interp numpy, jax the hypercube, the grids, the conventions, the interpolation
emu_pk.generate, .assemble [gen] run CLASS; shards → table or training set
emu_pk.train [train] fit the network
emu_pk.validate [gen] shape error and derivative error against CLASS

emu_pk.ratio is a correction measured from CLASS on a grid in $(\Sigma m_\nu, w_0, w_a, z, k)$ and applied multiplicatively to a massless-ΛCDM spectrum. It is exactly 1 at the ΛCDM massless corner, so it applies unconditionally; a Python branch on the neutrino mass would branch on a tracer and break the gradient. It gives neutrinos and dark energy to an emulator that has neither. This package's own network is trained on massive-neutrino w0waCDM spectra and needs no correction.

Conventions

$k$ in $h,\mathrm{Mpc}^{-1}$ and $P$ in $(h^{-1}\mathrm{Mpc})^3$ throughout. CLASS's $1/\mathrm{Mpc}$ is converted once, in generate, so nothing downstream carries an $h$.

$\Omega_m$ contains the neutrinos, and $\Omega_\nu = \Sigma m_\nu/(93.143 h^2)$, the rest mass of three states at CLASS's default temperature.

Validating it yourself

With [gen] installed, the numbers above are one command:

python -m emu_pk.validate --json my_validation.json

It scores shape error and derivative error against CLASS across the redshift range, reports the finite-difference floor of its own comparison, and splits the box edge and the extreme-quintessence corner out from the interior.

Tests

pip install 'emu_pk[dev]'
python -m pytest tests/ -q

230 tests. Every statement in emu_pk is executed by the suite and 99 % of its branches. [dev] installs everything the suite needs, including optax, since the tests that exercise the trainer import it. CLASS is not needed: it is replaced by a stub wherever a test wants a spectrum rather than a correct one, and the few tests that do want the real solver skip without it. The tests that compare conventions against the consuming package skip when it is not importable.

Documentation

Full documentation, including a tutorial with figures, is at emu-pk.readthedocs.io. Decisions that would look arbitrary from the source alone are in docs/design_notes.md.

Citing

If you use emu_pk, please cite it via CITATION.cff, and also:

  • CosmoPower — Spurio Mancini et al. (2022), MNRAS 511, 1771. The network architecture and the learned activation are theirs.
  • CLASS — Blas, Lesgourgues & Tram (2011), JCAP 07, 034. Every training spectrum and every validation reference is a CLASS solve.

Licence

BSD 3-Clause. See LICENSE.

Metadata

Release files for emu-pk 2.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for emu-pk 2.0.1
File Size Uploaded
emu_pk-2.0.1.tar.gz 17.6 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for emu-pk 2.0.1
File Interpreter ABI Platform
emu_pk-2.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 35.2 MB

Release files / emu_pk-2.0.1.tar.gz

Download URL emu_pk-2.0.1.tar.gz
Size 17.6 MB
Tags Source
SHA-256 checksum
How to use checksums
61767820a52ecb2bed0a6c0e374c7b23d923f0929333eddd03722d4281b7f335
BLAKE2b-256 checksum
How to use checksums
15b4cccde930b32c1b208feacd28118540715636b1135087e86bff4728ad895e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.

Transparency log

Release files / emu_pk-2.0.1-py3-none-any.whl

Download URL emu_pk-2.0.1-py3-none-any.whl
Size 17.6 MB
Tags Python 3
SHA-256 checksum
How to use checksums
467b2214e1dfcc7a1b111309b2c84f0bd089dc102265211723b69d7216d5d9f3
BLAKE2b-256 checksum
How to use checksums
66e6645062003a1f2f3cd36ec1620e4fb647d491642a44f65cade10c32836458
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.

Transparency log

Release history Release notifications | RSS feed

2.1.1

2 release files

2.1.0

2 release files

This release

2.0.1 This release

2 release files

2.0.0

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page