//! Fluid backend trait and implementations. //! //! This module defines the core `FluidBackend` trait that abstracts the source //! of thermodynamic property data, allowing the solver to switch between different //! backends (CoolProp, tabular data, mock for testing). use crate::errors::FluidResult; use crate::mixture::Mixture; use crate::types::{CriticalPoint, FluidId, Phase, Property, FluidState, ThermoState}; use entropyk_core::{Pressure, Temperature}; /// Trait for fluid property backends. /// /// Implementors must provide methods to query thermodynamic properties /// for various fluids. This allows the solver to work with different /// property sources (CoolProp, tabular data, mock data for testing). /// /// # Example /// /// ``` /// use entropyk_fluids::{FluidBackend, FluidId, Property, FluidState, ThermoState, FluidError, FluidResult, CriticalPoint}; /// /// struct MyBackend; /// impl FluidBackend for MyBackend { /// fn property(&self, _fluid: FluidId, _property: Property, _state: FluidState) -> FluidResult { /// Ok(1.0) /// } /// fn critical_point(&self, fluid: FluidId) -> FluidResult { /// Err(FluidError::NoCriticalPoint { fluid: fluid.0 }) /// } /// fn is_fluid_available(&self, _fluid: &FluidId) -> bool { false } /// fn phase(&self, _fluid: FluidId, _state: FluidState) -> FluidResult { /// Ok(entropyk_fluids::Phase::Unknown) /// } /// fn full_state(&self, _fluid: FluidId, _p: entropyk_core::Pressure, _h: entropyk_core::Enthalpy) -> FluidResult { /// Err(FluidError::UnsupportedProperty { property: "full_state".to_string() }) /// } /// fn list_fluids(&self) -> Vec { vec![] } /// } /// ``` pub trait FluidBackend: Send + Sync { /// Query a thermodynamic property for a fluid at a given state. /// /// # Arguments /// * `fluid` - The fluid identifier (e.g., "R134a", "CO2") /// * `property` - The property to query /// * `state` - The thermodynamic state specification /// /// # Returns /// The property value in SI units, or an error if the property /// cannot be computed (unknown fluid, invalid state, etc.) fn property(&self, fluid: FluidId, property: Property, state: FluidState) -> FluidResult; /// Compute the complete thermodynamic state of a fluid at a given pressure and enthalpy. /// /// This method is intended to be implemented by backends capable of natively calculating /// all key parameters (phase, saturation temperatures, qualities, limits) without the user /// needing to query them individually. /// /// # Arguments /// * `fluid` - The fluid identifier /// * `p` - The absolute pressure /// * `h` - The specific enthalpy /// /// # Returns /// The comprehensive `ThermoState` Snapshot, or an Error. fn full_state(&self, fluid: FluidId, p: Pressure, h: entropyk_core::Enthalpy) -> FluidResult; /// Get critical point data for a fluid. /// /// # Arguments /// * `fluid` - The fluid identifier /// /// # Returns /// The critical point (Tc, Pc, density), or an error if not available fn critical_point(&self, fluid: FluidId) -> FluidResult; /// Check if a fluid is available in this backend. /// /// # Arguments /// * `fluid` - The fluid identifier /// /// # Returns /// `true` if the fluid is available, `false` otherwise fn is_fluid_available(&self, fluid: &FluidId) -> bool; /// Get the phase of a fluid at a given state. /// /// # Arguments /// * `fluid` - The fluid identifier /// * `state` - The thermodynamic state /// /// # Returns /// The phase (Liquid, Vapor, TwoPhase, etc.) fn phase(&self, fluid: FluidId, state: FluidState) -> FluidResult; /// List all available fluids in this backend. fn list_fluids(&self) -> Vec; /// Calculate the bubble point temperature for a mixture at given pressure. /// /// The bubble point is the temperature at which a liquid mixture begins to boil /// (saturated liquid temperature). /// /// # Arguments /// * `pressure` - The pressure in Pa /// * `mixture` - The mixture composition /// /// # Returns /// The bubble point temperature in Kelvin fn bubble_point(&self, _pressure: Pressure, _mixture: &Mixture) -> FluidResult { Err(crate::errors::FluidError::UnsupportedProperty { property: "Bubble point calculation not supported by this backend".to_string(), }) } /// Calculate the dew point temperature for a mixture at given pressure. /// /// The dew point is the temperature at which a vapor mixture begins to condense /// (saturated vapor temperature). /// /// # Arguments /// * `pressure` - The pressure in Pa /// * `mixture` - The mixture composition /// /// # Returns /// The dew point temperature in Kelvin fn dew_point(&self, _pressure: Pressure, _mixture: &Mixture) -> FluidResult { Err(crate::errors::FluidError::UnsupportedProperty { property: "Dew point calculation not supported by this backend".to_string(), }) } /// Calculate the temperature glide for a mixture at given pressure. /// /// Temperature glide is the difference between dew point and bubble point /// temperatures: T_glide = T_dew - T_bubble. /// This is non-zero for zeotropic mixtures and zero for azeotropes/pure fluids. /// /// # Arguments /// * `pressure` - The pressure in Pa /// * `mixture` - The mixture composition /// /// # Returns /// The temperature glide in Kelvin fn temperature_glide(&self, pressure: Pressure, mixture: &Mixture) -> FluidResult { let t_bubble = self.bubble_point(pressure, mixture)?; let t_dew = self.dew_point(pressure, mixture)?; Ok(t_dew.to_kelvin() - t_bubble.to_kelvin()) } /// Check if a mixture is supported by this backend. /// /// # Arguments /// * `mixture` - The mixture to check /// /// # Returns /// `true` if the mixture is supported, `false` otherwise fn is_mixture_supported(&self, mixture: &Mixture) -> bool { // Default implementation: check if all components are available mixture .components() .iter() .all(|c| self.is_fluid_available(&FluidId::new(c))) } }