PDE solver (spectroxide.solver)#

Python wrapper around the Rust spectroxide binary. Runs the full photon-Boltzmann PDE (Kompaneets + double Compton + bremsstrahlung) with adaptive redshift stepping and parses the JSON output.

This page documents the PDE solver only. For the analytic Green’s function approximation see Analytic Green’s function (spectroxide.greens); for the precomputed PDE-based numerical Green’s function tables see PDE-based numerical Green’s function (spectroxide.greens_table).

Note

The Rust binary must be built once before any of these entry points work:

cargo build --release

When to call which function#

Four PDE entry points cover the common patterns. Pick by what you’re scanning over:

Use case

Function

One PDE solve for a custom injection scenario, custom heating history, or tabulated photon source.

solve() — returns a structured SolverResult.

Many injection redshifts at fixed amplitude (single-burst energy injection).

run_sweep() — one Rust process loops over z_injections internally.

Many injection redshifts at fixed x_inj (monochromatic photon injection).

run_photon_sweep() — same idea for the photon-sweep case.

Many x_inj values, batched.

run_photon_sweep_batch().

For parameter scans over scenario knobs (gamma_x, epsilon, m_ev, f_x, …), call solve() in a Python loop — there is no built-in batched entry point for arbitrary scenarios.

Injection scenarios#

The injection argument to solve() is a dict with a "type" key and scenario-specific parameter keys. delta_rho is always a top-level argument (not an injection key). For the physics behind each scenario and full derivations, see the paper.

"type"

Required keys

"single_burst"

z_h, sigma_z

"decaying_particle"

f_x [eV], gamma_x [1/s]

"annihilating_dm"

f_ann [eV/s]

"annihilating_dm_pwave"

f_ann [eV/s]

"monochromatic_photon"

x_inj, delta_n_over_n, z_h, sigma_z, sigma_x

"decaying_particle_photon"

x_inj_0, f_inj, gamma_x [1/s]

"dark_photon_resonance"

epsilon, m_ev [eV]

Each parameter name is mapped to the corresponding Rust CLI flag --<kebab-case> (e.g. f_x --f-x, delta_n_over_n --delta-n-over-n).

Custom heating and photon-source callables#

For arbitrary heating histories or frequency-dependent photon sources, pass a Python callable to solve() instead of using the injection dict. Both are tabulated on a log-spaced grid and dispatched to the corresponding Rust tabulated-* subcommand; values outside the integration range are treated as zero.

dq_dz — energy-injection history#

solve(dq_dz=lambda z: dQ_dz(z), method="pde",
      z_min=1e3, z_max=3e6, n_z=5000)
  • Signature: dq_dz(z) -> float (or array). The wrapper attempts a vectorised call dq_dz(z_arr) first and falls back to scalar evaluation. Vectorise where you can — the tabulation grid has 5000 points by default.

  • Quantity: \(d(\Delta\rho/\rho_\gamma)/dz\), the per-redshift derivative of the fractional energy perturbation. Dimensionless.

  • Sign: positive for heating (energy added to the photon bath).

  • Tabulation grid: log-spaced in \((1+z)\) from max(z_end, z_min) to z_max with n_z points. Defaults \(z_{\min}=10^3\), \(z_{\max}=3\times10^6\), \(n_z=5000\).

  • Mode: with method="pde" the callable is tabulated and the Rust PDE solver integrates it. Without method="pde", solve falls back to the analytic Green’s function (no Rust binary).

photon_source — frequency-dependent photon injection#

solve(photon_source=lambda x, z: source(x, z),
      z_min=1e3, z_max=3e6, n_z=5000,
      x_min=0.01, x_max=30.0, n_x=500)
  • Signature: photon_source(x, z) -> float. Called scalar-by-scalar on the tabulation grid (no vectorisation), so keep it cheap.

  • Quantity: \(d(\Delta n)/dz\) at frequency \(x\) and redshift \(z\), the per-redshift derivative of the photon-occupation perturbation. Dimensionless.

  • Frequency: \(x = h\nu/(k_{\rm B} T_z)\) — the same dimensionless variable used everywhere in the solver.

  • Sign: positive for photon injection at \((x, z)\).

  • Tabulation grid: 2-D, log-spaced in both z (capped at 500 points) and x. Pass an explicit x= array to use a custom frequency grid.

  • Mode: PDE only (the analytic Green’s function does not handle arbitrary frequency-resolved sources).

