Models¶
sbmlsim simulates models written in the Systems Biology Markup Language (SBML). It loads a model into libroadrunner, reads the units of the model and keeps a few settings with it: values to change before every simulation, the variables to record and the settings of the integrator. This page uses the HCTZ model of the Tutorial, a whole body model of the diuretic hydrochlorothiazide.
Loading a model¶
A Simulator loads a model from the path of an SBML file or from the SBML itself as a string:
from sbmlsim.resources import HCTZ_SBML
from sbmlsim.simulator import Simulator
simulator = Simulator()
model = simulator.load(HCTZ_SBML)
print(type(model).__name__)
print(model.r.model.getModelName())
The model is a RoadrunnerSBMLModel, and model.r is its roadrunner.RoadRunner instance, for anything sbmlsim does not cover. You can also create one directly with RoadrunnerSBMLModel(source=HCTZ_SBML) and pass it to the simulator. A hierarchical model (SBML comp) is flattened when it is loaded.
sbmlsim.resources ships four models, which the documentation, the examples and the tests use:
| name | model |
|---|---|
REPRESSILATOR_SBML |
the repressilator of Elowitz and Leibler, a genetic oscillator |
DEMO_SBML |
a small demo model with three compartments and units |
MIDAZOLAM_SBML |
a whole body pharmacokinetics model of midazolam |
HCTZ_SBML |
a whole body pharmacokinetics and pharmacodynamics model of hydrochlorothiazide |
Models in other formats¶
sbmlsim simulates SBML only. A model in another format is converted to SBML first and then loaded like any other model. These converters write SBML:
| format | converter | installation |
|---|---|---|
| CellML | cellml2sbml of sbml2cellml, CellML 2.0 to SBML Level 3 Version 2 |
pip install sbml2cellml |
XPP (.ode) |
xpp2sbml of sbmlutils |
installed with sbmlsim |
| Heta | the Heta compiler, heta build --export=SBML |
npm i -g heta-compiler or a standalone binary |
sbmlsim reads the units of a model from its SBML, so check the units of a converted model: a model whose format has no units, e.g. XPP, is dimensionless.
CellML¶
sbml2cellml converts between SBML and CellML 2.0 in both directions. cellml2sbml converts a CellML model and the models it imports into SBML. Every variable of CellML becomes a parameter with a rule, the units are converted and a reset of CellML becomes an event. sbml2cellml supports python 3.13 and 3.14 on linux and macOS, not on windows:
In python, convert_cellml2sbml(Path("model.cellml"), sbml_path=Path("model.xml")) of sbml2cellml.cellml2sbml does the same and returns the libsbml.SBMLDocument. The documentation of sbml2cellml lists what is not converted yet.
XPP¶
sbmlutils, a dependency of sbmlsim, converts the .ode files of XPP with xpp2sbml: the parameters, initial values, differential equations, auxiliary quantities, functions and global flags, which become events. It does not convert tables, sum, shift, set, boundary conditions, random numbers and arrays. A decay model as an .ode file, converted and simulated:
from pathlib import Path
from sbmlutils.converters.xpp import xpp2sbml
from sbmlsim.simulation import Simulation
from sbmlsim.simulator import Simulator
Path("decay.ode").write_text(
"# first order decay\npar k=0.5\ninit x=10\ndx/dt=-k*x\ndone\n", encoding="utf-8"
)
xpp2sbml(xpp_file=Path("decay.ode"), sbml_file=Path("decay.xml"))
res = Simulator().run("decay.xml", Simulation(end=10, times=[0, 5, 10]))
print(res.q["x"].round(4))
Heta¶
Heta is a domain-specific language for structured, modular and convertible models. The Heta compiler, a program of Node.js, builds a modeling platform, a directory of Heta modules, into SBML and other formats. It installs with npm (Node.js 18 or newer) or as a standalone binary for linux, macOS on Apple silicon and windows, see its installation. In the directory of the platform, with the model in index.heta:
The compiler writes the SBML into the directory dist of the platform.
Looking inside a model¶
parameter_df and species_df list the parameters and species with their values, SBML units and names:
from sbmlsim.model import RoadrunnerSBMLModel
parameters = RoadrunnerSBMLModel.parameter_df(model.r)
selected = parameters[parameters.sid.isin(["BW", "PODOSE_hctz", "Mr_hctz"])]
print(selected[["sid", "value", "unit", "name"]].to_string(index=False))
species = RoadrunnerSBMLModel.species_df(model.r)
selected = species[species.sid.isin(["Cve_hctz", "Aurine_hctz"])]
print(selected[["sid", "amount", "unit", "name"]].to_string(index=False))
sid value unit name
BW 75.0 kg body weight [kg]
Mr_hctz 297.7 g_per_mole Molecular weight hctz [g/mole]
PODOSE_hctz 0.0 mg oral dose hctz [mg]
sid amount unit name
Cve_hctz 0.0 mmole hydrochlorothiazide (venous blood plasma)
Aurine_hctz 0.0 mmole hydrochlorothiazide (urine)
The unit column holds the ids of the unit definitions in the SBML file. model.uinfo has the same units written for pint, which sbmlsim uses for every conversion (see Units). The id of a species is its amount, the id in brackets its concentration:
for sid in ["BW", "Ka_dis_hctz", "Cve_hctz", "[Cve_hctz]", "time"]:
print(f"{sid:12} {model.uinfo[sid]}")
The tables show the current values, which change when you simulate. loaded_value gives the value of a parameter, compartment or species as the model was loaded, whatever ran on it since:
model.sbml_ids() gives the ids of the constant parameters and of the species, e.g. to build a sensitivity design over every parameter (see Sampling).
Changes for every simulation¶
changes set values of the model before every simulation of it. They are applied before the model is initialized, so initial assignments that depend on them follow. Here the patient weighs 100 kg instead of the 75 kg of the model, and both get the same 25 mg tablet:
import matplotlib.pyplot as plt
from sbmlsim import Q
from sbmlsim.plot import label_axes
from sbmlsim.simulation import Change, Simulation
heavy = RoadrunnerSBMLModel(source=HCTZ_SBML, changes={"BW": Q(100, "kg")})
dose = Simulation(
time_unit="hr", end=24, changes=[Change(0, {"PODOSE_hctz": Q(25, "mg")})]
)
fig, ax = plt.subplots(figsize=(6, 3.5))
for m, label in [(model, "75 kg"), (heavy, "100 kg")]:
res = simulator.run(m, dose)
ax.plot(res.q["time"].to("hr"), res.q["[Cve_hctz]"].to("µM"), label=label)
label_axes(ax, "time", "HCTZ plasma")
ax.legend(title="body weight")

