ReferencesBiosimulant LibraryBioSignal & Metadata

BioSignal & SignalSpec

The communication-step kernel uses a typed signal family plus declared port contracts.

SignalSpec

Ports are declared as name -> SignalSpec mappings:

import biosimulant as biosim
 
def inputs(self):
    return {
        "current": biosim.SignalSpec.scalar(
            dtype="float64",
            max_age=0.001,
            stale_policy="error",
        ),
        "state_vector": biosim.SignalSpec.array(
            dtype="float32",
            shape=(10,),
        ),
        "state": biosim.SignalSpec.record(
            schema={"v": "float64", "u": "float64"},
        ),
        "spikes": biosim.SignalSpec.event(
            schema={"ids": "list[int]"},
        ),
    }

SignalSpec can declare:

  • signal_type
  • kind
  • dtype
  • shape
  • schema
  • emitted_unit on outputs
  • accepted_profiles on inputs
  • interpolation
  • max_age
  • stale_policy
  • description

Typed runtime signals

Do not instantiate BioSignal directly. Emit one of:

  • ScalarSignal
  • ArraySignal
  • RecordSignal
  • EventSignal
def get_outputs(self):
    return {
        "current": biosim.ScalarSignal(
            source="stimulus",
            name="current",
            value=12.5,
            emitted_at=0.1,
            spec=self.outputs()["current"],
        )
    }

Runtime semantics

  • Signals preserve their source timestamp as emitted_at.
  • State signals are delivered with hold-last-value semantics until replaced by a non-empty output mapping from the same source module.
  • Event signals are delivered once per connection per source timestamp.
  • max_age and stale_policy govern stale-read handling on input ports.

On interpolation

SignalSpec.interpolation is currently a declared port policy, not a runtime interpolation engine. The world reads committed boundary values from the signal store. Use zoh or none as the runtime mental model today, even if a port spec declares linear.

Receiving signals

def set_inputs(self, signals):
    current = signals.get("current")
    if current is not None:
        self.I_ext = float(current.value)

Always treat inputs as optional. A port may have no committed upstream signal at a given boundary.

Helper functions

biosimulant.signals includes small helpers for common adapter code:

  • unwrap_payload(value, max_depth=1): unwraps typed signal objects and one exact {"payload": value} carrier by default.
  • coerce_float(value, keys=("value", "count", "payload")): extracts a float from scalar-like signals and record carriers, returning None on invalid input.
  • scalar_or_record_input(unit, description, dtype="float64"): declares a scalar input that also accepts a record payload carrier.
  • make_signal(spec, source, name, value, emitted_at): constructs ScalarSignal, ArraySignal, RecordSignal, or EventSignal from a SignalSpec.
from biosimulant.signals import coerce_float, scalar_or_record_input
 
def inputs(self):
    return {"growth_rate": scalar_or_record_input("1/hour", "Growth rate.")}
 
def set_inputs(self, signals):
    value = coerce_float(signals.get("growth_rate"))
    if value is not None:
        self.growth_rate = value

See Also