pip install bngsimPrebuilt wheels are available for:
| Platform | Architectures | Python |
|---|---|---|
| Linux | x86_64 | 3.10–3.13 |
| macOS | arm64, x86_64 | 3.10–3.13 |
| Windows | x86_64 | 3.10–3.13 |
See SUPPORT_MATRIX.md for the full platform × Python ×
backend matrix, what each optional extra activates, and the release checklist.
# Requires: cmake ≥ 3.20, C++17 compiler, Python ≥ 3.10
cd bngsim
pip install .pip install . uses build isolation, so pip provisions the build backend
(scikit-build-core, pybind11) automatically from [build-system].requires.
Pass --no-build-isolation only if you have already installed the build
backend into the active environment yourself:
pip install "scikit-build-core>=0.10" "pybind11>=2.13"
pip install --no-build-isolation .Otherwise --no-build-isolation fails with
ModuleNotFoundError: No module named 'scikit_build_core'.
Resource-constrained builds (containers, CI). The C++ build defaults to compiling with all available cores. On a machine with many CPUs but limited RAM (e.g. a 12-core container capped at ~6 GiB), parallel compilation of the vendored SUNDIALS/NFsim translation units can exhaust memory and get OOM-killed. Cap the parallelism with the standard CMake env var:
CMAKE_BUILD_PARALLEL_LEVEL=2 pip install .Managed builds fetch the pinned SUNDIALS release archive recorded in
third_party/sundials/VENDOR.json and apply the one local carry it lists
(#1301). ExprTk is vendored directly.
Set -DBNGSIM_USE_SYSTEM_SUNDIALS=ON to use an environment-managed SUNDIALS
install instead. See scripts/SUNDIALS_VENDORING.md for the supported
refresh/check workflow.
The vendored release is the one CI tests. Against a system SUNDIALS of another
major.minor release, the dense difference quotient bngsim forms for jacobian="fd" (or
for a model whose closed-form Jacobian is declined) still reproduces the vendored
CVODE's, not that release's. Results stay valid, but can differ from that CVODE's
own in the last digits, and the configure step warns. A patch release of the
vendored one does not warn: 7.2.0 to 7.2.1 left CVODE's quotient as it was.
A system SUNDIALS also lacks the #1301 carry to CVODES's staggered sensitivity
corrector: configure warns, and a forward-sensitivity run whose RHS refuses a
state can come back wrong. Use the fetched SUNDIALS for sensitivity work.
For models with more than ~50 species, the ODE backend routes the CVODE Newton solve to a sparse direct linear solver (SuiteSparse/KLU). Without KLU the build falls back to the dense solver, which factorizes the full N×N Jacobian at O(N³). For a genome-scale model this is the difference between minutes and hours — e.g. a 74,795-species network has a Jacobian that is 99.997% zeros (dense storage ≈ 45 GB), so the dense path is catastrophic.
KLU is discovered automatically at build time from any standard install
location, conda prefix, HPC module, or CMAKE_PREFIX_PATH (GH #209). If none is
found, the build compiles a pinned KLU subset from source and bundles it
(macOS/Linux; needs bash and git on PATH and network access to GitHub).
Every pip build sets BNGSIM_REQUIRE_KLU=ON, so if that also fails, or on
Windows (no from-source fallback), the build stops with an error. Only a build
that turns that off (a direct CMake build, or pip with
--config-settings=cmake.define.BNGSIM_REQUIRE_KLU=OFF) can fall back to
dense-only, so verify (below) after installing that way on a new machine.
macOS — found from Homebrew:
brew install suite-sparseLinux (system package) — found from /usr:
sudo apt-get install libsuitesparse-dev # Debian/Ubuntu
sudo dnf install suitesparse-devel # RHEL/Rocky/FedoraHPC / conda (no root, modules-based clusters). Provide SuiteSparse, then
build bngsim from source — discovery honors $CONDA_PREFIX and
CMAKE_PREFIX_PATH with no explicit paths:
conda install -c conda-forge suitesparse # discovered via $CONDA_PREFIX
# …or on a module cluster: module load suitesparse
# Build from source. $CONDA_PREFIX is searched automatically; -DBNGSIM_REQUIRE_KLU=ON
# fails the build (instead of going dense-only) if SuiteSparse is somehow not found.
CMAKE_ARGS="-DBNGSIM_REQUIRE_KLU=ON" \
pip install --no-binary=bngsim --force-reinstall bngsimIf SuiteSparse lives on a non-standard prefix that is neither $CONDA_PREFIX nor
on CMAKE_PREFIX_PATH, point CMake at it with -DKLU_ROOT=<prefix> (or
-DSUITESPARSE_ROOT, or an explicit -DKLU_INCLUDE_DIR/-DKLU_LIBRARY_DIR).
Watch the configure log for BNGsim: sparse Jacobian support ENABLED (KLU) (vs
DISABLED (KLU not found)).
Verify KLU is active in any install — no model needed:
python -c "import bngsim; print('KLU available:', bngsim.capabilities()['features']['klu'])"
# -> KLU available: TrueIf it prints False, the install is dense-only — large models will be orders of
magnitude slower, and bngsim.capabilities()['missing']['klu'] carries the
rebuild recipe. A large model run on a dense-only install also emits a one-time
bngsim.DenseSolverFallbackWarning at run(). See
GH #209 for details.
pip install -e for bngsim is only partially in-place:
- Python modules are imported from
python/bngsim/via_editable_skbc_bngsim.pth. - The compiled runtime extension is imported from your environment's
site-packages/bngsim/_bngsim_core...so.
That means rebuilding build/{wheel_tag}/_bngsim_core...so is not enough on its
own. Python will keep importing the copy in site-packages/ until you reinstall or run
cmake --install against the live environment. The current editable hook is the default
scikit-build-core redirect mode with editable.rebuild = false, so imports do not
automatically refresh the copied extension.
Use the helper below from the checkout's root directory
to rebuild and reinstall the extension for the current interpreter. On macOS it also
reconfigures stale editable caches to the current interpreter architecture and rebuilds only
the _bngsim_core extension target:
uv run --no-sync python scripts/rebuild_editable.pyThis drives cmake against the environment you are in, so find_package(pybind11)
has to resolve from there. pybind11 is a [build-system] requirement — uv
installs it into a transient isolated build env, never into .venv — so the
dev extra declares it (uv sync --extra dev). Without it the script falls back
to a system-wide pybind11 if there is one, says so on the pybind11: line it
prints, and names the fix if cmake then comes up empty (GH #229).
Then run a quick regression check against the installed runtime:
uv run python -m pytest python/tests/test_method_normalization.py -qpip install bngsim # core package
pip install bngsim[antimony] # Antimony .ant loading
pip install bngsim[bngl] # BNGL .bngl loading (PyBioNetGen, for BNG2.pl)
pip install bngsim[pandas] # pandas DataFrame support
pip install bngsim[hdf5] # HDF5 save/load (h5py)
pip install bngsim[dev] # all of the above except bngl, + pytest + ruff- BNGL loading (
Model.from_bngl,Model.load("x.bngl")) shells out to BNG2.pl for network generation, because BNGsim has no BNGL parser — see Loading models.bngsim[bngl]supplies it via PyBioNetGen, but$BNG2_PL/$BNGPATHpointing at a BioNetGen you already have works just as well and takes precedence. - BNG2.pl is Perl, so
perlmust be onPATHtoo. macOS and most Linux distributions ship one; stock Windows does not, and BNGL loading reports itself unavailable there rather than failing at load time. - The extra is deliberately not a base dependency and is not folded into
dev.bionetgenis a 12.8 MB wheel that requireslibroadrunner,seabornandnetworkx, so promoting it would put RoadRunner into every install — contradicting theroadrunnerextra's developer-only policy (seepyproject.toml) — and add a Perl runtime requirement to a package that runs fine without one. It also inverts the layering: PyBioNetGen consumes BNGsim through its in-Python bridge. - It is not the
paritydependency group either. That group pins an exact PyBioNetGen commit for engine-routing provenance; loading a BNGL file needs BNG2.pl, not that commit. Keeping them separate is whydevomitsbngl— a developer environment gets BNG2.pl from--group parityor$BNGPATH.
- Antimony-backed model loading requires
bngsim[antimony], which pinsantimony>=3.1.3. The3.1.2release carrying the upstream memory fix for repeated SBML conversion workloads (loadSBMLFile+getSBMLStringloops) was never published to PyPI;3.1.3is the first published version that includes it, so no CI-artifact wheel is needed any more. - Antimony is deliberately an extra, not a base dependency. It publishes no
sdist, and its wheels cover fewer platforms than BNGsim's own —
macosx_14_0arm64 (BNGsim targets 11.0),macosx_15_0x86_64 (BNGsim targets 10.15),manylinux_2_28_x86_64andwin_amd64. Making it a base dependency would turnpip install bngsiminto a hard "no matching distribution" failure on pre-14 Apple Silicon, pre-15 Intel macOS, linux aarch64 and musllinux.
Downstream tools (PyBNF, PyBioNetGen, etc.) can probe what an installed
bngsim actually supports without a try/except dance. Two surfaces:
Module-level boolean flags — match the existing pattern; safe to read unconditionally.
import bngsim
bngsim.HAS_NFSIM # compiled C++ NFsim backend
bngsim.HAS_RULEMONKEY # compiled C++ RuleMonkey backend
bngsim.HAS_KLU # compiled SuiteSparse/KLU sparse linear solver
bngsim.HAS_LAPACK_DENSE # compiled BLAS/LAPACK dense factor (opt-in, speed only)
bngsim.HAS_LIBSBML # optional Python dependency 'python-libsbml'
bngsim.HAS_ANTIMONY # optional Python dependency 'antimony'
bngsim.HAS_BNGL # BNG2.pl *and* perl are reachable — see belowHAS_BNGL is the one flag that is not an import check. BNGL loading needs an
external Perl toolchain, which can arrive from the bngl extra, from
$BNGPATH, or from a BNG2.pl on PATH — and a machine with bionetgen
importable but no perl cannot load BNGL at all. So it is probed when you read
it, not fixed at import (which also keeps import bngsim from ever pulling in
bionetgen), and it tracks an environment variable you export at runtime.
HAS_LAPACK_DENSE is the one flag that is True on the macOS wheels
(Accelerate) and False on the manylinux and Windows ones. When it is False,
every result is the same and some are slower. A large rule-derived network whose
LU factor fills in (see Linear solver)
stays on KLU instead of the BLAS dense factor, typically 4-10x slower on
networks of a few hundred to a few thousand species. Other dense factorizations
use the built-in LU on every install; the BLAS path for them is opt-in via
BNGSIM_LAPACK_DENSE=1 and gives the same trajectory. So the flag answers
both whether that variable does anything and whether the fill routing is
available. A source build picks the backend up from a system
LAPACK (apt-get install liblapack-dev, dnf install lapack-devel, or a
conda-forge openblas) with no other configuration.
Aggregator — bngsim.capabilities() returns a structured dict whose
schema is stable across releases. Feature names will not be renamed or
removed; new features may be added.
import bngsim
caps = bngsim.capabilities()
caps["version"] # bngsim package version, e.g. '0.4.1'
caps["features"]["sbml_ssa"] # bool — can this install run SBML SSA?
caps["features"]["antimony_import"] # bool — needs both libsbml AND antimony
caps["missing"] # dict[str, str] — one entry per
# unavailable feature, saying why
caps["build"] # {"commit": ..., "stale": ...}caps["missing"][name] distinguishes the two failure modes a downstream
tool needs to report differently:
- Compiled backend gap — message starts with " backend not
present in this install" and names both the vendored source path and
the CMake flag (e.g.
-DBNGSIM_BUILD_RULEMONKEY=OFF). NFsim and RuleMonkey are vendored inthird_party/and built by default in source builds, so this typically means the install came from a wheel that excludes the backend (or the user explicitly built with the flag set OFF). Caller should tell users to reinstall a wheel/build that includes the backend. - Optional Python dependency — message starts with "optional
dependency" and names the PyPI package (
'python-libsbml','antimony'). Caller should tell users topip installit. - External toolchain —
missing["bngl"]starts with "BNGL loading unavailable" and then names every location that was searched for BNG2.pl, plus the fixes. Print it verbatim: on a machine carrying several BioNetGen installs the useful sentence is which ones were consulted, and a found-BNG2.pl-but-no-perlinstall needs a different fix from a missing one.
Stable feature keys: nfsim, rulemonkey, klu, lapack_dense, mir,
libsbml, antimony, vivarium, bngl, sbml_import, sbml_ssa,
sbml_psa, antimony_import, codegen, output_sensitivities,
effective_ic_sensitivity, event_sensitivities,
cross_compartment_sensitivities, per_species_atol, tracking_atol.
Behaviour keys — the last four answer a different question from the rest:
not "was this backend compiled in?" but "does this build compute the thing
correctly?". Each one stands for a fix whose absence is a wrong number rather
than a refusal, so nothing else can be gated on: a version string identifies a
release cycle rather than a build (bngsim bumps __version__ at the start of
one), and a hasattr probe cannot see a change in what a build computes. They
are event_sensitivities (forward sensitivities survive a discrete event),
cross_compartment_sensitivities (a reaction crossing compartments of
different size keeps the analytic ∂f/∂p rather than putting the whole model on
difference quotients), per_species_atol (run(atol=...) takes a vector) and
tracking_atol (run(atol=TrackingAtol(...)) is honoured). All four are
published on every build, so a False is an answer you can act on — an absent
key would mean only "too old to have been asked".
What no key here can answer — whether a particular model gets the analytic
∂f/∂p. cross_compartment_sensitivities is the near miss: it says whether this
build still declines over a cross-compartment reaction, one specific past cause,
while a model on a build where that key is True can still decline for an
underivable construct, a comparison the switch scope cannot solve, or a build-time
derivation budget that expired. That last one means the answer is not even a pure
function of (build, model source). So it is read per run, off the Simulator:
sim.has_analytic_sens_rhs and sim.sens_rhs_decline_reason (issue #438). See
the PyBNF guide.
Which build is this? — caps["build"]["commit"] is the commit the compiled
extension was built from (None if it was built outside a git checkout), and it
is the only thing in the public API that separates two installs reporting the
same version. caps["build"]["stale"] is True when the C++ source beside
that extension is newer than it, unless the rebuild script recorded those exact
source bytes as the ones it was built from (issue #528). That can happen only in
a source checkout, where the extension is built separately and does not rebuild
on import. Every other check here passes on such an install, because nothing in
the Python layer moved.
bngsim also warns about it at import — read the key when your own logging is
ready, and print the warning where someone will see it.