Format conversion & interchange (SBML · SED-ML · OMEX)

BNGsim converts BioNetGen networks to and from the community-standard interchange formats — SBML (the BioModels model carrier), SED-ML (the simulation protocol), and OMEX/COMBINE (the archive bundling them). Two things make this worth reaching for:

  1. Publish a BioNetGen model to the standards world. Take a .net (with its .bngl actions) and emit SBML, a SED-ML protocol, and a COMBINE/OMEX archive ready for BioModels deposit or a journal supplement. This goes beyond BioNetGen’s own SBML export: BNGsim translates the model’s actions (simulate / parameter_scan, with their overrides) into SED-ML and packages the result as OMEX — neither of which writeSBML() does.

  2. Prove the conversion is faithful — a guarantee BioNetGen does not provide. Every conversion can be gated on a validation ladder and fails loud rather than silently emitting a model whose dynamics drifted. The reverse direction (SBML → .net) is itself a key part of this: it lets the forward .net → SBML path be checked by round-trip identity. The correctness check is the feature.

Five console scripts ship with the package, each backed by a Python API under bngsim.convert.

You do not need to convert to run a stochastic method on an SBML model. BNGsim’s PSA (and SSA) run directly on Model.from_sbml(...) — see PSA on SBML / Antimony models. Convert when you want a .net/.bngl artifact or a standards deliverable, not to unlock a method.

Network conversion (.net ⇄ SBML)

import bngsim

# .net → SBML (the more faithful direction: SBML carries amount/concentration
# semantics and non-unit compartment volumes the plain .net text cannot)
report = bngsim.convert.net_to_sbml("model.net", "model.xml")
print(report.ok)         # True when every check passed
print(report.summary())  # counts + per-level verdicts

# SBML → .net (reverse); also exposed at top level as bngsim.sbml_to_net.
# Defaults to validate="L2": reload the emitted .net and confirm it reproduces the
# source ODE right-hand side, so assignment-rule / time-dependent forcing the flat
# .net cannot carry is caught rather than silently frozen to a constant.
bngsim.convert.sbml_to_net("model.xml", "model.net")
bngsim-net2sbml model.net -o model.xml      # .net → SBML
bngsim-sbml2net model.xml -o model.net      # SBML → .net

Compartmental BNGL (SBML → cBNGL)

Plain .net is flat and unit-volume, so it refuses models with non-unit or cross-compartment volumes. Targeting cBNGL (.bngl with a begin compartments block) recovers them: static compartment volumes, cross-compartment transport (via a per-species signed-flux split), and fixed-time SBML events translated into a BNGL actions block.

# SBML → cBNGL (.bngl with a begin compartments block)
report = bngsim.convert.sbml_to_bngl("model.xml", "model.bngl")

# Prove faithfulness with the BNG2.pl round-trip oracle (validate="bng2"): flatten
# the emitted .bngl via `BNG2.pl generate_network`, reload, and compare the ODE RHS
# to the source. Needs BNG2.pl on $BNGPATH or PATH.
report = bngsim.convert.sbml_to_bngl("model.xml", "model.bngl", validate="bng2")
print(report.rhs_faithful)   # True when the round-trip reproduces the source RHS
bngsim-sbml2bngl model.xml -o model.bngl            # SBML → cBNGL
bngsim-sbml2bngl model.xml -o model.bngl --gate     # + BNG2.pl round-trip proof

cBNGL faithfulness is validated by the BNG2.pl round-trip rather than an in-tree reader — a deliberate choice that keeps the check an independent oracle.

Capability boundary (fail-loud). Constructs a target cannot carry faithfully are surfaced, not hidden: tfun table functions and time-varying compartment volumes are refused under the default strict=True (.net→SBML); cBNGL additionally refuses state-triggered events, live volumes, and the BNG-dialect gaps it cannot express (!=, non-finite parameters); SBML events are dropped with a note (SBML→.net). Pass --allow-lossy (CLI) / strict=False (API) to downgrade a refusal to a ConversionWarning and emit a best-effort artifact — the validation ladder below still catches any numerical drift that results.

Validate the conversion (the L0–L4 ladder)

net_to_sbml / sbml_to_net take validate={"L1", "L2", "full", None}. net_to_sbml defaults to "L1" (a fast structural + ODE-RHS round-trip); sbml_to_net defaults to "L2" (the round-trip-identity RHS check). "full" runs the complete ladder and gates on the hard levels:

Level

Check

Gates?

L0

syntactic validity — the target passes its own validator

yes

L1

structural equivalence — species/reaction counts + topology

yes

L2

round-trip identity — X→Y→X reproduces the ODE RHS

