refprop-rs 0.3.1

Safe Rust bindings for NIST REFPROP – thermodynamic & transport properties of refrigerants, pure fluids, and mixtures
Documentation
use refprop::{Fluid, FluidApi, UnitSystem};

// ═══════════════════════════════════════════════════════════════════
//  FluidApi — generic dispatch with Fluid
// ═══════════════════════════════════════════════════════════════════

/// Helper: accepts any FluidApi implementor.
fn density_at(f: &impl FluidApi, t: f64, p: f64) -> f64 {
    f.get("D", "T", t, "P", p).unwrap()
}

/// Helper: accepts a trait object (`dyn FluidApi`).
fn critical_temp(f: &dyn FluidApi) -> f64 {
    f.critical_point().unwrap().temperature
}

#[test]
fn generic_get_with_fluid() {
    let fluid = Fluid::with_units("R134A", UnitSystem::engineering()).unwrap();
    let d = density_at(&fluid, 25.0, 10.0);
    assert!(d > 0.0, "density should be positive, got {d:.4}");
}

#[test]
fn dyn_dispatch_with_fluid() {
    let fluid = Fluid::with_units("R134A", UnitSystem::engineering()).unwrap();
    let tc = critical_temp(&fluid);
    assert!(
        (tc - 101.06).abs() < 1.0,
        "R134A Tc ≈ 101 °C via dyn FluidApi, got {tc:.4}"
    );
}

// ═══════════════════════════════════════════════════════════════════
//  All FluidApi methods work via generic bound
// ═══════════════════════════════════════════════════════════════════

fn exercise_all_methods(f: &impl FluidApi) {
    // get
    let d = f.get("D", "T", 25.0, "P", 5.0).unwrap();
    assert!(d > 0.0);

    // props_tp
    let tp = f.props_tp(25.0, 5.0).unwrap();
    assert!(tp.temperature > 24.0 && tp.temperature < 26.0);

    // props_ph round-trip
    let ph = f.props_ph(5.0, tp.enthalpy).unwrap();
    assert!((ph.temperature - 25.0).abs() < 0.5);

    // props_ps round-trip
    let ps = f.props_ps(5.0, tp.entropy).unwrap();
    assert!((ps.temperature - 25.0).abs() < 0.5);

    // props_td round-trip
    let td = f.props_td(25.0, tp.density).unwrap();
    assert!((td.pressure - 5.0).abs() < 0.1);

    // props_th round-trip
    let th = f.props_th(25.0, tp.enthalpy).unwrap();
    assert!((th.pressure - 5.0).abs() < 0.1);

    // props_ts round-trip
    let ts = f.props_ts(25.0, tp.entropy).unwrap();
    assert!((ts.pressure - 5.0).abs() < 0.1);

    // props_pd round-trip
    let pd = f.props_pd(5.0, tp.density).unwrap();
    assert!((pd.temperature - 25.0).abs() < 0.5);

    // props_dh round-trip
    let dh = f.props_dh(tp.density, tp.enthalpy).unwrap();
    assert!((dh.temperature - 25.0).abs() < 0.5);

    // props_ds round-trip
    let ds = f.props_ds(tp.density, tp.entropy).unwrap();
    assert!((ds.temperature - 25.0).abs() < 0.5);

    // props_hs round-trip
    let hs = f.props_hs(tp.enthalpy, tp.entropy).unwrap();
    assert!((hs.temperature - 25.0).abs() < 0.5);

    // props_tq — saturated liquid
    let tq = f.props_tq(0.0, 0.0).unwrap();
    assert!(tq.quality.abs() < 1.0);

    // props_pq
    let pq = f.props_pq(tq.pressure, 100.0).unwrap();
    assert!((pq.quality - 100.0).abs() < 1.0);

    // saturation_t
    let sat_t = f.saturation_t(0.0).unwrap();
    assert!(sat_t.pressure > 0.0);
    assert!(sat_t.density_liquid > sat_t.density_vapor);

    // saturation_p
    let sat_p = f.saturation_p(sat_t.pressure).unwrap();
    assert!((sat_p.temperature - 0.0).abs() < 0.5);

    // transport
    let trn = f.transport(25.0, tp.density).unwrap();
    assert!(trn.viscosity > 0.0, "viscosity must be positive");
    assert!(
        trn.thermal_conductivity > 0.0,
        "thermal conductivity must be positive"
    );

    // critical_point
    let crit = f.critical_point().unwrap();
    assert!(crit.pressure > 0.0);
    assert!(crit.density > 0.0);

    // info
    let info = f.info().unwrap();
    assert!(info.molar_mass > 0.0);
    assert!(info.gas_constant > 8.0 && info.gas_constant < 8.5);

    // converter
    let _conv = f.converter();
}

#[test]
fn fluid_api_all_methods() {
    let fluid = Fluid::with_units("R134A", UnitSystem::engineering()).unwrap();
    exercise_all_methods(&fluid);
}

// ═══════════════════════════════════════════════════════════════════
//  FluidApi is object-safe (can use Box<dyn FluidApi>)
// ═══════════════════════════════════════════════════════════════════

#[test]
fn fluid_api_is_object_safe() {
    let fluid = Fluid::with_units("R134A", UnitSystem::engineering()).unwrap();
    let boxed: Box<dyn FluidApi> = Box::new(fluid);

    let d = boxed.get("D", "T", 25.0, "P", 5.0).unwrap();
    assert!(d > 0.0);

    let sat = boxed.saturation_t(0.0).unwrap();
    assert!(sat.pressure > 0.0);
}

// ═══════════════════════════════════════════════════════════════════
//  Trait consistency: trait method == direct method
// ═══════════════════════════════════════════════════════════════════

#[test]
fn trait_and_inherent_give_same_result() {
    let fluid = Fluid::with_units("R134A", UnitSystem::engineering()).unwrap();

    // Direct inherent call
    let d_direct = fluid.get("D", "T", 25.0, "P", 5.0).unwrap();
    // Via trait
    let d_trait = FluidApi::get(&fluid, "D", "T", 25.0, "P", 5.0).unwrap();

    assert!(
        (d_direct - d_trait).abs() < 1e-10,
        "Inherent ({d_direct}) and trait ({d_trait}) should be identical"
    );
}