Skip to content

Plant families

Any number or choice in a scenario can be a distribution. A scenario with distributions describes a family of plants, and each plant is one draw from it. examples/plant_family.yaml describes 48 temperature loops that differ in their process, their tuning, their disturbances, their plan and their fault:

# A family of plants: every distribution below is drawn once per plant, and all plants run as
# lanes of one batch. The trace (family.trace) records every drawn value by its address.
seed: 21
duration: 30d
dt: 20s
library: process@1
family: {plants: 48}

units:
  - id: TIC-101
    template: temperature_loop
    K: {lognormal: {median: 0.8, sigma: 0.2}}           # process gain differs between plants
    tau: {lognormal: {median: 10min, sigma: 0.3}}
    theta: {uniform: [30s, 3min]}
    tuning: simc                                         # tuned from each plant's own model...
    detune: {lognormal: {median: 1.3, sigma: 0.4}}       # ...by engineers of varying caution

exogenous:
  - target: TIC-101.feed_temp
    unit: degC
    source: {kind: ou, mean: 25, std: {uniform: [1, 3]}, tau: {uniform: [2h, 12h]}}

regimes:
  grade_A: {TIC-101.sp: 80}
  grade_B: {TIC-101.sp: {uniform: [85, 90]}}

degradation:
  - {target: TIC-101, kind: fouling, rate: {loguniform: [0.003, 0.02]}}

plan:
  start: grade_A
  production:
    - {at: {uniform: [8d, 12d]}, to: grade_B, over: 6h}
  maintenance:
    - {at: {uniform: [15d, 25d]}, task: clean, target: TIC-101}

interventions:
  - {at: {uniform: [2d, 28d]}, target: TIC-101, fault: sensor_bias, magnitude: {normal: {mean: 0, sd: 1.5}}}

output:
  every: 5min

Distributions

Form Meaning
{normal: {mean: 1.0, sd: 0.1}} Normal distribution.
{lognormal: {median: 20min, sigma: 0.4}} Log-normal; sigma is the standard deviation of the logarithm. Always positive.
{uniform: [1min, 5min]} Uniform between two values.
{loguniform: [0.5, 5]} Log-uniform between two positive values.
{choice: {thermocouple: 0.7, magnetic_flowmeter: 0.3}} or {choice: [a, b, c]} One of several options, with weights or equally likely.

Numbers may be durations. A choice may pick anything, including a template name, which changes the plant's structure.

One drawn plant

homeostat.draw(config, plant=k) replaces every distribution with a value and returns the concrete config and a trace. homeostat.prepare(config, plant=k) and homeostat.simulate(config) draw first (plant 0 by default), and the run records the draws in run.meta["trace"]: the address of each value in the config (for example units/TIC-101/K), the value, its distribution and its log-probability.

Each value comes from its own random stream, keyed by its address and the plant, so adding or changing one distribution never changes the others. A config with a family section describes several plants; simulate() refuses it and asks for family().

A family

import homeostat

fam = homeostat.family("examples/plant_family.yaml")   # 48 plants x 30 days in about 15 s
fam.lanes       # plant, history, group, lane, noise key, feasible
fam.draws()     # the drawn values: one row per plant, one column per address
fam.run(7)      # plant 7 as an ordinary single-lane Run
fam.rejected    # draws that failed validation and were drawn again

family() draws plants plants and runs histories histories of each (the same plant with different noise). The sizes come from the config's family section or from the arguments: homeostat.family(config, plants=100, histories=3).

Plants that share a structure run together as the lanes of one batch, with per-lane parameter values, starting regimes and events; running many plants per batch costs little more than running one. Plants whose structure differs (for example a different template) run in separate batches. A plant in a family gives exactly the same data as the same plant run alone with the same noise key, and fam.run(plant) describes that plant: its scenario, draws, timeline, config hash and events.

A draw that fails validation, for example a non-stationary AR source or a dead time outside its range, is rejected and drawn again; after 20 failed attempts in a row the family fails with E_DRAW_REJECTED.

Tuning each plant

When a plant's model is drawn, its controller should be tuned for it. tuning: simc computes the controller gain and integral time from each plant's own model (SIMC rules), and detune makes the controller slower and more robust; drawing detune gives plants tuned by engineers of varying caution.