refprop-rs 0.3.1

Safe Rust bindings for NIST REFPROP – thermodynamic & transport properties of refrigerants, pure fluids, and mixtures
Documentation
# 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


| Preset                      | T   | P   | D      | H      | S          | Viscosity | Conductivity |
|-----------------------------|-----|-----|--------|--------|------------|-----------|--------------|
| `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


| Property         | Options                                        |
|------------------|------------------------------------------------|
| 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)


| Pair      | Description              |
|-----------|--------------------------|
| `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


| Key   | Property              |
|-------|-----------------------|
| `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


| Constructor | Description |
|---|---|
| `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


| Method | Input | Output |
|---|---|---|
| `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


| OS | Library files detected |
|---|---|
| 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
```

| Module              | Role                                                  |
|---------------------|-------------------------------------------------------|
| `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