A rigorous multi-component distillation column simulator, originally written in Fortran in the mid-1990s by Roger Boulton, Emeritus Distinguished Professor of Enology and of Chemical Engineering, University of California, Davis.
The code has been converted to Python and wrapped in a modern FastAPI + React web application.
The original Fortran program (Fortran Source/MULTST.FOR) solved the full MESH equations (Material balances, Equilibrium relationships, Summation, and Heat/enthalpy balances) simultaneously across all stages using Newton–Raphson iteration on a block-tridiagonal Jacobian. It was demonstrated on a 10-component brandy distillate system — ethanol, water, and eight congeners — but applies to any distillate if the component set and concentrations are changed.
"This from the mid 1990s … I never published it but is far beyond anything I have seen that is relevant to brandy, but all distillates if the components and concentrations are varied." — Roger Boulton
.
├── distillation.py # Core solver (Python/NumPy port of MULTST.FOR)
├── backend/
│ ├── main.py # FastAPI REST API
│ ├── requirements.txt # Python dependencies
│ └── test_api.py # Quick smoke test
├── frontend/
│ ├── src/
│ │ ├── App.jsx # Main layout and state
│ │ ├── App.css # Styles
│ │ ├── api.js # HTTP client
│ │ └── components/
│ │ ├── ConfigPanel.jsx # Input form
│ │ ├── ResultsPanel.jsx # Charts and table
│ │ └── ProfileChart.jsx # Recharts wrapper
│ ├── package.json
│ └── vite.config.js # Dev proxy → backend
├── Fortran Source/
│ ├── MULTST.FOR # Original Fortran source
│ ├── TEN.DAT # 10-component input file
│ └── 10CPTPR.OUT # Reference output
├── distillation_output.txt # Python solver output (matches Fortran exactly)
└── start.ps1 # One-command launcher (Windows PowerShell)
- Python 3.11+ (
numpy,fastapi,uvicorn— seebackend/requirements.txt) - Node.js 18+
# Python backend
pip install -r backend/requirements.txt
# Node frontend
cd frontend
npm install
cd ..Option A — single script (Windows):
.\start.ps1Opens two terminal windows (backend on port 8000, frontend on port 5173).
Option B — manually in two terminals:
# Terminal 1 — backend
cd backend
python -m uvicorn main:app --reload --port 8000
# Terminal 2 — frontend
cd frontend
npm run devThen open http://localhost:5173 in your browser.
The interactive API documentation is at http://localhost:8000/docs.
The UI pre-loads the 10-component brandy system from TEN.DAT. Hit Run Simulation to solve immediately. All inputs are editable:
| Section | What you can change |
|---|---|
| Column Specification | Stages, distillate rate, reflux ratio, pressure, temperature bounds |
| Murphree Efficiency | Uniform stage efficiency (0–1) |
| Feed | Stage, temperature, vapour fraction, all 10 component flow rates |
| Liquid Sidestreams | Stage and flow rate |
| Components | Antoine constants, enthalpy parameters, K-factor polynomial coefficients (advanced) |
Results are shown as:
- Summary cards — convergence status, top proof, reboiler/condenser duties
- Convergence history — residual at each Newton–Raphson iteration
- Charts — temperature, proof, ethanol mole fraction, congener profiles, K-factors
- Data table — full per-stage numerical output
- Initial guess — constant-molar-flow approximation per column section
- Newton–Raphson loop — iterates until sum-of-squares of corrections < 0.1
- Block-tridiagonal Jacobian — exploits the stage-to-stage coupling structure; each stage couples only to its neighbours, reducing the linear solve from O(N³) to O(N)
- Gaussian elimination with partial pivoting (
_gausl) — in double precision for numerical stability
| Component pair | Model |
|---|---|
| Ethanol – water | Van Laar activity coefficients (A₁₂ = 0.7715, A₂₁ = 0.3848) |
| Congeners (3–10) | Polynomial K-factor as a function of ethanol mole fraction (7 coefficients per component) |
| All vapour pressures | Antoine equation — input in log₁₀/mmHg, converted internally to ln/atm |
| Enthalpies | Linear: H = H₀ + Cₚ·T |
| Stage efficiency | Murphree (per-stage or uniform) |
| # | Component | Role |
|---|---|---|
| 1 | Ethanol | Primary alcohol |
| 2 | Water | Co-solvent |
| 3 | Acetic acid | Volatile acid |
| 4 | Acetaldehyde | Aldehyde (heads fraction) |
| 5 | Isobutanol | Fusel alcohol |
| 6 | Isoamyl alcohol | Fusel alcohol |
| 7 | Propanol | Fusel alcohol |
| 8 | Isoamyl acetate | Ester |
| 9 | Ethyl acetate | Ester |
| 10 | Furfural | Aldehyde |
The Python solver replicates the original Fortran output to 4 significant figures for all 26 stages and 10 components. The default case converges in 7 Newton–Raphson iterations.
Reference values (26-stage column, reflux = 70, feed at stage 6):
| Stage | T (°C) | FL total | Ethanol FL |
|---|---|---|---|
| 1 (reboiler) | 99.98 | 46.65 | 0.002 |
| 6 (feed) | 96.11 | 70.92 | 0.965 |
| 26 (condenser) | 78.63 | 24.50 | 17.993 |
Returns the default 10-component configuration as JSON (suitable for direct use as a POST /api/simulate body).
Request body schema:
{
"components": [
{
"name": "Ethanol",
"antoine": [8.04494, 1554.30, 222.65],
"enthalpy": [0.0, 28.0, 9200.0, 28.0],
"kconst": [1111.11, 1111.11, 1111.11, 1111.11, 1111.11, 1111.11, 1111.11]
}
],
"column": { "nst": 26, "dest": 0.35, "rflx": 70.0, "p": 1.0, "tt": 78.3, "tb": 100.0 },
"murphree": { "uniform": true, "value": 0.5 },
"feeds": [
{ "stage": 6, "tf": 90.0, "fkv": 0.0, "flows": [2.011, 47.983, ...] }
],
"liquid_sidestreams": [{ "stage": 22, "flow": 3.0 }],
"vapor_sidestreams": []
}Response includes converged, n_iterations, residuals, stages (full per-stage profiles), heat_duties, and component_names.
Full schema available at /docs.
The solver can also be used directly without the web layer:
from distillation import simulate
result = simulate(config_dict) # same schema as the POST body
for stage in result["stages"]:
print(stage["stage"], stage["temperature"], stage["proof_liquid"])For file-based use (reads Fortran fixed-format input, writes formatted output):
from distillation import run_from_file
run_from_file("Fortran Source/TEN.DAT", "output.txt")- More components — add entries to the
componentslist; increasenkaccordingly. The solver is dimensioned for up to 10 components by the current array sizes indistillation.py(trivially extended). - Different solvents — replace the Van Laar constants in
_mquac()or generalise to a full UNIQUAC/NRTL model. - Non-uniform efficiencies — pass
murphree.per_stageas an array. - Multiple feeds — add entries to
feeds; each specifies its own stage, temperature, vapour fraction, and composition.
Original Fortran code: Roger Boulton, Emeritus Distinguished Professor of Enology and of Chemical Engineering, Department of Viticulture and Enology, University of California, Davis.
Python conversion and web application: developed in collaboration with Claude (Anthropic).