207 lines
6.2 KiB
Rust
207 lines
6.2 KiB
Rust
//! 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<f64>) -> Power {
|
||
/// Power::from_watts(0.0)
|
||
/// }
|
||
/// fn compute_residuals(&self, _: &FluidState, _: &FluidState, _: &FluidState, _: &FluidState, _: &mut ResidualVector, _: Option<f64>) {}
|
||
/// fn n_equations(&self) -> usize { 3 }
|
||
/// fn ua(&self) -> f64 { self.ua }
|
||
/// }
|
||
/// let model: Box<dyn HeatTransferModel> = 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<f64>,
|
||
) -> 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<f64>,
|
||
);
|
||
|
||
/// 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>) -> 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);
|
||
}
|
||
}
|