Both callables are spot-checked for non-finite output at five log-spaced redshifts before tabulation.

Quick example#

from spectroxide import solve

# Decaying-particle injection (one of the built-in scenarios)
result = solve(
    injection={
        "type": "decaying_particle",
        "f_x": 1e6,                  # eV
        "gamma_x": 1e-15,            # 1/s
    },
)
print(result.mu, result.y)
x, dn = result.x, result.delta_n     # numpy arrays
from spectroxide import run_sweep

# Single-burst energy injection at multiple z_h, one Rust process
result = run_sweep(
    delta_rho=1e-5,
    z_injections=[1e4, 1e5, 1e6],
)
for r in result["results"]:
    print(r["z_h"], r["pde_mu"], r["pde_y"])
from spectroxide import run_photon_sweep

# Monochromatic photon injection at x_inj = 0.5, ΔN/N = 1e-6
result = run_photon_sweep(
    x_inj=0.5,
    delta_n_over_n=1e-6,
    z_injections=[1e5, 5e5],
)
from spectroxide import solve

# Arbitrary dQ/dz callable; tabulated and handed to Rust
dq_dz = lambda z: 1e-9 / (1.0 + z)
result = solve(dq_dz=dq_dz, method="pde")
import numpy as np
from spectroxide import solve

# Scan decaying-particle gamma_x — call solve() in a Python loop
results = [
    solve(injection={"type": "decaying_particle",
                     "f_x": 1e6, "gamma_x": g})
    for g in np.logspace(-16, -14, 20)
]
mu_arr = np.array([r.mu for r in results])

Python wrappers around the Rust spectroxide binary.

Provides convenience functions for running the full PDE solver from Python (subprocess + JSON over stdout) and for quick single-injection calculations using the pure-Python Green’s function module.

Conventions#

  • Δρ/ρ is the fractional energy injection in the photon background.

  • ΔN/N is the fractional photon-number perturbation (photon injection).

  • Injection scenarios are passed as a dict with a "type" key; see the injection parameter of solve() for the supported types.

  • All redshifts are dimensionless; all temperatures are in kelvin.

For the typed cosmology container (Cosmology dataclass) and the flat ΛCDM background quantities, see Cosmology (spectroxide.cosmology). solve and run_sweep accept either a Cosmology instance or a plain dict via the cosmo= keyword.

Reference#

solve

Unified entry point for spectral-distortion calculations.

run_sweep

Run a single-burst PDE sweep over injection redshifts.

run_photon_sweep

Photon-injection sweep over multiple z_h at fixed x_inj.

run_photon_sweep_batch

Batch photon-injection sweep over multiple x_inj values.

spectroxide.solver.solve(injection=None, cosmo=None, z_start=None, z_end=500.0, method='pde', z_h=None, delta_rho=1e-05, x=None, x_min=0.01, x_max=30.0, n_x=500, dq_dz=None, photon_source=None, table=None, z_min=1000.0, z_max=3000000.0, n_z=5000, verify_hash=True, debug=False, **kwargs)[source]#

Unified entry point for spectral-distortion calculations.

Dispatches between the Rust PDE solver, the analytic Green’s function, and the precomputed Green’s-function table. Returns a structured SolverResult with frequency grid, distortion, μ, y, and an intensity property.

For multi-redshift sweeps use run_sweep() (single-burst energy injection) or run_photon_sweep() / run_photon_sweep_batch() (monochromatic photon injection); solve() itself runs one PDE per call.

