Skip to content

16. Automating the Instructor from Python

Everything the Instructor Station does, a script can do: load a scenario, start the session, inject a fault, act as the trainee, end the exercise and save the report. The session classes live in the OTS extender and are ordinary .NET classes, so pythonnet drives them the same way the FluentAPI examples elsewhere on this site drive the engine.

What you will learn

  • Loading a plant and its scenario pack without the user interface
  • Running a session headlessly and reading its state
  • Acting as the operator through OtsSession.Execute
  • Writing the report and the journal of every scenario to a folder

Prerequisites

  • Python 3.10 or later with pythonnet (pip install pythonnet)
  • DWSIM Patreon Classic, level 3, installed; DWSIM_BIN set to its folder
  • Page 4 for what a scenario is, page 13 for the packs

Why script it

  • Regression: after retuning a plant or upgrading DWSIM, run the whole pack with no operator and check that each fault still produces its alarms at about the same time. The times in the tables of page 13 were produced this way.
  • Baselines and worked answers: a report of "nobody acted" and a report of "the right action at the right time" for every scenario, for the trainee to compare with.
  • Batch grading: a scripted operator that models a policy (act at the first alarm, act at HH, do nothing) shows what each policy scores.

The script

examples/features/ots_batch.py runs every scenario of a file:

set DWSIM_BIN=C:\Users\you\AppData\Local\DWSIM
python ots_batch.py ots_two_stage_separation.dwxmz --out reports
python ots_batch.py ots_two_stage_separation.dwxmz --out reports_scripted --scripted --only "3."

It prints one block per scenario, like the Exercise tab does:

7 scenarios, 3 interlocks
3. Gas blow-by: 480 steps in 135 s, failed 0, score 45/70
    No HH pressure in V-200: Passed (never happened)
    No low level trip: Failed (interlock 'V-100 low level trip' tripped happened at 00:06:35)
    Take LIC-100 to manual: Failed (no matching action before 00:07:00)
    HP level held above L: Passed (reached at 00:00:00)

and writes <scenario>.md (the report) and <scenario>.journal.csv for each one.

How it works, piece by piece

Loading. Automation3.LoadFlowsheet opens the file without a window. The scenario pack and the interlock logic are read from the file with the same store classes the Instructor Station uses:

from DWSIM.Automation import Automation3
from DWSIM.Extensions.OperatorTraining.Scenarios import ScenarioStore
from DWSIM.Extensions.OperatorTraining.Interlocks import InterlockStore

auto = Automation3()
flowsheet = auto.LoadFlowsheet(path)
scenarios = ScenarioStore.Load(flowsheet)
interlocks = InterlockStore.Load(flowsheet)

The session. One OtsSession per run. LoadScenario restores the initial state, arms the faults and resets the objectives; Start(True) runs from that state on a background thread. The script waits by polling StepIndex; SpeedFactor 20 is the fastest the session paces itself.

from DWSIM.Extensions.OperatorTraining.Runtime import OtsSession, SessionState

session = OtsSession(flowsheet)
session.Interlocks.Load(interlocks)
session.LoadScenario(scenario)
session.SpeedFactor = 20.0
session.Start(True)
while session.StepIndex < steps and session.State == SessionState.Running:
    time.sleep(0.05)
report = session.EndExercise()      # freezes, decides the objectives, returns the Markdown report

While it runs, session.SimTime, session.Exercise.States (one per objective, with Status, Score, Detail), session.Alarms, session.Interlocks and session.Journal are the live objects the windows read.

Acting as the trainee. Every operator action is an OperatorCommand built by the Ops factory and run through session.Execute, which queues it for the next step and journals it in the Operator category, so OperatorAction objectives and the report see it:

from DWSIM.Extensions.OperatorTraining.Remote import Ops

lic = find(flowsheet, "LIC-100")          # the PID controller object, by tag
session.Execute(Ops.Mode(lic, True))      # LIC-100 mode -> MANUAL
session.Execute(Ops.ManualOutput(lic, 20.0))
session.Execute(Ops.SetPoint(find(flowsheet, "PIC-100"), 28.0))
session.Execute(Ops.ValveOpening(find(flowsheet, "FV-100"), 35.0))
session.Execute(Ops.AckAll())
session.Execute(Ops.InterlockReset("V-100 low level trip", False, "script"))

The script's scripted_operator is called after every step (session.StepCompleted) and decides from session.SimTime and the scenario name what to do. Replace it with your own policy.

Faults by hand. Scheduled faults come from the scenario; extra ones are activated the way the Malfunctions tab does:

from DWSIM.Extensions.OperatorTraining.Malfunctions import MalfunctionCatalog

fault = MalfunctionCatalog.Create("ValveStuck", "LV-100", 1.0, 0.0)   # kind, target tag, severity, ramp
session.Malfunctions.Activate(fault, session.SimTime)
session.Malfunctions.Deactivate(fault)

Snapshots and backtrack are session.SaveSnapshot(name), session.RestoreSnapshot(name) and session.Backtrack(seconds), with the session frozen (session.Freeze()).

Writing a scenario pack. The store classes write as well as read. A scenario is plain data:

from DWSIM.Extensions.OperatorTraining.Scenarios import Scenario, ScheduledMalfunction, Objective, ObjectiveKind
from DWSIM.Extensions.OperatorTraining.Interlocks import InterlockCondition, ConditionKind

sc = Scenario()
sc.Name = "8. Two faults"
sc.InitialStateId = "a"; sc.ScheduleId = "schedule1"; sc.SpeedFactor = 5.0
m = ScheduledMalfunction(); m.Kind = "ValveStuck"; m.TargetObject = "LV-100"; m.AtSeconds = 60.0
sc.Malfunctions.Add(m)
o = Objective(); o.Name = "No HH level"; o.Kind = ObjectiveKind.Avoid; o.Points = 20.0
o.Condition.Kind = ConditionKind.AlarmActive; o.Condition.Target = "LIT-100"; o.Condition.AlarmLevel = "HH"
sc.Objectives.Add(o)
scenarios.Add(sc)
ScenarioStore.Save(flowsheet, scenarios)
auto.SaveFlowsheet(flowsheet, path, True)

Targets may be given by tag (LV-100) or by internal name; the Instructor Station stores names, and both resolve.

Notes

  • The session paces itself even without a window: 20x with a 5 s step is four steps per second, so a 40 minute scenario takes about two minutes. Overruns are counted but harmless here.
  • One flowsheet, one session at a time. Two plants in one process work (that is how the remote-station tests run), two sessions on one flowsheet do not.
  • auto.ReleaseResources() at the end; the process otherwise keeps the engine's threads alive.
  • The same classes are reachable from C# or from the DWSIM Python script blocks; only the bootstrap differs.

The last page collects the questions instructors ask most.