Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Run tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e .[test]
- name: Test with pytest
run: |
pytest tests/ -v
96 changes: 93 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,98 @@
<img src="https://raw.githubusercontent.com/pathsim/pathsim-chem/main/docs/source/logos/chem_logo.png" width="300" alt="PathSim-Chem Logo" />
</p>

------------
<p align="center">
<strong>Chemical engineering blocks for PathSim</strong>
</p>

<p align="center">
<a href="https://pypi.org/project/pathsim-chem/"><img src="https://img.shields.io/pypi/v/pathsim-chem" alt="PyPI"></a>
<img src="https://img.shields.io/github/license/pathsim/pathsim-chem" alt="License">
</p>

<p align="center">
<a href="https://docs.pathsim.org/chem">Documentation</a> &bull;
<a href="https://pathsim.org">PathSim Homepage</a> &bull;
<a href="https://github.com/pathsim/pathsim-chem">GitHub</a>
</p>

---

PathSim-Chem extends the [PathSim](https://github.com/pathsim/pathsim) simulation framework with blocks for chemical engineering and thermodynamic property calculations. All blocks follow the standard PathSim block interface and can be connected into simulation diagrams.

## Features

- **IK-CAPE Thermodynamics** — 50+ blocks implementing the DECHEMA IK-CAPE standard for thermodynamic property calculations
- **Pure Component Correlations** — Antoine, Wagner, Kirchhoff, Rackett, Aly-Lee, DIPPR, and 10 more temperature-dependent property correlations
- **Mixing Rules** — Linear, quadratic, Lorentz-Berthelot, and other calculation-of-averages rules for mixture properties
- **Activity Coefficients** — NRTL, Wilson, UNIQUAC, and Flory-Huggins models for liquid-phase non-ideality
- **Equations of State** — Peng-Robinson and Soave-Redlich-Kwong cubic EoS with mixture support
- **Fugacity Coefficients** — EoS-based and virial equation fugacity calculations
- **Excess Enthalpy & Departure** — NRTL, UNIQUAC, Wilson, Redlich-Kister excess enthalpy and EoS departure functions
- **Chemical Reactions** — Equilibrium constants, kinetic rate constants, and power-law rate expressions
- **Tritium Processing** — GLC columns, TCAP cascades, bubblers, and splitters for tritium separation

## Install

```bash
pip install pathsim-chem
```

## Quick Example

Compute the vapor pressure of water at 100 °C using the Antoine equation:

```python
from pathsim_chem.thermodynamics import Antoine

# Antoine coefficients for water (NIST)
antoine = Antoine(a0=23.2256, a1=3835.18, a2=-45.343)

# Evaluate at 373.15 K
antoine.inputs[0] = 373.15
antoine.update(None)
P_sat = antoine.outputs[0] # ≈ 101325 Pa
```

Use activity coefficients in a simulation:

```python
from pathsim_chem.thermodynamics import NRTL

# Ethanol-water NRTL model
nrtl = NRTL(
x=[0.4, 0.6],
a=[[0, -0.801], [3.458, 0]],
c=[[0, 0.3], [0.3, 0]],
)

# Evaluate at 350 K
nrtl.inputs[0] = 350
nrtl.update(None)
gamma_ethanol = nrtl.outputs[0]
gamma_water = nrtl.outputs[1]
```

## Modules

| Module | Description |
|---|---|
| `pathsim_chem.thermodynamics.correlations` | 16 pure component property correlations (Antoine, Wagner, etc.) |
| `pathsim_chem.thermodynamics.averages` | 10 mixing rules for calculation of averages |
| `pathsim_chem.thermodynamics.activity_coefficients` | NRTL, Wilson, UNIQUAC, Flory-Huggins |
| `pathsim_chem.thermodynamics.equations_of_state` | Peng-Robinson, Soave-Redlich-Kwong |
| `pathsim_chem.thermodynamics.corrections` | Poynting correction, Henry's law |
| `pathsim_chem.thermodynamics.fugacity_coefficients` | RKS, PR, and virial fugacity coefficients |
| `pathsim_chem.thermodynamics.enthalpy` | Excess enthalpy and enthalpy departure functions |
| `pathsim_chem.thermodynamics.reactions` | Equilibrium constants, rate constants, power-law rates |
| `pathsim_chem.tritium` | GLC, TCAP, bubbler, splitter blocks for tritium processing |

## Learn More

- [Documentation](https://docs.pathsim.org/chem) — API reference and examples
- [PathSim](https://github.com/pathsim/pathsim) — the core simulation framework
- [Contributing](https://docs.pathsim.org/pathsim/latest/contributing) — how to contribute

# PathSim Chemical Engineering Toolbox
## License

This toolbox extends the core pathsim package with domain specific component models for chemical engineering.
MIT
211 changes: 211 additions & 0 deletions docs/source/examples/equation_of_state.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Cubic Equations of State\n",
"\n",
"Simulating the compressibility factor of methane across a pressure sweep using the Peng-Robinson and Soave-Redlich-Kwong equations of state wired into a PathSim simulation."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Cubic equations of state compute the compressibility factor $Z = Pv/(RT)$ by solving a cubic polynomial at each $(T, P)$ condition. The EoS blocks take two inputs (temperature and pressure) and produce two outputs (molar volume and compressibility factor).\n",
"\n",
"At low pressure $Z \\to 1$ (ideal gas). At moderate pressure attractive forces cause $Z < 1$, and at high pressure excluded-volume effects push $Z > 1$."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"\n",
"from pathsim import Simulation, Connection\n",
"from pathsim.blocks import Source, Constant, Scope\n",
"\n",
"from pathsim_chem.thermodynamics import PengRobinson, RedlichKwongSoave"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## System Definition\n",
"\n",
"Create Peng-Robinson and SRK blocks for pure methane. A `Constant` block supplies a fixed temperature while a `Source` sweeps pressure from 0.1 MPa to 30 MPa."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Critical properties of methane\n",
"Tc, Pc, omega = 190.6, 4.6e6, 0.011\n",
"\n",
"# EoS blocks\n",
"pr = PengRobinson(Tc=Tc, Pc=Pc, omega=omega)\n",
"rks = RedlichKwongSoave(Tc=Tc, Pc=Pc, omega=omega)\n",
"\n",
"# Fixed temperature, logarithmic pressure sweep\n",
"import numpy as np\n",
"T_const = Constant(250) # 250 K (above Tc, supercritical)\n",
"P_src = Source(func=lambda t: 10**(4 + t * 0.035)) # 10 kPa to ~30 MPa over 100s\n",
"\n",
"# Scopes: record Z from both EoS (output port 1)\n",
"scp_pr = Scope(labels=[\"Z_PR\"])\n",
"scp_rks = Scope(labels=[\"Z_RKS\"])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Wiring\n",
"\n",
"Both EoS blocks receive the same $(T, P)$ inputs. We record the compressibility factor (output port 1) from each."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"sim = Simulation(\n",
" blocks=[T_const, P_src, pr, rks, scp_pr, scp_rks],\n",
" connections=[\n",
" # Temperature -> both EoS (input port 0)\n",
" Connection(T_const, pr, rks),\n",
" # Pressure -> both EoS (input port 1)\n",
" Connection(P_src, pr[1], rks[1]),\n",
" # Z output (port 1) -> scopes\n",
" Connection(pr[1], scp_pr),\n",
" Connection(rks[1], scp_rks),\n",
" ],\n",
" dt=1.0,\n",
")\n",
"\n",
"sim.run(100)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"time, Z_pr = scp_pr.read()\n",
"_, Z_rks = scp_rks.read()\n",
"P_vals = 10**(4 + time * 0.035) / 1e6 # MPa\n",
"\n",
"fig, ax = plt.subplots(figsize=(7, 5))\n",
"ax.semilogx(P_vals, Z_pr[0], label=\"Peng-Robinson\")\n",
"ax.semilogx(P_vals, Z_rks[0], \"--\", label=\"Soave-Redlich-Kwong\")\n",
"ax.axhline(1.0, color=\"gray\", linestyle=\"-.\", alpha=0.5, label=\"Ideal gas\")\n",
"ax.set_xlabel(\"Pressure [MPa]\")\n",
"ax.set_ylabel(\"Compressibility Factor Z\")\n",
"ax.set_title(\"Methane at T = 250 K\")\n",
"ax.legend()\n",
"ax.grid(True, alpha=0.3)\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Both EoS give very similar results. The characteristic dip below $Z = 1$ at moderate pressures reflects attractive intermolecular forces, while the rise above $Z = 1$ at high pressures is due to repulsive (excluded volume) effects."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Mixture\n",
"\n",
"The EoS blocks also support mixtures through van der Waals one-fluid mixing rules. Here we set up a methane-ethane mixture and sweep pressure."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"pr_mix = PengRobinson(\n",
" Tc=[190.6, 305.3],\n",
" Pc=[4.6e6, 4.872e6],\n",
" omega=[0.011, 0.099],\n",
" x=[0.7, 0.3],\n",
")\n",
"\n",
"T_const2 = Constant(300)\n",
"P_src2 = Source(func=lambda t: 10**(4 + t * 0.035))\n",
"scp_mix = Scope(labels=[\"Z_mixture\"])\n",
"\n",
"sim_mix = Simulation(\n",
" blocks=[T_const2, P_src2, pr_mix, scp_mix],\n",
" connections=[\n",
" Connection(T_const2, pr_mix),\n",
" Connection(P_src2, pr_mix[1]),\n",
" Connection(pr_mix[1], scp_mix),\n",
" ],\n",
" dt=1.0,\n",
")\n",
"\n",
"sim_mix.run(100)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"time_m, Z_mix = scp_mix.read()\n",
"P_mix = 10**(4 + time_m * 0.035) / 1e6\n",
"\n",
"fig, ax = plt.subplots(figsize=(7, 5))\n",
"ax.semilogx(P_vals, Z_pr[0], label=\"Pure CH₄ (250 K)\")\n",
"ax.semilogx(P_mix, Z_mix[0], \"--\", label=\"70/30 CH₄-C₂H₆ (300 K)\")\n",
"ax.axhline(1.0, color=\"gray\", linestyle=\"-.\", alpha=0.5)\n",
"ax.set_xlabel(\"Pressure [MPa]\")\n",
"ax.set_ylabel(\"Compressibility Factor Z\")\n",
"ax.set_title(\"Peng-Robinson: Pure vs Mixture\")\n",
"ax.legend()\n",
"ax.grid(True, alpha=0.3)\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The mixture shows a deeper dip because ethane ($T_c = 305.3$ K) is near its critical temperature at 300 K, leading to stronger non-ideal behavior."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.11.0"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
Loading