//! Heat Transfer Model Trait //! //! Defines the Strategy Pattern interface for heat transfer calculations. //! This trait is object-safe for dynamic dispatch. use crate::ResidualVector; use entropyk_core::{Enthalpy, MassFlow, Power, Pressure, Temperature}; /// Fluid state for heat transfer calculations. /// /// Represents the thermodynamic state at a port (inlet or outlet). #[derive(Debug, Clone, Copy)] pub struct FluidState { /// Temperature in Kelvin pub temperature: f64, /// Pressure in Pascals pub pressure: f64, /// Specific enthalpy in J/kg pub enthalpy: f64, /// Mass flow rate in kg/s pub mass_flow: f64, /// Specific heat capacity at constant pressure in J/(kg·K) pub cp: f64, } impl Default for FluidState { fn default() -> Self { Self { temperature: 300.0, pressure: 101_325.0, enthalpy: 0.0, mass_flow: 0.1, cp: 1000.0, } } } impl FluidState { /// Creates a new fluid state. pub fn new(temperature: f64, pressure: f64, enthalpy: f64, mass_flow: f64, cp: f64) -> Self { Self { temperature, pressure, enthalpy, mass_flow, cp, } } /// Creates a fluid state with default properties for a given temperature. pub fn from_temperature(temperature: f64) -> Self { Self { temperature, ..Default::default() } } /// Returns the heat capacity rate C = ṁ × Cp in W/K. pub fn heat_capacity_rate(&self) -> f64 { self.mass_flow * self.cp } /// Creates a FluidState from strongly-typed physical quantities. pub fn from_types( temperature: Temperature, pressure: Pressure, enthalpy: Enthalpy, mass_flow: MassFlow, cp: f64, ) -> Self { Self { temperature: temperature.to_kelvin(), pressure: pressure.to_pascals(), enthalpy: enthalpy.to_joules_per_kg(), mass_flow: mass_flow.to_kg_per_s(), cp, } } /// Returns temperature as a strongly-typed Temperature. pub fn temperature(&self) -> Temperature { Temperature::from_kelvin(self.temperature) } /// Returns pressure as a strongly-typed Pressure. pub fn pressure(&self) -> Pressure { Pressure::from_pascals(self.pressure) } /// Returns enthalpy as a strongly-typed Enthalpy. pub fn enthalpy(&self) -> Enthalpy { Enthalpy::from_joules_per_kg(self.enthalpy) } /// Returns mass flow as a strongly-typed MassFlow. pub fn mass_flow(&self) -> MassFlow { MassFlow::from_kg_per_s(self.mass_flow) } } /// Trait for heat transfer calculation models. /// /// This trait uses the Strategy Pattern to allow different heat transfer /// calculation methods (LMTD, ε-NTU, etc.) to be used interchangeably. /// /// # Object Safety /// /// This trait is object-safe and can be used with dynamic dispatch: /// /// ``` /// # use entropyk_components::heat_exchanger::model::{HeatTransferModel, FluidState}; /// # use entropyk_components::ResidualVector; /// # use entropyk_core::Power; /// struct SimpleModel { ua: f64 } /// impl HeatTransferModel for SimpleModel { /// fn compute_heat_transfer(&self, _: &FluidState, _: &FluidState, _: &FluidState, _: &FluidState, _: Option) -> Power { /// Power::from_watts(0.0) /// } /// fn compute_residuals(&self, _: &FluidState, _: &FluidState, _: &FluidState, _: &FluidState, _: &mut ResidualVector, _: Option) {} /// fn n_equations(&self) -> usize { 3 } /// fn ua(&self) -> f64 { self.ua } /// } /// let model: Box = Box::new(SimpleModel { ua: 1000.0 }); /// ``` pub trait HeatTransferModel: Send + Sync { /// Computes the heat transfer rate Q̇ (Watts). /// /// # Arguments /// /// * `hot_inlet` - Hot side inlet state /// * `hot_outlet` - Hot side outlet state /// * `cold_inlet` - Cold side inlet state /// * `cold_outlet` - Cold side outlet state /// /// # Returns /// /// The heat transfer rate in Watts (positive = heat flows from hot to cold) fn compute_heat_transfer( &self, hot_inlet: &FluidState, hot_outlet: &FluidState, cold_inlet: &FluidState, cold_outlet: &FluidState, dynamic_ua_scale: Option, ) -> Power; /// Computes residuals for the solver. /// /// The residuals represent the error in the heat transfer equations /// that the solver will attempt to drive to zero. fn compute_residuals( &self, hot_inlet: &FluidState, hot_outlet: &FluidState, cold_inlet: &FluidState, cold_outlet: &FluidState, residuals: &mut ResidualVector, dynamic_ua_scale: Option, ); /// Returns the number of equations this model contributes. fn n_equations(&self) -> usize; /// Returns the nominal UA value (overall heat transfer coefficient × area) in W/K. fn ua(&self) -> f64; /// Returns the UA calibration scale (default 1.0). UA_eff = ua_scale × ua_nominal. fn ua_scale(&self) -> f64 { 1.0 } /// Sets the UA calibration scale (e.g. from Calib.f_ua). fn set_ua_scale(&mut self, _s: f64) {} /// Returns the effective UA used in heat transfer. If dynamic_ua_scale is provided, it is used instead of ua_scale. fn effective_ua(&self, dynamic_ua_scale: Option) -> f64 { self.ua() * dynamic_ua_scale.unwrap_or_else(|| self.ua_scale()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_fluid_state_default() { let state = FluidState::default(); assert_eq!(state.temperature, 300.0); assert_eq!(state.pressure, 101_325.0); assert_eq!(state.cp, 1000.0); } #[test] fn test_fluid_state_heat_capacity_rate() { let state = FluidState::new(300.0, 101_325.0, 0.0, 0.5, 2000.0); let c = state.heat_capacity_rate(); assert!((c - 1000.0).abs() < 1e-10); } #[test] fn test_fluid_state_from_temperature() { let state = FluidState::from_temperature(350.0); assert_eq!(state.temperature, 350.0); assert_eq!(state.pressure, 101_325.0); } }