Skip to main content

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.

PyPI Python License: MIT

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 Pillowimport PIL or python-dateutilimport 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

For a zero-setup option, the repo ships a self-contained Dockerfile that bundles JupyterLab + vle-thermo + the example notebooks. Build and run it locally:

git clone https://github.com/miguelju/vle.git
cd vle
docker build -f deploy/docker/Dockerfile.standalone -t vle-standalone .
docker run --rm -p 8888:8888 vle-standalone

A publish script (deploy/scripts/publish-docker.sh) is also included if you want to host the image yourself at ghcr.io/<your-user>/vle-thermo for others to docker pull. CI does not currently publish an upstream image — see 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 (Python dict ↔ Rust HashMap, Python list ↔ Rust Vec, etc.), GIL acquisition, and turning a Result::Err(...) into a Python raise.

  • 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:

  1. Ships as a normal pip install (no separate Rust toolchain for the end user).
  2. Works on every OS Python supports (Linux x86_64/aarch64, macOS arm64, Windows).
  3. Marshals types automatically across the boundary.
  4. Bridges error handling (Rust Result → Python exception).

The realistic alternatives:

  • setuptools-rust — works, but predates pyproject.toml and requires a setup.py shim. 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:

  1. Compile the Rust crate as a cdylib (C-compatible dynamic library) with PyO3's #[pymodule] entry-point function compiled in.
  2. Link against the right Python ABI. PyO3's abi3-py310 feature 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).
  3. Rename the resulting .so to Python's import convention (_engine.abi3.so for abi3, _engine.cpython-310-darwin.so otherwise).
  4. Pack that file plus the pure-Python sources into a standards-compliant .whl (the binary distribution format pip understands), with the right ABI/platform tags in the filename so pip picks the matching wheel for the user's machine.
  5. Repeat (1)–(4) for every (OS, arch) combination in CI — cibuildwheel calls 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 .so into python/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 .whl you can pip install or upload to PyPI.

Tracing a call across the boundary

To see all of this concretely, follow the version() call:

  1. Rust sideengine/src/py_bindings.rs declares #[pyfunction] fn version() -> &'static str and registers it inside #[pymodule] fn _engine(...). PyO3's macros expand these into CPython-callable C functions plus the PyInit__engine symbol the OS loader needs.
  2. Buildmaturin develop compiles engine/ into python/src/vle/_engine.abi3.so with that PyInit__engine symbol.
  3. Python sidepython/src/vle/__init__.py does from vle._engine import version. The first time Python imports vle._engine, CPython dlopens the .so, calls PyInit__engine, and gets a module object with version already bound.
  4. Runtimevle.version() is now a plain Python function call. PyO3's generated wrapper acquires the GIL, calls into the Rust implementation, converts the returned &'static str to a Python str, 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vle_thermo-0.3.2.tar.gz (94.6 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

vle_thermo-0.3.2-cp310-abi3-win_amd64.whl (236.8 kB view details)

Uploaded CPython 3.10+Windows x86-64

vle_thermo-0.3.2-cp310-abi3-manylinux_2_28_x86_64.whl (375.3 kB view details)

Uploaded CPython 3.10+manylinux: glibc 2.28+ x86-64

vle_thermo-0.3.2-cp310-abi3-manylinux_2_28_aarch64.whl (366.7 kB view details)

Uploaded CPython 3.10+manylinux: glibc 2.28+ ARM64

vle_thermo-0.3.2-cp310-abi3-macosx_11_0_arm64.whl (326.4 kB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

File details

Details for the file vle_thermo-0.3.2.tar.gz.

File metadata

  • Download URL: vle_thermo-0.3.2.tar.gz
  • Upload date:
  • Size: 94.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for vle_thermo-0.3.2.tar.gz
Algorithm Hash digest
SHA256 17c6f540f24ae3ef336b044f1d09bc4e63aff0e00d1ca8569d07aa7d9edc5f6f
MD5 9ed4ff0e2c5574cf5e28b246b55ec6b2
BLAKE2b-256 e618b1db3f8162d284839d28e78c4f747502230c741a1e8db47f930786837836

See more details on using hashes here.

Provenance

The following attestation bundles were made for vle_thermo-0.3.2.tar.gz:

Publisher: release.yml on miguelju/vle

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file vle_thermo-0.3.2-cp310-abi3-win_amd64.whl.

File metadata

  • Download URL: vle_thermo-0.3.2-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 236.8 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

Hashes for vle_thermo-0.3.2-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 6d1651a970f000a3afb5ac0f33c61db9d42361103720df43bd80e039a8fd7b4b
MD5 5c0516e77652b6f917f6e91f28acee9b
BLAKE2b-256 316f46fbb05328c24d05ec338c1cba305d5daf07012c0c289d754397a67679bc

See more details on using hashes here.

Provenance

The following attestation bundles were made for vle_thermo-0.3.2-cp310-abi3-win_amd64.whl:

Publisher: release.yml on miguelju/vle

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file vle_thermo-0.3.2-cp310-abi3-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for vle_thermo-0.3.2-cp310-abi3-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 617235590edcac4fb272c715823b5122a64c425992752223c7431893643457e2
MD5 c932686e5ad5cba5f2f9d94285b4bc39
BLAKE2b-256 1e781820e9376975bd78eaffd445de9c2634b4e512c975b334fb005dc00420c3

See more details on using hashes here.

Provenance

The following attestation bundles were made for vle_thermo-0.3.2-cp310-abi3-manylinux_2_28_x86_64.whl:

Publisher: release.yml on miguelju/vle

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file vle_thermo-0.3.2-cp310-abi3-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for vle_thermo-0.3.2-cp310-abi3-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 e67480da84d83e0b5fcdc09a96528c0ee4596e1c5666d21079b3f7ed70729d00
MD5 1d2f83e635ff70e0c91cd36b78a08e53
BLAKE2b-256 99db62d7d4a2f26a2b98f59c7e556b6a07ab5e29d4b6435b42cb3f33cf48a2a2

See more details on using hashes here.

Provenance

The following attestation bundles were made for vle_thermo-0.3.2-cp310-abi3-manylinux_2_28_aarch64.whl:

Publisher: release.yml on miguelju/vle

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file vle_thermo-0.3.2-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for vle_thermo-0.3.2-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 343f69f79b97f197f60eed075dd36d96ff898738bdcf32c6191eaca2c1a9a761
MD5 ff7be860e3d6785edc1d88bf25a86f79
BLAKE2b-256 e60e2128ec111a0b6f63b9a5ec937ffabc1cc4049003b6f49d204a5b11349150

See more details on using hashes here.

Provenance

The following attestation bundles were made for vle_thermo-0.3.2-cp310-abi3-macosx_11_0_arm64.whl:

Publisher: release.yml on miguelju/vle

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page