BioModule API
BioModule is the single runnable unit in the communication-step kernel. Temporal
modules advance state across windows; finite modules implement input-to-output
computation behind the same contract.
Invocation policies require biosimulant>=0.0.26. Model and Lab manifests stay
on the existing schema and do not declare the policy.
Contract
class BioModule:
execution_policy = ExecutionPolicy.EACH_WINDOW
def setup(self, config: dict[str, Any] | None = None) -> None: ...
def reset(self) -> None: ...
def set_inputs(self, signals: dict[str, BioSignal]) -> None: ...
def execute(
self,
inputs: Mapping[str, BioSignal],
*,
context: ExecutionContext,
) -> Mapping[str, Any | BioSignal]: ...
def advance_window(self, start: float, end: float) -> None: ...
def get_outputs(self) -> dict[str, BioSignal]: ...
def inputs(self) -> Mapping[str, SignalSpec]: ...
def outputs(self) -> Mapping[str, SignalSpec]: ...
def snapshot(self) -> dict[str, Any]: ...
def restore(self, snapshot: Mapping[str, Any]) -> None: ...
def visualize(self) -> VisualSpec | list[VisualSpec] | None: ...New modules override the canonical execute() hook and explicitly declare a
policy. Existing temporal modules may continue overriding advance_window() and
normally get_outputs(). A module must not override both computation hooks.
Required semantics
execute(inputs, *, context)is the canonical hook for new finite and temporal modules.advance_window(start, end)is the supported temporal compatibility hook and is not deprecated.ExecutionPolicy.EACH_WINDOWis the compatibility default.- Use
ONCE_BEFORE_RUNfor preprocessing, finite inference, and pure one-shot pipelines; useONCE_AFTER_RUNfor final analysis and export. - Use canonical
EACH_WINDOWwhen AI computation consumes evolving state or a new settle-independent temporal module advances through positive windows. - A canonical module that implicitly inherits
EACH_WINDOWis accepted with a registration warning; first-party and generated modules declare it explicitly. - Canonical
get_outputs()returns only the latest result successfully normalized and committed by BioWorld. Directexecute()calls do not update this cache. - Existing temporal
get_outputs()must return declared typed signals. inputs()andoutputs()must returnport -> SignalSpecmappings when the module participates in validated wiring.- Signals should be emitted as
ScalarSignal,ArraySignal,RecordSignal, orEventSignal. snapshot()/restore()should round-trip the module state needed for deterministic continuation from a communication boundary.
Canonical temporal example
import biosimulant as biosim
class Gain(biosim.BioModule):
execution_policy = biosim.ExecutionPolicy.EACH_WINDOW
def __init__(self, gain: float = 1.0) -> None:
self.gain = float(gain)
def inputs(self):
return {
"x": biosim.SignalSpec.scalar(
dtype="float64",
max_age=0.2,
stale_policy="warn",
)
}
def outputs(self):
return {"y": biosim.SignalSpec.scalar(dtype="float64")}
def execute(self, inputs, *, context):
latest = inputs.get("x")
if latest is None:
return {}
assert context.window_start is not None
assert context.window_end is not None
return {"y": float(latest.value) * self.gain}
def snapshot(self):
return {"gain": self.gain}
def restore(self, snapshot):
self.gain = float(snapshot["gain"])BioWorld stamps the result at context.window_end. Canonical modules never run
during zero-time settle() in 0.0.26; retain the temporal compatibility hook when
settling is scientifically required.
Finite-computation example
class Predictor(biosim.BioModule):
execution_policy = biosim.ExecutionPolicy.ONCE_BEFORE_RUN
def inputs(self):
return {"features": biosim.SignalSpec.array(dtype="float32", shape=(4,))}
def outputs(self):
return {"score": biosim.SignalSpec.scalar(dtype="float64")}
def execute(self, inputs, *, context):
return {"score": float(predict(inputs["features"].value))}BioWorld wraps raw results using the output specs and stamps them at the current BioWorld boundary. Connected non-optional inputs must be available before invocation. Completed once-policy modules are not polled or retimestamped.
Valid phase edges move from before-run to each-window to after-run. Cycles remain valid only inside the each-window phase.
ExecutionContext
ExecutionContext is immutable. run_start and run_end describe the positive
public BioWorld.run() call. window_start and window_end are present only for
EACH_WINDOW; simulated_time resolves to the start, window end, or run end for
before, window, or after invocation respectively. Wall-clock IDs, retries, and
platform scheduling metadata are deliberately excluded.
BioWorld.run(duration <= 0) remains setup-only, so once policies require a
positive-duration run. ONCE_AFTER_RUN runs only after all positive windows commit
successfully.
Convenience subclasses
BioModule remains the escape hatch for full control. Two optional subclasses
remove common adapter boilerplate without changing the base contract.
SignalEmitterBioModule
Use SignalEmitterBioModule when your module owns its lifecycle but wants
shared output storage and typed signal wrapping.
class Reporter(biosim.SignalEmitterBioModule):
def outputs(self):
return {"summary": biosim.SignalSpec.record(schema={"count": "int"})}
def advance_window(self, start, end):
self.publish_outputs(end, {"summary": {"count": 3}})It provides source_name(), emit_signal(), publish_outputs(),
get_outputs(), and clear_outputs().
StatefulBioModule
Use StatefulBioModule for fixed-step stateful models. Subclasses implement
biology hooks and let the base handle window stepping, input overrides, and
bounded history.
class Counter(biosim.StatefulBioModule):
def __init__(self):
super().__init__(integration_step=0.1, record_initial_state=True)
self.count = 0
def outputs(self):
return {"count": biosim.SignalSpec.scalar(dtype="int64")}
def step(self, h):
self.count += 1
def record_state(self, t):
self._history.append({"t": t, "count": self.count})
def output_payload(self, t):
return {"count": self.count}Port declarations
def inputs(self):
return {
"state": biosim.SignalSpec.record(
schema={"v": "float64", "u": "float64"},
max_age=0.001,
stale_policy="error",
)
}
def outputs(self):
return {
"spikes": biosim.SignalSpec.event(
schema={"ids": "list[int]"},
description="Discrete spike deliveries",
)
}Outputs declare the emitted profile. Inputs declare the accepted profile and freshness policy.
Durability
reset() is an optional convenience hook for UI-driven workflows you own. The kernel durability contract is snapshot() / restore(), which is what BioWorld.branch() and snapshot-based reruns rely on.
Notes
- The world binds emitted outputs to the registered module name and the declared output port.
- Empty output mappings do not clear prior committed state outputs from that module.
visualize()is optional and should return JSON-serializable visual specs.