Parameters:
  • injection (Mapping, optional) –

    PDE injection scenario. Required when method="pde" unless dq_dz or photon_source is given. Must contain a "type" key; supported types are "single_burst", "decaying_particle", "annihilating_dm", "annihilating_dm_pwave", "monochromatic_photon", "decaying_particle_photon", and "dark_photon_resonance". Remaining keys are scenario parameters, e.g.:

    {"type": "single_burst", "z_h": 2e5}
    

    Note that delta_rho is a top-level argument, not an injection key. For dark photons:

    {"type": "dark_photon_resonance", "epsilon": 1e-9, "m_ev": 1e-7}
    

    triggers the Rust solver to compute γ_con and z_res internally and install the impulsive depletion IC at z_res.

  • cosmo (Cosmology, Mapping, or None, optional) – Cosmological parameters. Accepts a Cosmology dataclass or a plain dict; None (default) uses Rust defaults.

  • z_start (float, optional) – Starting redshift for the PDE. None (default) picks a sensible value: just above z_h for transient injections, 3e6 for continuous scenarios, or the Rust CLI default for dq_dz / photon_source.

  • z_end (float, optional) – Final redshift (default 500).

  • method ({"pde", "greens_function", "table"}, optional) – Solver mode (default "pde").

  • z_h (float, optional) – Injection redshift for Green’s-function or table single-burst modes. Default None.

  • delta_rho (float, optional) – Fractional energy injection Δρ/ρ. Default 1e-5.

  • x (array_like, optional) – Custom dimensionless frequency grid. None (default) uses np.logspace(log10(x_min), log10(x_max), n_x).

  • x_min (float, optional) – Minimum frequency (default 0.01).

  • x_max (float, optional) – Maximum frequency (default 30.0).

  • n_x (int, optional) – Number of frequency points (default 500).

  • dq_dz (callable, optional) – Custom heating rate d(Δρ/ρ)/dz. With method="pde", tabulates and runs the Rust PDE solver; with method="greens_function", uses the Python Green’s function; with method="table", convolves the precomputed table.

  • photon_source (callable, optional) – Frequency-dependent photon source f(x, z) -> float returning d(Δn)/dz. Requires method="pde".

  • table (GreensTable, PhotonGreensTable, str, Path, or None, optional) – Precomputed table for method="table". May be a table object, a path to a saved .npz file, or None (default) to auto-load the default cache (building it on demand).

  • z_min (float, optional) – Lower integration bound for dq_dz in table/GF mode (default 1e3).

  • z_max (float, optional) – Upper integration bound (default 3e6).

  • n_z (int, optional) – Number of redshift points for integration (default 5000).

  • verify_hash (bool, optional) – For method="table": validate the cached table against the binary’s physics hash before use. Default True.

  • debug (bool, optional) – Use the DEBUG quality preset instead of PRODUCTION. Default False.

  • **kwargs – PDE-mode tuning knobs forwarded to the Rust binary (dy_max, n_points, dtau_max, number_conserving, no_dcbr, cosmo_params, timeout, n_threads, …); see run_sweep() for the full list and their defaults.

Returns:

SolverResult – Structured result with attributes x, delta_n, mu, y, delta_rho_over_rho, method, z_h, plus the SolverResult.delta_I property converting to physical intensity.

Raises:
  • ValueError – If incompatible arguments are supplied (e.g. method="pde" but neither injection nor dq_dz/photon_source).

  • TypeError – If table is neither GreensTable, PhotonGreensTable, str, Path, nor None.

spectroxide.solver.run_sweep(delta_rho=1e-05, z_injections=None, z_end=500.0, z_start=None, cosmo_params=None, project_root=None, timeout=600.0, dy_max=None, n_points=None, dtau_max=None, dtau_max_photon_source=None, number_conserving=True, nc_z_min=None, no_dcbr=False, production_grid=None, debug=False, n_threads=None)[source]#

Run a single-burst PDE sweep over injection redshifts.

Calls the Rust binary once with a list of z_injections and a fixed delta_rho; the binary loops over redshifts internally (parallelised via n_threads).

For other PDE workloads use solve() instead:

  • Custom injection scenario → solve(injection={...}).

  • Tabulated heating history → solve(dq_dz=callable, method="pde").

  • Frequency-dependent photon source → solve(photon_source=callable).

  • Monochromatic photon injection sweep → run_photon_sweep().

