362 lines
11 KiB
Rust

//! 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"));
}
}
}