"""
Heat Exchanger Model
This module provides a counter-current heat exchanger model with thermal dynamics
using effectiveness-NTU method.
Author: Thorsten Gressling <gressling@paramus.ai>
License: MIT License
"""
import numpy as np
import logging
from typing import Optional, Tuple, Dict, Any
from ..base import ProcessModel
logger = logging.getLogger(__name__)
[docs]
class HeatExchanger(ProcessModel):
"""Counter-current heat exchanger model with thermal dynamics."""
[docs]
def __init__(
self,
U: float = 500.0, # Overall heat transfer coefficient [W/m²·K]
A: float = 10.0, # Heat transfer area [m²]
m_hot: float = 1.0, # Hot fluid mass flow rate [kg/s]
m_cold: float = 1.2, # Cold fluid mass flow rate [kg/s]
cp_hot: float = 4180.0, # Hot fluid specific heat [J/kg·K]
cp_cold: float = 4180.0, # Cold fluid specific heat [J/kg·K]
V_hot: float = 0.1, # Hot fluid volume [m³]
V_cold: float = 0.1, # Cold fluid volume [m³]
rho_hot: float = 1000.0, # Hot fluid density [kg/m³]
rho_cold: float = 1000.0, # Cold fluid density [kg/m³]
name: str = "HeatExchanger"
):
"""
Initialize counter-current heat exchanger.
Args:
U: Overall heat transfer coefficient [W/m²·K]
A: Heat transfer area [m²]
m_hot: Hot fluid mass flow rate [kg/s]
m_cold: Cold fluid mass flow rate [kg/s]
cp_hot: Hot fluid specific heat [J/kg·K]
cp_cold: Cold fluid specific heat [J/kg·K]
V_hot: Hot fluid volume [m³]
V_cold: Cold fluid volume [m³]
rho_hot: Hot fluid density [kg/m³]
rho_cold: Cold fluid density [kg/m³]
name: Model name
"""
super().__init__(name)
self.U = U
self.A = A
self.m_hot = m_hot
self.m_cold = m_cold
self.cp_hot = cp_hot
self.cp_cold = cp_cold
self.V_hot = V_hot
self.V_cold = V_cold
self.rho_hot = rho_hot
self.rho_cold = rho_cold
# Calculate heat capacities
self.C_hot = m_hot * cp_hot # Hot fluid heat capacity rate [W/K]
self.C_cold = m_cold * cp_cold # Cold fluid heat capacity rate [W/K]
self.C_min = min(self.C_hot, self.C_cold)
self.C_max = max(self.C_hot, self.C_cold)
self.C_ratio = self.C_min / self.C_max if self.C_max > 0 else 0
# Calculate NTU (Number of Transfer Units)
self.NTU = U * A / self.C_min if self.C_min > 0 else 0
# Calculate effectiveness for counter-current configuration
if abs(self.C_ratio - 1.0) < 1e-6:
self.effectiveness = self.NTU / (1 + self.NTU)
else:
if self.NTU > 0:
exp_term = np.exp(-self.NTU * (1 - self.C_ratio))
self.effectiveness = (1 - exp_term) / (1 - self.C_ratio * exp_term)
else:
self.effectiveness = 0
# Thermal time constants
self.tau_hot = self.rho_hot * self.V_hot * self.cp_hot / self.C_hot if self.C_hot > 0 else np.inf
self.tau_cold = self.rho_cold * self.V_cold * self.cp_cold / self.C_cold if self.C_cold > 0 else np.inf
self.parameters = {
'U': U, 'A': A, 'm_hot': m_hot, 'm_cold': m_cold,
'cp_hot': cp_hot, 'cp_cold': cp_cold, 'V_hot': V_hot, 'V_cold': V_cold,
'rho_hot': rho_hot, 'rho_cold': rho_cold, 'effectiveness': self.effectiveness,
'NTU': self.NTU, 'tau_hot': self.tau_hot, 'tau_cold': self.tau_cold
}
# Define state and input variables
self.state_variables = {
'T_hot_out': 'Hot fluid outlet temperature [K]',
'T_cold_out': 'Cold fluid outlet temperature [K]'
}
self.inputs = {
'T_hot_in': 'Hot fluid inlet temperature [K]',
'T_cold_in': 'Cold fluid inlet temperature [K]',
'm_hot': 'Hot fluid mass flow rate [kg/s]',
'm_cold': 'Cold fluid mass flow rate [kg/s]'
}
self.outputs = {
'T_hot_out': 'Hot fluid outlet temperature [K]',
'T_cold_out': 'Cold fluid outlet temperature [K]',
'Q': 'Heat transfer rate [W]',
'effectiveness': 'Heat exchanger effectiveness [-]'
}
[docs]
def dynamics(self, t: float, x: np.ndarray, u: np.ndarray) -> np.ndarray:
"""
Heat exchanger dynamics with thermal time constants.
Args:
t: Time
x: [T_hot_out, T_cold_out]
u: [T_hot_in, T_cold_in, m_hot_new, m_cold_new]
Returns:
[dT_hot_out/dt, dT_cold_out/dt]
"""
T_hot_out = x[0]
T_cold_out = x[1]
T_hot_in = u[0]
T_cold_in = u[1]
# Update flow rates if provided
if len(u) > 2:
m_hot_current = u[2] if u[2] > 0 else self.m_hot
m_cold_current = u[3] if u[3] > 0 else self.m_cold
else:
m_hot_current = self.m_hot
m_cold_current = self.m_cold
# Recalculate heat capacity rates with current flow rates
C_hot_current = m_hot_current * self.cp_hot
C_cold_current = m_cold_current * self.cp_cold
C_min_current = min(C_hot_current, C_cold_current)
C_max_current = max(C_hot_current, C_cold_current)
# Recalculate effectiveness with current conditions
if C_min_current > 0:
C_ratio_current = C_min_current / C_max_current
NTU_current = self.U * self.A / C_min_current
if abs(C_ratio_current - 1.0) < 1e-6:
eff_current = NTU_current / (1 + NTU_current)
else:
exp_term = np.exp(-NTU_current * (1 - C_ratio_current))
eff_current = (1 - exp_term) / (1 - C_ratio_current * exp_term)
else:
eff_current = 0.0
# Calculate steady-state outlet temperatures using effectiveness-NTU method
Q_max = C_min_current * (T_hot_in - T_cold_in)
Q_actual = eff_current * Q_max
if C_hot_current > 0:
T_hot_out_ss = T_hot_in - Q_actual / C_hot_current
else:
T_hot_out_ss = T_hot_in
if C_cold_current > 0:
T_cold_out_ss = T_cold_in + Q_actual / C_cold_current
else:
T_cold_out_ss = T_cold_in
# Calculate time constants with current flow rates
if m_hot_current > 0:
tau_hot_current = self.rho_hot * self.V_hot * self.cp_hot / C_hot_current
else:
tau_hot_current = self.tau_hot
if m_cold_current > 0:
tau_cold_current = self.rho_cold * self.V_cold * self.cp_cold / C_cold_current
else:
tau_cold_current = self.tau_cold
# First-order dynamics towards steady-state
dT_hot_out_dt = (T_hot_out_ss - T_hot_out) / tau_hot_current
dT_cold_out_dt = (T_cold_out_ss - T_cold_out) / tau_cold_current
return np.array([dT_hot_out_dt, dT_cold_out_dt])
[docs]
def steady_state(self, u: np.ndarray) -> np.ndarray:
"""
Calculate steady-state outlet temperatures.
Args:
u: [T_hot_in, T_cold_in, m_hot, m_cold]
Returns:
[T_hot_out_ss, T_cold_out_ss]
"""
T_hot_in = u[0]
T_cold_in = u[1]
# Use flow rates if provided, otherwise use design values
if len(u) > 2:
m_hot = u[2] if u[2] > 0 else self.m_hot
m_cold = u[3] if u[3] > 0 else self.m_cold
else:
m_hot = self.m_hot
m_cold = self.m_cold
# Calculate heat capacity rates
C_hot = m_hot * self.cp_hot
C_cold = m_cold * self.cp_cold
C_min = min(C_hot, C_cold)
C_max = max(C_hot, C_cold)
if C_min > 0:
C_ratio = C_min / C_max
NTU = self.U * self.A / C_min
# Effectiveness for counter-current heat exchanger
if abs(C_ratio - 1.0) < 1e-6:
effectiveness = NTU / (1 + NTU)
else:
exp_term = np.exp(-NTU * (1 - C_ratio))
effectiveness = (1 - exp_term) / (1 - C_ratio * exp_term)
# Calculate heat transfer and outlet temperatures
Q_max = C_min * (T_hot_in - T_cold_in)
Q_actual = effectiveness * Q_max
T_hot_out_ss = T_hot_in - Q_actual / C_hot
T_cold_out_ss = T_cold_in + Q_actual / C_cold
else:
# No flow case
T_hot_out_ss = T_hot_in
T_cold_out_ss = T_cold_in
return np.array([T_hot_out_ss, T_cold_out_ss])
[docs]
def calculate_heat_transfer_rate(self, T_hot_in: float, T_cold_in: float,
T_hot_out: float, T_cold_out: float) -> float:
"""
Calculate the actual heat transfer rate.
Args:
T_hot_in: Hot fluid inlet temperature [K]
T_cold_in: Cold fluid inlet temperature [K]
T_hot_out: Hot fluid outlet temperature [K]
T_cold_out: Cold fluid outlet temperature [K]
Returns:
Heat transfer rate [W]
"""
Q_hot = self.C_hot * (T_hot_in - T_hot_out)
Q_cold = self.C_cold * (T_cold_out - T_cold_in)
# Return average (should be equal in steady state)
return (Q_hot + Q_cold) / 2
[docs]
def calculate_lmtd(self, T_hot_in: float, T_cold_in: float,
T_hot_out: float, T_cold_out: float) -> float:
"""
Calculate Log Mean Temperature Difference (LMTD).
Args:
T_hot_in: Hot fluid inlet temperature [K]
T_cold_in: Cold fluid inlet temperature [K]
T_hot_out: Hot fluid outlet temperature [K]
T_cold_out: Cold fluid outlet temperature [K]
Returns:
LMTD [K]
"""
# Temperature differences at each end
delta_T1 = T_hot_in - T_cold_out # Hot inlet - Cold outlet
delta_T2 = T_hot_out - T_cold_in # Hot outlet - Cold inlet
if abs(delta_T1 - delta_T2) < 1e-6:
# Avoid division by zero when temperature differences are equal
return delta_T1
else:
return (delta_T1 - delta_T2) / np.log(delta_T1 / delta_T2)
[docs]
def describe(self) -> dict:
"""
Introspect metadata for documentation and algorithm querying.
Returns:
dict: Metadata about the model including algorithms,
parameters, equations, and usage information.
"""
return {
'type': 'HeatExchanger',
'description': 'Counter-current shell-and-tube heat exchanger with effectiveness-NTU method',
'category': 'unit/heat_transfer',
'algorithms': {
'effectiveness_ntu': 'ε = (1-exp(-NTU(1-Cr)))/(1-Cr*exp(-NTU(1-Cr))) for counter-current flow',
'heat_transfer': 'Q = ε * Cmin * (Th,in - Tc,in)',
'thermal_dynamics': 'τ * dT/dt = Tss - T where τ = ρVcp/C',
'lmtd': 'LMTD = (ΔT1 - ΔT2)/ln(ΔT1/ΔT2)'
},
'parameters': {
'U': {
'value': self.U,
'units': 'W/m²·K',
'description': 'Overall heat transfer coefficient'
},
'A': {
'value': self.A,
'units': 'm²',
'description': 'Heat transfer area'
},
'm_hot': {
'value': self.m_hot,
'units': 'kg/s',
'description': 'Hot fluid mass flow rate'
},
'm_cold': {
'value': self.m_cold,
'units': 'kg/s',
'description': 'Cold fluid mass flow rate'
},
'cp_hot': {
'value': self.cp_hot,
'units': 'J/kg·K',
'description': 'Hot fluid specific heat capacity'
},
'cp_cold': {
'value': self.cp_cold,
'units': 'J/kg·K',
'description': 'Cold fluid specific heat capacity'
},
'effectiveness': {
'value': self.effectiveness,
'units': '-',
'description': 'Heat exchanger thermal effectiveness'
},
'NTU': {
'value': self.NTU,
'units': '-',
'description': 'Number of Transfer Units'
}
},
'state_variables': self.state_variables,
'inputs': self.inputs,
'outputs': self.outputs,
'valid_ranges': {
'U': {'min': 50.0, 'max': 5000.0, 'units': 'W/m²·K'},
'A': {'min': 0.1, 'max': 1000.0, 'units': 'm²'},
'm_hot': {'min': 0.001, 'max': 100.0, 'units': 'kg/s'},
'm_cold': {'min': 0.001, 'max': 100.0, 'units': 'kg/s'},
'T_hot_in': {'min': 273.15, 'max': 773.15, 'units': 'K'},
'T_cold_in': {'min': 273.15, 'max': 373.15, 'units': 'K'}
},
'applications': [
'Process heating and cooling',
'Heat recovery systems',
'Condensers and reboilers',
'Oil and gas processing',
'Chemical reactor cooling',
'Power plant heat exchangers',
'HVAC systems'
],
'limitations': [
'Assumes constant fluid properties',
'No phase change modeling',
'Counter-current flow configuration only',
'Lumped thermal capacitance model',
'Negligible pressure drop effects',
'No fouling resistance included'
]
}