jkitchin's picture
Upload folder using huggingface_hub
f5f42f3 verified

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

  1. Random seed sequences: Python and Fortran simulations use different random noise realizations
  2. Short-term transients: First few hours show larger differences due to controller transients
  3. Numerical precision: Minor floating-point differences accumulate over long simulations
  4. 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