Pular para conteúdo

16. Automatizando o Instrutor pelo Python

Tudo que a Estação do Instrutor faz, um script pode fazer: carregar um cenário, iniciar a sessão, injetar uma falha, agir como o treinando, encerrar o exercício e salvar o relatório. As classes da sessão vivem no extender do OTS e são classes .NET comuns, então o pythonnet as aciona do mesmo jeito que os exemplos de FluentAPI deste site acionam o motor.

O que você vai aprender

  • Carregar uma planta e seu pacote de cenários sem a interface
  • Rodar uma sessão sem janelas e ler seu estado
  • Agir como o operador por OtsSession.Execute
  • Gravar o relatório e o diário de cada cenário numa pasta

Pré-requisitos

  • Python 3.10 ou mais novo com pythonnet (pip install pythonnet)
  • DWSIM Patreon Classic, nível 3, instalado; DWSIM_BIN apontando para a pasta dele
  • Página 4 para o que é um cenário, página 13 para os pacotes

Por que automatizar

  • Regressão: depois de resintonizar uma planta ou atualizar o DWSIM, rode o pacote inteiro sem operador e confira que cada falha ainda produz seus alarmes mais ou menos no mesmo instante. Os tempos das tabelas da página 13 foram produzidos assim.
  • Linhas de base e gabaritos: um relatório de "ninguém agiu" e um de "a ação certa na hora certa" para cada cenário, para o treinando comparar.
  • Correção em lote: um operador roteirizado que modela uma política (agir no primeiro alarme, agir no HH, não fazer nada) mostra quanto cada política pontua.

O script

examples/features/ots_batch.py roda todos os cenários de um arquivo:

set DWSIM_BIN=C:\Users\voce\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."

Ele imprime um bloco por cenário, como a aba Exercise faz:

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)

e grava <cenário>.md (o relatório) e <cenário>.journal.csv para cada um.

Como funciona, peça por peça

Carregar. Automation3.LoadFlowsheet abre o arquivo sem janela. O pacote de cenários e a lógica de intertravamento são lidos do arquivo com as mesmas classes de armazenamento que a Estação do Instrutor usa:

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)

A sessão. Um OtsSession por corrida. LoadScenario restaura o estado inicial, arma as falhas e zera os objetivos; Start(True) roda a partir desse estado numa thread de fundo. O script espera consultando StepIndex; SpeedFactor 20 é o mais rápido que a sessão se permite.

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()      # congela, decide os objetivos, devolve o relatório Markdown

Enquanto roda, session.SimTime, session.Exercise.States (um por objetivo, com Status, Score, Detail), session.Alarms, session.Interlocks e session.Journal são os objetos vivos que as janelas leem.

Agir como o treinando. Toda ação do operador é um OperatorCommand construído pela fábrica Ops e executado por session.Execute, que o enfileira para o próximo passo e registra no diário na categoria Operator, então os objetivos OperatorAction e o relatório o enxergam:

from DWSIM.Extensions.OperatorTraining.Remote import Ops

lic = find(flowsheet, "LIC-100")          # o objeto do controlador PID, pelo 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"))

O scripted_operator do script é chamado depois de cada passo (session.StepCompleted) e decide, pelo session.SimTime e pelo nome do cenário, o que fazer. Troque-o pela sua política.

Falhas à mão. As falhas agendadas vêm do cenário; as extras são ativadas do jeito que a aba Malfunctions faz:

from DWSIM.Extensions.OperatorTraining.Malfunctions import MalfunctionCatalog

fault = MalfunctionCatalog.Create("ValveStuck", "LV-100", 1.0, 0.0)   # tipo, tag alvo, severidade, rampa
session.Malfunctions.Activate(fault, session.SimTime)
session.Malfunctions.Deactivate(fault)

Snapshots e backtrack são session.SaveSnapshot(nome), session.RestoreSnapshot(nome) e session.Backtrack(segundos), com a sessão congelada (session.Freeze()).

Escrever um pacote de cenários. As classes de armazenamento gravam além de ler. Um cenário é só dados:

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

sc = Scenario()
sc.Name = "8. Duas falhas"
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 = "Sem nível HH"; 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)

Os alvos podem ser dados pelo tag (LV-100) ou pelo nome interno; a Estação do Instrutor grava nomes, e os dois resolvem.

Observações

  • A sessão se paceja mesmo sem janela: 20x com passo de 5 s são quatro passos por segundo, então um cenário de 40 minutos leva uns dois minutos. Os overruns são contados mas não atrapalham aqui.
  • Um fluxograma, uma sessão de cada vez. Duas plantas num processo funcionam (é assim que os testes de estação remota rodam); duas sessões no mesmo fluxograma, não.
  • auto.ReleaseResources() no fim; senão o processo mantém as threads do motor vivas.
  • As mesmas classes são alcançáveis do C# ou dos blocos de script Python do DWSIM; só o bootstrap muda.

A última página reúne as perguntas que os instrutores mais fazem.