The step¶
Homeostat runs on a global clock with a fixed base step dt, and a batch dimension of lanes: every array holds one value per lane, and one step advances every lane.
What happens in a step¶
At each time t:
- Apply the events due at t.
- Compute every operator's outputs, in the order of the same-step dependencies.
- Record the values.
- Update every operator's state from the same inputs.
A controller's output at t therefore drives the process state at t+1: sampled-data control with a zero-order hold, as in a real control system. A real scan period or transport delay is modelled explicitly, with a controller scan or a delay operator.
The steady start¶
By default (init: steady), a run starts from a steady state rather than from arbitrary initial values:
- noise is off, and stochastic sources sit at their means;
- quantization is passed through, so the plant cannot hunt between two quantization levels;
- ageing states, such as fouling, are held at their initial values;
- the plant is stepped at t = 0 until every state stops changing, compared over windows long enough for every operator to take a turn;
- stochastic sources then start from their stationary distributions.
The run begins at the starting regime's setpoints with no start-up transient. init: none starts from the operators' initial states instead, and init: {burn_in: 2h} runs with noise for that long before t = 0 and discards it.
If the plant cannot settle, for example because a loop saturates at its initial setpoint or an integrating state is unbalanced, the error names the states that are still moving.
Multi-rate stepping¶
An operator can update less often than the base step: with a period of n steps it is discretized for its own step, updates only on its turn, holds its outputs in between, and draws random numbers only on its turn. Before its first turn (with a phase) it shows outputs from its initial state. See Multi-rate plants.
A resumable engine¶
The engine can be advanced in pieces: run(until) steps to a time, schedule(event) adds events, and the run continues from where it stopped. result(clear=True) hands over what was recorded so far, so a long run can be collected piece by piece; snapshot() saves the engine and Engine.restore() continues it later, exactly. fork() copies the engine, random streams included, so two copies can continue differently from exactly the same state. Planners that react to the simulated state are built on this: they read the state, decide, schedule events and continue.