primat.backend — backend dispatch¶
primat.backend¶
Dispatch layer choosing between the compiled C extension
(primat._primat_c, wrapping primat-c’s cprimat_run) and the
pure-Python solver (primat.main.PRIMAT).
HAS_C_BACKEND is probed once at import time (True iff the extension
built successfully – see setup.py’s optional_build_ext, which lets
pip install succeed even without a C compiler). run_bbn() is the
single dispatch entry point; everything else in this module supports it.
Feature gap (the one remaining C-unsupported PRIMAT.__init__
extension):
background=(a customprimat.background.Backgroundobject) – an inherently-Python extension point (arbitrary user Python subclassing the background), with no way to cross the C ABI. A non-Nonebackground=always forces the Python backend underforce_backend in (None, "auto"), and raisesValueErrorunderforce_backend="c".
The former extra_rho and decay_era gaps are now closed:
extra_rho(extra Friedmann energy-density callables) is supported on the C backend via a tabulated handoff –_tabulate_extra_rho()evaluates the summedrho(Tg)on a dense log-Tg grid and passes the(Tg[], rho[])arrays to the C extension, which splines them and addsrho(Tg)insidecpr_bg_Hubble(seeprimat-c/src/background.candconfig.h’sextra_rho_*fields). Both backends agree to the cross-backend tolerance.decay_era(the long-lived-isotope Decay-Time era pastT_end) is ported:cpr_nuclear_network_decay_era(primat-c/src/nuclear_network.c) mirrors_integrate_decay_era’s matrix-exponential decay propagation (scaling-and-squaring Padé-13). It changes no result-dict observable on either backend (Y_finalis the end-of-LT state); its only output is the optionaloutput_decay_evolutionTSV, which both backends write in the identical schema.
Set PRIMAT_BACKEND_LOG=1 in the environment (or call with
log_backend=True) to print, on every run_bbn()/run_mc() call,
which backend actually ran and why – chiefly to catch a silent
force_backend="auto" fallback to Python (e.g. because a C-unsupported
feature was requested, or the extension failed to build) during development.
custom_network (the GUI “Customise Reactions” override: removed/replaced/
added reactions plus rate-table overrides) is supported on both backends:
primat-c’s cprimat_run/cpr_mc_uncertainty take an optional
CPRCustomNetwork* (primat-c/include/network_data.h), and
primat/_primat_c_src/_wrapper.c parses the same dict shape
(UpdateNuclearRates/kept_to_custom_network, see
primat/network_data.py/primat/gui/custom_rates.py) into one. It is no
longer part of python_only_feature below.
output_time_evolution=True is supported on both backends: the C
extension’s cprimat_run populates CPRResults’s
evol_* in-memory arrays (primat-c/include/api.h) and
primat/_primat_c_src/_wrapper.c hands them back as an "evolution" dict
key (plain Python lists, no numpy C-API dependency in the extension); this
module assembles the same primat.evolution.EvolutionResult shape
the Python backend produces, with no disk I/O on either backend’s part.
data_dir/user_nuclear_dir (see CLAUDE.md’s “Rates directory
resolution” section) are supported on both backends: data_dir fully
replaces the shipped data tree; user_nuclear_dir is an additive overlay
for nuclear networks and rate tables. They are ordinary params dict keys
applied generically via cpr_config_set_by_name on the C side, so no
special-casing is needed here — except that data_dir must also be
forwarded as the data_dir positional argument to _c_ext.run_bbn/
_c_ext.run_mc (the C extension’s cpr_config_init_defaults takes the
data folder there rather than via cpr_config_set_by_name).
run_mc() is the MC counterpart of run_bbn(): it dispatches between
primat._primat_c’s run_mc (wrapping primat-c/src/mc.c’s threaded
cpr_mc_uncertainty) and primat.main.mc_uncertainty (joblib), returning
the same primat.main.MCResult shape either way – the “common
language” the two backends share for MC results (CLAUDE.md’s backend-parity
mandate). The C path uses a pthread/xoshiro256** RNG, not NumPy’s
default_rng, so individual samples are not bit-for-bit comparable across
backends (only statistically, mean/std convergence – see mc.h).
prev (incremental sample reuse) is supported on the C path, mirroring
cpr_mc_uncertainty’s prev_centrals/prev_values parameters (see
mc.h): run_mc() checks the same reuse-guard mc_uncertainty does
internally (seed/quantities/params/custom_network all matching), plus one
more condition the C side cannot check for itself – prev.backend must
equal the backend about to compute the extension, since the two backends’
RNG streams are not interchangeable. A prev that fails the guard (e.g.
computed by the other backend) is silently ignored, exactly like
mc_uncertainty’s own fallback – never an error, and never a forced
backend switch. custom_network is supported on both backends, same as
run_bbn().
- primat.backend.run_bbn(params=None, force_backend=None, extra_rho=None, custom_network=None, background=None, log_backend=False, progress=None)[source]¶
Run one BBN computation, dispatching to the C or Python backend.
This mirrors
PRIMAT(params=params, ...).solve()’s result dict (same keys:YPBBN,DoH,Neff, … – seeprimat.main.PRIMAT.solveandtests/test_backend_parity.py), so callers can switch backends transparently.- Parameters:
params (dict[str, Any] | None) – dict, optional. Same
PRIMATConfigoverrides accepted byPRIMAT(params=...).force_backend (str | None) –
{None, "auto", "c", "python"}.None/"auto"(default) picks the C extension when it is available and the request has no C-unsupported feature (see module docstring), otherwise the Python backend."c"/"python"force that backend, raisingRuntimeError/ValueErrorrespectively if the C backend is unavailable or the request uses a C-unsupported feature.extra_rho (list | None) – forwarded to
PRIMAT.__init__verbatim.extra_rho/backgroundare Python-only (see module docstring), so any non-Nonevalue forces the Python backend regardless offorce_backend(exceptforce_backend="c", which raises instead).custom_networkis supported on both backends and never forces a fallback.custom_network (dict[str, Any] | None) – forwarded to
PRIMAT.__init__verbatim.extra_rho/backgroundare Python-only (see module docstring), so any non-Nonevalue forces the Python backend regardless offorce_backend(exceptforce_backend="c", which raises instead).custom_networkis supported on both backends and never forces a fallback.background – forwarded to
PRIMAT.__init__verbatim.extra_rho/backgroundare Python-only (see module docstring), so any non-Nonevalue forces the Python backend regardless offorce_backend(exceptforce_backend="c", which raises instead).custom_networkis supported on both backends and never forces a fallback.log_backend (bool) – bool, default False. Print which backend actually ran and why (module docstring); also triggered by setting the
PRIMAT_BACKEND_LOGenvironment variable.progress (bool | None) – bool, optional.
None(default) defers toparams['show_progress'](DEFAULT_PARAMSdefaultTrue); pass an explicitTrue/Falseto override it for this call. Controls the compact[primat] HT. MT. LT. done.stderr phase markers on both backends (suppressed whenverbose=True).
- Returns:
dict – the BBN result dict (
YPBBN,DoH,Neff, …, plus aY_finalsub-dict of every tracked nuclide’s final mass fraction).- Return type:
Example
>>> run_bbn({"network": "small"})["YPBBN"] 0.24700... >>> run_bbn({"network": "small"}, force_backend="python")["YPBBN"] 0.24699...
- primat.backend.run_mc(num_mc, quantities=None, params=None, force_backend=None, seed=0, n_jobs=-1, prev=None, custom_network=None, log_backend=False, progress=None)[source]¶
Run an MC nuclear-rate/tau_n uncertainty propagation, dispatching to the C or Python backend (the MC counterpart of
run_bbn()).This mirrors
primat.main.mc_uncertainty()’s return value (anprimat.main.MCResult, indexed by quantity name – same.central/.mean/.std/.valuesper quantity), so callers can switch backends transparently; see this module’s docstring for the RNG caveat (C samples are statistically, not bit-for-bit, comparable to Python’s).- Parameters:
num_mc (int) – int. Number of MC samples.
quantities (str | list[str] | None) – str or list of str, optional. A result-dict key (
'YPBBN','DoH', …) or nuclide name, or a list of either.None(default) uses every tracked nuclide’s final Y plus the full_DEFAULT_MC_OBSERVABLESset (see_default_mc_quantities()). Regardless of what is passed here, the returnedMCResultalways additionally contains every tracked nuclide and every_DEFAULT_MC_OBSERVABLESentry this network/custom_network actually produces – at no extra solving cost, since each MC sample already runs a full solve – so a TSV dump (dump_mc_samples()) is always complete even whenquantitiesonly asked for one or two values for display.params (dict[str, Any] | None) – forwarded verbatim; see
primat.main.mc_uncertainty’s docstring.seed (int | None) – forwarded verbatim; see
primat.main.mc_uncertainty’s docstring.n_jobs (int) – forwarded verbatim; see
primat.main.mc_uncertainty’s docstring.force_backend (str | None) –
{None, "auto", "c", "python"}, same semantics asrun_bbn().prev (MCResult | None) – supported on both backends (see module docstring); a previously computed
primat.main.MCResultto extend rather than recompute from scratch. Reused only when it is sample-compatible (same seed/quantities/params/custom_network) and came from the same backend that will compute this call (prev.backend); otherwise silently ignored, mirroringmc_uncertainty’s own fallback. Never forces a backend switch or raises.custom_network (dict[str, Any] | None) – supported on both backends (forwarded to
cpr_mc_uncertainty’sCPRCustomNetwork*); never forces a fallback.log_backend (bool) – bool, default False. Print which backend actually ran and why (module docstring); also triggered by setting the
PRIMAT_BACKEND_LOGenvironment variable.progress (bool | None) – bool, optional.
None(default) defers toparams['show_progress'](DEFAULT_PARAMSdefaultTrue); pass an explicitTrue/Falseto override it for this call. Controls the[MC] Running N samples...banner and theN/total (XX%)counter on both backends.
- Returns:
primat.main.MCResult
- Return type:
MCResult
Example
>>> run_mc(50, ['YPBBN', 'DoH'], params={'network': 'small'})['YPBBN'].std >>> run_mc(50, force_backend='python')['DoH'].mean
- primat.backend.dump_mc_samples(mc)[source]¶
Serialise an
primat.main.MCResultto TSV text: one column per quantity (header = quantity names, in their original order), one row per MC sample – the on-disk “common language” for MC results shared by both backends (CLAUDE.md’s backend-parity mandate), and the same shape written to<output_mc_file_prefix>_samples.tsvwhenoutput_mc_samples=True.- Parameters:
mc (MCResult) – primat.main.MCResult.
- Returns:
str – TSV text, with a trailing newline.
- Return type:
- primat.backend.dump_mc_covariance(mc)[source]¶
Serialise an
primat.main.MCResult’s full sample covariance matrix (mc.cov(); ddof=1, all MC quantities inquantity_namesorder) to the two-header-line TSV written to<output_mc_file_prefix>_covariance.tsvwhenoutput_mc_covariance=True.The joint (off-diagonal) covariance – e.g. between
YPBBNandDoH, which are driven by the same MC samples – is exactly what a user needs to build a multi-observable likelihood; the per-observable variances live on the diagonal (C[i, i] == mc[q_i].std**2).File layout (author spec):
# Covariance matrix of the N=100 primat MC samples (seed=0): ... quantity Neff YPBBN ... Neff <C[0,0]> <C[0,1]> ... ...
i.e. line 1 is a single
#comment naming the file (with N, seed and the ddof=1 estimator convention); line 2 is the tab-separated quantity names labelling both columns and rows; then one row per quantity, its name first.- Parameters:
mc (MCResult) – primat.main.MCResult.
- Returns:
str – TSV text, with a trailing newline.
- Return type:
- primat.backend.dump_mc_correlation(mc)[source]¶
Serialise an
primat.main.MCResult’s full sample correlation matrix (mc.corr(); unit diagonal, ddof=1) to the two-header-line TSV written to<output_mc_file_prefix>_correlation.tsvwhenoutput_mc_correlation=True.Same layout as
dump_mc_covariance()(line 1 = a#comment; line 2 = the quantity names; then one labelled row per quantity), with its own header wording and a unit diagonal. A quantity that was identical in every sample (zero variance) has NaN off-diagonal entries – seeprimat.main.MCResult.corr().- Parameters:
mc (MCResult) – primat.main.MCResult.
- Returns:
str – TSV text, with a trailing newline.
- Return type:
- primat.backend.dump_final_with_sigma(names, Y, sigma=None, num_mc=None)[source]¶
Render the
output_final.dat-format final-abundances text.Two columns (
nuclide Y) whensigmaisNone– identical to the plain single-run format written byNuclearNetwork._write_final_result. Three columns (nuclide Y sigma_N<num_mc>) when an MCsigmadict is supplied, so the sample count backing the uncertainty estimate is recorded directly in the header rather than only in the (separate) MC-samples file.The header row uses the same column widths as the data rows so the column names sit directly above their respective values (no
#comment prefix that would shift the label two characters to the right).- Parameters:
names (list[str]) – list of str. Nuclide names, in the order to write them.
Y (dict[str, float]) – dict, name -> final mass-fraction abundance.
sigma (dict[str, float] | None) – dict, name -> 1-sigma MC uncertainty on
Y[name], optional.num_mc (int | None) – int, required when
sigmais given – the MC sample count, recorded in the header (e.g.sigma_N50).
- Returns:
str – the file text, with a trailing newline.
- Return type: