Skip to main content

Behaviors

Behaviors define how an instance's state evolves over simulated time. A behavior is declared on the ontology — so every instance of the ontology carries its dynamics with it, in any model it is composed into.

The primary behavior construct is the state manager.

State Managers​

A state manager pairs a declarative I/O specification with an implementation:

  • consumed — a SedaroQL query for the state the behavior reads
  • produced — a query naming the state the behavior writes
  • outputs — a query naming the state recorded to the simulation's output stream
  • engine — the engine whose clock the behavior runs on
  • an implementation — a pure function from consumed values to produced values
@statemanager(
engine="gnc",
consumed="(length, prev!(angle), prev!(angleRate), prev!(timeStep as s))",
produced="(angle, angleRate)",
outputs="(angle, angleRate)",
)
def propagate_angle(length, theta, omega, step):
...

The queries are the language-level contract. They give the Simulation Compiler everything it needs to:

  • type-check the behavior against the model, including unit conversions declared with as casts (timeStep as s);
  • schedule behaviors within and across engines based on true data dependencies (prev!(...) marks reads of the previous frame, breaking cycles);
  • detect input changes efficiently and skip redundant work;
  • record exactly the declared outputs to the result stream.

Consuming and Producing State​

Queries may reach across the model: a behavior can consume fields of related instances, aggregate over all instances of a type (Thruster.torque), or reference other agents. See the SedaroQL reference for the full expression grammar, including spreads, matches, and the reserved queries (time, timeStep, elapsedTime, startTime, stopTime).

Implementations Are Pluggable​

The implementation attached to a state manager is a function reference, not part of the model's semantics. The same declared behavior can be implemented in Python or in compiled Rust, and swapped without touching the model — a key piece of SedaroML's language-agnosticism.

Implementations are expected to be pure: consumed values in, produced values out, no mutation of inputs and no hidden state. This is what makes simulations deterministic and reproducible (see Execution Semantics). For pseudo-randomness, seed deterministically via the seed!(...) query rather than global RNG state.

Engines​

A model splits its simulation into named engines — e.g. gnc, power, thermal — each with its own internal clock that advances asynchronously from the others. Every state manager belongs to exactly one engine. A model's engine set is the union of the engines declared by behaviors across its whole tree of instances; engines exchange state with each other through the consumed/produced queries that cross them.

Execution of engines — scheduling, state exchange, determinism guarantees, interpolation — is the Simulator's responsibility. SedaroML's job is to declare which behaviors belong to which engine and what they read and write.

Time-Step Guidance​

Ontologies can be opinionated about how fast their engine's clock may advance. A time-step manager is a special state manager that produces the engine's time step:

@time_step(engine="gnc")
def time_step() -> float:
return 0.1 # seconds

Time-step guidance may be dynamic — it can consume state like any other behavior (e.g. shrink the step during a burn, relax it during coast). Because the guidance lives on the ontology, a component enforces its own accuracy requirements in any model it is composed into.

Every engine used by a model must have time-step guidance; a model whose engine has no time-step manager is rejected before build.