Cookbook¶
Short, copy-paste recipes for the most common tasks. Each assumes the package is
installed (pip install tep-studio, or pip install -e . from a checkout). If you
run from a source checkout without installing, prefix commands with PYTHONPATH=src.
Check that the plant runs (and is stabilized)¶
import tep_studio as tep
tep.quickstart() # short closed-loop run; prints stabilized + peak reactor pressure
For a full run you control:
from tep_studio import ClosedLoopSimulation
result = ClosedLoopSimulation(horizon=24.0).run()
print(result.stabilized, result.peak["reactor_pressure_max"])
Discover the variables you can touch¶
import tep_studio as tep
tep.list_measurements() # 41 (name, unit, description) — observations / CVs
tep.list_manipulated_variables() # 12 (name, unit, description) — the action vector
tep.list_disturbances() # 28 (name, description) — IDVs you can inject
tep.list_setpoints() # closed-loop setpoint field names
From the terminal: tep list disturbances (or measurements, mvs, setpoints).
Change a setpoint¶
Setpoints are a frozen ControllerSetpoints; copy-and-replace the field you want,
then hand the controller to the runner:
import dataclasses
from tep_studio import ClosedLoopSimulation, RickerMultiLoopController, TennesseeEastmanProcess
sim = TennesseeEastmanProcess()
meas, _ = sim.reset()
controller = RickerMultiLoopController()
controller.reset(meas) # seeds default setpoints from the Mode-1 state
controller.setpoints = dataclasses.replace(controller.setpoints, production_rate=24.0)
result = ClosedLoopSimulation(simulator=sim, controller=controller, horizon=24.0).run()
Inject a disturbance (IDV)¶
from tep_studio.analysis import DisturbanceActivation, ScenarioConfig, run_scenario
cfg = ScenarioConfig(
horizon=12.0,
disturbances=(DisturbanceActivation(idv="idv_01", start_time=1.0),), # A/C ratio step at 1 h
)
run = run_scenario(cfg)
print(run.metrics["iae"]["reactor_pressure"])
Terminal equivalent: tep run --horizon 12 --idv idv_01@1.0. See
control/examples/disturbance_scenario.py.
Run a step test¶
Open-loop MV step (response only, no model identification):
from tep_studio.analysis import ScenarioConfig, StepTestSpec, run_mv_step_test
cfg = ScenarioConfig(loop_type="open", horizon=1.0, control_interval=0.01)
spec = StepTestSpec(kind="mv", target="d_feed_valve", baseline=63.0, step_value=70.0, step_time=0.25)
frame = run_mv_step_test(cfg, spec).to_frame()
Closed-loop setpoint step:
from tep_studio.analysis import ScenarioConfig, StepTestSpec, run_setpoint_step_test
cfg = ScenarioConfig(loop_type="closed", horizon=8.0)
spec = StepTestSpec(kind="setpoint", target="reactor_level", baseline=75.0, step_value=70.0, step_time=1.0)
frame = run_setpoint_step_test(cfg, spec).to_frame()
See control/examples/mv_step_test.py.
Train an RL agent¶
The Gymnasium environment registers itself on import tep_studio:
import gymnasium, tep_studio
env = gymnasium.make("TennesseeEastman-v0", horizon=24.0)
obs, info = env.reset(seed=0)
obs, reward, terminated, truncated, info = env.step(env.action_space.sample())
Shape your own reward from the full step result, and train with stable-baselines3
(pip install stable-baselines3):
def reward_fn(result): # result is an AdvanceResult
return (3000.0 - float(result.measurements[6])) / 3000.0 # stay off the pressure trip
env = gymnasium.make("TennesseeEastman-v0", reward_fn=reward_fn)
See simulation/examples/rl_training.py.
Linearize the plant (control theory)¶
A discrete-time local model x_{k+1} - x* ≈ A (x_k - x*) + B (u_k - u*):
from tep_studio import OptimizationAdapter, TennesseeEastmanProcess
sim = TennesseeEastmanProcess(rtol=1e-9, atol=1e-11) # tight tolerances → clean Jacobian
sim.reset(mode="mode1")
x0, u0 = sim.state.copy(), sim.state[38:50].copy()
A, B = OptimizationAdapter(sim).linearize(x0, u0, control_interval=0.001)
# A is (50, 50), B is (50, 12); use for eigenvalues, controllability, LQR, ...
For small intervals the continuous Jacobian is A_continuous ≈ (A - I) / control_interval.
Bring your own controller¶
ClosedLoopSimulation accepts anything satisfying the Controller protocol
(setpoints, reset, compute_action); no subclassing. The PI primitives
(DiscretePI, VelocityPI) are reusable building blocks. A worked example that
swaps in a custom reactor-temperature loop is in control/examples/custom_controller.py.
Export a dataset¶
One run to CSV/Parquet:
from tep_studio.analysis import ScenarioConfig, build_dataset, run_scenario
run = run_scenario(ScenarioConfig(horizon=12.0))
payload, filename = build_dataset([run], fmt="csv") # or fmt="parquet"
open(filename, "wb").write(payload)
A multi-seed batch from the terminal: tep dataset --seeds 1,2,3 --horizon 12 --out data.csv.
Batch jobs are independent, so tep dataset runs them across all CPU cores by default
(pass -j N to cap workers, or -j 1 for sequential). In Python the same applies:
run_batch(spec, max_workers=N) (None = all cores). For lower-level, per-step control
see Working with Data and TrajectoryDataset.
Speed vs. fidelity (the integrator)¶
Simulations use a fast fixed-step RK4 integrator by default: ~8× faster than the adaptive SciPy solver, and faithful to the model's 0.0005 h design step (it matches RK45 to ~0.001%). For an adaptive reference solve, request RK45:
from tep_studio import TennesseeEastmanProcess
sim = TennesseeEastmanProcess(solver_method="RK45") # adaptive; rtol/atol now apply
from tep_studio.analysis import ScenarioConfig
ScenarioConfig(solver_method="RK45") # for run_scenario / run_batch
From the terminal: tep run --solver RK45 / tep dataset --solver RK45. Note: rtol/atol
affect only the SciPy methods; the fixed-step RK4/Euler ignore them. Keep the default
fixed_step=0.0005: the model is stiff, so coarser substeps are rejected (loud error).
Use the command line¶
tep run --horizon 24 --idv idv_06@2.0 --setpoint production_rate=24 --out run.csv
tep dataset --seeds 1,2,3 --horizon 12 --out dataset.csv
tep list disturbances
tep version
Launch the web Simulation Studio¶
See the Interface (Studio) page for the tabs and workflow.