Multi-rate plants¶
The base step dt must suit the fastest part of a plant. Slower parts can update less often: they cost less, and a controller can run at its real scan rate.
seed: 1
duration: 12h
dt: 1s
library: process@1
units:
- {id: FIC-101, template: flow_loop, sp: 20, span: [0, 40]} # fast: every step
- {id: TIC-102, template: heat_exchanger, sp: 70, period: 10s} # slow: every 10 s
- {id: TIC-103, template: temperature_loop, sp: 80, scan: 5s} # a controller scanned every 5 s
exogenous:
- {target: TIC-102.flow, from: FIC-101.cv}
periodon a unit makes every operator of the unit update at that period;periodalso applies to a custom operator.scanon a loop template sets the controller's scan period. It overrides the unit'speriodfor the controller.
A period must be a whole multiple of dt; anything else is an error (E_BAD_PERIOD).
How a slow operator behaves¶
An operator with a period of n steps is discretized exactly for its own step, updates only on its turn, and holds its outputs in between. It also draws random numbers only on its turn, so a slow noise source is as slow as its period says. The result on the base grid is exactly the result of running that operator alone at the coarser step.
Other operators read its held outputs at every base step, which is how a sampled-data controller behaves in a real plant: its output changes at each scan and holds between scans.
Settling (init: steady) keeps the same schedule, and compares the states over windows long enough for every operator to take its turn.
In Python¶
With the core API, meta sets an operator's period, and phase its offset within the period:
from homeostat.core import Graph
from homeostat.core.operators import FirstOrder, Schedule
g = Graph()
g.add(Schedule("u", value=1.0))
g.add(FirstOrder("y", K=2.0, tau=30.0), inputs={"u": "u"}, meta={"period": 10.0, "phase": 2.0})
An operator with a phase takes its first turn at the phase: before it, it shows outputs from its initial state and does not update.