Parameters:
  • delta_rho (float, optional) – Fractional energy injection Δρ/ρ. Default 1e-5.

  • z_injections (sequence of float, optional) – Injection redshifts to sweep over. If None (default), the Rust binary uses a 15-point log-spaced grid from 2e3 to 5e5.

  • z_end (float, optional) – Final redshift for PDE evolution. Default 500.

  • z_start (float, optional) – Starting redshift for PDE evolution. None (default) uses the Rust CLI default (5e6).

  • cosmo_params (Mapping, optional) – Cosmological parameters. None uses Rust defaults.

  • project_root (str or Path, optional) – Path to the Rust project root. None (default) auto-detects relative to this module.

  • timeout (float, optional) – Maximum time in seconds to wait for the Rust binary. Default 600.

  • dy_max (float, optional) – Maximum step in y_C taken by the PDE. Must lie in (0, 0.1]. None (default) uses the Rust default.

  • n_points (int, optional) – Number of frequency-grid points. None (default) inherits from the active quality preset (PRODUCTION or DEBUG).

  • dtau_max (float, optional) – Cap on the dimensionless Compton optical-depth step Δτ_C. None uses the Rust default.

  • dtau_max_photon_source (float, optional) – Cap on the optical-depth step during active photon-source injection. None inherits from the active preset.

  • number_conserving (bool, optional) – Enforce strict photon-number conservation in the PDE. Default True.

  • nc_z_min (float, optional) – Below this redshift, photon-number conservation is relaxed. None uses the Rust default.

  • no_dcbr (bool, optional) – Disable DC+BR entirely (diagnostic). Default False.

  • production_grid (bool, optional) – Use the production-quality frequency grid. None inherits from the active preset.

  • debug (bool, optional) – If True, use the DEBUG quality preset. Default False.

  • n_threads (int, optional) – Number of threads for parallel sweep execution. None uses all available CPU cores.

Returns:

dict – Parsed JSON output. Each per-redshift entry in results carries keys z_h, pde_mu, pde_y, drho, x, delta_n.

Raises:
  • FileNotFoundError – If the Rust binary is unavailable and cargo cannot build it.

  • RuntimeError – If the Rust solver exits non-zero or returns malformed JSON.

spectroxide.solver.run_photon_sweep(x_inj, delta_n_over_n=1e-05, sigma_x=None, z_injections=None, z_end=500.0, cosmo_params=None, project_root=None, timeout=600.0, dy_max=None, n_points=None, dtau_max=None, dtau_max_photon_source=None, number_conserving=True, nc_z_min=None, no_dcbr=False, production_grid=None, debug=False, n_threads=None)[source]#

Photon-injection sweep over multiple z_h at fixed x_inj.

Calls the Rust photon-sweep subcommand, which parallelises across injection redshifts internally using native threads.

Parameters:
  • x_inj (float) – Injection frequency (dimensionless x = h ν / (k_B T_z)).

  • delta_n_over_n (float, optional) – Fractional photon-number injection ΔN/N. Default 1e-5.

  • sigma_x (float, optional) – Frequency width of the injection Gaussian. Default None (uses x_inj × 0.05 on the Rust side).

  • z_injections (sequence of float, optional) – Injection redshifts. Default None — Rust uses 150 log-spaced points from 1e3 to 5e6.

  • z_end (float, optional) – Final redshift for PDE evolution. Default 500.

  • cosmo_params (Mapping, optional) – Cosmological parameters. Default None (Rust defaults).

  • project_root (str or Path, optional) – Path to the Rust project root. Default None (auto-detected).

  • timeout (float, optional) – Timeout in seconds. Default 600.

  • dy_max (float, optional) – See run_sweep().

  • n_points (int, optional) – See run_sweep().

  • dtau_max (float, optional) – See run_sweep().

  • dtau_max_photon_source (float, optional) – See run_sweep().

  • number_conserving (bool, optional) – See run_sweep().

  • nc_z_min (float, optional) – See run_sweep().

  • no_dcbr (bool, optional) – See run_sweep().

  • production_grid (bool, optional) – See run_sweep().

  • debug (bool, optional) – See run_sweep().

  • n_threads (int, optional) – See run_sweep().

Returns:

