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. |
|
Many injection redshifts at fixed amplitude (single-burst energy injection). |
|
Many injection redshifts at fixed |
|
Many |
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.
|
Required keys |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 calldq_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)toz_maxwithn_zpoints. 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. Withoutmethod="pde",solvefalls 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) andx. Pass an explicitx=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/Nis the fractional photon-number perturbation (photon injection).Injection scenarios are passed as a dict with a
"type"key; see theinjectionparameter ofsolve()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#
Unified entry point for spectral-distortion calculations. |
|
Run a single-burst PDE sweep over injection redshifts. |
|
Photon-injection sweep over multiple |
|
Batch photon-injection sweep over multiple |
- 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
SolverResultwith frequency grid, distortion,μ,y, and anintensityproperty.For multi-redshift sweeps use
run_sweep()(single-burst energy injection) orrun_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"unlessdq_dzorphoton_sourceis 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_rhois 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
γ_conandz_resinternally and install the impulsive depletion IC atz_res.cosmo (Cosmology, Mapping, or None, optional) – Cosmological parameters. Accepts a
Cosmologydataclass 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_hfor transient injections,3e6for continuous scenarios, or the Rust CLI default fordq_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
Δρ/ρ. Default1e-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. Withmethod="pde", tabulates and runs the Rust PDE solver; withmethod="greens_function", uses the Python Green’s function; withmethod="table", convolves the precomputed table.photon_source (callable, optional) – Frequency-dependent photon source
f(x, z) -> floatreturningd(Δn)/dz. Requiresmethod="pde".table (GreensTable, PhotonGreensTable, str, Path, or None, optional) – Precomputed table for
method="table". May be a table object, a path to a saved.npzfile, or None (default) to auto-load the default cache (building it on demand).z_min (float, optional) – Lower integration bound for
dq_dzin table/GF mode (default1e3).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
DEBUGquality preset instead ofPRODUCTION. 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, …); seerun_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 theSolverResult.delta_Iproperty converting to physical intensity.- Raises:
ValueError – If incompatible arguments are supplied (e.g.
method="pde"but neitherinjectionnordq_dz/photon_source).TypeError – If
tableis neitherGreensTable,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_injectionsand a fixeddelta_rho; the binary loops over redshifts internally (parallelised vian_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
Δρ/ρ. Default1e-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_Ctaken 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 (
PRODUCTIONorDEBUG).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
DEBUGquality 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
resultscarries keysz_h,pde_mu,pde_y,drho,x,delta_n.- Raises:
FileNotFoundError – If the Rust binary is unavailable and
cargocannot 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_hat fixedx_inj.Calls the Rust
photon-sweepsubcommand, 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. Default1e-5.sigma_x (float, optional) – Frequency width of the injection Gaussian. Default None (uses
x_inj × 0.05on 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, ordy_maxis 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_injvalues.Calls the Rust
photon-sweep-batchsubcommand, 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. Default1e-5.sigma_x (float, optional) – Frequency width of each injection Gaussian. Default None (Rust uses
x_inj × 0.05per 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_injvalue. Each has keysx_inj,delta_n_over_n,results.- Raises:
ValueError – If
x_inj_valuesis empty, contains non-finite or non-positive entries, or ifsigma_x/dy_maxare 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.
Structured result from a |
|
Intensity distortion |
- class spectroxide.solver.SolverResult[source]#
Bases:
objectStructured 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)onx.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/Tfrom 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 defaultT_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_handdelta_rhofor a delta-function energy injection at one redshift. Uses the cosmo-aware Green’s function soJ_Compton(z)correctly suppresses y atz_h ≲ 1100.Custom heating — provide
dq_dz, a callable returningd(Δρ/ρ)/dz(positive for heating). The spectrum is computed viaspectroxide.greens.distortion_from_heating()andμ/yare extracted by separate integrations.- param z_h:
Injection redshift (single-burst mode). Required unless
dq_dzis given. Default None.- type z_h:
float, optional
- param delta_rho:
Fractional energy injection
Δρ/ρ. Default1e-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
xis provided.- type x_min:
float, optional
- param x_max:
Maximum dimensionless frequency (default 30.0). Ignored when
xis provided.- type x_max:
float, optional
- param n_x:
Number of frequency points (default 500). Ignored when
xis provided.- type n_x:
int, optional
- param dq_dz:
Heating rate
d(Δρ/ρ)/dz. When given,z_handdelta_rhoare ignored.- type dq_dz:
callable, optional
- param z_min:
Lower integration bound for
dq_dzmode (default1e3).- 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_hnordq_dzis 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-quality settings (default). |
|
Fast settings for interactive exploration and debugging. |
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:
- Returns:
str – Hexadecimal hash of the physics source files baked into the binary at build time.
- Raises:
FileNotFoundError – If neither the prebuilt binary nor
cargoare available.RuntimeError – If the binary exits with non-zero status.