//! FFI bindings to CoolProp C++ library. //! //! This module provides low-level FFI bindings to the CoolProp library. //! All functions are unsafe and require proper error handling. #![allow(dead_code)] use libc::{c_char, c_double, c_int}; use std::ffi::CString; /// Error codes returned by CoolProp #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(i32)] pub enum CoolPropError { /// No error occurred NoError = 0, /// Input error code InputError = 1, /// Library not loaded LibraryNotLoaded = 2, /// Unknown property value UnknownPropertyValue = 3, /// Unknown fluid UnknownFluid = 4, /// Unknown parameter UnknownParameter = 5, /// Not implemented NotImplemented = 6, /// Invalid number of parameters InvalidNumber = 7, /// Could not load library CouldNotLoadLibrary = 8, /// Invalid fluid pair InvalidFluidPair = 9, /// Version mismatch VersionMismatch = 10, /// Internal error InternalError = 11, } impl CoolPropError { /// Convert CoolProp error code to Rust result pub fn from_code(code: i32) -> Result<(), CoolPropError> { match code { 0 => Ok(()), _ => Err(match code { 1 => CoolPropError::InputError, 2 => CoolPropError::LibraryNotLoaded, 3 => CoolPropError::UnknownPropertyValue, 4 => CoolPropError::UnknownFluid, 5 => CoolPropError::UnknownParameter, 6 => CoolPropError::NotImplemented, 7 => CoolPropError::InvalidNumber, 8 => CoolPropError::CouldNotLoadLibrary, 9 => CoolPropError::InvalidFluidPair, 10 => CoolPropError::VersionMismatch, _ => CoolPropError::InternalError, }), } } } /// Output parameters for CoolProp #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(i32)] pub enum CoolPropParam { /// Nothing Nothing = 0, /// Pressure [Pa] Pressure = 1, /// Temperature [K] Temperature = 2, /// Density [kg/m³] Density = 3, /// Specific enthalpy [J/kg] Enthalpy = 4, /// Specific entropy [J/kg/K] Entropy = 5, /// Specific internal energy [J/kg] InternalEnergy = 6, /// Specific heat at constant pressure [J/kg/K] Cv = 7, /// Specific heat at constant pressure [J/kg/K] Cp = 8, /// Quality [-] Quality = 9, /// Viscosity [Pa·s] Viscosity = 10, /// Thermal conductivity [W/m/K] Conductivity = 11, /// Surface tension [N/m] SurfaceTension = 12, /// Prandtl number [-] Prandtl = 13, } /// Input parameters for CoolProp #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(i32)] pub enum CoolPropInputPair { /// No input None = 0, /// Pressure & Temperature PT = 1, /// Pressure & Density PD = 2, /// Pressure & Enthalpy PH = 3, /// Pressure & Entropy PS = 4, /// Pressure & Internal Energy PU = 5, /// Temperature & Density TD = 6, /// Temperature & Enthalpy TH = 7, /// Temperature & Entropy TS = 8, /// Temperature & Internal Energy TU = 9, /// Enthalpy & Entropy HS = 10, /// Density & Internal Energy DU = 11, /// Pressure & Quality PQ = 12, /// Temperature & Quality TQ = 13, } // CoolProp C functions extern "C" { /// Get a property value using pressure and temperature /// Get a property value using pressure and temperature #[cfg_attr(target_os = "macos", link_name = "\x01__Z7PropsSIPKcS0_dS0_dS0_")] #[cfg_attr(not(target_os = "macos"), link_name = "_Z7PropsSIPKcS0_dS0_dS0_")] fn PropsSI( Output: *const c_char, Name1: *const c_char, Value1: c_double, Name2: *const c_char, Value2: c_double, Fluid: *const c_char, ) -> c_double; /// Get a property value using input pair #[cfg_attr(target_os = "macos", link_name = "\x01__Z8Props1SIPKcS0_")] #[cfg_attr(not(target_os = "macos"), link_name = "_Z8Props1SIPKcS0_")] fn Props1SI(Fluid: *const c_char, Output: *const c_char) -> c_double; /// Get CoolProp version string #[cfg_attr(target_os = "macos", link_name = "\x01__Z23get_global_param_stringPKcPci")] #[cfg_attr(not(target_os = "macos"), link_name = "get_global_param_string")] fn get_global_param_string( Param: *const c_char, Output: *mut c_char, OutputLength: c_int, ) -> c_int; /// Get fluid info #[cfg_attr(target_os = "macos", link_name = "\x01__Z22get_fluid_param_stringPKcS0_Pci")] #[cfg_attr(not(target_os = "macos"), link_name = "get_fluid_param_string")] fn get_fluid_param_string( Fluid: *const c_char, Param: *const c_char, Output: *mut c_char, OutputLength: c_int, ) -> c_int; // Check if fluid exists // CoolProp doesn't have a direct C isfluid function. We usually just try to fetch a string or param or we can map it downstream // But let's see if we can just dummy it or use get_fluid_param_string // There is no C CriticalPoint, it's just Props1SI("Tcrit", "Water") } /// Get a thermodynamic property using pressure and temperature. /// /// # Arguments /// * `property` - The property to retrieve (e.g., "D" for density, "H" for enthalpy) /// * `p` - Pressure in Pa /// * `t` - Temperature in K /// * `fluid` - Fluid name (e.g., "R134a") /// /// # Returns /// # Safety /// This function calls the CoolProp C++ library and passes a CString pointer. /// The caller must ensure the fluid string is properly null-terminated if needed and valid. pub unsafe fn props_si_pt(property: &str, p: f64, t: f64, fluid: &str) -> f64 { let prop_c = std::ffi::CString::new(property).unwrap(); let fluid_c = CString::new(fluid).unwrap(); PropsSI(prop_c.as_ptr(), c"P".as_ptr(), p, c"T".as_ptr(), t, fluid_c.as_ptr()) } /// Get a thermodynamic property using pressure and enthalpy. /// /// # Arguments /// * `property` - The property to retrieve /// * `p` - Pressure in Pa /// * `h` - Specific enthalpy in J/kg /// * `fluid` - Fluid name /// /// # Returns /// # Safety /// This function calls the CoolProp C++ library and passes a CString pointer. /// The caller must ensure the fluid string is valid. pub unsafe fn props_si_ph(property: &str, p: f64, h: f64, fluid: &str) -> f64 { let prop_c = std::ffi::CString::new(property).unwrap(); let fluid_c = CString::new(fluid).unwrap(); PropsSI(prop_c.as_ptr(), c"P".as_ptr(), p, c"H".as_ptr(), h, fluid_c.as_ptr()) } /// Get a thermodynamic property using temperature and quality (saturation). /// /// # Arguments /// * `property` - The property to retrieve (D, H, S, P, etc.) /// * `t` - Temperature in K /// * `q` - Quality (0 = saturated liquid, 1 = saturated vapor) /// * `fluid` - Fluid name /// /// # Returns /// # Safety /// This function calls the CoolProp C++ library and passes a CString pointer. /// The caller must ensure the fluid string is valid. pub unsafe fn props_si_tq(property: &str, t: f64, q: f64, fluid: &str) -> f64 { let prop_c = std::ffi::CString::new(property).unwrap(); let fluid_c = CString::new(fluid).unwrap(); PropsSI(prop_c.as_ptr(), c"T".as_ptr(), t, c"Q".as_ptr(), q, fluid_c.as_ptr()) } /// Get a thermodynamic property using pressure and quality. /// /// # Arguments /// * `property` - The property to retrieve /// * `p` - Pressure in Pa /// * `x` - Quality (0-1) /// * `fluid` - Fluid name /// /// # Returns /// # Safety /// This function calls the CoolProp C++ library and passes a CString pointer. /// The caller must ensure the fluid string is valid. pub unsafe fn props_si_px(property: &str, p: f64, x: f64, fluid: &str) -> f64 { let prop_c = std::ffi::CString::new(property).unwrap(); let fluid_c = CString::new(fluid).unwrap(); PropsSI( prop_c.as_ptr(), c"P".as_ptr(), p, c"Q".as_ptr(), // Q for quality x, fluid_c.as_ptr(), ) } /// Get critical point temperature for a fluid. /// /// # Arguments /// * `fluid` - Fluid name /// /// # Returns /// # Safety /// This function calls the CoolProp C++ library and passes a CString pointer. /// The caller must ensure the fluid string is valid. pub unsafe fn critical_temperature(fluid: &str) -> f64 { let fluid_c = CString::new(fluid).unwrap(); Props1SI(fluid_c.as_ptr(), c"Tcrit".as_ptr()) } /// Get critical point pressure for a fluid. /// /// # Arguments /// * `fluid` - Fluid name /// /// # Returns /// # Safety /// This function calls the CoolProp C++ library and passes a CString pointer. /// The caller must ensure the fluid string is valid. pub unsafe fn critical_pressure(fluid: &str) -> f64 { let fluid_c = CString::new(fluid).unwrap(); Props1SI(fluid_c.as_ptr(), c"pcrit".as_ptr()) } /// Get critical point density for a fluid. /// /// # Arguments /// * `fluid` - Fluid name /// /// # Returns /// # Safety /// This function calls the CoolProp C++ library and passes a CString pointer. /// The caller must ensure the fluid string is valid. pub unsafe fn critical_density(fluid: &str) -> f64 { let fluid_c = CString::new(fluid).unwrap(); Props1SI(fluid_c.as_ptr(), c"rhocrit".as_ptr()) } /// Check if a fluid is available in CoolProp. /// /// # Arguments /// * `fluid` - Fluid name /// /// # Returns /// # Safety /// This function calls the CoolProp C++ library and passes a CString pointer. /// The caller must ensure the fluid string is valid. pub unsafe fn is_fluid_available(fluid: &str) -> bool { let fluid_c = CString::new(fluid).unwrap(); // CoolProp C API does not expose isfluid, so we try fetching a property let res = Props1SI(fluid_c.as_ptr(), c"Tcrit".as_ptr()); if res.is_finite() && res != 0.0 { true } else { false } } /// Get CoolProp version string. /// /// # Returns /// Version string (e.g., "6.14.0") pub fn get_version() -> String { unsafe { let mut buffer = vec![0u8; 32]; let result = get_global_param_string( c"version".as_ptr(), buffer.as_mut_ptr() as *mut c_char, buffer.len() as c_int, ); if result == 0 { String::from_utf8_lossy(&buffer) .trim_end_matches('\0') .to_string() } else { String::from("Unknown") } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_version() { let version = get_version(); assert!(!version.is_empty()); } #[test] fn test_fluid_available() { // Test some common refrigerants unsafe { assert!(is_fluid_available("R134a")); assert!(is_fluid_available("R410A")); assert!(is_fluid_available("CO2")); } } }