Solver configuration¶
Solver configuration¶
sim.set_tolerances(rtol=1e-10, atol=1e-10)
sim.set_max_steps(50000)
# Per-run overrides
result = sim.run(
t_span=(0, 100), n_points=101,
rtol=1e-12, atol=1e-12, max_steps=100000,
)
# Solver diagnostics
print(result.solver_stats)
# {'n_steps': 1247, 'n_rhs_evals': 2891, 'n_jac_evals': 43, ...}
Jacobian strategy¶
The ODE solver (CVODE BDF/Newton) needs the Jacobian matrix ∂f/∂y at each
Newton iteration. BNGsim supports three strategies, selectable via the
jacobian keyword:
# Default: auto-select best available strategy
sim = bngsim.Simulator(model, method="ode") # jacobian="auto"
# Force analytical Jacobian (error if model has Functional/MM rates)
sim = bngsim.Simulator(model, method="ode", jacobian="analytical")
# Force finite-difference Jacobian (baseline, for benchmarking)
sim = bngsim.Simulator(model, method="ode", jacobian="fd")
Strategy |
Description |
Cost per Jacobian |
Availability |
|---|---|---|---|
|
Analytical if available, else finite-difference; falls back to FD if the analytical attempt fails to integrate |
— |
All models |
|
Exact derivatives from mass-action stoichiometry |
O(nnz) ops, zero RHS evals |
All-Elementary models only |
|
Finite-difference approximation (SUNDIALS DQ for dense, colored FD for sparse) |
O(N) or O(n_colors) RHS evals |
All models |
After a run, sim.jacobian_strategy reports the strategy that actually produced
the result ("analytical", "fd", or "jax") — including "fd" when an
"auto" run fell back (see below).
When to use "fd": For A/B benchmarking, or if you suspect the analytical
Jacobian is causing issues. The "fd" option uses SUNDIALS’ internal
difference-quotient (DQ) approximation for dense models, and graph-colored
finite differences for sparse (KLU) models.
Analytical Jacobian: Available automatically when all reactions use Elementary (mass-action) rate laws. For models with Functional or Michaelis-Menten rates, the solver falls back to finite-difference. The analytical Jacobian provides ~10-27% speedup on large models by eliminating O(N) RHS evaluations per Jacobian update.
Auto-fallback on a solver failure (GH #176): jacobian="auto" is a bet —
an analytical Jacobian is a strict speedup where it integrates, but it is not
guaranteed to. A rate law that is discontinuous in a state variable (e.g. an
if() whose condition crosses a threshold the state sits at) has an exact
derivative that omits the jump, which can de-stabilize CVODE’s implicit
corrector even though the Jacobian is mathematically correct. Under "auto",
such a CVODE failure is caught and the integration is transparently retried once
with the finite-difference Jacobian (which straddles the step and regularizes
the corrector), so the default config still integrates the model. An explicit
jacobian="analytical" is not second-guessed — it surfaces the failure.
Build-time derivation budget (GH #95 / #187): for models with Functional or
Michaelis-Menten rate laws, the analytical Jacobian terms are derived once
(symbolically) at model load. That derivation is wall-clock-budgeted so a
pathological small model cannot hang the load — it simply falls back to the FD
Jacobian, which is just as fast to solve at small scale. The budget scales with
species count and becomes unbounded on genome-scale models
(≥ 20,000 species): there an FD Jacobian needs ~n_species RHS evaluations per
Newton step and is not a viable solver path, so the analytical Jacobian is
treated as mandatory and is always derived to completion. Override the budget with
BNGSIM_JAC_DERIV_BUDGET_S (seconds, or inf/none/0 to disable it entirely —
the manual genome-scale escape hatch).
Logging¶
import logging
bngsim.configure_logging(logging.DEBUG)
# Now all bngsim operations produce log output