dict – Parsed JSON with keys x_inj, delta_n_over_n, results.

Raises:

ValueError – If x_inj ≤ 0 / non-finite, sigma_x ≤ 0, or dy_max is outside (0, 0.1].

spectroxide.solver.run_photon_sweep_batch(x_inj_values, delta_n_over_n=1e-05, sigma_x=None, z_injections=None, z_end=500.0, cosmo_params=None, project_root=None, timeout=3600.0, dy_max=None, n_points=None, dtau_max=None, dtau_max_photon_source=None, number_conserving=True, nc_z_min=None, no_dcbr=False, production_grid=None, debug=False, n_threads=None)[source]#

Batch photon-injection sweep over multiple x_inj values.

Calls the Rust photon-sweep-batch subcommand, which parallelises all (x_inj, z_h) pairs in a single process, avoiding subprocess overhead and CPU oversubscription.

Parameters:
  • x_inj_values (sequence of float) – Injection frequencies (dimensionless). Must be non-empty, finite, and positive.

  • delta_n_over_n (float, optional) – Fractional photon-number injection ΔN/N. Default 1e-5.

  • sigma_x (float, optional) – Frequency width of each injection Gaussian. Default None (Rust uses x_inj × 0.05 per value).

  • z_injections (sequence of float, optional) – Injection redshifts. Default None (150 log-spaced from 1e3 to 5e6 on the Rust side).

  • z_end (float, optional) – Final redshift for PDE evolution. Default 500.

  • cosmo_params (Mapping, optional) – Cosmological parameters. Default None.

  • project_root (str or Path, optional) – Path to the Rust project root. Default None (auto-detected).

  • timeout (float, optional) – Timeout in seconds. Default 3600.

  • dy_max (float, optional) – See run_sweep().

  • n_points (int, optional) – See run_sweep().

  • dtau_max (float, optional) – See run_sweep().

  • dtau_max_photon_source (float, optional) – See run_sweep().

  • number_conserving (bool, optional) – See run_sweep().

  • nc_z_min (float, optional) – See run_sweep().

  • no_dcbr (bool, optional) – See run_sweep().

  • production_grid (bool, optional) – See run_sweep().

  • debug (bool, optional) – See run_sweep().

  • n_threads (int, optional) – See run_sweep().

Returns:

list of dict – Parsed JSON results, one per x_inj value. Each has keys x_inj, delta_n_over_n, results.

Raises:

ValueError – If x_inj_values is empty, contains non-finite or non-positive entries, or if sigma_x/dy_max are out of range.

Result container#

Structured return value from solve(). Bundles the frequency grid, distortion Δn(x), scalar μ/y/ΔT/T components, and a convenience property converting to intensity units.

SolverResult

Structured result from a solve() invocation.

SolverResult.delta_I

Intensity distortion [GHz], ΔI [Jy/sr]).

class spectroxide.solver.SolverResult[source]#

Bases: object

Structured result from a solve() invocation.

Variables:
  • x (ndarray of float64) – Dimensionless frequency grid x = h ν / (k_B T_z).

  • delta_n (ndarray of float64) – Spectral distortion Δn(x) on x.

  • mu (float) – Chemical-potential parameter μ (dimensionless).

  • y (float) – Compton y-parameter (dimensionless).

  • delta_rho_over_rho (float) – Fractional energy perturbation Δρ/ρ.

  • method (str) – Solver method used: one of "pde", "greens_function", "table".

  • z_h (float, optional) – Injection redshift (single-burst modes). None for continuous injection or custom heating histories.

  • rho_e (float, optional) – Final electron-photon temperature ratio T_e/T_z (PDE only).

  • accumulated_delta_t (float, optional) – Accumulated temperature shift ΔT/T from photon-number non-conservation absorbed into the blackbody temperature (PDE only).

property SolverResult.delta_I[source]#

Intensity distortion [GHz], ΔI [Jy/sr]).

Convenience wrapper around spectroxide.greens.delta_n_to_delta_I() using the default T_0 = 2.726 K.

Note

This property allocates two new NumPy arrays on every access (it is not a cached field). Bind to a local variable when reusing the result.

Returns:

