Control foundations: narratives, loops and sequences¶
A plant repository can say how its process operates and how its controls meet the operating objectives, in files next to the cause-and-effect list. It is documentation of control intent: what the loops are, what they measure and move, which setpoints they hold, how the plant is started up and shut down. It is not dynamic simulation, controller tuning, DCS or PLC code, commissioning or SIL verification (see Not modelled).
All of it is optional. cheme init writes templates of the four files (commented examples,
the philosophy's headings). A register without entries and a document that is still the
template, unchanged, count as not started: they get no findings, the report and
GET /api/control leave them out (has_control in GET /api/repo is false), and a plant
that does not document its control sees no empty pages. Any edit of a document starts it. The bundled skills control-narrative, control-loops and control-sequences
drive the work; each calls cheme validate and cheme control report.
| file | kind (cheme schema ...) |
what |
|---|---|---|
process/narrative.md |
narrative (front matter) |
the process sections: purpose, boundaries, normal conditions, operating modes, material and energy flow |
control/philosophy.md |
control_philosophy (front matter) |
operating objectives, control hierarchy, normal and disturbed operation, operator versus automatic actions, equipment and utility failures |
control/loops.yaml |
loops |
the regulatory control loops |
control/sequences.yaml |
sequences |
startup, shutdown and abnormal-mode sequences with their permissives |
control/cause-effect.yaml |
interlocks |
the trips (existing; a few optional fields added) |
Code: domains/control/schemas.py (the formats), domains/control/register.py (the checks),
domains/control/documents.py (front matter and headings), domains/control/report.py (the
reports), foundation/reviews.py coverage (review status). The worked example is
examples/process-separation (a depropanizer).
The documents¶
The narrative and the philosophy are Markdown: the text explains. Their YAML front matter names,
by id, what the text is about, so that cheme validate can check it:
---
schema: cheme/narrative/v1
sections:
- id: FEED
title: Feed let-down and flash
equipment: [D-101]
blocks: [FL, V1, D1] # blocks and steps of process/flowsheet.yaml
cases: [design]
modes: [normal, startup, shutdown]
loops: [FIC-101, LIC-102]
sequences: [SEQ-START]
interlocks: [I-01]
unresolved: [the column's own control is not described yet]
---
## FEED Feed let-down and flash
...
Each section needs a heading that starts with its id. The philosophy's front matter lists
objectives and failures (each with the loops, sequences and interlocks it rests on), and its
body has a heading for each topic: Operating objectives, Control hierarchy, Normal
operation and disturbances, Operator and automatic actions, Equipment and utility failures,
Assumptions and open points.
Values are not repeated in the prose: the operating case lives in process/flowsheet.yaml, the
setpoints in control/loops.yaml, the trips in the C&E. Write proposed behaviour as
proposed, and record what is open under unresolved. The text is never checked for meaning.
The loop register¶
schema: cheme/loops/v1
loops:
- id: FIC-105 # stable id, usually the loop tag
title: Hot pentane flow (slave)
section: PREHEAT # a narrative section
cases: [design] # operating cases it is designed for
variable: flow # flow, level, temperature, pressure, composition, speed, ratio, other
measurement: [{tag: FT-105}]
controller: FIC-105
action: reverse
final_elements:
- {tag: FV-105, kind: valve, actuator: pneumatic, fail: closed}
targets:
- {kind: setpoint, value: 5000, unit: kg/h, provenance: assumed, source: hot oil circulation,
cases: [design], stream: H1, property: flow}
modes: [manual, auto, cascade]
normal_mode: cascade
transitions:
- {from: auto, to: cascade, by: operator, condition: {loop: TIC-103, state: in auto}}
links:
- {type: cascade, loop: TIC-103} # TIC-103's output is this loop's setpoint
alarms:
- {tag: FT-105, condition: FAL, response: check the hot oil pumps}
interlocks: [I-01] # related trips, by C&E id
rationale: ...
operator: ... # judgement the operator has to make
unresolved: []
basis: [{path: process/flowsheet.yaml, revision: "sha256:...", relation: depends-on}]
Conventions¶
- Controller action.
direct: the output rises as the measurement rises;reverse: it falls. It says nothing about the valve. - Fail position.
failis where the final element goes on loss of signal, power or motive air:open,closed,last, for a drivestop, for a heateroff. It is independent of the controller's action. - Output and travel. Controller output 0 to 100 % moves a final element over
travel, by default[0, 100]: a rising output opens a valve, speeds up a drive, raises a heater's duty.travel: [100, 0]closes it as the output rises. - Split range. Two or more final elements each give the
rangeof the output they act on (range: [0, 50],range: [50, 100]). - Unknown is not a default. A value left out (
action,fail,variable, a target'svalue) is not known yet. It is reported ascheme.control-incompleteuntil it is given; nothing assumes air-to-open or reverse action for you. - Links are listed on the loop they act on:
cascade(the masterloopsets this loop's setpoint),ratio(the setpoint isratiotimes the flow oflooportag),feedforward(the measured disturbancetagadds to the output),override(this loop andloopdrive the same element through aselect: low | highselector). A cascade master needs no final element of its own. - Targets carry their unit, provenance (
given,assumed,calculated), source and optional origin as everywhere else (contract.md), the operating modes and cases they apply to, and optionally the stream value of the case they are (streamwithproperty: flow | temperature | pressure).
A loop is regulatory control. It is never a safeguard: protective functions are the interlocks
of the C&E, which interlocks refers to. When a loop measures or moves a tag of an interlock,
validate says so (cheme.control-shared-element, info) and concludes nothing: independence and
SIL are the HAZOP's and the SIL study's.
Sequences¶
schema: cheme/sequences/v1
sequences:
- id: SEQ-START
mode: startup # startup, shutdown, abnormal, normal, other
section: FEED
equipment: [D-101, E-101]
permissives: # what must hold before it may start
- {interlock: I-01, state: reset}
- {all: [{loop: FIC-101, state: in manual}, {loop: LIC-102, state: in manual}]}
interlocks: [I-01] # the trips that protect it, by C&E id (not repeated)
steps:
- id: S1
actions:
- {loop: FIC-101, action: setpoint, setpoint: {value: 1200, unit: kg/h, provenance: assumed}}
- {loop: FIC-101, action: auto, by: operator}
complete: {tag: LT-102, state: "> 30 %", delay: {value: 30, unit: s}}
timeout: {value: 30, unit: min}
on_timeout: abort # a step id, or abort
- id: S2
...
next: end # default: the step below
abort:
condition: {any: [{interlock: I-01, state: tripped}, {text: operator judges the column unstable}]}
actions: [{tag: FV-101, action: close}]
then: SEQ-STOP
reset: {latching: true, manual: true, condition: {interlock: I-01, state: reset},
restart: {sequence: SEQ-STOP, state: complete}}
An action is a tag start, stop, open or close; a loop put in manual, auto or
cascade or given a setpoint; another sequence run; or text. Start permissives are kept
apart from trips: the trips stay in the C&E and are named by id.
Conditions¶
Permissives, step entry and completion, mode transitions, abort, reset and an interlock's
logic share one condition format:
| form | example |
|---|---|
| an item in a state | {tag: LT-102, state: LAH}, {loop: FIC-101, state: in auto}, {interlock: I-01, state: reset}, {sequence: SEQ-STOP, state: complete}, {step: S2, state: complete} |
| AND, OR | {all: [...]}, {any: [...]} |
| voting | {vote: {k: 2, of: [a, b, c]}} (2oo3) |
| a delay, a dead band | delay: {value: 5, unit: s}, deadband: {value: 2, unit: "%"} on any condition |
| logic the format does not model | {text: ...}, reported as cheme.control-logic-text and never interpreted |
| not known yet | {unresolved: ...} |
The cause-and-effect list¶
Existing files load and render unchanged. An interlock may now also give, all optional:
logic (the trip condition when it is more than the single cause, e.g. 2oo3 with a delay; the
cause stays the matrix row), delay, latching (true/false; omitted: not stated),
reset: manual | automatic and bypass (whether and how it may be bypassed or inhibited).
The tags of logic are checked like the cause's.
What validate checks¶
cheme validate runs the checks when the repository has any of the files (not for
cheme validate <files>, which says so). Every finding names the file, the line, a rule and a
hint, and has its own id for accept:.
| rule | level | when |
|---|---|---|
cheme.control-duplicate |
error | a loop, sequence, step, section, objective or failure id given twice |
cheme.control-document |
error | front matter that is not valid |
cheme.control-cycle |
error | loops whose setpoints depend on each other (cascade links in a ring), or a loop linked to itself. Ratio, override and feedforward links follow a measurement or select between outputs and may form rings: they are intentional feedback |
cheme.control-unknown-tag |
warning | a tag in no list and on no sheet (a renamed or removed tag); not checked while a sheet fails to load or a list is invalid |
cheme.control-ref |
warning | a loop, sequence, step, interlock, case, section, block or stream that does not exist; not checked against a file that is invalid (loops, sequences, C&E) or missing (sections without a narrative, cases without a flowsheet) |
cheme.control-conflict |
warning | contradicting definitions: two loops on one final element without an override, two loops naming one controller, a setpoint outside its own limits, a mode a loop does not have, a split range in which not every element has its range, or a single element with one (overlaps and gaps between ranges are not checked) |
cheme.control-unit |
warning | a value without unit or provenance, a unit that does not fit (a pressure without (a)/(g), a temperature not in K or °C, a flow in metres, a delay or timeout that is not a time, also an interlock's delay and the delays in its logic, checked for every C&E list). Without Pint (extra units) only K, °C, the (a)/(g) pressures, %, s, ms, min and h are known, and another unit passes unchecked |
cheme.control-case |
warning | a target that names a stream value of an operating case and differs from it: a temperature by more than 0.5 K, a pressure by more than 0.5 % (absolute against absolute, gauge against gauge), a flow by more than 0.5 %; info when the case has no value yet or the two cannot be compared (a gauge target against an absolute case value) |
cheme.control-unreachable |
warning | a sequence step that no step leads to |
cheme.control-incomplete |
info | a draft gap: a loop without variable, measurement, action, final element, fail position, target or modes; an alarm without an operator response; a step without completion condition or actions; a timeout without on_timeout; a sequence without abort or reset; a startup without permissives; a narrative section without its heading; a philosophy topic missing |
cheme.control-unresolved |
info | an open point the record states |
cheme.control-logic-text |
info | logic or an action given as text |
cheme.control-shared-element |
info | a loop measures or moves a tag of an interlock |
cheme.control-alarm-trip |
info | an alarm at a high-high or low-low condition: a trip, which belongs in the C&E |
cheme.control-stale |
info | an entry of basis: changed since the revision recorded |
The draft findings are info so that a concept-stage record can be incomplete. A stage gate
decides when they must be gone; the template's checks/checks.yaml has this one at detail
engineering (a plant without control records passes it):
- id: control-complete
title: Loops and sequences complete, every control reference resolved
stage: detail
kind: validate
fail_on: info
rules: [cheme.control-incomplete, cheme.control-unresolved, cheme.control-ref,
cheme.control-unknown-tag, cheme.control-conflict, cheme.control-unit,
cheme.control-case, cheme.control-cycle, cheme.control-duplicate,
cheme.control-document, cheme.control-unreachable]
Review status¶
Whether a control file is drafted, reviewed or accepted comes from the review records in
reviews/ (foundation.reviews.coverage), never from a field of the file: the latest record
whose scope covers the file decides.
- drafted: no record covers it, or it changed since the record's
reviewed_commit; - reviewed: a current record covers it;
- accepted: a current record by a human (
reviewer_kind: human, or a reviewapproved_byone), with no finding of a blocking severity still open in any record that covers the file.
A record's reviewed_commit must be an ancestor of HEAD to count.
Accepted means the review records say so. It does not mean the controls are implemented, verified or approved for construction.
Reports and the viewer¶
cheme control report writes build/control-loops.html and .csv (the loop summary) and
build/control-sequences.html (each sequence with its permissives, steps, abort and reset),
next to the C&E matrix of cheme cause-effect. Each page shows the stable ids, the provenance
of each value, the review status of each file, the source commit and the findings.
cheme control report --json and GET /api/control return the same data
(domains.control.report.control_report(root)); the endpoint answers 404 when the plant has no
control records, so a viewer shows no empty page. cheme export-static records it, except in a
snapshot with --exclude (it names reviewers, like status).
Not modelled¶
The format stays small on purpose. Not modelled, and not claimed:
- dynamic simulation, controller tuning, MPC, DCS or PLC code generation, commissioning;
- SIL assignment or verification, protection independence, safe operating limits: nothing is derived from the descriptive logic;
- a complete alarm-management lifecycle (rationalisation, priorities by consequence, alarm floods): alarms are listed with the operator response only;
- bypass and inhibit records (who bypassed what, when):
bypasson an interlock states the design intent only; - timers, counters, latches and arithmetic beyond
delay,deadband,voteand the reset fields: write them astext, which stays visible ascheme.control-logic-text; - whether an action is correct (a reverse-acting controller on a fail-closed valve, say): the process gain is not known to the format.