vle-units
Dimensional analysis and unit conversion for vapor-liquid equilibrium (VLE) and thermodynamic calculations.
Works standalone. Designed as the unit layer for
vle-thermo but useful in any Rust
project that needs thermodynamic-flavored units (gauge pressure, °C / °F,
kJ/kmol, barg, psig, mmHg, etc.).
Features
- Compile-time typed quantities via
uom— zero runtime cost, full dimension safety. Useuom::si::f64::*directly (ThermodynamicTemperature,Pressure,MolarEnergy,TemperatureInterval, …); the crate's canonical units (K, kPa, kJ/kmol, K) match VLE conventions. - Runtime unit registry — parse user-supplied strings like
"25 degC","3.5 barg","1 atm"at the FFI boundary. - Gauge ↔ absolute conversion with a runtime-configurable atmospheric pressure (never hardcoded). Default: 101.325 kPa.
- Extensible — define custom units and derived dimensions at runtime (TOML or direct API) without recompiling.
Install
[]
= "0.1"
Example
use UnitRegistry;
let mut reg = with_vle_defaults;
// Parse user input in arbitrary units.
let t = reg.parse?;
let p = reg.parse?;
assert!;
assert!; // gauge + P_atm
// Configure atmospheric pressure for a different location / altitude.
reg.set_atmospheric_pressure?;
# Ok::
Full API docs: https://docs.rs/vle-units.
How it works
vle-units is two thin layers stacked on top of uom:
┌────────────────────────────────────────┐
compile-time │ uom::si::f64::* │
(zero runtime cost) │ Pressure, ThermodynamicTemperature, │
│ MolarEnergy, TemperatureInterval, … │
│ — dimension mismatches caught by rustc│
└────────────────────────────────────────┘
▲
│ values enter in canonical
│ units (K, kPa, kJ/kmol, …)
│
┌────────────────────────────────────────┐
runtime │ vle_units::UnitRegistry │
(string-driven, extensible) │ • parses "3.5 barg", "25 degC", … │
│ • runtime-configurable P_atm for │
│ gauge ↔ absolute pressure │
│ • define() new units / load TOML │
│ • exposed to Python via PyO3 so │
│ `pint` shares the same source │
└────────────────────────────────────────┘
Why uom underneath. uom is the most mature Rust units library: ~50
quantity types, ~500 unit definitions, dimension checking via the type system
(typenum-encoded SI exponent vectors), and zero runtime overhead — a
Pressure<f64> compiles to the same code as a bare f64. We get all of that
"for free" and use uom's types directly in engine code, without wrapping
them in our own aliases.
Why a layer on top. uom is purely compile-time. It can't:
- Parse a string like
"3.5 barg"that arrived from a Jupyter notebook, a TOML config, or a CLI flag. - Carry a runtime-configurable offset — gauge pressure depends on atmospheric pressure, which the operator must be able to override (84.5 kPa at 1500 m elevation, etc.).
- Let downstream users register a brand-new unit without recompiling the library.
The UnitRegistry provides exactly those three things, and nothing more.
Once a value crosses into the engine it's a plain f64 in canonical units,
ready to be wrapped in a uom quantity for type-safe algebra.
Defining custom dimensions and units
The defaults cover the VLE quantities (temperature, pressure, molar energy/entropy/volume, amount). To work with any other dimension — length, mass, time, current, luminous intensity, or anything derived — define it on the registry at runtime. Length is a good walkthrough because it isn't in the defaults:
use ;
let mut reg = with_vle_defaults;
// SI exponent vector is (L, M, T, I, Θ, N, J). Length is L^1.
reg.define_dimension?;
// `scale` is meters per 1 of this unit. `offset = 0.0` for everything that
// isn't affine (°C/°F have a non-zero offset, length units don't).
for in
// Round-trips are now possible across every registered unit.
let one_mile = reg.parse?;
let in_meters = reg.from_canonical?;
let in_yards = reg.from_canonical?;
assert!;
assert!;
# Ok::
A few things worth noting:
- The
DimensionVectoris the SI 7-tuple — you can build any derived dimension by combining exponents (e.g. velocity =[1, 0, -1, 0, 0, 0, 0], force =[1, 1, -2, 0, 0, 0, 0]). - Aliasing a unit is just registering it twice under different names with
the same scale (e.g.
"mi"and"mile","in"and"inch"). - The same data can be loaded from a TOML file via
load_from_tomlif you'd rather keep the catalog out of source code — seesrc/data/defaults.tomlfor the format.
Mini CLIs
Two ready-to-run demos ship in examples/. Both follow the same
~30-line shape — build a UnitRegistry, register the dimension and units, then
call reg.parse(&argv[1])? and reg.from_canonical(q.canonical, &argv[2])? —
so reading both side-by-side is the fastest way to see how the pattern
generalizes.
length_convert — a base-dimension example
examples/length_convert.rs covers the simplest
case: a single SI base dimension (length, L¹).
mass_flow_convert — a derived-dimension example
examples/mass_flow_convert.rs shows the same
flow for a derived dimension — mass ÷ time (M¹·T⁻¹) — with a realistic
catalog covering SI metric (kg/s, kg/h, g/s, t/h, …), US customary
(lb/s, lb/h, lb/day, oz/s, ston/h, …), and the engineering shorthand
you see on heat-balance sheets and refinery PFDs (klb/h, MMlb/day).
The two examples together demonstrate the full pattern — base or derived dimension, SI or imperial, simple scale-only units or affine. The same shape applies to anything else you'd add (volumetric flow, heat flux, viscosity, …): pick an SI exponent vector, register it, then register your units.
Why a separate crate?
Thermodynamics code needs a few things on top of a general units library:
- Gauge pressure (
barg,psig,kPag) where absolute = gauge + P_atm, and P_atm is a runtime parameter the operator can set. - Temperature differences vs. absolute temperatures (
ΔT in K vs. T in °C—uommodels these as distinct dimensions to prevent misuse). - Molar units (
kJ/kmol,cm³/mol) that map to the canonical internal units used by cubic-EOS codebases going back to the 1980s.
See docs/en/units/dimensional-analysis.md
for the full design rationale.
License
MIT. See LICENSE.