Skip to content

Latest commit

 

History

History
333 lines (274 loc) · 16.3 KB

installation.md

File metadata and controls

333 lines (274 loc) · 16.3 KB

Installation

From wheel (recommended)

pip install bngsim

Prebuilt 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.

From source

# 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.

Sparse solver (SuiteSparse / KLU) — required for large / genome-scale models

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-sparse

Linux (system package) — found from /usr:

sudo apt-get install libsuitesparse-dev     # Debian/Ubuntu
sudo dnf install suitesparse-devel           # RHEL/Rocky/Fedora

HPC / 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 bngsim

If 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: True

If 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.

Editable rebuilds for local development

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.py

This 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 -q

Optional dependencies

pip 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 note

  • 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 / $BNGPATH pointing at a BioNetGen you already have works just as well and takes precedence.
  • BNG2.pl is Perl, so perl must be on PATH too. 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. bionetgen is a 12.8 MB wheel that requires libroadrunner, seaborn and networkx, so promoting it would put RoadRunner into every install — contradicting the roadrunner extra's developer-only policy (see pyproject.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 parity dependency 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 why dev omits bngl — a developer environment gets BNG2.pl from --group parity or $BNGPATH.

Antimony version note

  • Antimony-backed model loading requires bngsim[antimony], which pins antimony>=3.1.3. The 3.1.2 release carrying the upstream memory fix for repeated SBML conversion workloads (loadSBMLFile + getSBMLString loops) was never published to PyPI; 3.1.3 is 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_0 arm64 (BNGsim targets 11.0), macosx_15_0 x86_64 (BNGsim targets 10.15), manylinux_2_28_x86_64 and win_amd64. Making it a base dependency would turn pip install bngsim into a hard "no matching distribution" failure on pre-14 Apple Silicon, pre-15 Intel macOS, linux aarch64 and musllinux.

Capability introspection

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 below

HAS_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 in third_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 to pip install it.
  • 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-perl install 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.