Use with PyBNF¶
When bngsim is installed, PyBNF automatically
uses it for BNGL model fitting instead of spawning run_network subprocesses:
pip install bngsim
cd tests/full_tests
pybnf -c T1-ssprop/polynomial.conf
# T1 benchmark: 3:16 (was 3+ hours without bngsim)
No configuration changes needed — PyBNF auto-detects bngsim at import time.
Scheduler-free cluster evaluation¶
A fitting run distributes thousands of independent evaluations across a cluster.
BNGsim is the stateless single-evaluation kernel for that workload — the
frontend (PyBNF) owns the scheduler: multistart, bootstrap, profile likelihood,
Slurm/MPI fan-out. BNGsim adds no scheduler code; it exposes the raw output +
sensitivity primitives (the (n_times, n_outputs, n_param) tensor), never a
pre-baked loss — objective/noise/normalization composition stays in the frontend.
Statelessness / re-entrancy. Every evaluation runs against an independent
model clone with no shared mutable state (the C++ engine is instance-based with no
globals, no file I/O, no stdout). run_batch clones the model per row, so many
threads — or many processes — evaluate concurrently against the one read-only
compiled .so without interfering. For a fixed (model, θ, sensitivity set, solver options) the result is deterministic, and batch rows are returned in input
order regardless of worker count.
Shared compiled artifact. The codegen cache is content-addressed by model
hash and updated atomically (compile to a process-unique temp file, then
os.replace into the cache), so concurrent jobs racing on the same model never
observe a partial .so. Point BNGSIM_CODEGEN_CACHE_DIR at node-local scratch,
or pre-warm it once on a login node and have worker jobs reuse the artifact:
# 1. Compile once on the login node into a shared/staged cache.
export BNGSIM_CODEGEN_CACHE_DIR=/scratch/$USER/bngsim_cache
python -c "import bngsim; bngsim.Simulator(bngsim.Model.from_net('model.net'), codegen=True)"
# 2. Every worker job inherits the env var and reuses the cached .so (no compile).
sbatch --export=ALL fit_job.slurm
Local batch over a parameter matrix. A sensitivity_params-configured
Simulator yields the full per-row output-sensitivity tensor from run_batch,
reusing the one shared artifact:
sim = bngsim.Simulator(model, method="ode", sensitivity_params=["kf", "kr"])
rows = sim.run_batch(
t_span=(0, 100), n_points=101,
params=[{"kf": kf, "kr": kr} for kf, kr in theta_matrix],
num_processors=8, # independent clones, one shared .so
)
for r in rows: # deterministic input order
g = r.output_sensitivities(["observable:Atot"]) # (n_times, 1, n_param)
Checkpoint / restart. EvaluationSpec is a frozen, JSON-serializable record
of one evaluation — model source (+ optional SHA-256 integrity guard), θ vector,
time grid, sensitivity set, solver options, and output selectors. Ship it to a
worker or write it to a checkpoint; evaluate() reconstructs the simulator and
runs deterministically. Pair it with the compact Result.summary() for cheap
indexing/logging without re-reading every full HDF5 payload:
spec = bngsim.EvaluationSpec(
model_source="model.net", model_format="net",
t_span=(0, 100), n_points=101,
params={"kf": 0.5}, sensitivity_params=("kf",),
outputs=("observable:Atot",),
)
blob = spec.to_json() # checkpoint / send to worker
result = bngsim.EvaluationSpec.from_json(blob).evaluate()
json.dump(result.summary(), open("eval_0001.json", "w")) # compact index entry
result.save("eval_0001.h5") # full arrays (HDF5)
spec.with_params(theta_row) stamps a θ row onto a base spec, so a sweep
serializes one spec plus a matrix rather than thousands of near-duplicates.