Skip to main content

Rover

This repository is still under development.

Statically partitioned hybrid simulator that couples:

  • BNGsim (ODE / analytical Jacobian) on a deterministic SBML partition
  • StochMod (constrained tau-leap) on a stochastic SBML partition

over a shared nanomolar array. Orchestration is a plain Python loop (SingleCell-shaped): both modules advance from the same pre-step state, then exchange results with write ownership on overlap species.

Where one step happens

HybridSimulator.run
  └─ rover.engine.run_steps
       ├─ s0 = counts          # shared nM
       ├─ stoch.advance_from(s0, dt)   # StochModModule (nM ↔ molecules; TFs as nM)
       ├─ bng.advance_from(s0, dt)     # BngsimModule (identity nM; mRNA frozen)
       └─ exchange(s0, s_stoch, s_bng) # owner-takes-all on overlap

Edit coupling in [rover/engine.py](rover/engine.py) (run_steps / exchange_counts) or advance_from under [rover/modules/](rover/modules/).

Partitioning is file membership plus stoichiometric write ownership: overlap mRNA/genes are stoch-owned; overlap TFs (modifiers only in StochMod) are det-owned. Exchange takes the owner’s post-step value (not Δ_stoch + Δ_bng). Det-owned TFs are passed into StochMod as nM (SingleCell-like), not molecule counts.

Quickstart

StochMod and BNGsim install from PyPI with Rover:

pip install -e ".[dev]"

python basic_hybrid.py
pytest

StochMod compiles per-model C propensity code on first load of each SBML, so a C compiler (gcc, clang, or MSVC cl) must be available. Wheels from PyPI already include the Python extension itself.

For local StochMod development, install an editable clone first (pip install -e /path/to/StochMod) so it shadows the PyPI package, then install Rover as above.

from rover import HybridSimulator

sim = HybridSimulator(
    "tests/data/LR/deterministic-interactions.xml",
    "tests/data/LR/stochastic-gene-expression.xml",
    dt=1.0,
)

sim.update("cyt_mrna__LIGAND_", 0.001582)
sim.update({"kTL1_1": 2.0, "kTC1_1": 0.01})

traj = sim.run(t_end=60.0)           # shape (61, n_species); live state stays 1-D nM
df = sim.to_dataframe()              # columns: time + species ids (nM)
# Large runs — pre-size a memmap .npy (OS page cache; kernels never open the file):
# traj = sim.run(t_end=259200, dt=30, results_path="out/traj.npy")
sim.reset()

One-shot helper (rebuilds models each call — prefer HybridSimulator for sweeps):

from rover import run_hybrid
traj, index = run_hybrid(det_xml, stoch_xml, t_end=50.0, dt=1.0)

Shared store currency is nanomolar. BNGsim storage is already nM (identity bridge). StochMod converts to molecule counts at the module boundary using SBML compartment volumes (nM · V · N_A · 1e-9), matching SingleCell.

Trajectory storage

Piece Where Why
Live _counts 1-D float64 nM in RAM Hot path for coupling
Trajectory (n_points, n_species) memory or memmap .npy History only; one row write per step

Do not have BNGsim/StochMod dump to disk each step — that would dominate cost. The orchestrator copies the live vector into the next trajectory row (sequential write; memmap is fine because the OS caches pages).

Performance notes

Absolute time is orchestrator-owned. Trajectory times are t0 + step*dt. Each coupling step advances a local interval [0, dt] in BNGsim (interactive clock rewound before advance(dt)). StochMod leaps once by dt per call.

Hybrid coupling is not the same cost model as a standalone batch run:

Mode What happens
StochMod.run(0, T, dt) / bngsim.Simulator.run(...) One Python call; entire trajectory stays in C
sim.run(t_end=T) with coupling dt T/dt Python↔C round-trips (set_state / advance / convert)

So t_end=259200, dt=1 means 259 200 coupling steps. Prefer a larger coupling dt when the biology allows it (e.g. dt=30 → ~8640 steps for 3 days).

BNGsim defaults: codegen on, jacobian="analytical", rtol=1e-4, atol=1e-6, max_steps=1e8. If codegen fails, Rover falls back to the interpreted RHS; if analytical Jacobian construction fails, it falls back to finite differences. On some Windows consoles codegen can fail with a charmap encoding error.

