use crate::converter::{Converter, UnitSystem};
use crate::traits::FluidApi;
use crate::backend::refprop::RefpropBackend;
use crate::error::*;
use crate::properties::*;
use std::env;
use std::path::Path;
use std::sync::Once;
pub struct Fluid {
backend: RefpropBackend,
conv: Converter,
}
impl Fluid {
pub fn new(fluid_name: &str) -> Result<Self> {
Self::with_units(fluid_name, UnitSystem::refprop())
}
pub fn with_units(fluid_name: &str, units: UnitSystem) -> Result<Self> {
Self::load_dotenv();
let refprop_path = Self::find_refprop_path()?;
let backend = RefpropBackend::new(fluid_name, &refprop_path)?;
let mm = backend.molar_mass_mix()?;
let conv = Converter::new(units, mm);
Ok(Self { backend, conv })
}
pub fn mixture(components: &[(&str, f64)]) -> Result<Self> {
Self::mixture_with_units(components, UnitSystem::refprop())
}
pub fn mixture_with_units(components: &[(&str, f64)], units: UnitSystem) -> Result<Self> {
Self::load_dotenv();
let refprop_path = Self::find_refprop_path()?;
let backend = RefpropBackend::new_mixture(components, &refprop_path)?;
let mm = backend.molar_mass_mix()?;
let conv = Converter::new(units, mm);
Ok(Self { backend, conv })
}
pub(crate) fn load_dotenv() {
static DOTENV_INIT: Once = Once::new();
DOTENV_INIT.call_once(|| {
if dotenvy::dotenv().is_ok() {
return;
}
if let Ok(dir) = std::env::var("CARGO_MANIFEST_DIR") {
let p = std::path::PathBuf::from(dir).join(".env");
if p.exists() {
let _ = dotenvy::from_path(&p);
return;
}
}
if let Ok(exe) = std::env::current_exe() {
if let Some(dir) = exe.parent() {
let p = dir.join(".env");
if p.exists() {
let _ = dotenvy::from_path(&p);
}
}
}
});
}
pub(crate) fn find_refprop_path() -> Result<String> {
let mut tried = Vec::<String>::new();
if let Ok(path) = env::var("REFPROP_PATH") {
if Path::new(&path).exists() {
return Ok(path);
}
tried.push(format!("REFPROP_PATH={path} (directory does not exist)"));
}
#[cfg(target_os = "windows")]
let standard_paths = [
r"C:\Program Files (x86)\REFPROP",
r"C:\Program Files\REFPROP",
];
#[cfg(target_os = "linux")]
let standard_paths = ["/opt/refprop", "/usr/local/lib/refprop"];
#[cfg(target_os = "macos")]
let standard_paths = ["/Applications/REFPROP", "/opt/refprop"];
for path in standard_paths {
if Path::new(path).exists() {
return Ok(path.to_string());
}
tried.push(format!("{path} (not found)"));
}
Err(RefpropError::LibraryNotFound(format!(
"REFPROP directory not found. Tried:\n - {}\n\
Set REFPROP_PATH to the directory containing REFPROP.DLL and the fluids/ folder.",
tried.join("\n - ")
)))
}
pub fn get(&self, output: &str, key1: &str, val1: f64, key2: &str, val2: f64) -> Result<f64> {
let v1 = self.conv.input_to_rp(key1, val1)?;
let v2 = self.conv.input_to_rp(key2, val2)?;
let raw = self.backend.get(output, key1, v1, key2, v2)?;
Ok(self.conv.output_from_rp(output, raw))
}
pub fn props_tp(&self, t: f64, p: f64) -> Result<ThermoProp> {
let raw = self
.backend
.props_tp(self.conv.t_to_rp(t), self.conv.p_to_rp(p))?;
Ok(self.convert_thermo(raw))
}
pub fn props_ph(&self, p: f64, h: f64) -> Result<ThermoProp> {
let raw = self
.backend
.props_ph(self.conv.p_to_rp(p), self.conv.h_to_rp(h))?;
Ok(self.convert_thermo(raw))
}
pub fn props_ps(&self, p: f64, s: f64) -> Result<ThermoProp> {
let raw = self
.backend
.props_ps(self.conv.p_to_rp(p), self.conv.s_to_rp(s))?;
Ok(self.convert_thermo(raw))
}
pub fn props_td(&self, t: f64, d: f64) -> Result<ThermoProp> {
let raw = self
.backend
.props_td(self.conv.t_to_rp(t), self.conv.d_to_rp(d))?;
Ok(self.convert_thermo(raw))
}
pub fn props_th(&self, t: f64, h: f64) -> Result<ThermoProp> {
let raw = self
.backend
.props_th(self.conv.t_to_rp(t), self.conv.h_to_rp(h))?;
Ok(self.convert_thermo(raw))
}
pub fn props_ts(&self, t: f64, s: f64) -> Result<ThermoProp> {
let raw = self
.backend
.props_ts(self.conv.t_to_rp(t), self.conv.s_to_rp(s))?;
Ok(self.convert_thermo(raw))
}
pub fn props_pd(&self, p: f64, d: f64) -> Result<ThermoProp> {
let raw = self
.backend
.props_pd(self.conv.p_to_rp(p), self.conv.d_to_rp(d))?;
Ok(self.convert_thermo(raw))
}
pub fn props_dh(&self, d: f64, h: f64) -> Result<ThermoProp> {
let raw = self
.backend
.props_dh(self.conv.d_to_rp(d), self.conv.h_to_rp(h))?;
Ok(self.convert_thermo(raw))
}
pub fn props_ds(&self, d: f64, s: f64) -> Result<ThermoProp> {
let raw = self
.backend
.props_ds(self.conv.d_to_rp(d), self.conv.s_to_rp(s))?;
Ok(self.convert_thermo(raw))
}
pub fn props_hs(&self, h: f64, s: f64) -> Result<ThermoProp> {
let raw = self
.backend
.props_hs(self.conv.h_to_rp(h), self.conv.s_to_rp(s))?;
Ok(self.convert_thermo(raw))
}
pub fn props_tq(&self, t: f64, q: f64) -> Result<ThermoProp> {
let raw = self
.backend
.props_tq(self.conv.t_to_rp(t), self.conv.q_to_rp(q)?)?;
Ok(self.convert_thermo(raw))
}
pub fn props_pq(&self, p: f64, q: f64) -> Result<ThermoProp> {
let raw = self
.backend
.props_pq(self.conv.p_to_rp(p), self.conv.q_to_rp(q)?)?;
Ok(self.convert_thermo(raw))
}
pub fn saturation_p(&self, p: f64) -> Result<SaturationProps> {
let raw = self.backend.saturation_p(self.conv.p_to_rp(p))?;
Ok(self.convert_sat(raw))
}
pub fn saturation_t(&self, t: f64) -> Result<SaturationProps> {
let raw = self.backend.saturation_t(self.conv.t_to_rp(t))?;
Ok(self.convert_sat(raw))
}
pub fn transport(&self, t: f64, d: f64) -> Result<TransportProps> {
let raw = self
.backend
.transport(self.conv.t_to_rp(t), self.conv.d_to_rp(d))?;
Ok(TransportProps {
viscosity: self.conv.eta_from_rp(raw.viscosity),
thermal_conductivity: self.conv.tcx_from_rp(raw.thermal_conductivity),
})
}
pub fn critical_point(&self) -> Result<CriticalProps> {
let raw = self.backend.critical_point()?;
Ok(CriticalProps {
temperature: self.conv.t_from_rp(raw.temperature),
pressure: self.conv.p_from_rp(raw.pressure),
density: self.conv.d_from_rp(raw.density),
})
}
pub fn info(&self) -> Result<FluidInfo> {
self.backend.fluid_info()
}
pub fn converter(&self) -> &Converter {
&self.conv
}
fn convert_thermo(&self, raw: ThermoProp) -> ThermoProp {
ThermoProp {
temperature: self.conv.t_from_rp(raw.temperature),
pressure: self.conv.p_from_rp(raw.pressure),
density: self.conv.d_from_rp(raw.density),
enthalpy: self.conv.h_from_rp(raw.enthalpy),
entropy: self.conv.s_from_rp(raw.entropy),
cv: self.conv.s_from_rp(raw.cv),
cp: self.conv.s_from_rp(raw.cp),
sound_speed: raw.sound_speed,
quality: self.conv.q_from_rp(raw.quality),
internal_energy: self.conv.h_from_rp(raw.internal_energy),
}
}
fn convert_sat(&self, raw: SaturationProps) -> SaturationProps {
SaturationProps {
temperature: self.conv.t_from_rp(raw.temperature),
pressure: self.conv.p_from_rp(raw.pressure),
density_liquid: self.conv.d_from_rp(raw.density_liquid),
density_vapor: self.conv.d_from_rp(raw.density_vapor),
}
}
}
impl FluidApi for Fluid {
fn get(&self, output: &str, key1: &str, val1: f64, key2: &str, val2: f64) -> Result<f64> {
self.get(output, key1, val1, key2, val2)
}
fn props_tp(&self, t: f64, p: f64) -> Result<ThermoProp> { self.props_tp(t, p) }
fn props_ph(&self, p: f64, h: f64) -> Result<ThermoProp> { self.props_ph(p, h) }
fn props_ps(&self, p: f64, s: f64) -> Result<ThermoProp> { self.props_ps(p, s) }
fn props_td(&self, t: f64, d: f64) -> Result<ThermoProp> { self.props_td(t, d) }
fn props_th(&self, t: f64, h: f64) -> Result<ThermoProp> { self.props_th(t, h) }
fn props_ts(&self, t: f64, s: f64) -> Result<ThermoProp> { self.props_ts(t, s) }
fn props_pd(&self, p: f64, d: f64) -> Result<ThermoProp> { self.props_pd(p, d) }
fn props_dh(&self, d: f64, h: f64) -> Result<ThermoProp> { self.props_dh(d, h) }
fn props_ds(&self, d: f64, s: f64) -> Result<ThermoProp> { self.props_ds(d, s) }
fn props_hs(&self, h: f64, s: f64) -> Result<ThermoProp> { self.props_hs(h, s) }
fn props_tq(&self, t: f64, q: f64) -> Result<ThermoProp> { self.props_tq(t, q) }
fn props_pq(&self, p: f64, q: f64) -> Result<ThermoProp> { self.props_pq(p, q) }
fn saturation_p(&self, p: f64) -> Result<SaturationProps> { self.saturation_p(p) }
fn saturation_t(&self, t: f64) -> Result<SaturationProps> { self.saturation_t(t) }
fn transport(&self, t: f64, d: f64) -> Result<TransportProps> { self.transport(t, d) }
fn critical_point(&self) -> Result<CriticalProps> { self.critical_point() }
fn info(&self) -> Result<FluidInfo> { self.info() }
fn converter(&self) -> &Converter { self.converter() }
}