# refprop-rs
Safe Rust bindings for [NIST REFPROP](https://www.nist.gov/srd/refprop) -- thermodynamic and transport properties of refrigerants, pure fluids, and mixtures.
## Features
- **Pure fluids** -- `Fluid::new("R134A")`, `Fluid::new("CO2")`, ...
- **Predefined mixtures** -- `Fluid::new("R410A")` (auto-loaded from `.MIX` files)
- **Custom mixtures** -- `Fluid::mixture(&[("R32", 0.5), ("R125", 0.5)])`
- **CoolProp-style `get()`** -- `fluid.get("D", "T", 0.0, "Q", 1.0)`
- **Configurable units** -- work in **°C + bar + kg/m³ + kJ/kg**, or K + kPa, or any mix
- **Flash calculations** -- TP, TD, TH, TS, TQ, PD, PH, PS, PQ, DH, DS, HS
- **Saturation, transport, critical point, fluid info**
- **Thread-safe** -- global mutex with automatic fluid re-setup
- **Parallel computation** *(opt-in)* -- `ParallelFluid` duplicates the DLL/SO per CPU core for true multi-threaded throughput via [rayon](https://docs.rs/rayon)
- **`FluidApi` trait** -- common interface for `Fluid` and `ParallelFluid`, write generic code that works with either backend
- **Dynamic loading** -- no compile-time linking, just point to your REFPROP installation
## Prerequisites
A licensed [REFPROP](https://www.nist.gov/srd/refprop) installation (v9.1 or v10).
## Installation
Add to your `Cargo.toml`:
```toml
[dependencies]
refprop-rs = { git = "https://github.com/math-dev-24/refprop-rs" }
```
To enable **parallel computation** (optional):
```toml
[dependencies]
refprop-rs = { git = "https://github.com/math-dev-24/refprop-rs", features = ["parallel"] }
```
The library name is `refprop`, so you import it as:
```rust
use refprop::{Fluid, UnitSystem};
// With the `parallel` feature:
use refprop::{ParallelFluid, UnitSystem};
```
## Configuration
Tell the library where REFPROP is installed. Create a `.env` file at
the project root (or set the environment variable directly):
```
REFPROP_PATH='C:\Program Files (x86)\REFPROP'
```
The library also checks standard install locations automatically.
## Quick start
### Engineering units (°C, bar, kg/m³, kJ/kg)
```rust
use refprop::{Fluid, UnitSystem};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let co2 = Fluid::with_units("CO2", UnitSystem::engineering())?;
// Everything is in °C, bar, kg/m³, kJ/kg -- no manual conversion!
let p = co2.get("P", "T", -5.0, "Q", 1.0)?;
println!("Psat(-5 °C) = {p:.2} bar");
let d = co2.get("D", "T", -5.0, "Q", 1.0)?;
println!("D_vap(-5 °C) = {d:.2} kg/m³");
let h = co2.get("H", "T", -5.0, "Q", 1.0)?;
println!("H_vap(-5 °C) = {h:.2} kJ/kg");
Ok(())
}
```
### REFPROP native units (K, kPa, mol/L, J/mol)
```rust
use refprop::Fluid;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let r134a = Fluid::new("R134A")?;
let d = r134a.get("D", "T", 273.15, "Q", 1.0)?;
println!("density = {d:.6} mol/L");
let props = r134a.props_tp(300.0, 500.0)?;
println!("{props}");
Ok(())
}
```
## Unit system
Choose your preferred units at construction time. All inputs and
outputs are automatically converted.
### Presets
| `UnitSystem::refprop()` | K | kPa | mol/L | J/mol | J/(mol·K) | µPa·s | W/(m·K) |
| `UnitSystem::engineering()` | °C | bar | kg/m³ | kJ/kg | kJ/(kg·K) | µPa·s | W/(m·K) |
| `UnitSystem::si()` | K | Pa | kg/m³ | J/kg | J/(kg·K) | Pa·s | W/(m·K) |
### Custom builder
Pick only the units you care about; the rest stay at REFPROP defaults:
```rust
use refprop::{Fluid, UnitSystem, TempUnit, PressUnit};
let units = UnitSystem::new()
.temperature(TempUnit::Celsius)
.pressure(PressUnit::Bar);
// density stays mol/L, energy stays J/mol, etc.
let fluid = Fluid::with_units("R134A", units)?;
let sat = fluid.saturation_t(0.0)?; // 0 °C directly
println!("Psat = {:.2} bar", sat.pressure);
```
### Available unit choices
| Temperature | `Kelvin`, `Celsius`, `Fahrenheit` |
| Pressure | `KPa`, `Bar`, `MPa`, `Pa`, `Atm`, `Psi` |
| Density | `MolPerL`, `KgPerM3` |
| Energy/Enthalpy | `JPerMol`, `KJPerKg`, `JPerKg` |
| Entropy/Cv/Cp | `JPerMolK`, `KJPerKgK`, `JPerKgK` |
| Viscosity | `MicroPaS`, `MilliPaS`, `PaS` |
| Conductivity | `WPerMK`, `MilliWPerMK` |
## Mixtures
```rust
use refprop::{Fluid, UnitSystem};
// Predefined mixture (from .MIX file)
let r410a = Fluid::with_units("R410A", UnitSystem::engineering())?;
// Custom composition
let r454c = Fluid::mixture_with_units(
&[("R32", 0.215), ("R1234YF", 0.785)],
UnitSystem::engineering(),
)?;
let p = r454c.get("P", "T", 0.0, "Q", 0.0)?;
println!("R454C Psat(0 °C) = {p:.2} bar");
```
## `get()` -- generic property lookup
```rust
// get(output, key1, val1, key2, val2) -> f64
let density = fluid.get("D", "T", 25.0, "P", 10.0)?;
```
### Input pairs (order-independent)
| `T`, `P` | Temperature + Pressure |
| `P`, `H` | Pressure + Enthalpy |
| `P`, `S` | Pressure + Entropy |
| `T`, `Q` | Temperature + Quality |
| `P`, `Q` | Pressure + Quality |
| `T`, `D` | Temperature + Density |
| `T`, `H` | Temperature + Enthalpy |
| `T`, `S` | Temperature + Entropy |
| `P`, `D` | Pressure + Density |
| `D`, `H` | Density + Enthalpy |
| `D`, `S` | Density + Entropy |
| `H`, `S` | Enthalpy + Entropy |
### Output keys
| `T` | Temperature |
| `P` | Pressure |
| `D` | Density |
| `H` | Enthalpy |
| `S` | Entropy |
| `Q` | Quality (vapor frac.) |
| `Cv` | Heat capacity (v) |
| `Cp` | Heat capacity (p) |
| `W` | Speed of sound |
| `E` | Internal energy |
| `ETA` | Dynamic viscosity |
| `TCX` | Thermal conductivity |
Units depend on the `UnitSystem` you chose at construction time.
## Flash & saturation methods
All methods respect the configured unit system.
```rust
let props = fluid.props_tp(25.0, 10.0)?; // TP flash
let props = fluid.props_ph(10.0, 250.0)?; // PH flash
let props = fluid.props_ps(10.0, 1.2)?; // PS flash
let props = fluid.props_tq(0.0, 1.0)?; // TQ flash (saturation)
let props = fluid.props_pq(5.0, 0.0)?; // PQ flash (saturation)
let props = fluid.props_td(50.0, 30.0)?; // TD flash
let props = fluid.props_th(50.0, 280.0)?; // TH flash
let props = fluid.props_ts(50.0, 1.1)?; // TS flash
let props = fluid.props_pd(5.0, 30.0)?; // PD flash
let props = fluid.props_dh(30.0, 280.0)?; // DH flash
let props = fluid.props_ds(30.0, 1.1)?; // DS flash
let props = fluid.props_hs(280.0, 1.1)?; // HS flash
let sat = fluid.saturation_t(0.0)?; // saturation at T
let sat = fluid.saturation_p(5.0)?; // saturation at P
let crit = fluid.critical_point()?; // Tc, Pc, Dc
let trn = fluid.transport(25.0, d)?; // viscosity, conductivity
let info = fluid.info()?; // molar mass, Ttrp, Tnbp, ...
```
## `FluidApi` trait -- generic code
`Fluid` and `ParallelFluid` both implement the `FluidApi` trait,
so you can write functions that work with either backend:
```rust
use refprop::{FluidApi, Result};
fn density(f: &impl FluidApi, t: f64, p: f64) -> Result<f64> {
f.get("D", "T", t, "P", p)
}
fn report(f: &dyn FluidApi) -> Result<()> {
let cp = f.critical_point()?;
println!("Tc = {:.2}, Pc = {:.2}", cp.temperature, cp.pressure);
let sat = f.saturation_t(0.0)?;
println!("Psat(0) = {:.2}", sat.pressure);
let info = f.info()?;
println!("M = {:.2} g/mol", info.molar_mass);
Ok(())
}
```
The trait includes all single-point methods: `get`, all flash variants
(`props_tp`, `props_ph`, ..., `props_hs`), `saturation_t`, `saturation_p`,
`transport`, `critical_point`, `info`, and `converter`.
Parallel-only methods (`par_get`, `par_props_*`, `worker_count`) remain
exclusive to `ParallelFluid`.
## Parallel computation
> **Feature gate:** `parallel` -- adds [`rayon`](https://docs.rs/rayon) and [`num_cpus`](https://docs.rs/num_cpus) as dependencies.
REFPROP (the Fortran DLL) uses global internal state, making it
impossible to call from multiple threads simultaneously.
`ParallelFluid` works around this by **copying the shared library once
per CPU core** into a temporary directory. Each copy is loaded
independently, giving it its own Fortran state. Work is then
distributed across these isolated instances via rayon.
### How it works
1. Detects the number of logical CPU cores (or use a custom count)
2. Copies the DLL/SO/dylib N times into a temp directory
3. Loads each copy as an independent `RefpropLibrary`
4. Distributes batch computations across workers via rayon
5. Cleans up temp files automatically on `Drop`
### Quick example
```rust
use refprop::{ParallelFluid, UnitSystem};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Auto-detects CPU cores, copies the DLL N times
let pool = ParallelFluid::with_units("R134A", UnitSystem::engineering())?;
println!("Workers: {}", pool.worker_count());
// 10 000 (T, P) → density computations in parallel
let temps: Vec<f64> = (0..10_000).map(|i| -20.0 + i as f64 * 0.01).collect();
let press: Vec<f64> = vec![10.0; 10_000];
let densities = pool.par_get("D", "T", &temps, "P", &press)?;
// Also works for batch flash calculations
let inputs: Vec<(f64, f64)> = temps.iter().zip(&press).map(|(&t, &p)| (t, p)).collect();
let props = pool.par_props_tp(&inputs); // Vec<Result<ThermoProp>>
// Single-point API identical to Fluid
let p = pool.get("P", "T", 0.0, "Q", 100.0)?;
// Mixtures too
let mix = ParallelFluid::mixture_with_units(
&[("R32", 0.5), ("R125", 0.5)],
UnitSystem::engineering(),
)?;
Ok(())
}
```
### Constructors
| `ParallelFluid::new(name)` | Pure/predefined mix, REFPROP units, auto core count |
| `ParallelFluid::with_units(name, units)` | Custom units, auto core count |
| `ParallelFluid::with_workers(name, n)` | REFPROP units, explicit worker count |
| `ParallelFluid::with_units_and_workers(name, units, n)` | Full control |
| `ParallelFluid::mixture(comps)` | Custom mixture, REFPROP units |
| `ParallelFluid::mixture_with_units(comps, units)` | Custom mixture, custom units |
| `ParallelFluid::mixture_with_units_and_workers(comps, units, n)` | Full control |
### Batch methods
| `par_get(out, k1, &v1s, k2, &v2s)` | Two `&[f64]` slices | `Result<Vec<f64>>` |
| `par_props_tp(&[(T, P)])` | `&[(f64, f64)]` | `Vec<Result<ThermoProp>>` |
| `par_props_ph(&[(P, H)])` | `&[(f64, f64)]` | `Vec<Result<ThermoProp>>` |
| `par_props_ps(&[(P, S)])` | `&[(f64, f64)]` | `Vec<Result<ThermoProp>>` |
| `par_props_tq(&[(T, Q)])` | `&[(f64, f64)]` | `Vec<Result<ThermoProp>>` |
| `par_props_pq(&[(P, Q)])` | `&[(f64, f64)]` | `Vec<Result<ThermoProp>>` |
All single-point methods from `Fluid` (`get`, `props_tp`, `critical_point`, etc.)
are also available on `ParallelFluid`.
### Platform support
| Windows 64-bit | `REFPRP64.DLL`, `REFPROP.DLL`, `refprop.dll` |
| Windows 32-bit | `REFPROP.DLL`, `refprop.dll` |
| Linux | `librefprop.so`, `libREFPROP.so` |
| macOS | `librefprop.dylib`, `libREFPROP.dylib` |
## Project structure
```
refprop-rs/
├── Cargo.toml single crate: refprop-rs
├── src/
│ ├── lib.rs public API & re-exports
│ ├── fluid.rs Fluid struct (high-level API)
│ ├── pool.rs ParallelFluid (feature = "parallel")
│ ├── traits.rs FluidApi trait (common interface)
│ ├── converter.rs UnitSystem + Converter
│ ├── sys.rs low-level FFI (libloading)
│ ├── error.rs error types
│ ├── properties.rs result structs
│ └── backend/
│ └── refprop.rs REFPROP backend (flash, sat, etc.)
└── examples/
├── demo.rs engineering units showcase
├── simple.rs pure fluid, native units
├── mixture.rs predefined & custom mixtures
└── parallel.rs parallel batch computation
```
| `sys` | Dynamic DLL loading + raw FFI function wrappers |
| `converter` | `UnitSystem` + `Converter` (unit conversion) |
| `traits` | `FluidApi` trait -- common interface for generic code |
| `fluid` | High-level API: `Fluid`, `get()`, flash, units |
| `pool` | `ParallelFluid` -- DLL pool + rayon batch methods |
| `backend::refprop` | Core REFPROP calls, global & isolated state management |
## License
MIT