Pure Python Backend for Tennessee Eastman Process
This document describes the pure Python implementation of the TEP simulator, which provides a Fortran-free alternative while maintaining statistical similarity to the original.
Overview
The Python backend (tep/python_backend.py) is a complete reimplementation of the original Fortran code (teprob.f) in pure Python. It enables running TEP simulations in environments where Fortran compilation is unavailable (e.g., cloud notebooks, restricted systems).
Backend Selection
from tep import TEPSimulator
# Use Python backend (default, no Fortran required)
sim_py = TEPSimulator(backend='python')
# Use Fortran backend (~5-10x faster, requires gfortran)
sim_f = TEPSimulator(backend='fortran')
# Auto-select best available backend
sim = TEPSimulator() # Uses Fortran if installed, otherwise Python
# Check available backends
from tep import get_available_backends, get_default_backend, is_fortran_available
print(get_available_backends()) # ['fortran', 'python'] or ['python']
print(get_default_backend()) # 'fortran' if installed, else 'python'
print(is_fortran_available()) # True if Fortran backend was compiled
Architecture
Fortran Common Blocks β Python Dataclasses
The Fortran code uses common blocks for shared state. These are replicated as Python dataclasses:
| Fortran Block | Python Class | Contents |
|---|---|---|
/CONST/ |
ConstBlock |
Thermodynamic properties (Antoine coefficients, enthalpies, densities) |
/TEPROC/ |
TEProcBlock |
Process state (temperatures, pressures, compositions, flows) |
/WLK/ |
WalkBlock |
Disturbance random walk parameters |
/RANDSD/ |
Instance variable _g |
Random number generator seed |
/PV/ |
_xmeas, _xmv arrays |
Measurements and manipulated variables |
/DVEC/ |
_idv array |
Disturbance vector |
Key Components
PythonTEProcess
βββ State Arrays
β βββ yy[50] - State variables
β βββ yp[50] - State derivatives
β βββ _xmeas[41] - Measurements
β βββ _xmv[12] - Manipulated variables
β βββ _idv[20] - Disturbances
βββ Common Blocks
β βββ _const - Physical constants (ConstBlock)
β βββ _teproc - Process state (TEProcBlock)
β βββ _wlk - Random walk state (WalkBlock)
βββ Initialization
β βββ teinit() - Initialize process to steady state
βββ Simulation
βββ tefunc() - Compute derivatives (called by integrator)
Helper Functions (TESUB1-8)
| Function | Purpose |
|---|---|
_tesub1 |
Reaction rate kinetics |
_tesub2 |
Reaction rate calculations |
_tesub3 |
Vapor-liquid equilibrium (VLE) |
_tesub4 |
Flash calculations for separator/stripper |
_tesub5 |
Liquid enthalpy calculation |
_tesub6 |
Vapor enthalpy calculation |
_tesub7 |
Linear congruential random number generator |
_tesub8 |
Disturbance random walk updates |
Random Number Generator
The Python backend replicates the Fortran linear congruential generator exactly:
def _tesub7(self, i: int) -> float:
"""Linear congruential RNG matching Fortran."""
self._g = (self._g * 9228907.0) % 4294967296.0
if i >= 0:
return self._g / 4294967296.0 # [0, 1)
else:
return 2.0 * self._g / 4294967296.0 - 1.0 # (-1, 1)
Setting the same seed produces identical random sequences:
# Reproducible simulation
sim = TEPSimulator(backend='python', random_seed=1234)
Statistical Validation
The Python backend was validated against reference data (d00.dat) from the original Fortran simulator using 24-hour simulations to capture full process dynamics.
Mean Value Comparison (24-hour simulation)
| XMEAS | Description | Python Mean | Reference Mean | Rel. Diff (%) |
|---|---|---|---|---|
| 1 | A Feed (kscmh) | 0.2501 | 0.2511 | 0.42 |
| 2 | D Feed (kg/hr) | 3658.15 | 3663.54 | 0.15 |
| 3 | E Feed (kg/hr) | 4513.99 | 4511.52 | 0.05 |
| 4 | A+C Feed (kscmh) | 9.344 | 9.344 | <0.01 |
| 5 | Recycle Flow | 26.903 | 26.908 | 0.02 |
| 6 | Reactor Feed | 42.335 | 42.339 | 0.01 |
| 7 | Reactor Pressure | 2706.5 | 2705.4 | 0.04 |
| 8 | Reactor Level | 75.00 | 74.98 | 0.03 |
| 9 | Reactor Temp | 120.40 | 120.40 | <0.01 |
| 10 | Purge Rate | 0.336 | 0.338 | 0.36 |
| 11 | Sep Temp | 80.06 | 80.09 | 0.04 |
| 12 | Sep Level | 50.00 | 50.06 | 0.14 |
| 13 | Sep Pressure | 2635.2 | 2634.2 | 0.04 |
| 14 | Sep Underflow | 25.16 | 25.12 | 0.17 |
| 15 | Stripper Level | 50.00 | 49.99 | 0.01 |
| 16 | Stripper Pressure | 3103.7 | 3102.5 | 0.04 |
| 17 | Stripper Underflow | 22.95 | 22.91 | 0.17 |
| 18 | Stripper Temp | 65.81 | 65.72 | 0.13 |
| 19 | Stripper Steam | 233.5 | 230.3 | 1.38 |
| 20 | Compr Work | 341.4 | 341.5 | 0.02 |
| 21 | Reactor CW Out | 94.62 | 94.60 | 0.02 |
| 22 | Sep CW Out | 77.27 | 77.29 | 0.02 |
21 of 22 measurements within 0.5%, all within 1.5% of reference.
Variance Comparison
Process variance ratios (Python std / Reference std) range from 0.97 to 1.43, indicating that the process dynamics and disturbance characteristics are correctly replicated.
| Measurement Group | Variance Ratio Range |
|---|---|
| Flow rates (1-6, 10, 14, 17) | 0.97 - 1.18 |
| Pressures (7, 13, 16) | 1.39 - 1.42 |
| Levels (8, 12, 15) | 0.98 - 1.04 |
| Temperatures (9, 11, 18, 21-22) | 0.99 - 1.43 |
| Heat/Work (19, 20) | 1.21 - 1.36 |
Sources of Remaining Differences
- Random seed sequences: Python and Fortran simulations use different random noise realizations
- Short-term transients: First few hours show larger differences due to controller transients
- Numerical precision: Minor floating-point differences accumulate over long simulations
- Reference conditions: Reference data may have slightly different initial conditions
Conclusion: The Python backend is statistically equivalent to the Fortran implementation for practical applications.
Performance Considerations
| Aspect | Fortran Backend | Python Backend |
|---|---|---|
| Speed | ~5-10x faster | Baseline |
| Accuracy | Original code | Statistical match |
| Dependencies | gfortran required | numpy only |
| Portability | Platform-specific build | Any Python environment |
For production use with performance requirements, prefer the Fortran backend when available. The Python backend is ideal for:
- Environments without Fortran compilers
- Educational and prototyping purposes
- Debugging and understanding process dynamics
- Cross-platform deployment
Usage Examples
Basic Simulation
from tep import TEPSimulator
sim = TEPSimulator(backend='python')
result = sim.simulate(duration_hours=1.0)
print(f"Final reactor temp: {result.measurements[-1, 8]:.2f} C")
print(f"Final reactor pressure: {result.measurements[-1, 6]:.2f} kPa")
With Fault Injection
sim = TEPSimulator(backend='python')
sim.set_disturbance(1, True) # IDV(1): A/C feed ratio step
result = sim.simulate(duration_hours=2.0)
Direct Backend Access
from tep import PythonTEProcess
# Low-level access
process = PythonTEProcess(random_seed=42)
process.teinit()
# Get state and derivatives
time = 0.0
process.tefunc(time)
print(f"State derivatives: {process.yp[:5]}")
Testing
Run the Python backend tests:
pytest tests/test_python_backend.py -v
Tests include:
- Initialization correctness
- Random seed reproducibility
- Disturbance activation
- Thermodynamic calculations (Antoine, enthalpy, density)
- Comparison against Fortran backend (when available)
- Validation against reference trajectories