Skip to content

Your first scenario

A scenario describes a plant and what happens to it. This one has a single flow control loop, a disturbance nobody measures, a setpoint change and a transmitter fault. Save it as first.yaml:

seed: 7
duration: 2h
dt: 1s
library: process@1

units:
  - {id: FIC-101, template: flow_loop, sp: 50}

exogenous:
  - target: FIC-101.pressure          # upstream pressure: a disturbance nobody measures
    unit: bar
    source: {kind: ou, mean: 0, std: 0.2, tau: 5min}

interventions:
  - {at: 30min, target: FIC-101.sp, value: 60}
  - {at: 1h, target: FIC-101, fault: sensor_bias, magnitude: 2}

output:
  every: 10s

What each part does:

  • seed, duration and dt: the random seed, the length of the run and the base step. Durations are seconds or strings such as 30s, 10min, 2h or 1d.
  • library: process@1: the process-industry library, pinned to its major version 1. It provides the templates, sensor presets and faults.
  • units: one flow_loop, a flow controller with its valve, the flow's response to the valve, and a flowmeter. The id FIC-101 gives the ISA tags: the flowmeter reading is FT-101 and the valve is FV-101.
  • exogenous: the loop's pressure port is driven by an Ornstein–Uhlenbeck process with a standard deviation of 0.2 bar and a correlation time of 5 minutes.
  • interventions: at 30 minutes the setpoint steps to 60 m³/h; at 1 hour the flowmeter starts reading 2 m³/h too high.
  • output: record every 10 seconds.

Run it

import pandas as pd
import homeostat

run = homeostat.simulate("first.yaml")

run.observed.columns   # ['FT-101', 'FIC-101.sp', 'FIC-101.mode', 'FIC-101.out']

The run starts from a steady state at the initial setpoint, so there is no start-up transient. It takes well under a second.

run.observed is what a historian would record: the flowmeter reading and the controller's setpoint, mode and output. run.truth holds the true value of every signal, including the pressure disturbance and the true flow FIC-101.cv, which no instrument reports directly.

See the fault where it hides

A transmitter bias is a classic example of a fault that the control loop hides. The controller trusts its reading and holds it at the setpoint, so the reading looks perfect while the real flow drifts away:

before = (run.truth.index > pd.Timedelta("40min")) & (run.truth.index < pd.Timedelta("60min"))
after = run.truth.index > pd.Timedelta("90min")

run.measured["FT-101"][before].mean()   # 60.1: on setpoint
run.truth["FIC-101.cv"][before].mean()  # 60.0

run.measured["FT-101"][after].mean()    # 60.0: still on setpoint
run.truth["FIC-101.cv"][after].mean()   # 58.0: the real flow is 2 m³/h low

The fault is recorded in the event log, with the event it expands to:

run.meta["events"][["at", "target", "action", "value", "label", "origin"]]
at target action value label origin
1800 FIC-101.sp set 60 planned
3600 FT-101.bias.offset shift 2 fault:sensor_bias fault:sensor_bias@FIC-101

When something is wrong

Scenarios are checked before they run. Every problem comes with a code, the place in the file, the reason and a fix. For example, an intervention that targets the true flow instead of its setpoint:

[E_TARGET_REGULATED] interventions[0]: 'FIC-101.cv' is the regulated variable of loop FIC-101;
nobody can set it directly. Fix: Change its reference instead: target 'FIC-101.sp'.

All codes are listed in Error codes.

Next

  • Reading a run: everything a run returns.
  • Scenario files: templates, wiring, measurements and custom operators.
  • Tutorial: a plant with a reactor, a mixer and three materials.