The heavier patient has a larger body, so the same dose gives a lower concentration in the plasma.
A change is a quantity, which is converted into the unit of the model, or a plain number, which is taken in the unit of the model (sbmlsim logs a warning for it). A simulation which sets the same target wins over the change of the model, see Simulations.
Choosing what is recorded¶
By default a simulation records every species (amount and concentration), parameter, reaction and compartment of the model. selections records only what you need, which makes large models and scans faster and their results smaller:
small = RoadrunnerSBMLModel(
source=HCTZ_SBML, selections=["time", "[Cve_hctz]", "Aurine_hctz"]
)
print(simulator.run(small, dose))
model.set_selections([...]) changes the selections of a loaded model.
Integrator settings¶
The integrator of roadrunner (CVODE by default) is configured on the simulator, e.g. Simulator(relative_tolerance=1e-8), and the simulator sets its settings on every model it runs. A model takes settings of its own with settings={...} or model.set_integrator_settings(...); a setting which the simulator does not set, e.g. maximum_num_steps, stays with the model, also in the worker processes of a parallel scan. The absolute tolerance is set per state from the kind of the state, model.tolerances() lists it. Simulations explains the settings and the tolerances.
Compilation speed¶
roadrunner compiles every model into machine code when it is loaded. The optimization level jit_optimization, O0 to O3, trades the time of the compilation against the speed of the simulation. O2 is the default and simulates fast. O0 compiles about four times faster and simulates up to a quarter slower, which pays off when a run loads many models and simulates little with each, e.g. when it initializes many experiments, or in tests.
Set the level on a model, on an ExperimentRunner or an OptimizationProblem (for all their models), or with the environment variable SBMLSIM_JIT_OPTIMIZATION for everything which is not given a level:
from sbmlsim.model import JitOptimization
fast_compile = RoadrunnerSBMLModel(
source=HCTZ_SBML, jit_optimization=JitOptimization.O0
)
The worker processes of a scan or a fit compile the model at the level of the main process. O0 gives the same results as roadrunner's default compiler bitwise, the other levels can differ from them in the last bits.
Models in an experiment¶
A simulation experiment describes its models without loading them, as an AbstractModel with the source and the changes. The ExperimentRunner loads them when it runs the experiment, and a simulator loads one with load:
from sbmlsim.model import AbstractModel
description = AbstractModel(source=HCTZ_SBML, changes={"BW": Q(100, "kg")})
loaded = simulator.load(description)
print(type(loaded).__name__, loaded.changes)
The source of a model is a path, absolute or relative to the base_path of the experiment, or the SBML itself. sbmlsim never downloads a model: a source which is neither an existing file nor SBML raises an OSError.
The models of a package¶
A model package which has several variants of its model, a factory which builds them and the analyses which use them describes them once in a project, which the run layer, the release and the credibility assessment read. The licenses of the models travel with them in their COMBINE archive.