How Biosimulant Works
Biosimulant is an open-source Python engine for composing biological simulators out of small modules with explicit typed ports. The current kernel is a communication-step orchestrator: every module advances across the same window, and outputs are committed atomically at the boundary.
If you want a runnable introduction, start with the Library Quickstart (Python). This page explains the architecture behind that example.
The five core primitives
| Primitive | Role |
|---|---|
BioModule | A runnable simulation component |
BioWorld | The communication-step orchestrator |
SignalSpec | A declared port contract |
Typed BioSignal subclasses | Runtime payloads crossing module boundaries |
WiringBuilder | A validated graph builder for named module ports |
BioWorld: communication windows
Create a world with a required communication step:
world = biosim.BioWorld(communication_step=0.1)For each window [t, t + communication_step]:
- the world reads committed signals at
t - it delivers those inputs to every connected target module
- every module advances across the same window via
advance_window(start, end) - the world commits all outputs atomically at
t + communication_step
This means:
- signal exchange is synchronized at shared communication boundaries
- state signals use hold-last-value semantics until replaced
- event signals are delivered once per connection per source timestamp
- stale reads are checked from the consuming port’s
SignalSpec
The kernel does not expose an execution-order scheduling contract, a global external tick parameter, rollback, or algebraic-loop solving.
Final outputs are still committed at the final boundary. Downstream modules see
those outputs on a later communication turn. For workflow-style labs with report,
export, or visualization modules downstream of a final producer, runners can use
settle_steps / world.settle() to perform zero-time propagation turns after
the simulation duration without changing simulated time.
BioModule: the runnable unit
Modules no longer participate in a per-module due-time scheduler. The world-facing hook is:
def advance_window(self, start: float, end: float) -> None:
...The full module surface includes:
setup(config=None)set_inputs(signals)advance_window(start, end)get_outputs()inputs()/outputs()returningport -> SignalSpecsnapshot()/restore(snapshot)- optional
visualize()
reset() still exists as a convenience method on the base class, but snapshot/restore is the kernel durability mechanism for branching and replay.
Signals: declared contracts plus typed payloads
Port declarations use SignalSpec:
def outputs(self):
return {
"membrane_potential": biosim.SignalSpec.scalar(
dtype="float64",
emitted_unit="mV",
)
}Runtime payloads are emitted as one of:
ScalarSignalArraySignalRecordSignalEventSignal
All runtime signals carry:
sourcenamevalueemitted_at- optional bound
SignalSpec
BioSignal itself is an abstract base class. Do not instantiate it directly.
Signal freshness and event semantics
The world preserves emitted_at when routing signals. Consumers can declare:
max_age: how stale a state signal may be before it is considered too oldstale_policy:warn,error, orignore
Event signals are handled differently from state signals:
- state signals remain available until replaced by a later non-empty output mapping
- event signals remain in the store but are delivered once per connection per source timestamp
SignalSpec.interpolation is currently a declared policy, not a runtime interpolation engine. The world reads committed boundary values.
Wiring and composition
Graphs are built explicitly:
builder = biosim.WiringBuilder(world)
builder.add("eye", Eye()).add("lgn", LGN())
builder.connect("eye.visual_stream", ["lgn.retina"])
builder.apply()Connection validation happens against declared SignalSpec contracts. That keeps the orchestration layer generic: a module can be an ODE solver, an ONNX wrapper, or a bridge to an external simulator, and the world still treats it the same way.
Snapshots and branching
BioWorld.snapshot() captures:
- current simulation time
- committed signal store
- connection delivery state
- per-module snapshots
- setup config
restore() rehydrates that world state, and branch() deep-copies the world so two futures can diverge from the same communication boundary.
Manifests and packaged composition
The current manifest surface matches the current kernel:
- models declare
biosim.entrypointand requiredbiosim.communication_step - labs declare required
runtime.communication_step - labs may declare optional
runtime.settle_stepsfor final graph propagation - labs should not declare legacy per-entry timing, ordering, or external tick fields
- port metadata should prefer structured typed
ioentries over bare strings
SimUI and platform layers
SimUI and the hosted platform sit above the same kernel. They consume:
- world lifecycle events such as
STARTED,STEP, andFINISHED - transport-safe visualization specs from
visualize() - snapshots and status payloads for streaming or replay
Those layers do not change the kernel semantics. They expose them through browser and API surfaces.