This project studies a jacketed exothermic Continuous Stirred Tank Reactor (CSTR) with the reaction
The goal is simple:
- control reactor temperature
$T_R$ - use coolant flow rate
$F_C$ as the manipulated variable - study the nonlinear process
- linearize the model around a steady state
- design and compare classical PI controllers
The code is written in plain Python and is organized so that each part of the control problem lives in a separate file.
This reactor is hard to control because it is nonlinear and exothermic. That means the reaction releases heat, and the heat release changes with temperature. If the cooling is not enough, the reactor temperature can rise quickly.
The project follows the standard classical process-control workflow:
- build the nonlinear model from material and energy balances
- find a steady-state operating point
- linearize the nonlinear model around that operating point
- derive the transfer function from coolant flow to reactor temperature
- test the open-loop response
- design PI controllers
- compare closed-loop tracking and disturbance rejection
| File | What it does |
|---|---|
| main.py | Runs the full project from start to finish |
| models/cstr.py | Contains the nonlinear CSTR model, steady-state solver, and linearization code |
| models/parameters.py | Stores the physical and kinetic parameters |
| controllers/pi.py | Contains the PI controller and classical PI tuning rules |
| simulation/analysis.py | Contains the simulation and analysis functions explained below |
| simulation/derivation.py | Builds the symbolic derivation report with SymPy |
| plots/visualization.py | Makes the plots and diagrams |
| utils/metrics.py | Computes performance measures like IAE, ISE, and ITAE |
| utils/fopdt.py | Estimates a FOPDT model from a step response |
The reactor uses these assumptions:
- perfect mixing
- constant volume
- constant density
- constant heat capacity
- one irreversible first-order reaction
- heat transfer through the jacket
- dynamic behavior in time
The nonlinear model is based on the following balances:
The main.py file is the entry point.
It does these steps:
- loads the model parameters
- creates the reactor model
- finds a stable steady-state operating point
- prints the symbolic derivation report
- simulates nonlinear open-loop behavior
- computes the linearized model and transfer function
- estimates a FOPDT model from the step response
- computes PI gains using Ziegler-Nichols and IMC rules
- simulates closed-loop tracking
- simulates closed-loop disturbance rejection
- saves all figures into
artifacts/figures/
This file contains the main simulation and analysis routines. Each function is explained below in simple English.
| Function | File | What it does |
|---|---|---|
simulate_open_loop_disturbance |
simulation/analysis.py | Runs the nonlinear reactor with open-loop disturbances in coolant flow and feed concentration |
linear_analysis |
simulation/analysis.py | Linearizes the model, builds the state-space model, and returns the transfer function, poles, zeros, and step response |
build_pi_transfer_function |
simulation/analysis.py | Builds the PI controller transfer function |
closed_loop_linear_response |
simulation/analysis.py | Combines the plant and PI controller in unity feedback and simulates the linear closed-loop step response |
simulate_closed_loop_step |
simulation/analysis.py | Runs the nonlinear reactor with PI control for setpoint tracking |
simulate_closed_loop_disturbance_rejection |
simulation/analysis.py | Runs the nonlinear reactor with PI control while a disturbance is applied |
closed_loop_metrics |
simulation/analysis.py | Computes overshoot, rise time, settling time, IAE, ISE, and ITAE |
classical_closed_loop_analysis |
simulation/analysis.py | Runs all PI tuning rules and collects the results in one place |
dominant_second_order_characteristics |
simulation/analysis.py | Estimates damping ratio and natural frequency from the dominant poles |
_rk4_step |
simulation/analysis.py | Takes one fixed RK4 integration step for the nonlinear reactor |
This function runs the nonlinear reactor without a controller.
What it changes over time:
- coolant flow disturbance
- feed concentration disturbance
How it works:
- it creates a time grid
- it defines disturbance profiles as small step changes
- it calls the nonlinear plant model in models/cstr.py
- it returns time, reactor temperature, jacket temperature, concentration, coolant flow, and the disturbance history
Why it matters:
- it shows how the reactor behaves when no controller is protecting it
- it helps you see the natural nonlinearity of the process
This function turns the nonlinear model into a linear model around the chosen steady state.
How it works:
- it calls the linearization code in models/cstr.py
- it builds the state-space model
$\dot{x} = Ax + Bu$ ,$y = Cx + Du$ - it converts that model into a transfer function
- it finds the poles and zeros
- it computes a unit-step response
Why it matters:
- the transfer function is needed for classical PI design
- poles and zeros help you judge stability and speed
This function creates the PI controller transfer function:
How it works:
- it uses the controller gain
$K_c$ - it uses the integral time
$\tau_I$ - it returns a standard transfer function object from the
controllibrary
Why it matters:
- this is the mathematical PI controller used in the closed-loop analysis
This function connects the PI controller and the linear plant.
How it works:
- it multiplies the controller transfer function by the plant transfer function
- it closes the loop with unity feedback
- it simulates the step response of the closed-loop linear system
Why it matters:
- it shows how the controller changes the plant response in the linear domain
This function runs the full nonlinear reactor with PI control.
How it works:
- it creates a discrete PI controller from controllers/pi.py
- at each time step it computes a new coolant flow command
- it advances the nonlinear plant with one RK4 step
- it stores the temperature and control flow history
Why it matters:
- it checks whether the controller really works on the nonlinear model, not just on the linear approximation
This function tests how well the PI controller rejects load disturbances.
How it works:
- it runs the nonlinear closed-loop simulation
- at a chosen time, it applies one disturbance such as feed concentration, feed temperature, or coolant inlet temperature
- it continues the simulation and records the reactor temperature and controller action
Why it matters:
- it shows how the controller recovers after a disturbance
- this is one of the most important tests in process control
This function computes standard performance measures.
How it works:
- it compares the temperature response with the setpoint
- it calculates overshoot, rise time, settling time, IAE, ISE, and ITAE
Why it matters:
- it gives an objective way to compare controllers
This function runs several PI tuning methods and stores the results.
How it works:
- it loops through each tuning rule
- it simulates the closed-loop nonlinear system
- it computes performance metrics for each method
- it returns a dictionary with time histories, gains, and metrics
Why it matters:
- it makes the comparison between Ziegler-Nichols and IMC easy to print and plot
This function gives a simple second-order summary of the poles.
How it works:
- it looks for the dominant complex poles
- it estimates damping ratio
$\zeta$ - it estimates natural frequency
$\omega_n$
Why it matters:
- it helps explain whether the response is likely to be underdamped, well damped, or slow
This is a small numerical integration helper.
How it works:
- it uses the classical fourth-order Runge-Kutta method
- it advances the nonlinear model by one time step
Why it matters:
- it gives a stable and accurate time-domain simulation for the nonlinear reactor
This file contains the physical reactor model.
It provides:
- the nonlinear dynamic equations
- the steady-state solver
- the linearization method
- the transfer-function helper
This file contains the PI controller.
It provides:
- the discrete PI controller class
- Ziegler-Nichols tuning
- IMC tuning
This file builds the symbolic derivation report.
It uses SymPy to print:
- the material balance
- the energy balance
- the steady-state equations
- the Taylor linearization
- the transfer function form
This file makes the figures.
It creates:
- open-loop response plots
- closed-loop tracking plots
- closed-loop disturbance rejection plots
- pole-zero maps
- Bode plots
- root locus plots
- process-control diagrams
- performance tables
This file computes performance metrics.
It gives:
- overshoot
- rise time
- settling time
- IAE
- ISE
- ITAE
This file fits a first-order-plus-dead-time model from a step response.
That fitted model is then used for classical PI tuning.
The main script main.py connects all of the pieces.
In plain English, it does this:
- get the reactor parameters from models/parameters.py
- solve for a steady-state operating point with models/cstr.py
- print the derivation report from simulation/derivation.py
- simulate the open-loop reactor with simulation/analysis.py
- linearize the plant and extract the transfer function
- tune PI controllers with controllers/pi.py
- simulate closed-loop tracking and disturbance rejection
- create the plots with plots/visualization.py
- print the performance table using utils/metrics.py
This project computes standard integral error measures to quantify control performance. Let the tracking error be
- IAE (Integral of Absolute Error):
- ISE (Integral of Squared Error):
- ITAE (Integral of Time-weighted Absolute Error):
These measures are computed numerically in utils/metrics.py using trapezoidal integration over the simulation time grid.
The project saves figures in artifacts/figures/.
Important outputs include:
- open-loop response
- open-loop vs closed-loop response
- closed-loop disturbance rejection for feed concentration step
- closed-loop disturbance rejection for feed temperature step
- linear step response
- pole-zero map
- Bode plot
- root locus
- classical closed-loop comparison
- performance table
- process-control diagram
The project uses:
numpyscipymatplotlibcontrolsympy
Install them with:
pip install -r requirements.txtFrom the project root:
python main.pyThis will:
- print the balances and derivation steps
- print the transfer function and state-space matrices
- generate the plots
- compare Ziegler-Nichols PI and IMC PI
- save the results automatically
This project is written to be easy to follow. The code separates the reactor model, controller design, simulation, derivation, and plotting into different files so each part can be understood on its own.