Files
Entropyk/crates/components/src/heat_exchanger/model.rs

207 lines
6.2 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 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);
}
}