yes

L3

numerical equivalence — trajectories agree (scale-aware)

yes

L4

symbolic RHS equivalence (sympy)

no — best-effort

report = bngsim.convert.net_to_sbml("model.net", "model.xml", validate="full")
report.ok                      # False if any hard gate (L0–L3) failed
report.validation.level("L3")  # the per-level result + metrics
bngsim-net2sbml model.net -o model.xml --gate full   # exits non-zero on failure
bngsim-validate-conversion model.net                 # grade without keeping output

The standalone bngsim-validate-conversion grades either direction (inferred from the source suffix) and prints a per-level report. L4 is non-gating: it reports equal / inconclusive / not-equal, forgives floating-point round-off (and symbolic cancellation) the engine cannot reduce, and never blocks a conversion the numeric gates accept.

Carry the simulation protocol — the OMEX deliverable

SBML carries structure + math only; the simulation protocol (which runs, over what horizon, with what method) lived in the source .bngl and was discarded when the .net was generated. Give the converter that .bngl and it recovers the whole actions block — every simulate / parameter_scan with its parameter and concentration overrides — into SED-ML, then bundles it with the SBML into a COMBINE archive. The .bngl also drives the L3 gate over the model’s own horizon instead of a blanket one.

Starting from a .bngl model (the full export pipeline). The converter’s model input is the flattened .net (bngsim loads .net/SBML/Antimony, not rule-based .bngl), so a rule-based model is first expanded to a network with BioNetGen, then packaged — passing the same .bngl so its actions become the SED-ML protocol and its source rides along:

# 1. Flatten the rule-based model to a network with BioNetGen (the .bngl needs a
#    generate_network() action — rule-based models you simulate already have one).
BNG2.pl model.bngl     # → model.net

# 2. Package a BioModels-ready, verified-faithful OMEX
bngsim-omex pack model.net --bngl model.bngl --gate full   # → model.omex
# Equivalent API call. A verified-faithful, runnable deliverable: SBML + the real
# SED-ML protocol + manifest.xml, gated L0–L4 — plus the original .net and .bngl
# bundled for provenance (see below).
report = bngsim.convert.net_to_omex(
    "model.net", "model.omex", bngl="model.bngl", gate="full",
)
bngsim-omex unpack model.omex      # inspect the packaged archive's contents

Provenance: the archive carries your rule-based source, not just the SBML. By default (include_source=True / drop --no-source) the archive also bundles the original .net (a secondary model entry) and the rule-based .bngl (a source entry) alongside the SBML. The SBML stays the master curated entry, so this is non-breaking for SBML-only consumers — but a published deposit then carries the modeller’s actual formulation, not only its flattened SBML projection. BioModels accepts COMBINE archives with such supporting files (“model files along with the supporting documents … can also be submitted in COMBINE archive format”); SBML receives full curation while the bundled BNGL/.net ride along as the authoritative source. Pass include_source=False / --no-source for a lean SBML + SED-ML archive.

Provenance: the faithfulness verdict travels with the archive. Also by default (provenance=True / drop --no-provenance) the archive records how it was made: a COMBINE-standard metadata.rdf (dcterms creator = bngsim <version>, the creation date, a description — the channel BioModels/COMBINE tools read) and a bngsim-conversion.json carrying the full faithfulness verdict — gate level and per-level L0–L4 result, ok / rhs_faithful / max_rhs_delta, and any dropped/lossy notes. So the verified-faithful claim is auditable by anyone who opens the archive, and a future reader can tell which bngsim version produced it. The created timestamp is injectable (created="…") for byte-reproducible archives.

If you omit the .bngl (or it carries no simulate action), BNGsim still bundles a runnable default protocol (a t=0..100 uniform time course) — but it emits a ConversionWarning and marks the SED-ML as a synthesized default, so a consumer can never mistake the placeholder for the modeller’s actual protocol. A SED-ML sidecar can also be emitted next to a plain SBML conversion with bngsim-net2sbml --sidecar.

Round-trip back from a published archive (OMEX → .net)

The reverse of packing: take a published COMBINE archive — model + SED-ML protocol(s), possibly across several files — and recover an editable BioNetGen workflow. omex_to_net writes the .net and composes every experiment from every SED-ML entry into a single .bngl actions block, so the simulation protocol comes back as runnable BNGL rather than being lost on import.

# .omex → .net (+ a <stem>.bngl actions block carrying the composed protocol)
report = bngsim.convert.omex_to_net("model.omex", "model.net")  # gate="full" default
bngsim-omex to-net model.omex -o model.net   # → model.net + model.bngl