Preparing your own SBML pair

Rover does not ingest a single model. You split your network into two SBML files and pass them as:

HybridSimulator(deterministic_sbml, stochastic_sbml, dt=...)

The bundled examples happen to be gene expression plus protein binding (mRNA, genes, TFs). That biology is not required. Any species can live on either side of the split. Rover never classifies a species by what it represents; only by which file lists it and whether it is a reactant/product there.

What each file contains

File Simulator Put here
Deterministic SBML BNGsim (ODE) Reactions you want integrated continuously
Stochastic SBML StochMod (tau-leap) Reactions you want as discrete molecule events

A species may appear in one file or both. IDs are the join key: the same species id in both files is one shared state entry. Exclusive species (present in only one file) are fine.

Typical pattern, independent of molecule type:

  1. Put a reaction in exactly one file — the side that should fire it.
  2. If the other side’s rate laws need that species as an input, also declare it in the other file, usually as a modifier (not a reactant/product).
  3. Keep compartment ids and sizes consistent for shared species.

Do not put the same reaction in both files. Species that are reactants/products in both files are ambiguous (see ownership below); avoid that unless you intend the name-based fallback.

Write ownership (who updates shared species)

After each coupling step, overlap species take the owner’s post-step value. Ownership is inferred from stoichiometry, not from names like mrna / gene / prot:

  • Reactant or product only in the stochastic file → StochMod owns it. BNGsim holds it fixed over the ODE window (so the deterministic file can still read it, e.g. as a modifier).
  • Reactant or product only in the deterministic file → BNGsim owns it. StochMod sees it as nanomolar (not molecule counts) and does not write it back — the usual pattern for a regulator that appears only as a modifier in stochastic rate laws.
  • Modifier-only in a file does not confer ownership.
  • If a species is a reactant/product in both files, Rover falls back to a name heuristic (mrna / gene → stochastic, otherwise deterministic). Do not rely on that for a custom model: keep stoichiometric participation on one side only.

So: a low-copy ion channel, a promoter, or an enzyme can be stochastic; a high-abundance metabolite or a transcription factor can be deterministic. Put the species as reactant/product in the file that should own it, and as a modifier (if needed) in the other.

Units and ICs

Shared state is nanomolar. Use SBML initialConcentration in nM, compartment sizes in litres, and substance units of nanomole (the example files define substance as mole with scale="-9"). StochMod converts stoch-owned species to molecule counts with nM · V · N_A · 1e-9; det-owned overlap is passed through as nM, so stochastic kinetic laws that read those species should be written in nM, not counts.

If both files set an overlap species’ initial condition, BNGsim’s value is used and a mismatch is logged.

Example layout (bundled LR pair): [tests/data/LR/deterministic-interactions.xml](tests/data/LR/deterministic-interactions.xml) and [tests/data/LR/stochastic-gene-expression.xml](tests/data/LR/stochastic-gene-expression.xml).

Metadata

Release files for rover-sim 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for rover-sim 0.1.0
File Size Uploaded
rover_sim-0.1.0.tar.gz 26.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for rover-sim 0.1.0
File Interpreter ABI Platform
rover_sim-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 50.0 kB

Release files / rover_sim-0.1.0.tar.gz

Download URL rover_sim-0.1.0.tar.gz
Size 26.9 kB
Tags Source
SHA-256 checksum
How to use checksums
3accb6677d94c4ace6865d503409e53989783afa685cf29511bc807fb6f215a0
BLAKE2b-256 checksum
How to use checksums
8ebf4e53e433c0c16bf2b52a9a11911aa64ac6cea484d0cf6a34c273d9d48916
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / rover_sim-0.1.0-py3-none-any.whl

Download URL rover_sim-0.1.0-py3-none-any.whl
Size 23.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
89f57cd9abb7a430a7eb4eb2c4097786d5ecfde85cdd80059a8aabefc3aabb65
BLAKE2b-256 checksum
How to use checksums
8c815f63dc4d90bf35cc1ac876abe8c1d31c485423cc5c4bf72d9c6622b9a2fa
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page