API reference¶
bngsim.Model¶
Factory methods:
Model.from_net(path)— Load from BNG.netfileModel.from_antimony(path)— Load from Antimony.antfile (requiresantimony,python-libsbml)Model.from_antimony_string(text)— Load from Antimony stringModel.from_sbml(path)— Load from SBML.xmlfile (requirespython-libsbml)Model.from_sbml_string(text)— Load from SBML XML string
Properties:
n_species,n_reactions,n_observables,n_parameters,n_functions,n_eventsspecies_names,param_names,observable_names
Methods:
set_param(name, value)— Set a parameter valueget_param(name)— Get a parameter valueset_params(dict)— Set multiple parameters (atomic)reset()— Reset species to initial concentrationsclone()— Deep copy for parallel workerssave_concentrations()— Snapshot current state as new initial conditionsset_concentration(name, value)— Set a single species concentrationget_concentration(name)— Get a single species concentrationadd_table_function(name, *, file, times, values, index)— Add a piecewise-linear table functionn_table_functions,table_function_names— TFUN introspection
bngsim.Simulator¶
Constructor:
Simulator(model, method="ode", *, jacobian, codegen, net_path, sensitivity_params, poplevel, xml_path, gml)method:"ode","ssa","psa","nf","nf_reject","nfsim","nf_exact","rulemonkey","rm"(and aliases)ODE-specific kwargs:
jacobian("auto","analytical","fd"),codegen(bool),net_path(BioNetGen.netpath only),sensitivity_params(list[str])For SBML and Antimony models, use
codegen=Truewithoutnet_path.net_pathis not a generic model path and should not point to SBML XML.PSA:
poplevel(float, required). Accepts.net, SBML, and Antimony models (same dispatch as SSA, sharing thevalidate_for_ssagate). NFsim/RuleMonkey:xml_path(str, required),gml(int).
Simulation:
run(t_span, n_points, *, seed, rtol, atol, max_steps, timeout, steady_state, steady_state_tol)→Resultrun_batch(t_span, n_points, *, params, seed, num_processors, squeeze, timeout, steady_state, steady_state_tol)→list[Result]orResultcompute_all_sensitivities(t_span, n_points, *, params, chunk_size, n_workers, rtol, atol, max_steps)→Resultwith full sensitivity tensor
timeout is a wall-clock budget in seconds (or None to disable, the default).
When the budget is exceeded, run / run_batch raise
bngsim.SimulationTimeout (a typed BngsimError) carrying timeout
(the configured limit) and elapsed (actual wall-clock time at the trip).
Supported on every backend (ODE, SSA, PSA, NFsim, RuleMonkey). NFsim and
RuleMonkey poll the budget at a coarser granularity than ODE/SSA/PSA
(between stepTo() output points for NFsim; every ~1024 SSA events for
RuleMonkey, via its upstream cancellation hook), so the surfaced elapsed
time may overshoot the budget by one such interval.
Interactive:
run_until(t, *, n_points, seed)→Resultintervene(params)— Change parameters mid-simulationsnapshot()→dict— Save staterestore(snapshot)— Restore state
Stop conditions:
add_stop_condition(condition, *, label)—strexpression orcallableclear_stop_conditions()
Steady-state (method ∈ "newton" (default), "integration", "kinsol" alias):
steady_state(*, tol, max_time, method, rtol, atol, max_steps, sensitivity_params)→SteadyStateResultsteady_state_batch(params, *, tol, max_time, method, rtol, atol, max_steps, n_workers)→list[SteadyStateResult]
Configuration:
set_tolerances(rtol, atol)— ODE solver tolerancesset_max_steps(max_steps)— Max internal solver steps
Properties:
method,model,current_time
bngsim.NfsimSession¶
Stateful NFsim session API for multi-action workflows where parameters or live particle counts are changed between simulation segments.
Constructor:
NfsimSession(xml_path, *, molecule_limit)— Create an NFsim session from a BNGL-generated NFsim XML file
Session control:
initialize(seed)— Initialize the live NFsim systemsimulate(t_start, t_end, *, n_points, timeout)→Result—timeoutis a wall-clock budget in seconds (Nonedisables, the default). Checked betweenstepTo()output points; on overrun raisesbngsim.SimulationTimeoutand the session must be destroyed before reuse.destroy()— Release the live NFsim session
Parameters:
set_param(name, value)— Set a parameter before initializationget_parameter(name)— Evaluate a parameter in the live session
Live counts:
get_molecule_count(molecule_type)— Count all live molecules of a typeadd_molecules(molecule_type, count)— Add default unbound moleculesget_species_count(pattern)— Count an exact single-molecule BNGL speciesadd_species(pattern, count)— Add exact single-molecule species instancesremove_species(pattern, count)— Remove exact single-molecule species instancesset_species_count(pattern, count)— Set the exact species count by adding or removing instances
Species count mutation currently supports exact, unbound, single-molecule BNGL
patterns such as "X(p~0,y)", "L(r)", and "TNF()". Patterns must list every
component and specify every stateful component state. Multi-molecule complex
patterns fail with a clear SimulationError.
bngsim.RuleMonkeySession¶
Stateful RuleMonkey session API for exact network-free workflows where parameters are configured before initialization and live particle counts can be changed between simulation segments.
Constructor:
RuleMonkeySession(xml_path, *, molecule_limit, block_same_complex_binding)— Create a RuleMonkey session from a BNGL-generated NFsim XML file
Session control:
initialize(seed)— Initialize the live RuleMonkey systemsimulate(t_start, t_end, *, n_points, timeout)→Result—timeoutis a wall-clock budget in seconds (Nonedisables, the default). Polled by upstream every ~1024 SSA events; on overrun raisesbngsim.SimulationTimeoutand the session must be destroyed before reuse.step_to(time, *, timeout)— Advance without recording output; honors the sametimeoutsemantics assimulate(...).destroy()— Release the live RuleMonkey session
Parameters and counts:
set_param(name, value)— Set a parameter before initializationclear_param_overrides()— Clear parameter overrides before initializationget_parameter(name)— Evaluate a parameter from XML plus overridesget_molecule_count(molecule_type)— Count all live molecules of a typeadd_molecules(molecule_type, count)— Add default unbound moleculesget_observable_names()— Return observable names from the XMLget_observable_values()— Return current observable values
bngsim.Result¶
Properties:
time—ndarray (n_times,)species—ndarray (n_times, n_species)with named accessobservables—ndarray (n_times, n_obs)with named access (e.g.result.observables["A_tot"])expressions—ndarray (n_times, n_expr)with named accesssensitivities—ndarray (n_times, n_species, n_params)forward sensitivity tensorhas_sensitivities—boolsensitivity_params—list[str]parameter names for sensitivitydataframe—pandas.DataFrame(requires pandas)xr— AMICI-style xarray accessor;result.xr.species,.observables,.expressions,.sensitivities,.sensitivities_icreturn labeledxarray.DataArrays withtime/state/observable/expression/parameter/ic_statecoords (requires xarray)solver_stats—dictwith solver diagnosticscustom_attrs—dictfor user metadatan_times,n_species,n_observables,n_expressionsspecies_names,observable_names,expression_names
Methods:
resolve_outputs(selectors)→list[dict]— resolve typed output selectors (species:/observable:/expression:, aliasesstate:→species:,function:→expression:, withexpression:foo()→expression:foo) to per-output metadata{"selector","kind","name","index","column_label"}. Bare names resolve only if unique across all three kinds; ambiguous/unresolved selectors raise with the candidates listed.outputs(selectors)→ndarray (n_times, n_outputs)— value columns for the named selectors (one column per selector, in order)fisher_information(sigma)→ndarray (n_params, n_params)— Fisher Information Matrix from sensitivity datagradient(loss_fn)→ndarray (n_params,)— parameter gradient ∇_p L from sensitivity tensor and loss functionto_gdat(path)— Export observables as BNG.gdatto_cdat(path)— Export species as BNG.cdatto_csv(path, *, kind="observables"|"species", sep=",", include_time=True, header=True)— Plain delimited-text export (CSV / TSV); no#prefix; SBML/RoadRunner-friendlyto_xarray()→xarray.Datasetbundling species/observables/expressions/sensitivities/sensitivities_ic with sharedtimecoord (requires xarray);custom_attrs+seedpropagate tods.attrssave(path)— Save to HDF5 (requires h5py)Result.load(path)— Load from HDF5 (class method)Result.squeeze(results)— Stack list into 3D batch result
Exceptions¶
All inherit from bngsim.BngsimError (which inherits RuntimeError):
ModelError—.netparse failures, invalid model stateSimulationError— Solver failures (convergence, NaN)SimulationTimeout— Wall-clock budget exceeded;.timeoutand.elapsedattributesParameterError— Unknown parameter name, invalid valueStopConditionMet— Stop condition triggered;.resulthas partial data
Universal .net reader¶
bngsim.parse_net_file(path)→dict— Parse a.netfile into an engine-agnostic Python dict with keys:parameters,species,observables,functions,reactions. Pure Python — no C++ extension needed for parsing.bngsim.build_model_from_parsed(parsed)→Model— Build a BNGsimModelfrom the dict returned byparse_net_file(). Routes throughModelBuilderfor full optimization (analytical Jacobian, conservation laws, etc.).
Utility functions¶
bngsim.reserved_names()→dictwith"constants"and"functions"listsbngsim.configure_logging(level)— Enable log output
Generated API (autodoc)¶
The curated summaries above cover the common surface. The full, always-in-sync reference below is generated directly from the package docstrings.
bngsim — Embeddable simulation engine for BioNetGen reaction networks.
Usage:
import bngsim
model = bngsim.Model.from_net("model.net")
sim = bngsim.Simulator(model, method="ode")
model.set_param("kf", 0.5)
result = sim.run(t_span=(0, 1000), n_points=1001)
result.time # (1001,) ndarray
result.observables # (1001, n_obs) ndarray
result.species # (1001, n_species) ndarray
See the package README for installation, usage, and API overview.
- class bngsim.Model(_core)[source]
Bases:
objectA BioNetGen reaction network model.
A Model holds species, reactions, observables, parameters, and functions. It can be loaded from
.netfiles and, via the factory methods below, from Antimony and SBML inputs.Models are not thread-safe. For parallel workers, use
clone()to create independent copies.- Parameters:
_core (
NetworkModel) – Internal C++ model object. Users should not construct this directly; use the factory methods instead.
Examples
>>> model = bngsim.Model.from_net("model.net") >>> model.n_species 5 >>> model.set_param("kf", 0.5) >>> model.get_param("kf") 0.5 >>> model.set_params({"kf": 1.0, "kr": 0.1})
- classmethod from_antimony(path)[source]
Load a model from an Antimony
.antfile.Antimony is a human-readable model description language. Internally converts to SBML via libantimony, then loads via libsbml for correct SBML semantics.
Requires:
pip install antimony python-libsbml- Parameters:
- Returns:
The loaded model.
- Return type:
Model- Raises:
ImportError – If
antimonyorlibsbmlis not installed.FileNotFoundError – If the file does not exist.
ModelError – If the file cannot be parsed.
- classmethod from_antimony_string(text)[source]
Load a model from an Antimony string.
- Parameters:
text (
str) – Antimony model text.- Returns:
The loaded model.
- Return type:
Model
- classmethod from_sbml(path, *, defer_jacobian=None)[source]
Load a model from an SBML
.xmlfile.- Parameters:
defer_jacobian (
bool|None) – GH #145 escape hatch. The analytical Functional Jacobian (GH #76) is derived lazily at the first ODE-solve setup by default (None); passdefer_jacobian=Falseto derive it eagerly at load instead (the pre-#145 behavior, for A/B and safety).BNGSIM_EAGER_JACOBIAN=1forces eager for every load path.
- Returns:
The loaded model.
- Return type:
Model- Raises:
ImportError – If
python-libsbmlis not installed.FileNotFoundError – If the file does not exist.
ModelError – If the file cannot be parsed.
- classmethod from_sbml_string(text, *, defer_jacobian=None)[source]
Load a model from an SBML XML string.
- classmethod from_net(path, *, defer_jacobian=None)[source]
Load a model from a BNG
.netfile.- Parameters:
- Returns:
The loaded model.
- Return type:
Model- Raises:
ModelError – If the file cannot be parsed.
FileNotFoundError – If the file does not exist.
- prepare_analytical_jacobian()[source]
Derive and attach the analytical Functional Jacobian (GH #76), at most once.
Idempotent (GH #145): the SymPy derivation runs only on the first call; later calls are no-ops guarded by the model’s once-only sentinel. Returns whether the model now carries a complete analytical Jacobian (
Falseif it fell back to finite differences, or was already FD / all-Elementary with the closed-form C++ Jacobian).This is the lazy-derivation entry point. The Jacobian is consumed only by ODE solves (CVODE’s dense Jacobian, the steady-state Newton solver, and codegen’s analytical-Jacobian emitter), so it is deferred off the model- load path (
from_sbml/from_netno longer derive) and triggered at ODE-solve setup. Call it directly to warm a parent template beforeclone()fan-out: a warmed parent passes the derived terms to clones (which re-compile the derivative ExprTk strings with no SymPy), so parallel fitting derives once, not once per worker.- Return type:
- property last_libsbml_parse_sec: float
Wall seconds the SBML loader spent in the libSBML parse phase (
readSBML*+ document-level error check).0.0for a non-SBML model (e.g.Model.from_net). SeeSimulator.last_libsbml_parse_sec.
- property last_interpret_sec: float
Wall seconds spent interpreting the parsed libSBML document into the internal
_coremodel (excludes libSBML parse, Jacobian derivation, and codegen).0.0for a non-SBML model. SeeSimulator.last_interpret_sec.
- property last_jacobian_sec: float
Wall seconds spent symbolically deriving this model’s analytical Functional Jacobian (GH #76).
0.0until the derivation runs (it is lazy since GH #145, and never runs on the SSA/PSA/NFsim paths). SeeSimulator.last_jacobian_sec.
- clone()[source]
Deep copy the model for parallel workers.
Each clone is fully independent — it has its own parameter values, species concentrations, and expression evaluator state.
- Returns:
An independent deep copy.
- Return type:
Model
- validate_for_ssa()[source]
Return SSA-compatibility issues detected by the SBML loader.
- Returns:
One entry per detected construct; empty for SSA-clean models and for models loaded outside the SBML path (
Model.from_net, builder).- Return type:
See also
bngsim.validate_for_ssamodule-level function with the same body.
- set_param(name, value)[source]
Set a parameter value by name.
- get_param(name)[source]
Get a parameter value by name.
- set_params(params)[source]
Set multiple parameters from a dict.
- Parameters:
- Raises:
ParameterError – If any parameter name is not found, or any value cannot be converted to float. Atomic: either all succeed or none do.
- Return type:
None
Examples
>>> model.set_params({"kf": 0.5, "kr": 0.1})
- reset()[source]
Reset all species to their initial concentrations.
Parameter values are not reset — only species concentrations.
- Return type:
- save_concentrations()[source]
Snapshot current species concentrations as the new initial state.
Subsequent
reset()calls will restore to this snapshot rather than the original initial conditions from the.netfile.Implements BNG
saveConcentrations()action.- Return type:
- set_concentration(name, value)[source]
Set a single species concentration by name.
- Parameters:
- Raises:
ModelError – If the species name is not found.
- Return type:
Notes
Implements BNG
setConcentration("name", value)action.
- get_concentration(name)[source]
Get a single species concentration by name.
- get_state()[source]
Bulk-copy the full live species-concentration vector (GH #102).
Returns a fresh
float64array of lengthn_species, ordered likespecies_names. This is the low-overhead per-step state-exchange primitive for driving bngsim as a reaction kernel from an external orchestrator (e.g. a hybrid SSA/ODE splitting loop): one Python call marshals the entire state, so per-step exchange cost stays negligible next to the ODE solve even at ~100K species.See also
set_statethe inverse bulk assignment.
species_namesthe ordering of the returned vector.
- Return type:
- set_state(state)[source]
Bulk-assign the full live species-concentration vector (GH #102).
- Parameters:
state (
ndarray) – 1-D array of lengthn_species, ordered likespecies_names. Copied into the model’s live concentrations; observables and other derived state are recomputed on the next RHS or observable evaluation.- Raises:
ValueError – If
stateis not 1-D or its length differs fromn_species.- Return type:
- property n_species: int
Number of species in the model.
- property n_reactions: int
Number of reactions in the model.
- property n_observables: int
Number of observable groups in the model.
- property n_parameters: int
Number of parameters in the model.
- property n_functions: int
Number of functions in the model.
- property param_is_expression: list[bool]
Per-parameter
is_expressionflag, parallel toparam_names.Truefor derivedConstantExpressionparameters such as the_rateLaw{N}symbols BNG2.pl emits when a BNGL rate law is a compound expression (e.g.chi*kon). These are not independent knobs — their values are computed from primary parameters and are re-evaluated automatically byset_param().
- property primary_param_names: list[str]
List of parameter names that are not derived constant expressions.
These are the genuine knobs of the model — primary rate constants, initial-condition parameters, etc. Use this when you want to expose the model to an external optimizer or sampler that should treat each parameter as an independent variable; varying a primary via
set_param()automatically propagates to derived parameters.
- add_table_function(name, *, file=None, times=None, values=None, index='time', method='linear')[source]
Add a table function (piecewise-linear interpolation of data).
The function is registered with the expression evaluator and can be referenced by name in rate law expressions.
- Parameters:
name (
str) – Function name (e.g.,"cumNcases").file (
str|Path|None) – Path to a.tfunfile. Mutually exclusive withtimes/values.times (
list[float] |None) – X (index) values. Must be used withvalues.values (
list[float] |None) – Y (function) values. Must be used withtimes.index (
str) – Index variable name. Default"time". Can also be a parameter or observable name for non-time-indexed table functions.method (
str) – Interpolation method:"linear"(default) or"step".
- Raises:
ModelError – If the file cannot be read or data is invalid.
ValueError – If arguments are inconsistent (e.g., both
fileandtimes).
- Return type:
Examples
>>> model.add_table_function("cumNcases", file="case_data.tfun") >>> model.add_table_function("response", file="dose.tfun", index="drug_conc") >>> model.add_table_function("drive", times=[0, 1, 2], values=[0, 5, 10])
- property n_table_functions: int
Number of registered table functions.
- class bngsim.Simulator(model, method='ode', *, xml_path='', poplevel=None, gml=None, connectivity=None, nfsim_v1143_compat=False, block_same_complex_binding=True, traversal_limit=None, jacobian='auto', force_dense_linear_solver=False, codegen=None, net_path='', sensitivity_params=None, sensitivity_ic=None, sensitivity_method='staggered', strict_ssa=True)[source]
Bases:
objectUnified simulation interface for ODE, SSA, PSA, and network-free methods.
- Parameters:
model (
Model) – The model to simulate.method (
str) –Simulation method:
Deterministic / network-based:
"ode"— CVODE adaptive BDF integrator (deterministic)"ssa"— Variant of Gillespie’s direct method (exact stochastic)"psa"— Partial Scaling Algorithm (approximate stochastic). Lin, Feng, Hlavacek, J. Chem. Phys. 150, 244101 (2019). Requirespoplevelkeyword argument.
Network-free (canonical tokens):
"nf"— Network-free simulation (default policy; currently routes tonf_reject)."nf_reject"— Rejection/null-event algorithm (NFsim-style). Requiresxml_pathkeyword argument."nf_exact"— Exact non-local network-free algorithm (RuleMonkey). Requiresxml_pathkeyword argument."nf_fixed"— Legacy compatibility token. Recognized but unavailable in this build.
Legacy aliases (accepted for compatibility):
"nfsim"→"nf_reject""rulemonkey"/"rm"→"nf_exact""dynstoc"/"ds"→ unavailable compatibility alias
poplevel (
float|None) – Critical population size N_c for PSA. Required whenmethod="psa". Must be > 1. Larger values are more conservative (less acceleration, less approximation error). Typical values: 100–1000.connectivity (
bool|None) – Only used formethod="nf"/"nf_reject"/"nfsim". Controls NFsim’s reaction-connectivity optimization at XML initialization.Falseuses the conservative full membership update path;Trueenables the inferred dependency-graph path. If omitted, the underlying NFsim wrapper default is used (currentlyFalse).nfsim_v1143_compat (
bool) – Only used formethod="nf"/"nf_reject"/"nfsim". When true, preserve NFsim v1.14.3’s extra selector draw for same-seed trajectory compatibility with the standalone CLI.block_same_complex_binding (
bool) – Only used formethod="nf"/"nf_reject"/"nfsim". NFsim-bscb: when True, two reactant patterns in a bimolecular rule cannot match molecules in the same complex. Default:Truein bngsim — NFsim CLI defaults it off, but bngsim defaults it on for correctness on BLBR/aggregation models. PassFalseto allow same-complex binding (BNG2.plcomplex=>1). This governs only the binding policy; complex bookkeeping forSpecies-typed observable counting is enabled automatically when the model declares such an observable, independent of this flag.traversal_limit (
int|None) – Only used formethod="nf"/"nf_reject"/"nfsim". NFsim-utl N: universal traversal limit.None(default) lets NFsim auto-compute a suggested limit from the XML.codegen (
bool|None) – Only used formethod="ode". When true, use a compiled C RHS. Models loaded from BioNetGen.netfiles use the.netcodegen path. SBML, Antimony, and other already-built models use model-based codegen. For SBML/Antimony models, passcodegen=Truewithoutnet_path.net_path (
str) – BioNetGen.netpath for the.netcodegen path. This is not a generic model path and should not point to SBML XML. Models loaded withModel.from_net()remember their source path, so most callers do not need to pass this manually.sensitivity_params (
list[str] |None) – Parameter names to integrate forward sensitivities for, alongside the state ODEs. The result then carries a(n_times, n_species, n_params)sensitivitiestensor whose(t, i, k)entry is∂x_i(t) / ∂p_kevaluated at the baseline parameter values. Only valid formethod="ode".sensitivity_ic (
list[str] |None) – Species names to integrate forward initial-condition sensitivities for. The result carries a(n_times, n_species, n_ic)sensitivities_ictensor whose(t, i, k)entry is∂x_i(t) / ∂x_k(0). Useful when fitting IC parameters via chain rule from a Python-side reparameterization (e.g.,model.set_concentration("Epo", 10**theta)) without a corresponding model parameter to differentiate against. Requires the codegen sensitivity RHS path; codegen is auto-enabled for any sensitivity workflow. Only valid formethod="ode".strict_ssa (
bool) –Only used for
method="ssa"/"psa". DefaultTrue.SBML loader records SSA-compatibility issues at load time (e.g.
reversible_non_mass_action,assignment_rule_on_reactant). WhenTruethe Simulator raisesSsaValidationErroron any error-severity issue — this is the safe default that prevents fitting workflows from silently consuming wrong dynamics on broken-under-SSA constructs.Pass
Falseto downgrade most error-severity issues to warnings (logged vialogging) and let the Simulator construct anyway. This mirrors roadrunner’s warn-and-run behavior undergillespieintegration. Useful when comparing bngsim against roadrunner on the same model, or when the user understands that the dynamics under SSA will be approximate for these constructs.Two issue codes remain non-overridable even with
strict_ssa=False:non_integer_stoichiometry(SSA requires ±1 fire deltas) andfast_reaction(no fast-equilibrium constraint solver).sensitivity_method (
str) –CVODES corrector strategy for the coupled state + sensitivity system. Both modes integrate state and all sensitivity ODEs as one extended ODE in a single CVODES pass; they differ only in how each integration step’s nonlinear solve is structured:
"staggered"(default,CV_STAGGERED): advance the state first, then — with the new state in hand — advance the sensitivity ODEs as a separate solve. Two smaller nonlinear solves per step instead of one big one. Often more robust on stiff or large systems; this is CVODES’ / BNGsim’s default."simultaneous"(CV_SIMULTANEOUS): solve state and all sensitivity variables together as one coupled nonlinear system at every step. Often a touch faster per step on small / well-conditioned problems; the per-step solve is larger so it can struggle on stiff or large systems. This is AMICI’s default, so this is the value to use when you want apples-to-apples timing against AMICI.
CVODES has a third mode (
CV_STAGGERED1, one parameter at a time) that BNGsim does not currently expose.force_dense_linear_solver (
bool) – Only used formethod="ode". DefaultFalse. Force CVODE’s dense direct linear solver even for large, low-density models that would otherwise auto-select sparse KLU. This is orthogonal tojacobian(which selects how the Jacobian is computed) — it overrides only the linear-solver kind. Intended for benchmarking the dense path against KLU on the same model; it has no effect in a build compiled without KLU (already always dense).xml_path (str)
gml (int | None)
jacobian (str)
Examples
>>> model = bngsim.Model.from_net("model.net") >>> sim = bngsim.Simulator(model, method="ode") >>> result = sim.run(t_span=(0, 100), n_points=101) >>> result.time.shape (101,)
>>> ssa = bngsim.Simulator(model, method="ssa") >>> result = ssa.run(t_span=(0, 100), n_points=101, seed=42)
>>> psa = bngsim.Simulator(model, method="psa", poplevel=300) >>> result = psa.run(t_span=(0, 100), n_points=101, seed=42)
>>> # Network-free (all equivalent): >>> nf1 = bngsim.Simulator(model, method="nf", xml_path="m.xml") >>> nf2 = bngsim.Simulator(model, method="nf_reject", xml_path="m.xml") >>> nf3 = bngsim.Simulator(model, method="nfsim", xml_path="m.xml") >>> rm = bngsim.Simulator(model, method="rm", xml_path="m.xml")
- Parameters:
xml_path (
str)jacobian (
str)model (Model)
method (str)
poplevel (float | None)
connectivity (bool | None)
nfsim_v1143_compat (bool)
block_same_complex_binding (bool)
traversal_limit (int | None)
force_dense_linear_solver (bool)
codegen (bool | None)
net_path (str)
sensitivity_method (str)
strict_ssa (bool)
- METHODS = {'ds', 'dynstoc', 'nf', 'nf_exact', 'nf_fixed', 'nf_reject', 'nfsim', 'ode', 'psa', 'rm', 'rulemonkey', 'ssa'}
- run(t_span=(0.0, 100.0), n_points=101, *, sample_times=None, seed=None, rtol=None, atol=None, max_steps=None, max_step=None, timeout=None, steady_state=False, steady_state_tol=None, carry_sensitivities=False)[source]
Run a simulation.
- Parameters:
t_span (
tuple[float,float]) –(t_start, t_end)time interval.n_points (
int) – Number of output time points (including t_start).sample_times (
list[float] |None) – Explicit output time points. When provided, overridest_spanandn_points. Must contain at least 3 values. Values are sorted automatically.seed (
int|None) – Random seed for stochastic methods. When omitted (orNone), bngsim draws a fresh seed from system entropy so consecutiverun()calls produce independent trajectories. Pass an explicit integer for reproducibility. The actual seed used is exposed viaResult.seed. Ignored formethod="ode".rtol (
float|None) – Relative tolerance for ODE solver. Default1e-8.atol (
float|None) – Absolute tolerance for ODE solver. Default1e-8.max_steps (
int|None) – Max internal solver steps per output point. Default10000.max_step (
float|None) – ODE-only. Upper bound on a single internal integrator step (time units).None(default) leaves the step unconstrained, except that a model loaded from SBML with a periodicfloor()/modulo dosing schedule auto-applies a bound that keeps the integrator from stepping over a narrow dose pulse (GH #88). Pass an explicit value to override that (or to bound any model);<= 0disables the bound.timeout (
float|None) – Wall-clock budget in seconds. When set (and positive), the simulator raisesbngsim.SimulationTimeoutif elapsed wall-clock time exceeds this limit.Noneor<= 0disables the budget. Supported on every backend (ODE/SSA/PSA/NFsim/RuleMonkey); RuleMonkey polls every ~1024 SSA events via its upstream cancellation hook. Partial results are not attached to the timeout exception.steady_state (
bool) – ODE-only. WhenTrue, the integrator checks||f(t,y)||_2 / n_speciesafter recording each output point and stops once it falls belowsteady_state_tol. The returnedResultis truncated to only the rows actually integrated (BNG2.plsimulate({steady_state=>1})parity, i.e.run_network -c). DefaultFalse.Result.solver_stats["steady_state_reached"]reports whether the criterion fired beforet_end.steady_state_tol (
float|None) – Tolerance for thesteady_statecheck above.Noneor<= 0falls back toatol(matching BNG2.pl, which reuses the integrator atol as the steady-state cutoff).carry_sensitivities (
bool) – ODE-only, pre-equilibration (GH #210, ADR-0052). WhenTrueand this run continues a carried-over species state from a priorrun()on the same persistentSimulator(a two-phase equilibrate-then-measure protocol with no reset between phases), the forward-sensitivity initial conditionsyS(0)are seeded from the prior phase’s final steady-state sensitivitydx_ss/dθinstead of a fresh start. This makesoutput_sensitivities()correct across the pre-equilibration boundary: the measurement phase’s IC isx_ss(θ), so∂x(0)/∂θis the equilibration sensitivity, not zero. Requires the equilibration phase to have been run on the sameSimulatorwith the samesensitivity_params(and no reset). Requesting sensitivities on a carried-over state without this flag raises (no silent wrong derivatives); a fresh single run is unaffected. DefaultFalse.
- Returns:
Simulation results with time, species, observables.
- Return type:
Result- Raises:
SimulationError – If the solver fails.
SimulationTimeout – If
timeoutis set and the wall-clock budget is exceeded.StopConditionMet – If a stop condition triggers (partial result attached).
ValueError – If t_span or n_points are invalid, or if output sensitivities were requested (
sensitivity_params/sensitivity_ic, including thecarry_sensitivitiespath) on a model that contains events. Events reinitialise the CVODE state discontinuously without a matching forward-sensitivity reinitialisation, so derivatives go silently stale at and after an event; bngsim refuses rather than return wrong numbers (GH #205). Discontinuity triggers (forcing pulses / piecewise-time dosing) do not jump state and are unaffected.
- run_batch(t_span=(0.0, 100.0), n_points=101, *, params=None, seed=None, rtol=None, atol=None, max_steps=None, max_step=None, num_processors=None, squeeze=False, timeout=None, steady_state=False, steady_state_tol=None)[source]
Run a batch of simulations over parameter sets.
For each parameter set: 1. Clone the model (independent copy) 2. Apply parameters via
set_params3. Reset species to initial conditions 4. Run the simulation (GIL released during each run) 5. Collect the result- Parameters:
t_span (
tuple[float,float]) –(t_start, t_end)time interval for each simulation.n_points (
int) – Number of output time points per simulation.params (
Sequence[dict[str,float]] |None) – Parameter sets. Each dict maps parameter names to values.seed (
int|None) – Base random seed for stochastic methods. Simulation i usesbase_seed + i. When omitted (orNone),base_seedis drawn fresh from system entropy on each call so consecutive batches produce independent trajectories. The actual per-sim seed is exposed viaResult.seedon each result.max_steps (
int|None) – Maximum internal solver steps per output point.num_processors (
int|None) – Number of threads for parallel execution. DefaultNone(sequential). The GIL is released during each simulation, so threads parallelize effectively.squeeze (
bool) – IfTrue, return a single Result with 3D arrays(n_sims, n_times, n_cols)instead of a list.steady_state (
bool) – ODE-only. WhenTrue, every simulation in the batch stops early once||f(t,y)||_2 / n_speciesfalls belowsteady_state_toland itsResultis truncated to the rows actually integrated (BNG2.plsimulate({steady_state=>1})/run_network -cparity, applied per parameter point). DefaultFalse. Because each point truncates independently, the per-Result row counts may differ; usesqueeze=False(the default) when mixing steady-state early-stop with heterogeneous equilibration times.steady_state_tol (
float|None) – Tolerance for thesteady_statecheck above.Noneor<= 0falls back toatol(matching BNG2.pl).max_step (float | None)
timeout (float | None)
- Returns:
One Result per parameter set (list), or a single squeezed Result with 3D arrays if
squeeze=True.- Return type:
list[Result] |Result- Raises:
SimulationError – If any simulation fails.
ValueError – If params is empty or t_span/n_points are invalid.
Examples
>>> param_sets = [{"k1": v} for v in [0.1, 1.0, 10.0]] >>> results = sim.run_batch( ... t_span=(0, 100), n_points=101, ... params=param_sets, num_processors=4, ... ) >>> len(results) 3
>>> batch = sim.run_batch( ... t_span=(0, 100), n_points=101, ... params=param_sets, squeeze=True, ... ) >>> batch.species.shape (3, 101, n_species)
- run_replicates(n_replicates, t_span=(0.0, 100.0), n_points=101, *, seed=None, timeout=None, num_processors=None, squeeze=False)[source]
Run
n_replicatesstochastic replicates of the same model.Unlike
run_batch()— a parameter scan that clones the model per point — replicates share identical parameters and differ only in RNG seed, so this reuses a single simulator and callsreset()between replicates instead of cloning + reconstructing one each time. Reusing the simulator also reuses its cached SSA dependency graph (built once), so on low-activity models the per-replicate cost collapses to the actual trajectory work rather than the fixed clone + graph-rebuild overhead.Replicate i uses
seed_base + i(seed_baseresolved once fromseed, or from system entropy whenseed is None; each value is exposed via the correspondingResult.seed), matching the seed schedulerun_batch()uses across parameter points.Sequential execution (
num_processorsNoneor1) reuses this simulator directly. Parallel execution clones the model once per worker thread (not per replicate) for thread-safety, each thread reusing its clone across the replicates it handles.SSA/PSA only — ODE has no replicate concept; use
run_batch()for ODE parameter scans.- Parameters:
- Returns:
One
Resultper replicate, or a single squeezed Result with 3D arrays(n_replicates, n_times, n_cols)whensqueeze=True.- Return type:
list[Result] |Result
Examples
>>> ssa = bngsim.Simulator(model, method="ssa") >>> reps = ssa.run_replicates(30, t_span=(0, 100), n_points=101, seed=0) >>> len(reps) 30
- compute_all_sensitivities(t_span=(0.0, 100.0), n_points=101, *, params=None, chunk_size=2, n_workers=None, rtol=None, atol=None, max_steps=None)[source]
Compute full sensitivity tensor via parallel chunked CVODES jobs.
Splits
Npparameters into⌈Np/chunk_size⌉independent CVODES forward-sensitivity jobs, runs them in parallel viamodel.clone()+ThreadPoolExecutor(GIL released during C++ CVODE integration), and stitches the partial sensitivity arrays into a complete(n_times, n_species, n_params)tensor.Benchmarks showed that 2-parameter sensitivity chunks add only ~1.2× overhead for large models (593–1281 species). With
⌈Np/2⌉parallel jobs, the full sensitivity tensor costs ~1.2× wall-clock of a plain ODE solve — making gradients nearly free with cores.- Parameters:
t_span (
tuple[float,float]) –(t_start, t_end)time interval.n_points (
int) – Number of output time points (including t_start).params (
list[str] |None) – Parameter names to compute sensitivities for. Default: all model parameters.chunk_size (
int) – Number of sensitivity parameters per CVODES job. Default 2, which benchmarking found to work well for large models because 2-parameter chunks add only ~1.2× overhead.n_workers (
int|None) – Number of parallel threads. Default:min(⌈Np/chunk_size⌉, os.cpu_count()). Set to 1 for serial execution (debugging/profiling).max_steps (
int|None) – Max internal solver steps per output point.
- Returns:
Simulation result with full
sensitivitiestensor of shape(n_times, n_species, n_params). Thesensitivity_paramsattribute lists all parameter names in the order they appear in the tensor. Species trajectories are from the first chunk’s ODE solve (all chunks produce identical trajectories since they share the same model and parameters).- Return type:
Result- Raises:
ValueError – If method is not ‘ode’, or params contains unknown names.
SimulationError – If any chunk simulation fails.
Examples
>>> model = bngsim.Model.from_net("model.net") >>> sim = bngsim.Simulator(model, method="ode") >>> result = sim.compute_all_sensitivities( ... t_span=(0, 100), n_points=101, ... chunk_size=2, n_workers=8, ... ) >>> result.sensitivities.shape # (101, n_species, n_params) (101, 149, 40) >>> fim = result.fisher_information(sigma=0.1) >>> grad = result.gradient( ... lambda species, time: np.sum((species - data)**2) ... )
Notes
Architecture: Each chunk clones the model (deep copy, thread-safe), creates a fresh
CvodeSimulator, runs CVODES withchunk_sizesensitivity parameters, and returns its partial(n_times, n_species, chunk_size)sensitivity tensor. The main thread stitches these along axis 2 (the parameter axis).Why ThreadPoolExecutor works: The GIL is released during C++ CVODE integration (
py::call_guard<py::gil_scoped_release>), so threads achieve true parallelism for the compute-intensive portion. Python overhead is negligible (model clone + setup).Optimal chunk_size: Benchmarks show that
chunk_size=2minimizes per-chunk overhead while keeping thread count manageable.chunk_size=1works but doubles the number of threads needed.
- steady_state(*, tol=1e-09, max_time=1000000.0, method='newton', rtol=None, atol=None, max_steps=None, sensitivity_params=None)[source]
Find the steady state of the ODE system f(y) = 0.
Solver methods:
"newton"(default): KINSOL Newton solver with analytical Jacobian; on non-convergence it falls back EXPLICITLY to the parity integration path."integration": CVODE BDF integrated until the BNG2.pl parity criterion||f(y)||_2 / n_species < tol(run_network -c)."kinsol": accepted alias for"newton".
- Parameters:
tol (
float) – Convergence tolerance on||f(y)||_2 / n_species. Default 1e-9.max_time (
float) – Max integration time for the integration path. Default 1e6.method (
str) –"newton"(default),"integration", or"kinsol"(alias for"newton").sensitivity_params (list[str], optional) – Parameter names for dY_ss/dp sensitivity.
- Return type:
SteadyStateResult
- steady_state_batch(params, *, tol=1e-09, max_time=1000000.0, method='newton', rtol=None, atol=None, max_steps=None, n_workers=None)[source]
Compute steady states for multiple parameter sets.
- add_stop_condition(condition, *, label='')[source]
Add a stop condition checked after each simulation.
- Parameters:
str: An expression string evaluated at each time point using observable names as variables. Simulation stops when the expression becomes true (> 0). Example:
"A_tot < 10"callable: A function
f(result) -> boolcalled after the simulation. If it returns True, the stop condition is triggered.
label (
str) – Human-readable label for the condition.
- Return type:
Examples
>>> sim.add_stop_condition("A_tot < 10", label="low_A") >>> sim.add_stop_condition( ... lambda r: r.species[-1, 0] < 5, ... label="very_low_A", ... )
- run_until(t, *, n_points=None, seed=None, rtol=None, atol=None, max_steps=None)[source]
Run simulation from current time to t.
This enables interactive simulation: run to a time point, inspect or modify the model, then continue. Supported for stateful model-backed solvers (ODE / SSA / PSA) only.
- Parameters:
- Returns:
Simulation result for the [current_time, t] interval.
- Return type:
Result
Examples
>>> sim.run_until(t=50) # simulate to t=50 >>> sim.intervene({"k1": 0.0}) # knock out a reaction >>> result = sim.run_until(t=100) # continue to t=100
- intervene(params)[source]
Apply a perturbation (parameter change) mid-simulation.
Use between
run_until()calls to modify the model during an interactive simulation session.Examples
>>> sim.run_until(t=50) >>> sim.intervene({"k1": 0.0}) # knock out reaction >>> sim.run_until(t=100) # continue
- snapshot()[source]
Capture the current simulation state.
Returns a dict that can be passed to
restore()to return to this point.- Returns:
Opaque snapshot of model + simulator state.
- Return type:
Examples
>>> sim.run_until(t=50) >>> snap = sim.snapshot() >>> sim.run_until(t=100) >>> sim.restore(snap) # back to t=50
- restore(snapshot=None)[source]
Restore simulation state from a snapshot.
- Parameters:
snapshot (
dict|None) – A snapshot fromsnapshot(). IfNone, restores the most recent snapshot from the internal stack.- Return type:
Examples
>>> snap = sim.snapshot() >>> sim.run_until(t=100) >>> sim.restore(snap) # back to snapshot point >>> sim.restore() # same (uses internal stack)
- get_state()[source]
Bulk-copy the live species-concentration vector (GH #102).
Thin delegator to
Model.get_state()on the underlying model. After a statefulrun_until/runthe model holds the final state (the ODE/SSA backends write it back), so this returns the post-step state. It is the per-stepgethalf of driving bngsim as a reaction kernel from an external orchestrator; pair withset_state().- Return type:
- set_state(state)[source]
Bulk-assign the live species-concentration vector (GH #102).
Thin delegator to
Model.set_state(). The C++ simulator reads the model’s current concentrations as the initial condition at the start of the nextrun_until/run, so a bulkset_statebetween steps is the per-stepsethalf of the kernel exchange (e.g. injecting the SSA-subset coupling species before advancing the ODE subset).
- set_tolerances(rtol=1e-08, atol=1e-08)[source]
Set ODE solver tolerances.
- set_max_steps(max_steps)[source]
Set maximum internal solver steps per output point.
- property method: str
Internal dispatch method (‘ode’, ‘ssa’, ‘psa’, ‘nfsim’, or ‘rulemonkey’).
- property requested_method: str
Original method token as provided by the user.
Useful for logging and reproducibility metadata. For example, if the user passed
method="nf", this returns"nf"whilemethodreturns the backend dispatch key ("nfsim"fornf/nf_reject).
- property model: Model
The model being simulated.
- property codegen_backend: str
The RHS codegen backend this Simulator hands the ODE engine.
Returns one of:
"mir"— in-process MIR micro-JIT (GH #78): the generated C source is JIT-compiled inside C++ (reached only whenBNGSIM_CODEGEN_JIT=miron a MIR-enabled build prepared codegen for this model)."cc"— native C compiled to a.sobyccanddlopen’d (auto-selected at/aboveBNGSIM_CODEGEN_THRESHOLDspecies — 256 by default — or whencodegen=Truewas passed explicitly)."exprtk"— the ExprTk bytecode interpreter, no native code (the default below the codegen threshold).
This is the backend that actually runs, not a request: it reflects exactly what
run()passes the engine — a non-empty JIT source selects MIR, else a non-empty.sopath selects cc, else ExprTk (mirroring theopts.codegen_*dispatch). Only meaningful formethod="ode"; other backends never codegen and report"exprtk".
- property jacobian_strategy: str
"analytical","fd", or"jax".This is the post-resolution strategy, not the requested
jacobian=mode. Withjacobian="auto"(the default) the engine uses the analytical Jacobian when the model has one — every Elementary mass-action law, plus Functional laws whose derivatives were symbolically derived within the build-time budget (GH #76/#95) — and finite differences otherwise. So this reports"analytical"only when the analytical Jacobian is genuinely complete and not overridden:jacobian="fd"→ always"fd".BNGSIM_ANALYTICAL_FUNCTIONAL_JAC=0(a Functional model loaded with this set never attaches its analytical terms) →"fd".a derivation that blew the budget and fell back →
"fd".jacobian="auto"whose analytical attempt failed to integrate and fell back to FD at run time (GH #176) →"fd".jacobian="jax"→"jax".
Mirrors the engine’s callback selection exactly (cvode_simulator.cpp): analytical iff the requested mode is auto/analytical and
analytical_jacobian_complete. Only meaningful formethod="ode".- Type:
The Jacobian strategy the ODE engine actually uses
- property last_codegen_sec: float
Wall seconds spent generating/compiling this model’s codegen RHS.
≈0.0for an ExprTk model (no codegen runs) or a codegen cache hit; thecccompile time on a cold"cc"model; the source-generation time on a"mir"model. Recorded once at setup by thebngsim._codegen.prepare_*entry points (whether codegen ran at model load or in this Simulator), so a singlerun()exposes the codegen cost directly — no run-twice-and-subtract needed. Purely a setup-time figure; the per-step integration hot path is never instrumented.
- property last_libsbml_parse_sec: float
Wall seconds the SBML loader spent in the libSBML parse phase (
readSBML*+ document-level error check) — the shared C++ core both engines use. Recorded once at load; setup-time only, never the hot path.0.0for a model not loaded from SBML (e.g. a.netmodel).
- property last_interpret_sec: float
Wall seconds spent interpreting the parsed libSBML document into the internal
_coremodel (bngsim’s Python interpretation layer, including thebuilder.build()core construction; excludes libSBML parse, the analytical-Jacobian derivation, and codegen, which are timed separately). Recorded once at load; setup-time only.
- property last_jacobian_sec: float
Wall seconds spent symbolically deriving this model’s analytical Functional Jacobian (GH #76,
sympysp.diff), with SymPy already imported — the one-time SymPy import is process-warmup, measured separately, not here.≈0.0for an all-Elementary model, an FD fallback, an over-budget derivation (GH #95), orBNGSIM_ANALYTICAL_FUNCTIONAL_JAC=0. A bngsim-only per-model cost — RoadRunner uses a difference-quotient Jacobian and has no analog. Setup-time only; the per-step integration hot path is never instrumented. Recorded wherever the derivation runs (at load today; at first ODE-solve setup after the GH #145 lazy deferral), so this accessor is stable across that change.
- property codegen_cache_hit: bool | None
Whether this model’s compiled
.sowas reused from the on-disk codegen cache (~/.cache/bngsim/codegen/).True— the.sowas found in the cache and loaded without recompiling (thecccompile was skipped).False— no cached.somatched, so it was compiled fresh.None— no.sowas involved at all: an ExprTk model (no codegen) or a MIR model (in-process JIT, which has no on-disk.socache).
This is the definitive cache signal recorded by the codegen pipeline at the
get_cached_so/ memo branch — not inferred fromlast_codegen_sec(a model-based cache hit still spends time on source generation, so a small wall time does not imply a cache hit). Only meaningful formethod="ode"with the"cc"backend.
- property current_time: float
Current time in interactive simulation.
- class bngsim.ReactionKernel(model, *, method='ode', **simulator_kwargs)[source]
Bases:
objectDrive a bngsim model per step as a pluggable reaction kernel (GH #102).
- Parameters:
model (
Model) – The model to drive. Its live species concentrations are the kernel’s state vector (ordered likestate_names).method (
str) – Simulation method passed tobngsim.Simulator. Default"ode". Stepping (advance()) requires a stateful backend (ode/ssa/psa);nfsim/rulemonkeymay be wrapped for introspection but cannot be advanced.**simulator_kwargs – Forwarded verbatim to
bngsim.Simulator(e.g.codegen,jacobian,poplevel,xml_path,sensitivity_params).
See also
ReactionKernel.from_simulatorwrap an already-configured Simulator.
bngsim.Model.get_statethe bulk state primitive the kernel exchanges.
Notes
Not thread-safe — it owns mutable model + simulator state. For parallel workers, build one kernel per
bngsim.Model.clone().- classmethod from_simulator(simulator)[source]
Wrap an already-constructed
bngsim.Simulator.Use this when the caller has configured solver options the kernel does not surface directly (custom tolerances via
set_tolerances, a prepared codegen.so, sensitivity parameters, …). The kernel adopts the simulator as-is, including its current interactive time.- Parameters:
simulator (
Simulator) – The simulator to drive.- Return type:
ReactionKernel
- get_state()[source]
Bulk-copy the live species-concentration vector (GH #102).
Returns a fresh
float64array of lengthn_species, ordered likestate_names. After anadvance()this is the post-step state. OneO(n_species)Python call — thegethalf of the per-step kernel exchange.- Return type:
GenericAlias[double]
- set_state(state)[source]
Bulk-assign the live species-concentration vector (GH #102).
- advance(dt, *, n_points=2, seed=None, **run_kwargs)[source]
Advance the simulation by
dtand return the post-step state.Integrates (or steps, for stochastic backends) from the current
timetotime + dt, then returnsget_state(). The model is left holding the post-step state, so the typical per-step loop isset_state(...) → advance(dt) → get_state()(or just use the returned array).- Parameters:
dt (
float) – Coupling step. Must be > 0.n_points (
int) – Output points recorded over the step. Default 2 (endpoints only), which is all an orchestrator needs and keeps recording cheap; the full sub-step trajectory is available vialast_result.seed (
int|None) – Random seed for stochastic backends (ssa/psa). Ignored byode.Nonedraws a fresh seed each step.**run_kwargs – Forwarded to
Simulator.run_until(e.g.rtol,atol,max_steps).
- Returns:
The post-step state vector (a copy; ordered like
state_names).- Return type:
GenericAlias[double]- Raises:
ValueError – If
dt <= 0.NotImplementedError – If the backend is network-free (
nfsim/rulemonkey), which has no stateful per-step API.
- reset()[source]
Reset to the model’s initial concentrations and
time = 0.Restores species to their initial values (
bngsim.Model.reset()) and rewinds the interactive clock, so the kernel can be re-driven from scratch. Clears any cached step result.- Return type:
- observables()[source]
Observable values at the current simulation state.
Returns a
float64array of lengthn_observables, ordered likeobservable_names. After anadvance()these are the post-step observables (read straight from the step result, no recomputation). Before the first advance — or after aset_state()with no advance since — they are computed once, side-effect-free, from the current state via a throwaway model clone.- Return type:
GenericAlias[double]
- property n_species: int
Number of species (length of the state vector).
- property n_observables: int
Number of observables.
- property time: float
Current interactive simulation time.
- property method: str
Internal backend dispatch key (
'ode'/'ssa'/ …).
- property model: Model
The wrapped model (its live concentrations are the kernel state).
- property simulator: Simulator
The underlying
bngsim.Simulatordriving the steps.
- property last_result: Result | None
The
bngsim.Resultfrom the most recentadvance().Nonebefore the first advance. Carries the full sub-step trajectory of the last step (pern_points), beyond the endpoint stateget_state()returns.
- class bngsim.UnitConverter(volume_factors, *, names=None)[source]
Bases:
objectBulk count ↔ concentration ↔ amount conversion over the state vector.
Wraps the per-species storage→amount factor vector
volume_factor(V_c;Species.volume_factor, baked at load time) and converts a wholeget_state/set_statestorage vector to and from the units an external orchestrator works in. See the module docstring for the unit algebra; the short version isamount = storage * V_c(volume-invariant) andconcentration = amount / V(needs a volume).Build one with
from_model()(orfrom_kernel()); it is immutable and cheap to keep alongside a kernel.- Parameters:
- classmethod from_model(model)[source]
Gather the per-species
volume_factorvector from a model/kernel.- Parameters:
model (
Model|ReactionKernel)- Return type:
UnitConverter
- classmethod from_kernel(model)
Alias — a kernel exposes
.model, sofrom_model()already accepts it; kept for symmetry with the rest of the API.- Parameters:
model (
Model|ReactionKernel)- Return type:
UnitConverter
- property volume_factors: NDArray[float64]
Per-species
V_c(a copy).
- property n_species: int
Length of the state vector this converter expects.
- to_amounts(storage)[source]
Storage vector → molecule amounts (
storage * V_c).
- from_amounts(amounts)[source]
Molecule amounts → storage vector (
amounts / V_c).
- to_counts(storage, *, policy='nearest', rng=None)[source]
Storage vector → integer molecule counts (rounded amounts).
- from_counts(counts)[source]
Integer molecule counts → storage vector (
counts / V_c).
- to_concentrations(storage, *, volume=None)[source]
Storage vector → concentrations (
storage * V_c / volume).volume=Nonereturns the at-load concentration (==storage). Pass the current compartment volume to report concentrations against a framework-grown compartment whose bakedV_cis now stale.
- from_concentrations(concentrations, *, volume=None)[source]
Concentrations → storage vector (
concentration * volume / V_c).Inverse of
to_concentrations(); pass the samevolumeyou would read the concentration against.
- class bngsim.CouplingMap(all_names, shared_names)[source]
Bases:
objectName ↔ index addressing of the shared coupling subset within a state vector.
Two coupled subsets share species by name but order their state vectors independently. A
CouplingMappins the index of each shared name in one subset’s vector, so the orchestrator gathers / scatters only the coupling subset and exchanges it in a single canonical order across subsets.- Parameters:
- classmethod from_model(model, shared_names)[source]
Build from a model/kernel’s
species_names.
- classmethod from_kernel(model, shared_names)
Build from a model/kernel’s
species_names.
- property indices: NDArray[int64]
Indices of the shared species into the full state vector (a copy).
- property n_shared: int
Number of shared (coupling) species.
- property n_full: int
Length of the full state vector this map addresses.
- gather(state)[source]
Extract the shared subset from a full state vector, in exchange order.
- scatter(state, values, *, copy=True)[source]
Write
values(exchange order) into the shared slots ofstate.Returns the updated full vector. With
copy=True(default)stateis not mutated;copy=Falsewrites in place and returns the same array.
- class bngsim.DiscreteExchange(n_species, *, policy='nearest', dither=True, nonneg=True, rng=None)[source]
Bases:
objectExplicit, inspectable rounding at the SSA/NFsim hand-off with leak accounting.
The SSA engine already rounds whatever continuous storage it is handed at the next
advance(round_initial_population_to_storage()), but that entry rounding is implicit and silent — repeatedly injecting continuous amounts and reading integer counts back sheds the fractional remainder every step, which leaks mass over a long hybrid run.DiscreteExchangemakes the boundary explicit and conserving: it carries the per-species fractional residual forward (error feedback / dithering), so the discrete counts track the continuous amounts with bounded, accounted error rather than a silent downward drift.- Parameters:
n_species (
int) – Length of the amount vectors handed across the boundary.policy (
Literal['nearest','floor','ceil','stochastic']) – Per-step rounding rule (RoundingPolicy).dither (
bool) – Carry the fractional residual into the next step (defaultTrue). With dithering the cumulative leak stays bounded by the carry; without it, each step rounds independently and leak can accumulate — setFalseonly when you explicitly want memoryless rounding.nonneg (
bool) – Clamp counts at 0 (defaultTrue) so the SSA boundary never receives a negative population. When dithering, a clamp keeps the unrepresentable deficit in the carry, so conservation accounting is undisturbed.
Examples
>>> dx = DiscreteExchange(3, policy="nearest") >>> counts = dx.discretize([0.4, 0.4, 0.4]) # rounds down this step >>> later = dx.discretize([0.4, 0.4, 0.4]) # carry has built up to 0.8 → rounds up >>> dx.leak # net mass injected vs the continuous input
- discretize(amounts)[source]
Round
amountsto integer counts, carrying the residual forward.Returns integer-valued
float64counts of lengthn_species. Updatescarry(dithering),last_residual(this step’scounts - amounts) andleak(cumulative net mass discretization has added or removed since construction /reset()).
- property carry: NDArray[float64]
Current per-species fractional residual buffer (a copy).
- property last_residual: NDArray[float64]
counts - amountsfrom the most recentdiscretize()(a copy).
- property leak: float
Cumulative net molecules added (>0) or removed (<0) by rounding.
With
dither=Truethis stays bounded (it equals-carry.sum()); a growing magnitude underdither=Falseis exactly the silent SSA-entry leak this boundary is meant to surface.
- bngsim.round_to_counts(amounts, policy='nearest', *, rng=None)[source]
Discretize continuous amounts to integer molecule counts (stateless).
- Parameters:
amounts (
ArrayLike) – Continuous molecule amounts (the volume-invariant exchange currency; useUnitConverter.to_amounts()to get here from storage).policy (
Literal['nearest','floor','ceil','stochastic']) – SeeRoundingPolicy."nearest"rounds half away from zero, matching the SSA engine’s entry rounding so an explicit hand-off and the implicit one agree."stochastic"rounds up with probability equal to the fractional part (unbiased), and needsrng.rng (
Generator|None) – Required forpolicy="stochastic"; ignored otherwise.
- Returns:
Integer-valued
float64counts (same dtype as the state vector, so it feeds straight back throughUnitConverter.from_counts()).- Return type:
GenericAlias[double]
- bngsim.round_half_up(value)[source]
Round a fractional molecule count to the nearest integer, ties away from zero.
bngsim’s repo-wide stochastic initial-count policy (GH #51):
floor(x + 0.5)forx >= 0andceil(x - 0.5)forx < 0. The two branches differ only at negative half-integers, which never arise for molecule populations. The rule is idempotent on integers (round_half_up(n) == n), so warm-restart paths that re-round already-integer state are no-ops.Examples
>>> [round_half_up(x) for x in (0.389, 0.10001, 5.7, 155.6747, 466.98)] [0, 0, 6, 156, 467] >>> [round_half_up(x) for x in (0.5, 1.5, 2.5)] # ties away from zero, not bankers' [1, 2, 3]
- Parameters:
value (
float) – Continuous molecule count, already in molecule-number space. Any volume / concentration factors must be applied before calling.- Returns:
Nearest integer count.
- Return type:
- Raises:
ValueError – If value is not finite.
- class bngsim.ConservationLedger(weights=None, *, atol=1e-09, rtol=1e-09, name='total')[source]
Bases:
objectTrack a conserved moiety across exchange-boundary round-trips (GH #102).
Records the moiety total at a baseline state and on every subsequent state, so an operator-split loop can assert the shared moiety is preserved across
get_state → orchestrator → set_stateexchanges and the discrete rounding.- Parameters:
weights (
ArrayLike|None) – Moiety weight vector (seemoiety_total()). Default: total count.atol (
float) – Absolute / relative tolerance forcheck()andassert_conserved(). The drift bound isatol + rtol * |baseline|.rtol (
float) – Absolute / relative tolerance forcheck()andassert_conserved(). The drift bound isatol + rtol * |baseline|.name (
str) – Label used in error messages.
- record(state)[source]
Record the moiety total of
state; set the baseline on first call.Returns the moiety total.
- check(state)[source]
Record
stateand return(within_tolerance, signed_drift).
- assert_conserved(state)[source]
Record
state; raiseConservationErrorif drift exceeds tol.Returns the signed drift from baseline.
- property max_abs_drift: float
Largest absolute drift from baseline seen so far.
- property n_records: int
Number of states recorded.
- exception bngsim.ConservationError[source]
Bases:
BngsimErrorA conserved moiety drifted beyond tolerance across the exchange boundary.
- bngsim.moiety_total(state, weights=None)[source]
Total of a conserved moiety:
weights · state(orstate.sum()).A conserved moiety of a reaction network is a left-null-space vector
wof the stoichiometry matrix;w · nis invariant under the reactions. The defaultweights=Noneis the all-ones vector — the total molecule count of a closed transfer network, the moiety an operator split must not leak.
- class bngsim.Divider(*, method='binomial', rng=None)[source]
Bases:
objectPartition molecule counts across daughter cells at division (GH #102).
Cell division is a pure count-space operation with no analogue elsewhere in bngsim: the molecules of each species are dealt out among the daughters, exactly conserving the parent total (
sum(daughters) == parentfor every partitioned species). It composes on top ofUnitConverter— convert storage → counts, divide, then convert each daughter’s counts back to storage viaUnitConverter.from_counts()and inject withset_state. Volume is the orchestrator’s to halve (seeset_compartment_volume()).- Parameters:
method (
Literal['binomial','multinomial','deterministic']) –"binomial"/"multinomial"(synonyms) deal each molecule to a uniformly-random daughter — the physically faithful stochastic split, exactly conserving."deterministic"splits as evenly as possible (floor share + largest-remainder distribution of the leftover), also exactly conserving; reproducible without an RNG.rng (
Generator|None) – Required for the stochastic methods.
- divide(counts, n_daughters=2, *, partition_mask=None)[source]
Partition integer
countsinton_daughtersdaughter vectors.- Parameters:
counts (
ArrayLike) – Non-negative, integer-valued molecule counts (e.g. fromUnitConverter.to_counts()).n_daughters (
int) – Number of daughters (default 2).partition_mask (
ArrayLike|None) –Truewhere a species is a partitioned molecule pool (split and conserved);Falsewhere it is shared environment (copied identically to every daughter, not split). Default: partition all.
- Returns:
n_daughtersinteger-valuedfloat64count vectors. For every partitioned species the daughters sum exactly to the parent count.- Return type:
- bngsim.make_subset_model(model, *, keep_reactions=None, fixed_species=None, compute_conservation_laws=False)[source]
Build an operator-split subset as its own model (GH #102 Stage 1, #6).
bngsim runs a whole network with one method — there is no native reaction-subset integration. For a static operator split the continuous (ODE) subset is supplied as its own model: the same species namespace, only the subset’s reactions, and the other operator’s species marked
fixed=Trueso the integrator holds them at the boundary values the orchestrator writes each step viaset_state. Thefixedmechanism already clamps a species in both the ODE (zeroed derivative) and SSA (skipped fire) backends — this helper just constructs the partitioned model; no engine change is involved.The full species set is kept (so the two subsets share one addressing space for
CouplingMap); only the reaction set is subset and the boundary species fixed. A species touched by no kept reaction is already inert, sofixed_speciesis needed only for boundary species whose value a kept rate law reads but should not evolve.- Parameters:
model (
Model|ReactionKernel) – The full network to partition.keep_reactions (
Sequence[int] |None) – 0-based indices of the reactions this subset integrates.Nonekeeps all reactions (a pure re-fix with no reaction split).fixed_species (
Sequence[str] |None) – Species names to additionally markfixed(the other operator’s species). Species already fixed in the source stay fixed.compute_conservation_laws (
bool) – Forwarded to the builder (defaultFalse— the dense O(n³) detector is unused by stepping and costly at scale; see GH #102 MVP).
- Returns:
A freshly built subset model at the source’s initial concentrations (taken from a reset clone, so it is immune to a prior simulation having left the source mid-trajectory). Inject a custom starting state with
set_stateon the subset afterwards.- Return type:
Model- Raises:
NotImplementedError – If the source uses features this reconstruction cannot reproduce faithfully (events, table functions, discontinuity triggers,
amount_valuedspecies, orper_species_volume_scalingreactions). The supported class — mass action, functional, and Michaelis–Menten rate laws, parameters, observables, functions — covers the issue’s ~100K first-order target.
- bngsim.get_compartment_volume(model, name)[source]
Read a compartment volume (a model parameter) by name.
There is no
Compartmentobject in bngsim — a compartment volume is a plain parameter, so this isModel.get_param(), named for the coupling use it serves: feeding the current volume intoUnitConverter.to_concentrations()/from_concentrations()as the live-volume override.
- bngsim.set_compartment_volume(model, name, volume)[source]
Couple a framework’s volume growth into bngsim’s compartment volume.
Sets the compartment-volume parameter. On the ODE subset this flows through bngsim’s variable-volume machinery natively (the integrator dilutes by the live compartment symbol; GH #74/#85), so growing or halving (at division) a compartment is just this call. On the SSA subset the baked per-reaction
ssa_volume_factordoes not track the new value — the exchange layer compensates by reading concentrations against the live volume (theUnitConverteroverride), but the stochastic propensities using the live volume are Stage 2.
- class bngsim.Result(core=None, *, custom_attrs=None, _time=None, _species=None, _observables=None, _expressions=None, _species_names=None, _observable_names=None, _expression_names=None, _solver_stats=None, _species_volume_factors=None, _seed=None, _sensitivities=None, _sensitivity_params=None, _sensitivities_ic=None, _sensitivity_ic_species=None, _observable_sensitivities=None, _expression_sensitivities=None, _observable_sensitivities_ic=None, _expression_sensitivities_ic=None)[source]
Bases:
objectSimulation result container.
- time
Time points.
- Type:
ndarray, shape (n_times,)
- species
Species concentrations at each time point.
- Type:
ndarray, shape (n_times, n_species)
- observables
Observable values at each time point.
- Type:
ndarray, shape (n_times, n_observables)
- solver_stats
Solver diagnostics (n_steps, n_rhs_evals, etc.).
- Type:
Examples
>>> result = sim.run(t_span=(0, 100), n_points=101) >>> result.time.shape (101,) >>> result.observables.shape (101, 5) >>> result.observables["Atot"] # named access array([100., 99.5, ...])
- Parameters:
core (ResultCore | None)
custom_attrs (dict[str, Any] | None)
_time (NDArray | None)
_species (NDArray | None)
_observables (NDArray | None)
_expressions (NDArray | None)
_species_names (list[str] | None)
_observable_names (list[str] | None)
_expression_names (list[str] | None)
_solver_stats (dict[str, int] | None)
_species_volume_factors (list[float] | None)
_seed (int | None)
_sensitivities (NDArray | None)
_sensitivity_params (list[str] | None)
_sensitivities_ic (NDArray | None)
_sensitivity_ic_species (list[str] | None)
_observable_sensitivities (NDArray | None)
_expression_sensitivities (NDArray | None)
_observable_sensitivities_ic (NDArray | None)
_expression_sensitivities_ic (NDArray | None)
- property seed: int | None
The integer RNG seed actually used for this simulation.
Nonefor deterministic (ODE) results and for results loaded from HDF5 files predating seed exposure. For stochastic methods (SSA, PSA, NFsim, RuleMonkey), this is the integer that was passed down to the backend — equal to theseed=keyword when the caller supplied one, or the freshly drawn integer when the caller passedseed=None(or omitted it).For squeezed batch results, this is the single seed if every underlying sim used the same one, otherwise
None; the per-sim seeds remain accessible on the individualResultobjects fromSimulator.run_batch(..., squeeze=False).
- property time: NDArray[float64]
Time points, shape
(n_times,).
- property species: NDArray[float64]
Species concentrations, shape
(n_times, n_species).
- property observables: _ObservableAccessor
Observable values with named access.
Can be used as: -
result.observables→ full array(n_times, n_obs)-result.observables["name"]→ single column(n_times,)
- property expressions: _ObservableAccessor
Expression (function) values with named access.
- property n_times: int
Number of time points.
- property n_species: int
Number of species.
- property n_observables: int
Number of observables.
- property n_expressions: int
Number of expressions.
- property has_simulation_data: bool
True iff the simulator produced at least one numeric column.
Returns
Falsefor runs that complete without any species, observable, or expression columns — e.g. a BNGL file with asimulateaction but an empty model body. Used by batch harnesses to distinguish “ran and produced data” from “ran and produced nothing meaningful” without an exception path.
- property expression_names: list[str]
Expression (function) names (bare, in-memory column keys).
Auto-generated
_rateLawNrate-law intermediates are filtered out; recover them viaraw_expression_names.
- property gdat_expression_names: list[str]
Function-column labels for
.gdat/.scanheaders.bngsim emits bare function headers (no
()suffix, issue #58), so this is identical toexpression_names. It is retained for consumers that assemble a BNG-native file header (e.g. a consumer-built.scan) and want an intent-revealing name.
- property raw_expression_names: list[str]
Unfiltered function names, including internal
_rateLawN.On a freshly-simulated result these include the auto-generated
_rateLawNrate-law intermediates thatexpression_namesfilters out (the #58 recoverability fix). On a result loaded from HDF5 or assembled from raw arrays, only the filtered (bare) set was persisted, so this returns the same columns asexpression_names.
- property raw_expressions: _ObservableAccessor
Unfiltered function values, including internal
_rateLawN.Columns correspond to
raw_expression_names. See that property for the freshly-simulated vs. loaded distinction.
- property raw_n_expressions: int
Number of unfiltered function columns (includes
_rateLawN).
- resolve_outputs(selectors)[source]
Resolve typed output selectors to structured metadata.
A selector names one output column of this result. Fitting frontends compare against named outputs (species, observables, global functions) rather than raw column indices; this is the unified, typed lookup that maps a selector string onto the column it refers to. No sensitivity or value computation happens here — it is pure name resolution.
- Parameters:
selectors (
str|Iterable[str]) –One selector or a sequence of them. A single string is treated as a one-element list (so
resolve_outputs("observable:Atot")andresolve_outputs(["observable:Atot"])are equivalent).Accepted forms:
"species:<name>"— a species column."observable:<name>"— an observable column."expression:<name>"— a global-function / expression column. A trailing()is stripped ("expression:foo()"→"expression:foo"), matching the BNG.gdat/.scanheader convention while in-memory column keys stay bare (issue #58).Aliases:
"state:"→"species:","function:"→"expression:".A bare name (no prefix) resolves only if it is unique across species, observables, and expressions. A bare
"foo()"that does not match any column verbatim is retried as the expressionfoo(the function-call convention).
- Returns:
One dict per input selector, in input order, each with keys:
"selector"— the canonical typed selector ("<kind>:<name>"), suitable as a stable downstream key."kind"—"species","observable"or"expression"."name"— the bare in-memory column name (the key used by the named accessors, e.g.result.observables[name])."index"— column index within that kind’s array."column_label"— the label as written to a.gdat/.cdatheader. bngsim emits bare headers (issue #58), so this is currently identical to"name".
- Return type:
- Raises:
ValueError – If a selector uses an unknown kind prefix, is empty, is unresolved, or is a bare name that matches more than one column. Ambiguity errors list every matching typed selector; unresolved errors list the available candidates.
TypeError – If a selector is not a string.
Examples
>>> result.resolve_outputs("observable:Atot") [{'selector': 'observable:Atot', 'kind': 'observable', 'name': 'Atot', 'index': 0, 'column_label': 'Atot'}] >>> [m["selector"] for m in result.resolve_outputs( ... ["species:A()", "function:scaled()", "Btot"])] ['species:A()', 'expression:scaled', 'observable:Btot']
- outputs(selectors)[source]
Return the value columns named by selectors.
Thin value accessor on top of
resolve_outputs(): resolves each selector and stacks the named columns into a single array.- Parameters:
selectors (
str|Iterable[str]) – Selectors accepted byresolve_outputs().- Returns:
Shape
(n_times, n_outputs)for a single-simulation result, with one column per selector in input order. For a stacked batch result (3-D arrays) the shape is(n_sims, n_times, n_outputs). An empty selector list yields a(n_times, 0)array.- Return type:
GenericAlias[double]- Raises:
ValueError, TypeError – Propagated from
resolve_outputs().
- output_sensitivities(selectors, *, axis='parameter')[source]
Return
d(named output)/dθfor each selector, stacked.Selector-addressed companion to
outputs(): resolves each selector and stacks the matching sensitivity slice — the chain-rule derivative of that output column with respect to the sensitivity parameters (default) or the differentiated initial conditions.Observable sensitivities are computed at simulation time from the CVODES species sensitivities via the linear chain rule
d obs_j/dθ = Σ_i c_ji·dx_i/dθ(GH #197);expression:selectors carry the codegen output-sensitivity chain rule for global functions (GH #198).species:selectors read the species sensitivities directly — except for an SBML AssignmentRule-target species, whose reported value is the rule’s live value (its state slot is frozen), so its output sensitivity follows the assignment expression: the sensitivity of the rule’s observable (linear-on-species rule) or function (everything else), GH #205. The raw integrated-state sensitivity for such a species stays available as the low-levelsensitivities_speciestensor.- Parameters:
- Returns:
Shape
(n_times, n_outputs, n_axis)for a single-simulation result ((n_sims, n_times, n_outputs, n_axis)for a stacked batch), one slice per selector in input order.n_axisis the number of sensitivity parameters (or IC species). An empty selector list yields a(n_times, 0, n_axis)array.- Return type:
GenericAlias[double]- Raises:
ValueError – If
axisis invalid; if the requested sensitivity axis was not computed for this result; or if a selector names a kind whose sensitivities are unavailable (e.g. anexpression:selector before GH #198).TypeError – Propagated from
resolve_outputs().
Examples
>>> sim = Simulator(model, method="ode", sensitivity_params=["k1"]) >>> result = sim.run(t_span=(0, 10), n_points=11) >>> result.output_sensitivities("observable:Atot").shape (11, 1, 1)
- fisher_information(sigma=1.0, *, outputs=None, axis='parameter')[source]
Compute the Fisher Information Matrix from sensitivity data.
The FIM quantifies how much information the simulated output trajectories carry about each parameter. It is a pure function of the model + output-sensitivity tensor — no measurement data, no residuals, no objective — so it measures the model’s practical identifiability (sloppiness), independent of any fit. It is computed as:
\[\text{FIM} = \sum_t \left(\frac{\partial Y}{\partial \theta}\right)^T \Sigma^{-1} \left(\frac{\partial Y}{\partial \theta}\right)\]where \(\partial Y / \partial \theta\) is the
(n_outputs, n_params)sensitivity matrix at time t, and \(\Sigma\) is the diagonal output-noise covariance.By default the FIM is built over species (back-compatible with the original species-only method). Pass outputs to build it over named outputs — any mix of
species:/observable:/expression:selectors (GH #202) — using the output-sensitivity tensor (GH #197/#198). For the richer eigenvalue / identifiability readout, seeidentifiability().- Parameters:
sigma (
float|GenericAlias[double]) –Output-noise standard deviation used only to scale the FIM (it is not measurement data). Defaults to
1.0(unscaled).scalar — homogeneous: every output shares the same σ.
1-D array — per-output σ. Length must match the number of outputs:
n_specieswhen outputs isNone, otherwise the number of selectors.
outputs (
str|Iterable[str] |None) – Output selectors (seeresolve_outputs()) to build the FIM over.None(default) uses every species — the original species-only behaviour.axis (
str) – Sensitivity axis:"parameter"(default) builds the FIM oversensitivity_params;"ic"oversensitivity_ic_species.
- Returns:
Symmetric positive-semidefinite FIM over
nparameters (or IC species, foraxis="ic").- Return type:
GenericAlias[double]- Raises:
ValueError – If the requested sensitivity axis was not computed (run with
sensitivity_params/sensitivity_ic); if sigma’s shape is wrong; if axis is invalid; or if this is a batch result (the FIM is defined per single simulation — iterate replicates).
Notes
The FIM is the Cramér–Rao lower bound on the inverse of the parameter covariance: \(\text{Cov}(\hat\theta) \geq \text{FIM}^{-1}\). Large diagonal entries indicate identifiable parameters; small entries indicate practical non-identifiability.
np.linalg.cond(fim)(orIdentifiabilityReport.condition_number) gauges the overall identifiability of the parameter set.Examples
>>> sim = Simulator(model, method="ode", ... sensitivity_params=["k1", "k2"]) >>> result = sim.run(t_span=(0, 100), n_points=101) >>> fim = result.fisher_information(sigma=0.1) # species >>> fim.shape (2, 2) >>> result.fisher_information(outputs=["observable:Atot"]).shape (2, 2)
- identifiability(sigma=1.0, *, outputs=None, axis='parameter', rtol=None)[source]
Model-identifiability readout from the Fisher Information Matrix.
Builds the FIM (see
fisher_information()) and returns its eigen-decomposition with practical-identifiability flags: which parameter combinations are well-constrained by the model and which are “sloppy” (near-zero eigenvalues / practically non-identifiable). This is sensitivity analysis of the model — no data, no residuals, no objective.- Parameters:
sigma (
float|GenericAlias[double]) – Output-noise σ; seefisher_information(). Defaults to1.0.outputs (
str|Iterable[str] |None) – Output selectors to build the FIM over;None(default) uses every species.axis (
str) – Sensitivity axis; seefisher_information().rtol (
float|None) – Relative eigenvalue cutoff for flagging non-identifiable / sloppy directions: a direction is flagged when its eigenvalue is belowrtol * lambda_max. Defaults ton * eps(NumPy’s numerical-rank tolerance), which flags only numerically singular directions; pass a larger value (e.g.1e-6) to surface sloppy-but-nonzero directions.
- Returns:
Eigenvalues/eigenvectors, numerical rank, condition number, per-direction identifiability flags, and the Cramér–Rao bound
FIM⁻¹(all-NaN + a warning when the FIM is rank-deficient).- Return type:
IdentifiabilityReport
Examples
>>> report = result.identifiability(sigma=0.1) >>> report.rank, report.condition_number (2, 3.4) >>> report.non_identifiable_directions []
- gradient(loss_fn)[source]
Compute parameter gradient from sensitivity data and a loss function.
Given forward sensitivities \(\partial Y / \partial p\) and a user-supplied loss function that returns the per-species loss gradient \(\partial L / \partial Y\) at each time point, computes the parameter gradient:
\[\nabla_p L = \sum_t \left(\frac{\partial Y}{\partial p}\right)^T \frac{\partial L}{\partial Y}(t)\]This enables gradient-based optimization via
scipy.optimize.minimize(method='L-BFGS-B').- Parameters:
loss_fn (
Callable[[GenericAlias[double],GenericAlias[double]],GenericAlias[double]]) –A function with signature
loss_fn(species, time) -> dL_dYwhere:species—(n_times, n_species)array of species valuestime—(n_times,)array of time pointsreturns
dL_dY—(n_times, n_species)array of \(\partial L / \partial Y_{i,t}\).
Common example (sum-of-squares vs data):
def loss_fn(species, time): return 2 * (species - data) # dL/dY for SSE
- Returns:
Parameter gradient \(\nabla_p L\).
- Return type:
GenericAlias[double]- Raises:
ValueError – If sensitivity data is not available.
TypeError – If
loss_fnis not callable.
Examples
>>> # Sum-of-squares loss vs observed data >>> data = np.random.randn(101, 2) # observed data >>> result = sim.compute_all_sensitivities( ... t_span=(0, 10), n_points=101 ... ) >>> grad = result.gradient( ... lambda species, time: 2 * (species - data) ... ) >>> grad.shape (n_params,)
>>> # Use with scipy L-BFGS-B >>> from scipy.optimize import minimize >>> def objective(p_vec): ... model.set_params(dict(zip(param_names, p_vec))) ... model.reset() ... result = sim.compute_all_sensitivities(...) ... loss = np.sum((result.species - data)**2) ... grad = result.gradient( ... lambda sp, t: 2 * (sp - data) ... ) ... return loss, grad >>> minimize(objective, x0, method='L-BFGS-B', jac=True)
Notes
The gradient computation is O(n_times × n_species × n_params) — a single matrix multiply per time point. With parallel
compute_all_sensitivities(), the total cost of computing both the loss value and its gradient is ~1.2× a plain ODE solve (with sufficient cores).
- sse_gradient(data, *, species_indices=None)[source]
Compute SSE loss and parameter gradient in one call.
Sum-of-squared-errors: \(L = \sum_{t,i} (Y_{t,i} - D_{t,i})^2\).
This is the most common objective in parameter estimation. The analytical derivative is \(\partial L / \partial Y = 2(Y - D)\), so no user-supplied
loss_fnis needed.- Parameters:
data (
GenericAlias[double]) – Observed data to compare against species trajectories. Must match the shape ofself.species(or the selected subset ifspecies_indicesis given).species_indices (
list[int] |None) – If given, only these species columns are used for the loss. Sensitivities for all parameters are still included.
- Returns:
(loss, gradient)where loss is the scalar SSE and gradient is shape(n_params,).- Return type:
- Raises:
ValueError – If sensitivity data is missing or shapes don’t match.
Examples
>>> result = sim.compute_all_sensitivities(...) >>> loss, grad = result.sse_gradient(observed_data) >>> # Use with scipy L-BFGS-B: >>> minimize(objective, x0, method='L-BFGS-B', jac=True)
- chi2_gradient(data, sigma, *, species_indices=None)[source]
Compute chi-squared loss and parameter gradient.
\(\chi^2 = \sum_{t,i} \left(\frac{Y_{t,i} - D_{t,i}}{\sigma_i}\right)^2\)
Equivalent to weighted SSE with weights \(1/\sigma_i^2\). The derivative is \(\partial L / \partial Y_{t,i} = 2(Y - D)/\sigma_i^2\).
- Parameters:
data (
GenericAlias[double]) – Observed data.sigma (
float|GenericAlias[double]) –Measurement noise standard deviation.
scalar — same σ for all species.
1-D array, shape ``(n_species,)`` — per-species σ.
2-D array, shape ``(n_times, n_species)`` — per-point σ.
species_indices (
list[int] |None) – Subset of species columns to include.
- Returns:
(chi2, gradient)where chi2 is the scalar value and gradient is shape(n_params,).- Return type:
Examples
>>> loss, grad = result.chi2_gradient(data, sigma=0.1) >>> loss, grad = result.chi2_gradient(data, sigma=per_species_sigma)
- neg_log_likelihood_gradient(data, sigma, *, species_indices=None)[source]
Compute negative Gaussian log-likelihood and gradient.
\(-\ln \mathcal{L} = \frac{1}{2} \sum_{t,i} \left[\left(\frac{Y_{t,i} - D_{t,i}}{\sigma_i}\right)^2 + \ln(2\pi\sigma_i^2)\right]\)
The constant term \(\ln(2\pi\sigma^2)\) doesn’t affect the gradient but is included in the loss value for correct log-likelihood.
- Parameters:
- Returns:
(nll, gradient)— negative log-likelihood and gradient.- Return type:
Examples
>>> nll, grad = result.neg_log_likelihood_gradient(data, sigma=0.1)
- property sensitivities: NDArray[float64]
Forward sensitivity data dY/dp.
Shape
(n_times, n_species, n_params)when sensitivities are available, or empty(0, 0, 0)otherwise.Example
>>> sim = Simulator(model, method="ode", ... sensitivity_params=["k"]) >>> result = sim.run(t_span=(0, 10), n_points=11) >>> result.sensitivities.shape (11, 2, 1) >>> result.sensitivities[-1, 0, 0] # dA/dk at t=10
- property has_sensitivities: bool
Whether sensitivity data is available.
- property sensitivities_ic: NDArray[float64]
Forward IC sensitivity data dY/dY(0).
Shape
(n_times, n_species, n_ic_species)when IC sensitivities are available, or empty(0, 0, 0)otherwise. Columnkis∂Y(t)/∂Y_k(0)where the ordering ofkmatchessensitivity_ic_species.
- property has_sensitivities_ic: bool
Whether IC sensitivity data is available.
- property sensitivities_species: NDArray[float64]
Alias for
sensitivities(species forward sensitivities).Provided so the four output-sensitivity blocks have a parallel, self-describing name on the same object:
sensitivities_species,sensitivities_observables,sensitivities_expressions(+ the_icvariants). Identical array tosensitivities.This is the raw integrated state tensor — for an SBML AssignmentRule-target species its slot is the frozen state’s sensitivity (~0), not the rule’s. The rule-following derivative is the one
output_sensitivities()returns forspecies:<name>(GH #205); this attribute is the documented low-level escape hatch.
- property sensitivities_observables: NDArray[float64]
Observable parameter sensitivities
d observable / dp.Shape
(n_times, n_observables, n_params)when computed, or empty(0, 0, 0)otherwise. The parameter axis matchessensitivity_params.
- property has_sensitivities_observables: bool
Whether observable parameter sensitivities are available.
- property sensitivities_expressions: NDArray[float64]
Expression (function) parameter sensitivities
d expression / dp.Shape
(n_times, n_expressions, n_params)when computed, or empty(0, 0, 0)otherwise. The parameter axis matchessensitivity_params.
- property has_sensitivities_expressions: bool
Whether expression parameter sensitivities are available.
- property sensitivities_observables_ic: NDArray[float64]
Observable IC sensitivities
d observable / dY(0).Shape
(n_times, n_observables, n_ic_species)when computed, or empty(0, 0, 0)otherwise. The IC axis matchessensitivity_ic_species.
- property has_sensitivities_observables_ic: bool
Whether observable IC sensitivities are available.
- property sensitivities_expressions_ic: NDArray[float64]
Expression (function) IC sensitivities
d expression / dY(0).Shape
(n_times, n_expressions, n_ic_species)when computed, or empty(0, 0, 0)otherwise. The IC axis matchessensitivity_ic_species.
- property has_sensitivities_expressions_ic: bool
Whether expression IC sensitivities are available.
- property solver_stats: dict[str, int]
Solver diagnostics dict.
Keys:
n_steps,n_rhs_evals,n_jac_evals,n_err_test_fails,n_nonlin_iters,n_nonlin_conv_fails.
- property ssa_diagnostics: dict[str, Any]
SSA boundary diagnostics dict (GH #110).
The exact SSA evaluates rate laws literally: it does not floor species at zero, and a reaction whose rate law evaluates negative fires in reverse (propensity
|rate|) so the SSA mean tracks the ODE. Both events are surfaced here instead of being silent.Keys:
n_negative_crossings(int): times a species count crossed from>= 0to< 0(no floor applied).first_negative_species(str): name of the first species to go negative;""if none.n_reverse_fires(int): reactions fired in reverse because their rate law was negative.first_reverse_reaction(str): label of the first reaction reversed;""if none.
All zero/empty on every non-SSA backend.
- property dataframe
Return a pandas DataFrame with time + observables.
Requires pandas (
pip install bngsim[pandas]).- Returns:
Columns:
time, then one per observable.- Return type:
- Raises:
ImportError – If pandas is not installed.
- property xr: _XarrayAccessor
Per-field xarray
DataArrayaccessor (AMICI-style).Each attribute access (
result.xr.species,result.xr.observables, etc.) builds a freshxarray.DataArraywith labeled coords. Mirrors AMICI’srdata.xr.x/.y/.sxergonomics so downstream code accustomed to that ecosystem can slice by name:>>> result.xr.species.sel(state="A").values >>> result.xr.observables.sel(observable="A_tot") >>> result.xr.sensitivities.sel(parameter="k1", state="A")
Available fields (empty when the corresponding block has zero width):
species— dims(time, state)observables— dims(time, observable)expressions— dims(time, expression)sensitivities— dims(time, state, parameter)(only when sensitivities were computed)sensitivities_ic— dims(time, state, ic_state)(only when IC sensitivities were computed)
Requires the optional xarray dependency (
pip install xarray). For a one-shotxarray.Datasetof every field with a sharedtimecoord, seeto_xarray().- Raises:
ImportError – If xarray is not installed (raised on first attribute access).
AttributeError – If the requested field is not a known xarray-exposable block, or if it has zero width.
- to_xarray()[source]
Bundle every field into a single
xarray.Dataset.One-call complement to
xr. The returned Dataset has a sharedtimecoord and exposesspecies,observables,expressions, and (when present)sensitivities/sensitivities_icas data variables, each with the same labeled coords as the per-field DataArrays onxr.custom_attrsis mirrored ontods.attrs; the stochasticseed(when present) is stored asds.attrs["seed"].- Returns:
A Dataset with all populated trajectory fields as data vars.
- Return type:
xarray.Dataset
- Raises:
ImportError – If xarray is not installed.
RuntimeError – If the result holds 3-D batch arrays. Iterate per-replicate and call
to_xarrayon eachResult.
Examples
>>> ds = result.to_xarray() >>> ds["species"].sel(species="A").plot() >>> ds.to_netcdf("result.nc") # archive via xarray's writer
- as_roadrunner(selections=None)[source]
Return a
bngsim.NamedArraymimickingrr.simulate.Drop-in shape replacement for libRoadRunner’s
simulate(...)return value: a 2-D array whose columns are labeled per selections. Used to swap RR for BNGsim in PyBNF stochastic- fitting workflows without changing call sites.- Parameters:
selections (
list[str] |None) –Column selectors to include, in order. Each entry is one of:
"time"— the time column."[X]"— the concentration of speciesX(BNGsim’s stored species value, which equalsamount/V_cuniformly per Phase 2)."X"— the amount of speciesX(concentration ×V_c); equals the stored value when the species’s compartment volume is 1 (the default and the .net case).
When None, defaults to RoadRunner’s
["time"] + [f"[{s}]" for s in species_names].- Returns:
Shape
(n_times, len(selections)); columns ordered to match selections. The result subclassesnumpy.ndarray, so all numpy operations work.- Return type:
- Raises:
ValueError – If a selector is unrecognized or names a species not in the model.
RuntimeError – Only if the result was loaded from disk and the array is 3-D batch-shaped (call
squeeze()per-row first).
Examples
Replace
rr.simulate(0, 100, 101)(RoadRunner) with the BNGsim equivalent:sim = bngsim.Simulator(model, method="ssa") arr = sim.run(t_span=(0, 100), n_points=101).as_roadrunner() # arr has columns ["time", "[X]", "[Y]", ...] x_trace = arr["[X]"]
Custom selections:
arr = result.as_roadrunner( selections=["time", "[X]", "X", "[Y]"] )
- summary()[source]
Return a compact, JSON-serializable description of this result.
The HPC scheduler-free contract (GH #203) pairs a serializable
EvaluationSpec(what was run) with a lightweight summary (what came back), so a cluster driver can index/log/checkpoint thousands of evaluations without re-reading every full HDF5 payload (usesave()/load()for the complete arrays). The summary carries shapes, output names, sensitivity availability, solver diagnostics, and the seed — every value a built-in JSON-encodable type.- Returns:
Keys:
version(bngsim version),is_batch,n_sims(Noneunless batch),n_times,t_start/t_end(Nonefor an empty time grid),shapes(per-array shape lists),species_names/observable_names/expression_names, thehas_sensitivities*flags,sensitivity_params,sensitivity_ic_species,solver_stats, andseed.- Return type:
Examples
>>> import json >>> s = result.summary() >>> json.dumps(s) # always succeeds '...'
- save(path)[source]
Save result to HDF5 file.
- Parameters:
- Raises:
ImportError – If h5py is not installed.
- Return type:
- classmethod load(path)[source]
Load result from HDF5 file.
- Parameters:
- Returns:
Loaded result.
- Return type:
Result- Raises:
ImportError – If h5py is not installed.
FileNotFoundError – If the file does not exist.
- to_gdat(path, *, print_functions=False, print_rate_laws=False)[source]
Export observables in BNG
.gdatformat.Function-column headers are always bare (no
()suffix) and identical across every simulation method (issue #58).- Parameters:
print_functions (
bool) – When True, append the user-named function columns after the observables (auto-generated_rateLawNrate-law columns are still omitted). Default False keeps the observables-only output, matching BNG2.pl’s behavior whenprint_functions=>1is not set.print_rate_laws (
bool) – When True, additionally append the auto-generated_rateLawNrate-law columns (bare names). These are internal rate-law intermediates, omitted by default. On a result loaded from HDF5 or assembled from raw arrays they are unavailable (only the filtered set is persisted), so this flag is a no-op there. Default False.
- Return type:
- to_cdat(path)[source]
Export species in BNG
.cdatformat.
- to_csv(path, *, kind='observables', sep=',', include_time=True, header=True)[source]
Export the trajectory as a plain delimited text file.
Unlike
to_gdat()/to_cdat()(BNG-native,#-prefixed, fixed-width space-padded), this writer produces a format SBML/ RoadRunner/Tellurium users expect: a header row of column names followed by data rows, with a user-chosen separator (,for CSV by default;"\t"for TSV; any single character).The first column is
time(unlessinclude_time=False); the remaining columns are the observable or species columns named exactly as they appear on the in-memoryResult. No#comment prefix is emitted, so the file can be loaded directly bypandas.read_csvornumpy.loadtxt.- Parameters:
path (
str|Path) – Output file path. The caller chooses the path; nothing is written to the current working directory implicitly.kind (
str) – Which trajectory block to write."observables"(default) matches what.gdatwould contain;"species"matches.cdat.Result.expressionsis not currently exported viato_csv— useResult.save()(HDF5) for a lossless capture.sep (
str) – Column separator. Default",". Use"\t"for TSV. Must be a single character.include_time (
bool) – Whether to prepend atimecolumn. DefaultTrue.header (
bool) – Whether to write the header row of column names. DefaultTrue.
- Raises:
ValueError – If
kindis not"observables"or"species", ifsepis not a single character, or if the trajectory is not a 2-D single-sim array (call this per-replicate on batch results).OSError – If the file cannot be opened for writing.
- Return type:
None
Notes
- Return type:
- Parameters:
Backend coverage: works on every method (ODE / SSA / PSA / NFsim / RuleMonkey) and on results loaded from HDF5. What is not exported by this writer:
expressions,sensitivities,solver_stats,custom_attrs,seed. Use HDF5 (Result.save()) when any of those need to round-trip.Examples
Plain CSV of observables, ready for
pandas.read_csv:sim = bngsim.Simulator(model, method="ode") result = sim.run(t_span=(0, 100), n_points=101) result.to_csv("out.csv")
Tab-separated species amounts:
result.to_csv("out.tsv", kind="species", sep="\t")
Headerless data block (e.g. to append to an existing file):
result.to_csv("data.csv", header=False)
- static squeeze(results)[source]
Stack a list of single-sim Results into one batch Result.
The returned Result has 3D arrays: -
species.shape == (n_sims, n_times, n_species)-observables.shape == (n_sims, n_times, n_observables)Any populated sensitivity block (species, observable, expression, and their IC variants — GH #196) is carried through with the sim axis prepended, e.g.
sensitivities.shape == (n_sims, n_times, n_rows, n_cols); a block that is empty on the inputs stays empty. The parameter / IC-species name lists are taken from the first result.- Parameters:
results (
list[Result]) – Results from individual simulations (all must have the same n_times, n_species, n_observables).- Returns:
A batch result with 3D arrays.
- Return type:
Result
- class bngsim.IdentifiabilityReport(fim, parameters, eigenvalues, eigenvectors, rank, condition_number, identifiable, non_identifiable_directions, cramer_rao_bound, threshold)[source]
Bases:
objectModel-identifiability readout of a Fisher Information Matrix (GH #202).
A pure function of the model + output-sensitivity tensor (no data, no residuals, no objective): the eigen-decomposition of the FIM and the practical-identifiability flags derived from it. This is sloppiness / practical-identifiability analysis of the model, independent of any fit.
Produced by
Result.identifiability().- fim
The (symmetric, positive-semidefinite) Fisher Information Matrix the readout was computed from, over
nparameters (or IC species).- Type:
ndarray, shape
(n, n)
- parameters
Names labelling the FIM axes — the
Result.sensitivity_params(axis="parameter") orResult.sensitivity_ic_species(axis="ic") in matrix order.
- eigenvalues
FIM eigenvalues in ascending order. Tiny negative values from round-off are clipped to
0.eigenvalues[0]is the smallest — the sloppiest (least-constrained) direction.- Type:
ndarray, shape
(n,)
- eigenvectors
Orthonormal eigenvectors as columns, aligned with
eigenvalues:eigenvectors[:, i]is the parameter combination foreigenvalues[i]. A near-zero eigenvalue marks its eigenvector as a practically non-identifiable parameter combination.- Type:
ndarray, shape
(n, n)
- rank
Numerical rank — the number of eigenvalues above
threshold.- Type:
- condition_number
\(\lambda_\max / \lambda_\min\);
infwhen rank-deficient. A large value signals an ill-conditioned (sloppy) parameter set.- Type:
- identifiable
Per-eigen-direction flag (aligned with
eigenvalues):Truewhere the eigenvalue exceedsthreshold,Falsefor a practically non-identifiable / sloppy direction.- Type:
ndarray of bool, shape
(n,)
- non_identifiable_directions
Indices into
eigenvalues/eigenvectorscolumns that are flagged non-identifiable (theFalseentries ofidentifiable), smallest eigenvalue first.
- cramer_rao_bound
\(\text{FIM}^{-1}\) — the Cramér–Rao lower bound on parameter variance, an identifiability aid only (a property of the FIM). This is not a data/noise-weighted fit covariance. All-
NaNwhen the FIM is rank-deficient (the inverse is undefined; a warning is emitted).- Type:
ndarray, shape
(n, n)
- threshold
Eigenvalue cutoff (
rtol * lambda_max) below which a direction is flagged non-identifiable.- Type:
- Parameters:
- fim: NDArray[float64]
- eigenvalues: NDArray[float64]
- eigenvectors: NDArray[float64]
- rank: int
- condition_number: float
- identifiable: NDArray[bool]
- cramer_rao_bound: NDArray[float64]
- threshold: float
- property is_identifiable: bool
True when every direction is identifiable (full-rank FIM).
- class bngsim.EvaluationSpec(model_source, model_format='net', method='ode', t_span=(0.0, 100.0), n_points=101, params=<factory>, sensitivity_params=(), sensitivity_ic=(), sensitivity_method='staggered', outputs=(), rtol=None, atol=None, max_steps=None, model_sha256=None)[source]
Bases:
objectA serializable, stateless description of one bngsim evaluation.
- Parameters:
model_source (
str) – For a path format (net/sbml/antimony), the model file path. For a*_stringformat, the inline model text.model_format (
str) – One of"net","sbml","antimony","sbml_string","antimony_string".method (
str) – Simulation method. Default"ode"(the only method with sensitivities).t_span (
tuple[float,float]) –(t_start, t_end)integration interval.n_points (
int) – Number of output time points (includingt_start).params (
Mapping[str,float]) – Parameter overrides applied to the model before running (the θ vector).sensitivity_params (
Sequence[str]) – Parameter names to compute forward output sensitivities for. Empty disables parameter sensitivities.sensitivity_ic (
Sequence[str]) – Species names whose initial conditions to differentiate against.sensitivity_method (
str) – CVODES sensitivity method ("staggered"/"simultaneous").outputs (
Sequence[str]) – Output selectors (species:/observable:/expression:…) that the caller intends to read off the result. Recorded for provenance; not applied during integration.rtol (
float|None) – Solver tolerances.Noneuses the Simulator defaults.atol (
float|None) – Solver tolerances.Noneuses the Simulator defaults.max_steps (
int|None) – Max internal solver steps per output point.Noneuses the default.model_sha256 (
str|None) – Hex SHA-256 of the model source (file bytes for a path format, the UTF-8 text for a*_stringformat). When set,build_model()verifies the live source matches and raises on mismatch — a cluster integrity guard against a stale/edited model file on a shared filesystem.
- model_source: str
- model_format: str = 'net'
- method: str = 'ode'
- n_points: int = 101
- sensitivity_method: str = 'staggered'
- to_dict()[source]
Return a deterministic, JSON-encodable dict of this spec.
paramsis emitted sorted by name and sequences as lists, so the dict (andto_json()) is byte-stable for a fixed spec — usable directly as a content key for caching/deduplication.
- classmethod from_dict(data)[source]
Reconstruct a spec from a
to_dict()mapping (extra keys rejected).
- to_json(*, indent=None)[source]
Serialize to a JSON string (keys sorted for byte-stability).
- classmethod from_json(text)[source]
Deserialize from a
to_json()string.- Parameters:
text (
str)- Return type:
EvaluationSpec
- with_params(params, *, merge=False)[source]
Return a copy with
paramsreplaced (or merged whenmerge=True).The ergonomic path for a parameter sweep / multistart: serialize one base spec, then stamp each θ row through
with_paramson the worker. Returns a new frozen instance; the original is untouched.
- compute_source_sha256()[source]
SHA-256 of the model source (file bytes for a path, UTF-8 text inline).
- Return type:
- build_model()[source]
Load the model from
model_sourcepermodel_format.When
model_sha256is set, the live source is hashed and compared first; a mismatch raisesValueError(cluster integrity guard).- Return type:
Model
- build_simulator()[source]
Build a
Simulatorwith θ applied and sensitivities wired.- Return type:
Simulator
- evaluate()[source]
Materialize and run this evaluation, returning the
Result.Deterministic for fixed inputs (model, θ, sensitivity set, solver options). Read named outputs via
Result.outputs()and gradients viaResult.output_sensitivities()withoutputsas selectors.- Return type:
Result
- class bngsim.SteadyStateResult(core)[source]
Bases:
objectResult of a steady-state computation.
- concentrations
Species concentrations at steady state.
- Type:
ndarray, shape (n_species,)
- residual
max|f(y)|at convergence.- Type:
- method_used
"integration"or"newton".- Type:
- converged
Whether steady state was found.
- Type:
- n_steps
Number of solver steps.
- Type:
- n_rhs_evals
Number of RHS evaluations.
- Type:
- sensitivity
dY_ss/dpmatrix, shape(n_species, n_params).- Type:
ndarray or None
Examples
>>> ss = sim.steady_state() >>> ss.converged True >>> ss["A"] # species A at steady state 50.0 >>> ss.concentrations array([50., 25., ...])
- residual
- method_used
- converged
- n_steps
- n_rhs_evals
- property concentrations
Steady-state species concentrations.
- property sensitivity
Sensitivity matrix dY_ss/dp, shape (n_species, n_params).
None if no sensitivity was requested.
- class bngsim.NfsimSession(xml_path, *, molecule_limit=None, connectivity=None, v1143_compat=False, block_same_complex_binding=True, traversal_limit=None)[source]
Bases:
objectStateful NFsim simulation session with per-step control.
Use this class when you need fine-grained control over an NFsim session: setting parameters before initialization, running multiple simulation segments, and mutating molecule or exact species counts between segments.
For simple one-shot NFsim simulations,
bngsim.Simulator(model, method="nf", xml_path=...)is easier.- Parameters:
xml_path (
str|Path) – Path to a BNG XML file (generated by BNG2.plwriteXML).molecule_limit (
int|None) – Global molecule limit (NFsim-gmlflag).connectivity (
bool|None) – Enable NFsim’s reaction-connectivity optimization.v1143_compat (
bool) – Enable NFsim v1.14.3 selector compatibility mode for same-seed trajectory reproducibility with the standalone CLI.block_same_complex_binding (
bool) – NFsim-bscb: when True, block bimolecular reactions whose two reactant patterns match molecules in the same complex. Default:Truein bngsim — the NFsim CLI default is off, but bngsim defaults it on for correctness on BLBR/aggregation models that polymerize within a single complex otherwise. PassFalseto allow same-complex binding (matching BNG2.pl’scomplex=>1). Note: this flag governs only the binding policy; complex bookkeeping needed to countSpecies-typed observables is enabled automatically whenever the model declares one, regardless of this setting.traversal_limit (
int|None) – NFsim-utl N: universal traversal limit.None(default) lets NFsim auto-compute a suggested limit from the XML.
- Raises:
RuntimeError – If NFsim support is not available in this bngsim build.
Examples
Basic one-shot simulation:
with bngsim.NfsimSession("model.xml") as nf: nf.initialize(seed=42) result = nf.simulate(0, 100, n_points=101)
Multi-step protocol:
with bngsim.NfsimSession("model.xml", molecule_limit=100000) as nf: nf.set_param("kf", 0.5) nf.initialize(seed=42) r1 = nf.simulate(0, 50, n_points=51) nf.add_species("L(r)", 500) r2 = nf.simulate(50, 100, n_points=51)
Parameter scan (new session per point):
for kf_val in [0.1, 1.0, 10.0]: with bngsim.NfsimSession("model.xml") as nf: nf.set_param("kf", kf_val) nf.initialize(seed=42) result = nf.simulate(0, 100, n_points=101)
- set_molecule_limit(limit)[source]
Set the global molecule limit.
Can be called before or after
initialize()to change the NFsim-gmlcap.
- set_param(name, value)[source]
Set a parameter value and propagate to dependents.
May be called before or after
initialize(). Pre-init writes are stored and applied on session start; post-init writes update the live NFsim System in place. In both cases bngsim re-evaluates every parameter whose BNG XMLexpr=transitively depends onname, so derived values likeLT = LT_conc_M*NA*V_simand_rateLaw*follow automatically.Pre-init writes are baked into the XML that NFsim parses, so any
<Species concentration="X">seed-species expression that referencesname(directly or through a derived parameter) is evaluated against the override-resolved namespace when the agent population is created. This matches BNG2.pl’s behavior whensetParameteris invoked betweengenerate_networkand amethod=>"nf"simulate.Post-init writes do not alter the live agent population — they only update reaction rates from the new parameter namespace. To change live counts after
initialize(), useadd_species()/remove_species().
- clear_param_overrides()[source]
Clear all parameter overrides set via
set_param.When called on a live session, parameters revert to their XML-time values and dependent expressions are re-evaluated so the System matches the original BNG XML namespace.
- Return type:
- evaluate(expr, overrides=None)[source]
Evaluate a BNG expression in the current parameter namespace.
Uses the same ExprTk evaluator that powers
set_parampropagation, so expressions can reference any BNG parameter, BNG built-in (_pi,_NA, …), or BNG-style construct (if(cond, then, else),&&,||,mratio).- Parameters:
- Returns:
Numeric value of the expression.
- Return type:
- Raises:
ParameterError – If the expression fails to compile.
- initialize(seed=None)[source]
Initialize the NFsim session.
Parses the XML, seeds the RNG, and prepares the simulation system. Must be called before
simulate().- Parameters:
seed (
int|None) – Random number generator seed. When omitted (orNone), bngsim draws a fresh seed from system entropy so consecutive session lifetimes produce independent trajectories. Pass an explicit integer for reproducibility. The actual seed used is exposed via theseedproperty and stamped onto everyResultreturned bysimulate().- Raises:
SimulationError – If initialization fails.
- Return type:
- destroy()[source]
Destroy the session and release C++ resources.
Safe to call multiple times. Called automatically by
__exit__when using the context manager.- Return type:
- simulate(t_start=None, t_end=None, n_points=None, *, timeout=None, relative_time=False, sample_times=None)[source]
Run a simulation segment and return a Result.
Session state is preserved across calls, enabling multi-step protocols (simulate, mutate, simulate again).
- Parameters:
t_start (
float|None) – Segment start time. Required unlesssample_timesis given.t_end (
float|None) – Segment end time. Required unlesssample_timesis given.n_points (
int|None) – Number of output time points (including endpoints). Required unlesssample_timesis given.sample_times (
list[float] |None) – Explicit output instants (GH #184). When provided, overridest_start/t_end/n_points: the returnedResult.timearray equals these times exactly (sorted ascending, treated as absolute), instead of a uniform grid. The live NFsim clock advances from the current session time by the span of the list, so this works mid-protocol (aftersetConcentration/ a prior segment), continuing the same trajectory. Must contain at least two finite points. Mutually exclusive withrelative_time(sample_times are absolute).timeout (
float|None) – Wall-clock budget in seconds.Noneor0disables the budget. Positive values arm a check between eachstepTo()output point; on overrun this method raisesbngsim.SimulationTimeoutand the session must be destroyed (or the context manager exited) before reuse — the live NFsim System has advanced an indeterminate distance into the segment and the session clock is not updated.relative_time (
bool) – WhenTrue, the returnedResult.timearray is offset so it begins at0.0and ends att_end - t_start— the convention used by BNG2.pl’ssimulate({method=>"nf", ...})action, which prints “NFsim timepoints are reported as time elapsed sincet_start=$t_start” at runtime. The internal NFsim clock andsession_logical_timeare unaffected, so multi-segment threading (simulate, mutate, simulate again) keeps the same absolute physical time it would have under the default. Recommended for hosts that thread NF protocols across multiple bngsimsimulate()calls and want their stitched time axis to match BNG2.pl’s. DefaultFalsepreserves the absolute-time labelling used by every other bngsim backend.
- Returns:
Simulation result for this segment.
- Return type:
Result- Raises:
SimulationError – If the session is not initialized or simulation fails.
SimulationTimeout – If
timeoutis set and the wall-clock budget is exceeded.
- step_to(time)[source]
Advance the simulation to time without recording output.
Useful for fast-forwarding before a protocol action.
- get_molecule_count(mol_type)[source]
Get the current count of a molecule type.
- add_molecules(mol_type, count)[source]
Add molecules of a given type in their default (unbound) state.
- Parameters:
- Raises:
ValueError – If count is not positive.
- Return type:
- get_species_count(pattern)[source]
Get the current count of an exact BNGL species pattern.
The current implementation supports exact single-molecule species patterns such as
"X(p~0,y)"or"TNF()". Mutation-oriented species patterns must list every component and specify every stateful component state so that the live-session operation is unambiguous. Multi-molecule complex patterns fail clearly.Symmetric components (e.g.
L(r~u~c~g, r~u~c~g)declares two sites namedr) are matched by sorted multiset of state/bond constraints across class members, so heterogeneous patterns likeL(r~u,r~c)andL(r~c,r~u)resolve to the same species count.
- add_species(pattern, count)[source]
Add exact BNGL species instances to the live NFsim session.
- remove_species(pattern, count)[source]
Remove exact BNGL species instances from the live NFsim session.
- set_species_count(pattern, count)[source]
Set the live count for an exact BNGL species pattern.
This is implemented as a diff against
get_species_count(pattern)and may add or remove exact species instances. A fractional count is rounded to the nearest integer (round-half-up, GH #51).
- get_parameter(name)[source]
Evaluate and return a parameter value from the live session.
Can evaluate expressions, not just named parameters.
- get_observable_names()[source]
Return observable names from the live session.
- get_observable_values()[source]
Return current observable values from the live session.
- save_species(path)[source]
Write the live System’s molecular species to a BNG
.speciesfile.Mirrors the artifact BNG2.pl emits when running
simulate({method=>"nf", get_final_state=>1, ...}). The file is a plain-text listing of species patterns and counts, formatted exactly as NFsim’s standalone CLI writes it. A host like PyBioNetGen that drives multi-segment NF protocols can read this file to thread state across segments without needing in-process snapshot support (see issue #52 for the in-process path).
- save_concentrations()[source]
Snapshot the live session’s full molecular state for later restore.
Captures every molecule’s count, component states, and bonds into an in-memory snapshot held by the session. A later
restore_concentrations()call rewinds the session to exactly this state. Overwrites any previous snapshot. Mirrors the BNGsaveConcentrations()action.Unlike
save_species()(which writes a.speciesfile for out-of-process state threading), this keeps the state in process — useful for equilibrate → snapshot → perturb → restore workflows without touching disk.- Raises:
SimulationError – If the session is not initialized.
- Return type:
- restore_concentrations()[source]
Restore the molecular state captured by
save_concentrations().Rewinds the live session to the most recently snapshotted state (counts, component states, and bonds), discarding any simulation progress since the snapshot. Mirrors the BNG
resetConcentrations()action.- Raises:
SimulationError – If the session is not initialized, or no snapshot has been saved with
save_concentrations().- Return type:
- has_saved_concentrations()[source]
Whether a
save_concentrations()snapshot is available to restore.- Return type:
- property initialized: bool
Whether
initialize()has been called.
- property seed: int | None
The integer RNG seed used by
initialize(), orNoneif the session has not been initialized yet.
- property destroyed: bool
Whether
destroy()has been called.
- property xml_path: str
Path to the BNG XML file.
- class bngsim.RuleMonkeySession(xml_path, *, molecule_limit=None, block_same_complex_binding=True)[source]
Bases:
objectStateful RuleMonkey simulation session with per-step control.
RuleMonkey reads BNG XML and implements the
nf_exactnetwork-free backend. Use this class for multi-action workflows that need to initialize, simulate, add molecules, and simulate again.- set_molecule_limit(limit)[source]
Set the global molecule limit before initialization.
- set_param(name, value)[source]
Set a parameter value before initialization.
- initialize(seed=None)[source]
Initialize the RuleMonkey session.
- simulate(t_start=None, t_end=None, n_points=None, *, timeout=None, sample_times=None)[source]
Run a simulation segment and return a Result.
- Parameters:
t_start (
float|None) – Segment endpoints and number of output samples. Required unlesssample_timesis given.t_end (
float|None) – Segment endpoints and number of output samples. Required unlesssample_timesis given.n_points (
int|None) – Segment endpoints and number of output samples. Required unlesssample_timesis given.sample_times (
list[float] |None) – Explicit output instants (GH #184). When provided, overridest_start/t_end/n_points: the returnedResult.timearray equals these times exactly (sorted ascending, treated as absolute), instead of a uniform grid. The live session advances from its current time by the span of the list, so this works mid-protocol (after a prior segment orsetConcentration), continuing the same trajectory. Must contain at least two finite points.timeout (
float|None) – Wall-clock budget in seconds.Noneor0disables the budget. Positive values arm a check polled by upstream every ~1024 SSA events; on overrun this method raisesbngsim.SimulationTimeoutand the session should be destroyed (or the context manager exited) before reuse — the live RuleMonkey session sits at the last completed event and subsequent state is undefined for bngsim purposes.
- Return type:
Result
- step_to(time, *, timeout=None)[source]
Advance the simulation to time without recording output.
- get_molecule_count(mol_type)[source]
Get the current count of a molecule type.
- add_molecules(mol_type, count)[source]
Add molecules of a given type in their default unbound state.
A fractional count is rounded to the nearest integer (round-half-up, GH #51), matching bngsim SSA and the cold-start seed.
- get_parameter(name)[source]
Return a parameter value from the loaded XML plus overrides.
- get_observable_values()[source]
Return current observable values from the live session.
- get_species_count(pattern)[source]
Get the current count of an exact BNGL species pattern.
Peer of
bngsim.NfsimSession.get_species_count(). pattern is parsed and canonicalized by RuleMonkey’s runtime species-pattern parser, so it need not be byte-identical to a label RuleMonkey emits. Scope is exact, fully-specified, connected species: every molecule lists every component, every stateful component carries a concrete~state, and bonds are numeric labels. Wildcards (!+,!?) or omitted components raise.
- add_species(pattern, count)[source]
Add exact BNGL species instances to the live RuleMonkey session.
- remove_species(pattern, count)[source]
Remove exact BNGL species instances from the live RuleMonkey session.
- set_species_count(pattern, count)[source]
Set the live count for an exact BNGL species pattern.
Drives the live count of pattern to exactly count, adding or removing the difference. A fractional count is rounded to the nearest integer (round-half-up, GH #51). Mirrors
bngsim.NfsimSession.set_species_count(); PyBioNetGen’s_apply_nfsim_concentration_changesprobes this method viagetattrto propagatesetConcentrationtormruns.
- evaluate(expr, overrides=None)[source]
Evaluate a BNG expression against the live session’s current state.
Resolvable symbols: every declared parameter, the clock
t/time(), every observable, and every global function — all settled against the current pool, exactly as a rate law would see them between events. overrides supplies extraname → valuebindings layered on top for a single evaluation; an override name shadows a model symbol on a clash.Note
Unlike
bngsim.NfsimSession.evaluate()(which only requires a live simulator), the RuleMonkey engine resolves expressions against the active session’s pool, so this method requires an initialized session — callinitialize()first.
- save_species(path)[source]
Write the live session’s molecular species to a BNG
.speciesfile.Exact peer of
bngsim.NfsimSession.save_species(). Enumerates the live complex pool, deduplicates by graph isomorphism, and writes a BNG-format.speciesfile (#comment header followed by one<pattern> <count>line per species) readable by BNG2.pl’sreadNFspecies. This is the artifact PyBioNetGen’ssimulate_nfhook reads to threadget_final_statecontinuation acrosssaveConcentrations/resetConcentrationssegments — binding it makes multi-segmentmethod=>"rm"runs reproduce the subprocess trajectory with no PyBioNetGen change.
- save_state(path)[source]
Save the active session state to a binary snapshot file.
Serializes the full molecular pool plus the RNG state, keyed by a fingerprint of the molecule-type schema. A later
load_state()(on a session built from the same XML) resumes the trajectory exactly. This is RuleMonkey’s in-process state-continuation path; NFsim has no equivalent (here RuleMonkey is ahead).For threading state into a BNG2.pl-driven host like PyBioNetGen, prefer
save_species()— that goes through BNG’s text.species+readNFspeciesmechanism, which is what the backend hook actually calls.Caveats¶
The schema fingerprint covers molecule-type schema only (molecule types, components, allowed states) — not ReactionRule or Observable patterns. Loading into a simulator built from a different XML that keeps the molecule schema but changes rules / observables succeeds silently and continues with the new rules.
The RNG state is serialized via the C++ stdlib’s
mt19937_64stream operator, which is not specified to be byte-identical across stdlib implementations. Save/load is reliable only between binaries built against the same toolchain.
- load_state(path)[source]
Load session state from a snapshot written by
save_state().Creates a new live session from the snapshot, replacing any existing one. The snapshot’s schema fingerprint must match this session’s XML (see
save_state()caveats). On success the session is initialized andcurrent_timereflects the snapshot’s logical time — feed it tosimulate(t_start, ...)to resume sampling.The RNG seed is not recoverable from a snapshot, so
seedreads backNoneafterload_state()even though the underlying RNG stream is restored.
- property initialized: bool
- property seed: int | None
The integer RNG seed used by
initialize(), orNoneif the session has not been initialized yet.
- property destroyed: bool
- property xml_path: str
- property current_time: float
- class bngsim.NamedArray(data, colnames)[source]
Bases:
ndarrayA 2-D ndarray with named columns (RoadRunner-compatible).
Constructed via
bngsim.Result.as_roadrunner(). End users rarely instantiate it directly; subclass primarily exists so that callers can identify the array shape (arr.colnames) and look up columns by name (arr["[X]"],arr[:, "[X]"]).- colnames
One entry per column, in column order. Always a fresh list — slicing / arithmetic that drops columns invalidates the original mapping, so the inherited list is left untouched on non-trivial transforms.
Examples
>>> result = sim.run(t_span=(0, 10), n_points=11) >>> arr = result.as_roadrunner() >>> arr.colnames ['time', '[X]', '[Y]'] >>> arr["[X]"] # 1-D column view array([ ... ]) >>> arr[:, "[X]"] # equivalent array([ ... ])
- exception bngsim.BngsimError[source]
Bases:
RuntimeErrorBase exception for all bngsim errors.
- exception bngsim.ConversionError[source]
Bases:
BngsimErrorA format conversion (e.g. SBML→.net) cannot be completed faithfully.
Raised by
bngsim.convert.sbml_to_net()when the source model uses a construct that the target format cannot represent without changing the model’s meaning — for example anhasOnlySubstanceUnitsspecies in a compartment whose volume is not 1, a cross-compartment reaction that needs per-species volume scaling, a rate-rule ODE, a Michaelis–Menten rate-law type, or a table (interpolation) function. The message names the offending construct and the model so the cause is actionable.Pass
strict=False(API) /--allow-lossy(CLI) to downgrade these to aConversionWarningand emit a best-effort.netanyway.
- exception bngsim.ConversionWarning[source]
Bases:
UserWarningA format conversion dropped or approximated part of the source model.
Emitted when the network channel is preserved but something outside it was left behind — most commonly SBML
<event>elements, which belong to the simulation-protocol channel (a SED-ML sidecar), not the.netnetwork. Also used whenstrict=Falsedowngrades an otherwise-fatalConversionError. Filter or escalate it like anyUserWarning:import warnings, bngsim warnings.simplefilter("error", bngsim.ConversionWarning) # promote warnings.simplefilter("ignore", bngsim.ConversionWarning) # silence
- exception bngsim.ModelError[source]
Bases:
BngsimErrorError loading or manipulating a model.
Raised when: - A .net file cannot be parsed - A model is in an invalid state for simulation - A reserved name conflict is detected
- exception bngsim.ParameterError[source]
Bases:
BngsimErrorError with parameter access.
Raised when: - A parameter name is not found in the model - A parameter value is invalid (NaN, Inf, wrong type)
- exception bngsim.SimulationError[source]
Bases:
BngsimErrorError during simulation.
Raised when: - CVODE fails to converge - NaN/Inf detected in species concentrations - Maximum number of steps exceeded
- exception bngsim.SimulationTimeout(message, *, timeout=0.0, elapsed=0.0, partial_result=None)[source]
Bases:
BngsimErrorRaised when a simulation exceeds its wall-clock budget.
Distinct from
SimulationErrorso callers (e.g. PyBNF’swall_time_sim) can classify wall-clock terminations separately from solver/convergence failures. Inherits fromBngsimError(and thereforeRuntimeError).- timeout
The configured wall-clock budget, in seconds.
- Type:
- elapsed
Actual elapsed wall-clock time at the point the timeout fired, in seconds.
elapsed >= timeoutalways holds.- Type:
- partial_result
Reserved for future use. Currently always
None— bngsim does not yet salvage a partial Result from a timed-out run.- Type:
Result | None
- exception bngsim.SsaBoundaryWarning[source]
Bases:
UserWarningAn SSA run hit a literal-rate-law boundary condition (GH #110).
Emitted once per run when the exact SSA either drove a species count negative (no non-negativity floor is applied — non-negativity is the rate law’s responsibility, matching the CVODE path) or fired a reaction in reverse because its rate law evaluated negative. Both keep the SSA mean consistent with the ODE; the warning surfaces what would otherwise be a silent boundary behavior. Filter or escalate it like any
UserWarning:import warnings, bngsim warnings.simplefilter("error", bngsim.SsaBoundaryWarning) # promote warnings.simplefilter("ignore", bngsim.SsaBoundaryWarning) # silence
The structured counts are always available on
result.ssa_diagnosticsregardless of warning filters.
- exception bngsim.DenseSolverFallbackWarning[source]
Bases:
UserWarningA large ODE model is running on the dense solver for lack of KLU (GH #209).
Emitted once per process when
Simulator.run()integrates a large model (n_speciespast the warn threshold) on the dense linear solver only because this bngsim install was built without SuiteSparse/KLU — not because the user asked forforce_dense_linear_solverorjacobian="jax"(both legitimately dense). Dense LU factorizes the full N×N Jacobian at O(N³); for a sparse genome-scale network that is the difference between minutes and hours. The fix is to rebuild bngsim with KLU (seebngsim.capabilities()and GH #209). Filter or escalate it like anyUserWarning:import warnings, bngsim warnings.simplefilter("ignore", bngsim.DenseSolverFallbackWarning) # silence warnings.simplefilter("error", bngsim.DenseSolverFallbackWarning) # promote
- exception bngsim.SsaValidationError(issues, *, override_attempted=False)[source]
Bases:
BngsimErrorAn SBML model contains constructs that cannot be simulated under SSA.
Raised by
bngsim.Simulatorwhenmethod="ssa"is requested on a model whose loader-captured SSA issues include any withseverity="error". The full issue list (errors and warnings) is available asissues.The error message advertises
strict_ssa=Falseas a workaround when at least one of the raised codes is overridable.override_attemptedflips the message to call out that the user did passstrict_ssa=Falsebut the listed codes are non-overridable.- NON_OVERRIDABLE_CODES = frozenset({'fast_reaction', 'non_integer_stoichiometry'})
- exception bngsim.StopConditionMet(message, *, result=None, condition='')[source]
Bases:
BngsimErrorA stop condition was triggered during simulation.
The partial result up to the trigger point is attached as
self.result.- result
Partial simulation result truncated at the stop point.
- Type:
Result
- condition
Description of the condition that triggered.
- Type:
- class bngsim.SsaIssue(severity, code, message, location=None)[source]
Bases:
objectA single SSA-compatibility finding from an SBML model.
- severity
"error"(Simulator init will raise) or"warning"(logged).
- code
Stable machine-readable identifier for the issue category.
- message
Human-readable description, ideally pointing the user at a fix.
- location
Optional locator string (e.g.
"reaction:R1","compartment:cell","species:S").
- severity: Literal['error', 'warning']
- code: str
- message: str
- bngsim.validate_for_ssa(model)[source]
Return SSA-compatibility issues recorded for model.
The list is populated by the SBML loader at load time, with an additional model-agnostic warning when the current species state contains non-integer molecule populations. Fractional populations are rounded by the C++ SSA setup path before simulation.
PyBNF and other consumers can call this before constructing a
Simulatorto pre-flight a model — the same list will be re-checked bySimulator(model, method="ssa").- Parameters:
model (
Model)- Return type:
list[SsaIssue]
- bngsim.reserved_names()[source]
Return dict of reserved constant and function names.
Example
>>> import bngsim >>> names = bngsim.reserved_names() >>> "_pi" in names["constants"] True >>> "time" in names["functions"] True
- bngsim.configure_logging(level=20, *, handler=None, fmt='%(asctime)s [bngsim] %(levelname)s %(message)s')[source]
Configure the
bngsimlogger.By default, bngsim is silent (no handler attached). Call this function to enable log output.
- Parameters:
- Returns:
The configured
bngsimlogger.- Return type:
Examples
>>> import bngsim, logging >>> bngsim.configure_logging(logging.DEBUG) >>> # Now all bngsim operations produce log output
- bngsim.normalize_method(requested)[source]
Normalize a user-requested method token to its canonical form.
- Parameters:
requested (
str) – The method string as provided by the user.- Returns:
(canonical, dispatch)where canonical is the normalized algorithm name (e.g."nf_reject") and dispatch is the internal backend key used for simulator creation (e.g."nfsim").- Return type:
- Raises:
ValueError – If the method token is not recognized at all, or if it maps to an unavailable backend.
- bngsim.capabilities()[source]
Return a structured capability report for this bngsim install.
- Returns:
A dict with three keys:
"version"— the bngsim package version string."features"—dict[str, bool]mapping each feature/backend name to its availability flag in this install."missing"—dict[str, str]mapping each unavailable feature to a human-readable explanation that distinguishes a missing compiled backend (rebuild flag) from a missing optional Python dependency (pip install ...).
"features"always contains the same keys regardless of build:nfsim,rulemonkey,klu,libsbml,antimony,vivarium,sbml_import,sbml_ssa,sbml_psa,antimony_import,codegen,output_sensitivities."missing"is empty when every feature is available.output_sensitivitiesreports whether this install can emit the(n_times, n_outputs, n_param)output-sensitivity tensor viaResult.output_sensitivities()(species/observable/expression derivatives w.r.t. parameters and ICs). Likecodegenit is alwaysTrue— it is the capability handshake fitting frontends (e.g. PyBNF) gate their gradient path on (GH #207).klureports whether the SuiteSparse/KLU sparse linear solver was compiled in. WhenFalsethe ODE backend has only the dense solver, so large/sparse models factorize the full N×N Jacobian at O(N³) — use this to detect a dense-only install before a slow genome-scale run (GH #209).Feature names are stable across releases; new features may be added but existing names will not be renamed or removed.
- Return type:
Examples
>>> import bngsim >>> caps = bngsim.capabilities() >>> set(caps) == {"version", "features", "missing"} True >>> caps["features"]["nfsim"] == bngsim.HAS_NFSIM True >>> caps["features"]["sbml_ssa"] == bngsim.HAS_LIBSBML True >>> caps["features"]["klu"] == bngsim.HAS_KLU True
- bngsim.prepare_codegen(net_path, model=None, emit_jac=True)[source]
Generate C code, compile, and return .so path (with caching).
This is the main entry point for the codegen pipeline. It generates combined RHS + sensitivity RHS when possible. The sensitivity RHS is included for all-Elementary models (analytical df/dp + J*v). For Functional/MM models, only the RHS is generated and CVODES uses internal FD for sensitivity.
GH #162: when
model(the built model for this .net) is supplied,emit_jacis set, and its analytical Jacobian is complete, the compiled analytical Jacobian (bngsim_codegen_jacdense /bngsim_codegen_jac_sparseCSC) is appended so a .net-loaded large sparse model gets a compiled per-step Jacobian rather than the interpreted fallback.GH #163: when
modelis supplied and it has observables/functions and norateOfcsymbol, the compiled output evaluator (bngsim_codegen_outputs, GH #136) is appended so the warm recording loop fills the per-row observable + function buffers with one compiled call instead of the interpreted ExprTk pass. This is INDEPENDENT ofemit_jac— outputs are emitted for everyjacobianstrategy (fd/jaxrecord observables too).The cache key gains a
:codegen_jac,:codegen_outputs, and/or:codegen_output_sens(GH #198) suffix so a .so carrying any of these callbacks never collides with one without it. The suffixes key off cheap O(1) model flags (not the generated source —_codegen_emit_flags), so a cross-process .so cache hit still avoids regenerating the (large) RHS source; a Jacobian derivation that fails the GH #95 budget reportsanalytical_jacobian_complete == Falseand drops the:codegen_jacsuffix — no cache poisoning.- Parameters:
net_path (
str) – Path to the .net file.model (optional) – The built model (
ModelorNetworkModel) for this .net. When an analytical Jacobian is wanted (emit_jac), the caller must have prepared it (prepare_analytical_jacobian). PassNoneto keep RHS(+sens)-only.emit_jac (
bool) – Whether to append the analytical Jacobian (jacobianinauto/analytical). Does not affect the output evaluator, which is emitted whenevermodelqualifies.
- Returns:
Path to the compiled shared library.
- Return type:
- bngsim.parse_net_file(path)[source]
Parse a BNG .net file into a structured dictionary.
- Parameters:
- Returns:
Structured contents of the
.netfile, with keys:parameters : list of (name, value, expression, is_expression) species : list of (name, init_conc, is_fixed) observables : list of (name, entries), entries = [(sp_idx0, factor), ...] functions : list of (name, expression) reactions : list of dict with keys reactants, products (0-based species indices), type ("elementary"/"functional"), rate_law (parameter or function name), stat_factor
- Return type:
- bngsim.build_model_from_parsed(parsed)[source]
Build a NetworkModel from parsed .net data via ModelBuilder.
- bngsim.sbml_to_net(sbml_path, out_path=None, *, validate='L2', strict=True, sedml=None, t_span=(0.0, 100.0), n_points=101, rhs_tol=1e-06)[source]
Convert an SBML model to a BioNetGen
.netnetwork.- Parameters:
out_path (
str|Path|None) – Where to write the.net. If None, the text is returned in the report but no file is written.validate (
str|None) –"L2"(default) runs the lightweight structural check plus a direct ODE-RHS identity self-check — it reloads the.netand confirms it reproduces the source right-hand side (the project’s faithfulness measure) at the initial and a nonlinear-probe state, no integration. This is what closes the GH #223 silent-loss hole: a network whose constructs were emitted as constants the.netcannot drive (assignment/rate-rule forcing the writer can’t carry) diverges here and is flagged (strict=Trueraises;strict=Falsewarns and recordsConversionReport.rhs_faithfulFalse)."L1"runs only the structural-equivalence check (counts + topology)."full"runs the complete L0–L4 conversion-validation ladder (GH #217) and gates on the hard levels L0–L3 — “convert and prove faithful”; the L4 symbolic check is recorded but never blocks.Noneskips validation. A"full"gate’s verdict is attached asConversionReport.validationand decidesConversionReport.ok.strict (
bool) – Raisebngsim.ConversionErroron constructs the.nettext format cannot carry faithfully.Falsedowngrades to abngsim.ConversionWarningand emits a best-effort network.sedml (
str|Path|None) – The SED-ML simulation protocol that accompanies the SBML (the COMBINE sidecar). When given, its time course is parsed and the"full"gate’s L3 numerical comparison runs over the model’s own horizon — the exact mirror ofnet_to_sbml()’sbngl=(avoids the blanket-grid stiff-hang and exercises the trajectory the modeller actually ran). The parsedProtocolSpecis attached to the report.t_span (
tuple[float,float]) – Fallback time grid for the"full"gate’s L3 comparison when nosedmlprotocol horizon is available (ignored for othervalidatevalues).n_points (
int) – Fallback time grid for the"full"gate’s L3 comparison when nosedmlprotocol horizon is available (ignored for othervalidatevalues).rhs_tol (
float) – Scale-relative tolerance for the"full"gate’s L2 ODE-RHS identity.
- Return type:
ConversionReport