The homeostat bench¶
penguineer bench homeostat measures how often investigate names the right root cause. It generates plants with Homeostat, injects one known fault into each, exports what a plant would export (historian, LIMS, lots, the plant graph), runs investigate on the first off-spec lot exactly as it runs on a real plant, and only then reads the answer.
bench homeostat: 85 validation scenarios (11 plants skipped: no lot off spec)
hit@1 89%
hit@3 96% (95% 90% to 99%)
wrong-confident rate 5%
data-fault catch rate 95%
fault type n hit@1 hit@3
catalyst_loss 12 92% 100%
feed_b_flow 10 80% 100%
feed_flow 7 100% 100%
feed_property 12 83% 100%
reactor_fouling 12 83% 83%
reactor_sensor_bias 12 92% 92%
reactor_sensor_drift 8 100% 100%
reactor_setpoint 12 92% 100%
The first run generates the scenarios, which takes a few minutes per fault type while Homeostat compiles each plant; later runs reuse them from .penguineer/bench/ in the project. --bundles runs on bundles you already have, and bench.json and scenarios.csv in the run folder hold every scenario's result.
The bench plant¶
A small continuous reactor train, defined in src/penguineer/bench/plant.py:
| Node | What it is | Tags |
|---|---|---|
| S-A, S-B | Two feeds | FT-101, FT-102 (flowmeters) |
| M-101 | Feed mixer; its outlet composition sets the reactor's feed ratio | AT-101 (online analyzer) |
| S-mix, E-201 | The mixed feed and its heater, loop TIC-201 | TT-200 (inlet temperature), TT-201, TIC-201 setpoint and output |
| R-301 | The reactor, loop TIC-301; the product property follows an Arrhenius law in its temperature, a power of the feed ratio and the catalyst activity | TT-301, TIC-301 setpoint and output |
| S-product, SP-1 | The product and its sample point | lab result every 4 hours, reported an hour later |
Each drawn plant differs in its volumes, its kinetics and its noise, and runs for three days: two grades alternate in the first day, and the fault arrives between hours 40 and 56. The graph each bundle carries is the true one, with dead times from the drawn volumes. Spec limits come from the in-control lots before the fault. A plant where no lot goes off spec is skipped: the fault was too small to matter, so it is neither a hit nor a miss.
The faults¶
| Fault type | What happens | Counts as found when a hypothesis names |
|---|---|---|
feed_property |
Feed B's composition steps up | AT-101, M-101 or S-B |
feed_flow |
Feed A's flow rises | FT-101 or S-A |
feed_b_flow |
Feed B's flow drops | FT-102 or S-B |
reactor_setpoint |
The reactor setpoint is lowered | the TIC-301 setpoint change |
reactor_fouling |
The reactor's heating fouls until its valve saturates | TIC-301 lost control (or R-301) |
reactor_sensor_bias |
TT-301 reads high; the loop cools the true temperature to match | a measurement fault on TT-301 |
reactor_sensor_drift |
TT-301 drifts high | a measurement fault on TT-301 |
catalyst_loss |
The catalyst loses activity; nothing measured moves | an unmeasured change in R-301 |
A process hypothesis claims that its tag moved and caused the deviation, so it counts only when the tag itself is listed: a tag that merely sits on the faulty equipment does not. The other kinds (a setpoint change, lost control, a measurement fault, an unmeasured change) are claims about their node.
Metrics¶
| Metric | Definition | Release gate |
|---|---|---|
| hit@3 | Share of scenarios where one of the top three hypotheses names the cause | ≥ 70% on the hidden split |
| hit@1 | The same for the leading hypothesis | reported |
| Wrong-confident rate | Share of scenarios where the leading hypothesis is labeled high confidence and is wrong | < 10% |
| Data-fault catch rate | Among measurement faults, share where the faulty tag leads as a measurement fault | ≥ 90% |
Rates carry a 95% Wilson interval in bench.json: at 85 scenarios an observed 96% is anything from 90% to 99%.
Splits and calibration¶
| Split | Seed | Used for |
|---|---|---|
| train | public (1000) | Fitting the test weights and rule scores |
| validation | public (2000) | Choosing the confidence thresholds, and the numbers above |
| hidden | KausalFlow's secret | The release gate, run by hand before a release (the Release gate workflow) |
scripts/fit_calibration.py searches the weights and rule scores on the train split (maximizing hit@3, plus half of hit@1 and half of the data-fault catch rate), then chooses the confidence thresholds on validation so that the wrong-confident rate stays at or under 5%. The result ships as src/penguineer/engines/calibration.json, and every report.json names the splits and the Homeostat version it was fitted with.
What the numbers do not say¶
- The bench is one plant family. A real plant has more tags, more loops and other faults, so treat these numbers as an upper bound, not a promise.
- The data-fault catch rate is 95% (19 of 20) on validation and 100% (21 of 21) on train, but 20 scenarios give a wide interval: 76% to 99%. The one miss is a small bias that started seven hours before the first off-spec lot, inside the newest reference lot (the lots in between ran another grade), so the loop output's move is partly in the reference and the leading hypothesis is an unmeasured change in R-301.
- The plant has no analyzer on the sampled stream, so the onset test never runs on the bench, and its weight is kept rather than fitted.
- The hidden split, and so the release gate, is not run by the public bench. The Release gate workflow runs it by hand before a release, with the seed from a repository secret, and shows only the summary scores (
python -m penguineer.bench.gate bench.json); the plants and per-scenario results never leave the runner. The gate also needs at least 150 scenarios and no errors.
In CI¶
The Bench workflow runs a smoke bench (3 validation plants per fault type) on every pull request, and the full train and validation bench every night. Each run uploads bench.json and scenarios.csv and writes the scores into the job summary.