Vapor-liquid equilibrium (VLE) thermodynamic calculator: 22+ cubic EOS, activity models, flash algorithms
Project description
vle-thermo
Vapor-liquid equilibrium (VLE) thermodynamic calculator with a Rust computation engine and a Python interface.
A modern Rust + Python port of two legacy thermodynamic codebases (VB6 ~15,000
lines + Pascal ~2,500 lines). The Rust core (vle-thermo crate) does the
computation; this Python package wraps it for interactive use in scripts and
Jupyter notebooks.
Install
pip install vle-thermo
Optional extras:
pip install "vle-thermo[plot]" # adds matplotlib
pip install "vle-thermo[db]" # adds `thermo` for extended component-database seeding
pip install "vle-thermo[dev]" # adds pytest, maturin
The distribution name on PyPI is vle-thermo, but the import name is
vle (following the common distribution-vs-import split, like
Pillow → import PIL or python-dateutil → import dateutil):
import vle
Status
0.1.x — the component database, CLI, and units layer work today. Numerical
kernels (flash algorithms, equation-of-state solvers, parameter regression) are
under active development. Treat 0.1.x as pre-release; semver promises begin
at 1.0.
See the roadmap for what's shipped vs. planned.
What works today
# Initialize the bundled component database (SQLite) and seed with the 15
# compounds from Chapter IV of the source thesis.
vle-db init
vle-db seed --source chapter4
# Browse and inspect
vle-db list
vle-db show methane
vle-db validate chapter4
from vle.db import list_components, get_component
for c in list_components():
print(c.name, c.tc, c.pc, c.omega)
methane = get_component("methane")
Unit-aware input/output (gauge pressure, °C, °F, psi, barg, mmHg, …):
from vle.units import ureg, Q_
T = Q_(25, "degC") # 298.15 K internally
P = Q_(3.5, "bar").to("kPa") # 350 kPa
Features (full roadmap)
- 22+ cubic equations of state — Peng-Robinson, RKS, van der Waals, Schmidt-Wenzel, Patel-Teja, and more
- 5 activity coefficient models — Wilson, van Laar, Margules, Scatchard-Hildebrand, Ideal
- 11 mixing rules — Classical (IVDW, IIVDW), Wong-Sandler, Huron-Vidal, MHV1/MHV2
- 6 saturation pressure correlations — Antoine, Riedel, Müller, RPM
- 6 flash calculation types — bubble/dew point (T/P), isothermal (Rachford-Rice), adiabatic
- Parameter regression — kij (binary interaction) and Aij (activity model)
Use it in Jupyter
A curated set of notebooks reproducing Chapter IV of the source thesis ships alongside the project at https://github.com/miguelju/vle/tree/main/notebooks. To run them in your own environment:
pip install "vle-thermo[plot]" jupyterlab
git clone https://github.com/miguelju/vle.git
cd vle/notebooks
jupyter lab
See deploy/NOTEBOOKS.md
for the full host-agnostic guide and
deploy/README.md
for the distribution story.
How the Python package wraps Rust — maturin + PyO3
This project is partly educational, so it's worth explaining the build glue in detail. Two tools split the work:
-
PyO3 is a Rust crate that handles the FFI bridge at runtime. You annotate Rust functions with
#[pyfunction]and Rust types with#[pyclass], and PyO3's procedural macros generate all the CPython C-API calls — argument unpacking, type conversion (Pythondict↔ RustHashMap, Pythonlist↔ RustVec, etc.), GIL acquisition, and turning aResult::Err(...)into a Pythonraise. -
maturin is a build tool that packages a PyO3-using Rust crate into a Python wheel at build time. PyO3 produces a Rust crate that can be a Python extension; maturin does the work of actually shipping it as one.
What "FFI" means in "FFI bridge". FFI stands for Foreign Function
Interface — the conventions and machinery that let code in one language
call functions written in another. Each language runtime has its own ideas
about how arguments are passed, how strings are laid out in memory, how
errors propagate, and how memory ownership works; you can't just call a
Rust function from Python directly any more than you can plug a US power
cord into a UK outlet. The universal adapter in practice is the C ABI
(Application Binary Interface): C compilers all agree on how arguments are
placed in registers and on the stack, how function names appear in the
symbol table, and how stack frames are laid out. Any language that can
produce C-compatible function signatures (Rust, Go, Zig, Swift) can be
called by any language that can call C (Python, Ruby, Lua, JavaScript via
N-API). PyO3 sits exactly on top of that contract: its macros generate
C-ABI functions from your Rust code, give them CPython-shaped signatures
(taking PyObject* arguments, returning PyObject*), and export the
PyInit_<modulename> symbol CPython's import loader looks for. That's the
"runtime" part of "runtime FFI bridge" — code that runs on every call
into the extension module to convert Python values into Rust values, drive
the Rust implementation, and convert the result back. (The build-time
counterpart — turning the cdylib into an importable .so and a
pip-installable wheel — is the part maturin handles.)
Why this stack (vs. the alternatives)
The numerical kernel needs to be fast — Python alone isn't — but Python is where the user lives (Jupyter, scripts, the data-science ecosystem). So we needed something that:
- Ships as a normal
pip install(no separate Rust toolchain for the end user). - Works on every OS Python supports (Linux x86_64/aarch64, macOS arm64, Windows).
- Marshals types automatically across the boundary.
- Bridges error handling (Rust
Result→ Python exception).
The realistic alternatives:
setuptools-rust— works, but predatespyproject.tomland requires asetup.pyshim. More moving parts.- A hand-rolled
setup.py+ cargo invocation — possible, fragile, reinvents wheel-packaging logic. cffi— only handles C-style FFI, not the higher-level PyO3 ergonomics (typed Python classes, automatic GIL handling, exception bridging).
maturin is the build tool the PyO3 maintainers built and recommend — it's
specifically aware of PyO3's abi3 mode, the wheel ABI tags, and the
cross-compilation gotchas. The full build configuration is one TOML block.
What maturin actually does
A Python "native extension module" is a shared library — .so on Linux,
.dylib on macOS, .pyd on Windows — that CPython's import machinery can
dlopen and find a PyInit_<modulename> symbol in. To produce one from a
PyO3 Rust crate, maturin runs the following pipeline:
- Compile the Rust crate as a
cdylib(C-compatible dynamic library) with PyO3's#[pymodule]entry-point function compiled in. - Link against the right Python ABI. PyO3's
abi3-py310feature builds one wheel that loads on CPython 3.10, 3.11, 3.12, 3.13, and every future 3.x — instead of one wheel per Python version, you ship one per (OS, arch). - Rename the resulting
.soto Python's import convention (_engine.abi3.sofor abi3,_engine.cpython-310-darwin.sootherwise). - Pack that file plus the pure-Python sources into a standards-compliant
.whl(the binary distribution formatpipunderstands), with the right ABI/platform tags in the filename sopippicks the matching wheel for the user's machine. - Repeat (1)–(4) for every (OS, arch) combination in CI —
cibuildwheelcalls maturin once per platform, producing the matrix of pre-built wheels you see on PyPI.
The end user types pip install vle-thermo, pip selects the wheel matching
their platform, and the Rust code lands on their machine already compiled.
No Rust toolchain required.
What that looks like in this repo
engine/ Rust crate
├── Cargo.toml ├── crate-type = ["cdylib", "rlib"]
│ └── pyo3 dep, gated behind the "python" feature
└── src/py_bindings.rs the #[pyfunction] + #[pymodule] glue lives here
python/ Python package
├── pyproject.toml [tool.maturin] points at ../engine/Cargo.toml
└── src/vle/ pure-Python code (vle.db, vle.units, vle.cli, …)
├── _engine.abi3.so ← dropped here by maturin at install time
└── __init__.py re-exports from vle._engine + Python helpers
The entire build configuration is the [tool.maturin] block in
python/pyproject.toml:
[tool.maturin]
manifest-path = "../engine/Cargo.toml" # which Rust crate to build
features = ["python"] # enables PyO3 in engine/Cargo.toml
python-source = "src" # vle.py files live in src/vle/
module-name = "vle._engine" # the cdylib becomes this module
Two commands matter day-to-day:
maturin develop— for local development. Builds the Rust crate, drops the resulting.sointopython/src/vle/, and installs the Python package into the active virtualenv in editable mode. Pure-Python edits show up immediately; Rust edits need a re-run.maturin build— for distribution. Produces a.whlyou canpip installor upload to PyPI.
Tracing a call across the boundary
To see all of this concretely, follow the version() call:
- Rust side —
engine/src/py_bindings.rsdeclares#[pyfunction] fn version() -> &'static strand registers it inside#[pymodule] fn _engine(...). PyO3's macros expand these into CPython-callable C functions plus thePyInit__enginesymbol the OS loader needs. - Build —
maturin developcompilesengine/intopython/src/vle/_engine.abi3.sowith thatPyInit__enginesymbol. - Python side —
python/src/vle/__init__.pydoesfrom vle._engine import version. The first time Python importsvle._engine, CPythondlopens the.so, callsPyInit__engine, and gets a module object withversionalready bound. - Runtime —
vle.version()is now a plain Python function call. PyO3's generated wrapper acquires the GIL, calls into the Rust implementation, converts the returned&'static strto a Pythonstr, and hands it back to the interpreter.
The takeaway: maturin is what makes step 2 a single command. PyO3 is what makes steps 1, 3, and 4 a handful of attributes instead of hundreds of lines of hand-written C glue. Together they collapse "ship Rust to Python users" into a normal Python development workflow.
Origin
Based on the thesis "Desarrollo de un Programa Computacional para el Cálculo del Equilibrio Líquido Vapor de Mezclas Multicomponentes bajo el Ambiente Windows" (Jackson & Mendible, Universidad Simón Bolívar, 1999), with additional models from Da Silva & Báez (1989). See the research paper (English translation) for algorithms, parameters, and their academic references.
License
MIT. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file vle_thermo-0.7.0.tar.gz.
File metadata
- Download URL: vle_thermo-0.7.0.tar.gz
- Upload date:
- Size: 116.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b63e479045e8d7c0872b644297e4fe9c3c7a82228a57c718e7bee54c1c0c9e9e
|
|
| MD5 |
9aec42a15822cfbae0c1e4f01c9f7d60
|
|
| BLAKE2b-256 |
2c0cd457eaeb6fbd022ba87e13ce27669bf688fada67e01f60d87c3af03945dd
|
Provenance
The following attestation bundles were made for vle_thermo-0.7.0.tar.gz:
Publisher:
release.yml on miguelju/vle
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vle_thermo-0.7.0.tar.gz -
Subject digest:
b63e479045e8d7c0872b644297e4fe9c3c7a82228a57c718e7bee54c1c0c9e9e - Sigstore transparency entry: 1917771701
- Sigstore integration time:
-
Permalink:
miguelju/vle@89a46eeddb8fb2666530cb62a44ea1e1323ea883 -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/miguelju
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@89a46eeddb8fb2666530cb62a44ea1e1323ea883 -
Trigger Event:
push
-
Statement type:
File details
Details for the file vle_thermo-0.7.0-cp310-abi3-win_amd64.whl.
File metadata
- Download URL: vle_thermo-0.7.0-cp310-abi3-win_amd64.whl
- Upload date:
- Size: 266.0 kB
- Tags: CPython 3.10+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a3f147b180568bc9f9c2c3008812e90cad321dd1e68d0487298ac2a7ca96273d
|
|
| MD5 |
9159936a030ad131c55903667b31dfc8
|
|
| BLAKE2b-256 |
4b8b676e64540262fdee4ce37f7457eae9a6a753e1110dba031f7c81f170e46a
|
Provenance
The following attestation bundles were made for vle_thermo-0.7.0-cp310-abi3-win_amd64.whl:
Publisher:
release.yml on miguelju/vle
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vle_thermo-0.7.0-cp310-abi3-win_amd64.whl -
Subject digest:
a3f147b180568bc9f9c2c3008812e90cad321dd1e68d0487298ac2a7ca96273d - Sigstore transparency entry: 1917772095
- Sigstore integration time:
-
Permalink:
miguelju/vle@89a46eeddb8fb2666530cb62a44ea1e1323ea883 -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/miguelju
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@89a46eeddb8fb2666530cb62a44ea1e1323ea883 -
Trigger Event:
push
-
Statement type:
File details
Details for the file vle_thermo-0.7.0-cp310-abi3-manylinux_2_28_x86_64.whl.
File metadata
- Download URL: vle_thermo-0.7.0-cp310-abi3-manylinux_2_28_x86_64.whl
- Upload date:
- Size: 405.3 kB
- Tags: CPython 3.10+, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1be35753a6daa405b02bf5cc89f7d8d28a4598c15a3b4b11d0033c3ef2eb5893
|
|
| MD5 |
1754b5f80bf737810a40db83b53b0a93
|
|
| BLAKE2b-256 |
e60bdea85763afeb3ddc3253eca666b1d2de2bf90622e4c9a8e0ee3d47d88384
|
Provenance
The following attestation bundles were made for vle_thermo-0.7.0-cp310-abi3-manylinux_2_28_x86_64.whl:
Publisher:
release.yml on miguelju/vle
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vle_thermo-0.7.0-cp310-abi3-manylinux_2_28_x86_64.whl -
Subject digest:
1be35753a6daa405b02bf5cc89f7d8d28a4598c15a3b4b11d0033c3ef2eb5893 - Sigstore transparency entry: 1917771848
- Sigstore integration time:
-
Permalink:
miguelju/vle@89a46eeddb8fb2666530cb62a44ea1e1323ea883 -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/miguelju
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@89a46eeddb8fb2666530cb62a44ea1e1323ea883 -
Trigger Event:
push
-
Statement type:
File details
Details for the file vle_thermo-0.7.0-cp310-abi3-manylinux_2_28_aarch64.whl.
File metadata
- Download URL: vle_thermo-0.7.0-cp310-abi3-manylinux_2_28_aarch64.whl
- Upload date:
- Size: 395.2 kB
- Tags: CPython 3.10+, manylinux: glibc 2.28+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5a021c55facc1686b16166ed1c73bdc8d1550bbef2a7f9f0f889880a1a788152
|
|
| MD5 |
73a06fe72758fa75215ef2689e964ac8
|
|
| BLAKE2b-256 |
b9f04ad18daaa1bdd682ac5ad879253d8bc0851ab8faaef73aec10e93d6515cb
|
Provenance
The following attestation bundles were made for vle_thermo-0.7.0-cp310-abi3-manylinux_2_28_aarch64.whl:
Publisher:
release.yml on miguelju/vle
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vle_thermo-0.7.0-cp310-abi3-manylinux_2_28_aarch64.whl -
Subject digest:
5a021c55facc1686b16166ed1c73bdc8d1550bbef2a7f9f0f889880a1a788152 - Sigstore transparency entry: 1917771990
- Sigstore integration time:
-
Permalink:
miguelju/vle@89a46eeddb8fb2666530cb62a44ea1e1323ea883 -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/miguelju
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@89a46eeddb8fb2666530cb62a44ea1e1323ea883 -
Trigger Event:
push
-
Statement type:
File details
Details for the file vle_thermo-0.7.0-cp310-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: vle_thermo-0.7.0-cp310-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 354.8 kB
- Tags: CPython 3.10+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bd357738b8a97301b28c5a92fde6107063876e07f5b57287d5999fb284e14e35
|
|
| MD5 |
b9f7fe9e5bd6ba6f39cd4df1debcab65
|
|
| BLAKE2b-256 |
b9f54893fe3160ace2f2c133a2c77d34a21e1391624d14b370342e64cffd19f2
|
Provenance
The following attestation bundles were made for vle_thermo-0.7.0-cp310-abi3-macosx_11_0_arm64.whl:
Publisher:
release.yml on miguelju/vle
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vle_thermo-0.7.0-cp310-abi3-macosx_11_0_arm64.whl -
Subject digest:
bd357738b8a97301b28c5a92fde6107063876e07f5b57287d5999fb284e14e35 - Sigstore transparency entry: 1917772242
- Sigstore integration time:
-
Permalink:
miguelju/vle@89a46eeddb8fb2666530cb62a44ea1e1323ea883 -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/miguelju
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@89a46eeddb8fb2666530cb62a44ea1e1323ea883 -
Trigger Event:
push
-
Statement type: