# -*- coding: utf-8 -*-
"""
Created on Tue Jul 30 14:32:39 2024
@author: Basile
"""
from component.base_component import BaseComponent
from correlations.convection.pipe_htc import gnielinski_pipe_htc
from correlations.heat_exchanger.e_NTU import e_NTU
from connector.mass_connector import MassConnector
from connector.work_connector import WorkConnector
from connector.heat_connector import HeatConnector
from CoolProp.CoolProp import PropsSI
import CoolProp.CoolProp as CP
[docs]
class HexeNTU(BaseComponent):
"""
**Component**: Heat Exchanger
**Model**: ε-NTU (Effectiveness - Number of Transfer Units) method.
**Description**:
This component models a heat exchanger using the ε-NTU method, a widely used approach for estimating heat transfer performance
in steady-state conditions when outlet temperatures are not known a priori. It calculates heat transfer based on fluid properties,
flow configuration, and geometry using thermal resistances and heat transfer correlations.
The model is applicable to various geometries (e.g., pipe-type, plate-type) and requires fluid and geometric properties.
The thermal effectiveness is computed via an external ε-NTU correlation, which supports multiple flow configurations
(e.g., CounterFlow, ParallelFlow, CrossFlow).
**Assumptions**:
- Steady-state operation.
- No heat loss to the environment.
- No pressure drop considered (isenthalpic mixing assumed).
- Thermophysical properties are evaluated at average temperatures.
- No phase change within the exchanger.
**Connectors**:
su_H (MassConnector): Hot fluid inlet connector.
su_C (MassConnector): Cold fluid inlet connector.
ex_H (MassConnector): Hot fluid outlet connector.
ex_C (MassConnector): Cold fluid outlet connector.
Q_hex (HeatConnector): Connector for total heat transfer rate.
**Parameters**:
Flow_Type : Flow configuration of the fluid ('CounterFlow', 'CrossFlow', 'Shell&Tube', 'ParallelFlow') [-]
A_htx: Total heat exchange area [m²]
L_HTX: Length of the heat exchanger [m]
V_HTX: Volume of the heat exchanger [m³]
A_canal_H: Cross-sectional area of hot fluid channels [m²]
A_canal_C: Cross-sectional area of cold fluid channels [m²]
D_h: Hydraulic diameter [m]
k_plate: Thermal conductivity of the separating plate [W/m.K]
t_plate: Thickness of the separating plate [m]
n_plates: Number of plates [-]
co_pitch: Plate corrugation pitch [m]
chevron_angle: Plate chevron angle [degrees]
fouling: Fouling resistance [m².K/W]
**Inputs**:
T_su_H: Hot fluid inlet temperature [K]
P_su_H: Hot fluid inlet pressure [Pa]
h_su_H: Hot fluid inlet enthalpy [J/kg]
fluid_H: Hot fluid identifier [-]
m_dot_H: Hot fluid mass flow rate [kg/s]
T_su_C: Cold fluid inlet temperature [K]
P_su_C: Cold fluid inlet pressure [Pa]
h_su_C: Cold fluid inlet enthalpy [J/kg]
fluid_C: Cold fluid identifier [-]
m_dot_C: Cold fluid mass flow rate [kg/s]
**Outputs**:
h_ex_H: Hot fluid outlet enthalpy [J/kg]
P_ex_H: Hot fluid outlet pressure [Pa]
h_ex_C: Cold fluid outlet enthalpy [J/kg]
p_ex_C: Cold fluid outlet pressure [Pa]
Q_dot: Heat transfer rate [W]
"""
def __init__(self):
super().__init__()
self.su_H = MassConnector()
self.su_C = MassConnector()
self.ex_H = MassConnector()
self.ex_C = MassConnector() # Mass_connector
self.Q_hex = HeatConnector()
def get_required_inputs(self):
# Return a list of required inputs
return ['P_su_H', 'T_su_H', 'm_dot_H', 'fluid_H', 'P_su_C', 'T_su_C', 'm_dot_C', 'fluid_C']
def get_required_parameters(self):
""" Returns the list of required parameters to describe the geometry and physical configuration """
return ['A_htx', 'L_HTX', 'V_HTX', 'Flow_Type',
'A_canal_h', 'A_canal_c', 'D_h',
'k_plate', 't_plate', 'n_plates',
'co_pitch', 'chevron_angle', 'fouling']
def solve(self):
self.check_calculable()
self.check_parametrized()
self.AS_H = CP.AbstractState('HEOS', self.su_H.fluid)
self.AS_C = CP.AbstractState('HEOS', self.su_C.fluid)
if self.calculable and self.parametrized:
# Detect Phase change
# self.detect_phase_change()
# Calcul de C_r
# cp_h = PropsSI('C', 'H', self.su_hot.h, 'P', self.su_hot.p, self.su_hot.fluid)
# cp_c = PropsSI('C', 'H', self.su_cold.h, 'P', self.su_cold.p, self.su_cold.fluid)
self.AS_H.update(CP.HmassP_INPUTS, self.su_H.h, self.su_H.p)
cp_h = self.AS_H.cpmass()
self.AS_C.update(CP.HmassP_INPUTS, self.su_H.h, self.su_C.p)
cp_c = self.AS_C.cpmass()
C_h = cp_h*self.su_H.m_dot #Heat capacity rate
C_c = cp_c*self.su_C.m_dot
C_min = min(C_h, C_c)
C_max = max(C_h, C_c)
C_r = C_min/C_max # Heat capacity ratio
# Calcul de NTU
T_w = (self.su_H.T + self.su_C.T)/2
# --- Heat transfer coefficient estimation using Gnielinski correlation ---
# mu_h, Pr_h, k_h = PropsSI(('V','PRANDTL','L'), 'H', self.su_hot.h, 'P', self.su_hot.p, self.su_hot.fluid)
# mu_c, Pr_c, k_c = PropsSI(('V','PRANDTL','L'), 'H', self.su_cold.h, 'P', self.su_cold.p, self.su_cold.fluid)
self.AS_H.update(CP.HmassP_INPUTS, self.su_H.h, self.su_H.p)
mu_h = self.AS_H.viscosity()
Pr_h = self.AS_H.Prandtl()
k_h = self.AS_H.conductivity()
self.AS_C.update(CP.HmassP_INPUTS, self.su_C.h, self.su_C.p)
mu_c = self.AS_C.viscosity()
Pr_c = self.AS_C.Prandtl()
k_c = self.AS_C.conductivity()
G_h = self.su_H.m_dot/self.params['A_canal_h']
G_c = self.su_C.m_dot/self.params['A_canal_c']
h_h = gnielinski_pipe_htc(mu_h, Pr_h, Pr_h, k_h, G_h, self.params['D_h'], self.params['L_HTX'])[0]
h_c = gnielinski_pipe_htc(mu_c, Pr_c, Pr_c, k_c, G_c, self.params['D_h'], self.params['L_HTX'])[0]
AU = (1/(self.params['A_htx']*h_h) + 1/(self.params['A_htx']*h_c) + self.params['t_plate']/(self.params['k_plate']*self.params['A_htx']) + self.params['fouling']/self.params['A_htx'])**(-1)
NTU = AU/C_min
# --- Calculate effectiveness from NTU correlation ---
eps = e_NTU(NTU, C_r, self.params)
# --- Estimate maximum heat transfer Q(ideal case with infinite area) ---
# h_c_Th = PropsSI('H','T',self.su_hot.T,'P',self.su_cold.p,self.su_cold.fluid)
# h_h_Tc = PropsSI('H','T',self.su_cold.T,'P',self.su_hot.p,self.su_hot.fluid)
self.AS_C.update(CP.PT_INPUTS, self.su_C.p, self.su_H.T)
h_c_Th = self.AS_C.hmass()
self.AS_H.update(CP.PT_INPUTS, self.su_H.p, self.su_C.T)
h_h_Tc = self.AS_H.hmass()
# DH_pc_c = PropsSI('H','Q',1,'P',self.su_cold.p,self.su_cold.fluid) - PropsSI('H','Q',0,'P',self.su_cold.p,self.su_cold.fluid)
self.AS_C.update(CP.PQ_INPUTS, self.su_C.p, 0)
h_l_cold = self.AS_C.hmass()
self.AS_C.update(CP.PQ_INPUTS, self.su_C.p, 1)
h_v_cold = self.AS_C.hmass()
DH_pc_c = h_v_cold - h_l_cold
# Special case for incompressibles
if "INCOMP" not in self.su_H.fluid:
# DH_pc_h = PropsSI('H','Q',1,'P',self.su_hot.p,self.su_hot.fluid) - PropsSI('H','Q',0,'P',self.su_hot.p,self.su_hot.fluid)
self.AS_H.update(CP.PQ_INPUTS, self.su_H.p, 0)
h_l_hot = self.AS_H.hmass()
self.AS_H.update(CP.PQ_INPUTS, self.su_H.p, 1)
h_v_hot = self.AS_H.hmass()
DH_pc_h = h_v_hot - h_l_hot
else:
DH_pc_h = 0
Qmax_c = self.su_C.m_dot*((h_c_Th - self.su_C.h))
Qmax_h = self.su_H.m_dot*((self.su_H.h - h_h_Tc))
Qmax = min(Qmax_c, Qmax_h)
Q = eps*Qmax # Actual heat exchanged
# --- Set exhaust states (new enthalpies) and link to connectors ---
self.ex_H.set_properties(H = self.su_H.h - Q/self.su_H.m_dot, fluid = self.su_H.fluid, m_dot = self.su_H.m_dot, P = self.su_H.p)
self.ex_C.set_properties(H = self.su_C.h + Q/self.su_C.m_dot, fluid = self.su_C.fluid, m_dot = self.su_C.m_dot, P = self.su_C.p)
self.Q_hex.set_Q_dot(Q)
self.defined = True
else:
if not self.calculable:
print("Input of the component not completely known. Required inputs:")
for input in self.get_required_inputs():
if input not in self.inputs:
print(f" - {input}")
if not self.parametrized:
print("Parameters of the component not completely known. Required parameters:")
for param in self.get_required_parameters():
if param not in self.params:
print(f" - {param}")
def print_results(self):
if self.defined:
print("=== Heat Exchanger Results ===")
print(f" - H_ex: fluid={self.ex_H.fluid}, T={self.ex_H.T}, p={self.ex_H.p}, m_dot={self.ex_H.m_dot}")
print(f" - C_ex: fluid={self.ex_C.fluid}, T={self.ex_C.T}, p={self.ex_C.p}, m_dot={self.ex_C.m_dot}")
print(f" - Q_dot: {self.Q_hex.Q_dot}")
else:
print("Heat Exchanger component is not defined. Ensure it is solved first.")