tuple of (ndarray, ndarray) – Frequency in GHz and intensity distortion in Jy/sr (= 10⁻²⁶ W m⁻² Hz⁻¹ sr⁻¹).

Convenience wrapper#

spectroxide.solver also exports run_single(), a thin wrapper around the analytic Green’s function in spectroxide.greens. Despite living in the solver module, it does not invoke the Rust PDE — it bundles single-burst and custom-heating calculations into a single dict-returning call.

spectroxide.solver.run_single(z_h=None, delta_rho=1e-05, x=None, x_min=0.01, x_max=30.0, n_x=500, dq_dz=None, z_min=1000.0, z_max=3000000.0, n_z=5000)[source]#

Quick calculation using the pure-Python Green’s function.

Two modes of operation#

Single burst (default) — provide z_h and delta_rho for a delta-function energy injection at one redshift. Uses the cosmo-aware Green’s function so J_Compton(z) correctly suppresses y at z_h 1100.

Custom heating — provide dq_dz, a callable returning d(Δρ/ρ)/dz (positive for heating). The spectrum is computed via spectroxide.greens.distortion_from_heating() and μ/y are extracted by separate integrations.

param z_h:

Injection redshift (single-burst mode). Required unless dq_dz is given. Default None.

type z_h:

float, optional

param delta_rho:

Fractional energy injection Δρ/ρ. Default 1e-5. Only used in single-burst mode.

type delta_rho:

float, optional

param x:

Custom dimensionless frequency grid. If None (default), a log-spaced grid is generated from x_min, x_max, n_x.

type x:

array_like, optional

param x_min:

Minimum dimensionless frequency (default 0.01). Ignored when x is provided.

type x_min:

float, optional

param x_max:

Maximum dimensionless frequency (default 30.0). Ignored when x is provided.

type x_max:

float, optional

param n_x:

Number of frequency points (default 500). Ignored when x is provided.

type n_x:

int, optional

param dq_dz:

Heating rate d(Δρ/ρ)/dz. When given, z_h and delta_rho are ignored.

type dq_dz:

callable, optional

param z_min:

Lower integration bound for dq_dz mode (default 1e3).

type z_min:

float, optional

param z_max:

Upper integration bound (default 3e6).

type z_max:

float, optional

param n_z:

Number of redshift points for integration (default 5000).

type n_z:

int, optional

returns:

dict – Keys x (ndarray, frequency grid), delta_n (ndarray, distortion Δn(x)), mu (float), y (float), z_h (float or None, single-burst mode), delta_rho (float or None).

raises ValueError:

If neither z_h nor dq_dz is provided, or if any input is out of range (negative redshift, non-finite values, …).

Quality presets#

Two preset dicts control grid resolution and timestep caps for solve(), run_sweep(), and the photon-sweep entry points. PRODUCTION is the default; pass debug=True to switch to the faster DEBUG preset for quick checks.

PRODUCTION

Production-quality settings (default).

DEBUG

Fast settings for interactive exploration and debugging.

spectroxide.solver.PRODUCTION[source]#

Production-quality settings (default). Suitable for publication plots and CosmoTherm comparisons. Uses 4000-point grid, tight timestep control for photon injection spikes (dtau_max_photon_source=1).

spectroxide.solver.DEBUG[source]#

Fast settings for interactive exploration and debugging. Uses 1000-point grid with relaxed timestep control. Results are qualitatively correct but may differ by ~10% at soft-photon injection peaks.

Build provenance#

spectroxide.solver.get_physics_hash(project_root=None, timeout=60.0)[source]#

Return the Rust binary’s compile-time physics-source hash.

Used to validate cached Green’s function tables against the binary that produced them. The result is cached for the process lifetime; if you rebuild the binary mid-session, restart the interpreter (or call get_physics_hash.cache_clear()).

Parameters:
  • project_root (str or Path, optional) – Path to the Rust project root. Defaults to ../../ relative to this module (the repository root).

  • timeout (float, optional) – Maximum time in seconds to wait for the binary (default 60.0).

Returns:

str – Hexadecimal hash of the physics source files baked into the binary at build time.

Raises: