feat(components): add ThermoState generators and Eurovent backend demo

This commit is contained in:
Sepehr
2026-02-20 22:01:38 +01:00
parent 375d288950
commit 4a40fddfe3
271 changed files with 28614 additions and 447 deletions

View File

@@ -0,0 +1,204 @@
//! 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) -> Power {
/// Power::from_watts(0.0)
/// }
/// fn compute_residuals(&self, _: &FluidState, _: &FluidState, _: &FluidState, _: &FluidState, _: &mut ResidualVector) {}
/// 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,
) -> 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,
);
/// 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: ua_scale × ua_nominal.
fn effective_ua(&self) -> f64 {
self.ua() * 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);
}
}