# -*- coding: utf-8
"""Module of class ParallelFlowHeatExchanger.
This file is part of project TESPy (github.com/oemof/tespy). It's copyrighted
by the contributors recorded in the version control history of the file,
available from its original location tespy/components/heat_exchangers/parallel.py
SPDX-License-Identifier: MIT
"""
import math
from tespy.components.component import component_registry
from tespy.components.heat_exchangers.base import HeatExchanger
[docs]
@component_registry
class ParallelFlowHeatExchanger(HeatExchanger):
r"""
Class for parallel flow heat exchanger.
.. image:: /api/_images/components/HeatExchanger.svg
:alt: flowsheet of the parallelflowheatexchanger
:align: center
:class: only-light
.. image:: /api/_images/components/HeatExchanger_darkmode.svg
:alt: flowsheet of the parallelflowheatexchanger
:align: center
:class: only-dark
Ports
-----
- Fluid inlets: in1, in2
- Fluid outlets: out1, out2
Mandatory Equations
-------------------
- mass flow equality constraint(s): :py:meth:`variable_equality_structure_matrix <tespy.components.component.Component.variable_equality_structure_matrix>`
- fluid composition equality constraint(s): :py:meth:`variable_equality_structure_matrix <tespy.components.component.Component.variable_equality_structure_matrix>`
- hot side to cold side heat transfer equation: :py:meth:`energy_balance_func <tespy.components.heat_exchangers.base.HeatExchanger.energy_balance_func>`
Parameters
----------
char_warnings : bool
Ignore warnings on default characteristics usage for this component.
design : list
List containing design parameters (stated as String).
design_path : str
Path to the components design case.
dp1 : float, dict
Hot side inlet to outlet absolute pressure change. Quantity:
:code:`pressure_difference`.
Equation: :py:meth:`dp_structure_matrix <tespy.components.component.Component.dp_structure_matrix>`.
dp2 : float, dict
Cold side inlet to outlet absolute pressure change. Quantity:
:code:`pressure_difference`.
Equation: :py:meth:`dp_structure_matrix <tespy.components.component.Component.dp_structure_matrix>`.
kA : float, dict
Deprecated, use :code:`UA` instead. Quantity:
:code:`heat_transfer_coefficient`.
kA_char : GroupedComponentCharacteristics
Deprecated, use :code:`UA_char` instead. Elements: :code:`kA_char1`,
:code:`kA_char2`.
kA_char1 : tespy.tools.characteristics.CharLine, dict
Deprecated, use :code:`UA_char1` instead.
kA_char2 : tespy.tools.characteristics.CharLine, dict
Deprecated, use :code:`UA_char2` instead.
label : str
The label of the component.
lmtd : float, dict
Effective logarithmic mean temperature difference |Q|/UA. Quantity:
:code:`temperature_difference`.
local_design : bool
Treat this component in design mode in an offdesign calculation.
local_offdesign : bool
Treat this component in offdesign mode in a design calculation.
offdesign : list
List containing offdesign parameters (stated as String).
pr1 : float, dict
Hot side outlet to inlet pressure ratio. Quantity: :code:`ratio`.
Equation: :py:meth:`pr_structure_matrix <tespy.components.component.Component.pr_structure_matrix>`.
pr2 : float, dict
Cold side outlet to inlet pressure ratio. Quantity: :code:`ratio`.
Equation: :py:meth:`pr_structure_matrix <tespy.components.component.Component.pr_structure_matrix>`.
printout : bool
Include this component in the network's results printout.
Q : float, dict
Heat transfer from hot side. Quantity: :code:`heat`.
Equation: :py:meth:`energy_balance_hot_func <tespy.components.heat_exchangers.base.HeatExchanger.energy_balance_hot_func>`.
td_log : float, dict
Deprecated, use :code:`lmtd` instead. Quantity:
:code:`temperature_difference`.
ttd_l : float, dict
Terminal temperature difference at hot side outlet to cold side inlet.
Quantity: :code:`temperature_difference`.
Equation: :py:meth:`ttd_l_func <tespy.components.heat_exchangers.parallel.ParallelFlowHeatExchanger.ttd_l_func>`.
ttd_u : float, dict
Terminal temperature difference at hot side inlet to cold side outlet.
Quantity: :code:`temperature_difference`.
Equation: :py:meth:`ttd_u_func <tespy.components.heat_exchangers.parallel.ParallelFlowHeatExchanger.ttd_u_func>`.
UA : float, dict
Heat transfer coefficient considering terminal temperature differences.
Quantity: :code:`heat_transfer_coefficient`.
Equation: :py:meth:`UA_func <tespy.components.heat_exchangers.base.HeatExchanger.UA_func>`.
UA_char : GroupedComponentCharacteristics
Equation for heat transfer based on UA and modification factor.
Elements: :code:`UA_char1`, :code:`UA_char2`.
Equation: :py:meth:`UA_char_func <tespy.components.heat_exchangers.base.HeatExchanger.UA_char_func>`.
UA_char1 : tespy.tools.characteristics.CharLine, dict
Hot side UA modification lookup table for offdesign.
UA_char2 : tespy.tools.characteristics.CharLine, dict
Cold side UA modification lookup table for offdesign.
zeta1 : float, dict
Deprecated, use :code:`zeta1_d4` instead.
zeta1_d4 : float, dict
Hot side geometry-independent friction coefficient zeta/D^4 for pressure
loss calculation.
Equation: :py:meth:`zeta_d4_func <tespy.components.component.Component.zeta_d4_func>`.
zeta2 : float, dict
Deprecated, use :code:`zeta2_d4` instead.
zeta2_d4 : float, dict
Cold side geometry-independent friction coefficient zeta/D^4 for
pressure loss calculation.
Equation: :py:meth:`zeta_d4_func <tespy.components.component.Component.zeta_d4_func>`.
Notes
-----
.. note::
The :code:`ParallelFlowHeatExchanger` implements parallel flow of both
streams, meaning the streams enter with a high temperature difference and
then gradually reduce their temperature difference to each other. The
initial temperature difference is the maximum temperature difference, the
final temperature difference is the minimum temperature difference.
Example
-------
Water at 75 °C is used to heat up an air stream 2500 l/s from 10 °C to
35 °C.
>>> from tespy.components import Sink, Source, ParallelFlowHeatExchanger
>>> from tespy.connections import Connection
>>> from tespy.networks import Network
>>> nw = Network(iterinfo=False)
>>> nw.units.set_defaults(**{
... "pressure": "bar", "pressure_difference": "bar",
... "temperature": "degC", "enthalpy": "kJ/kg",
... "volumetric_flow": "l/s", "heat_transfer_coefficient": "kW/K"
... })
>>> feed_water = Source("Feed water inlet")
>>> return_water = Sink("Water outlet")
>>> air_inlet = Source("Fresh air inlet")
>>> air_warm = Sink("Air outlet")
>>> he = ParallelFlowHeatExchanger("heat exchanger")
>>> c1 = Connection(feed_water, 'out1', he, 'in1')
>>> c2 = Connection(he, 'out1', return_water, 'in1')
>>> c3 = Connection(air_inlet, 'out1', he, 'in2')
>>> c4 = Connection(he, 'out2', air_warm, 'in1')
>>> nw.add_conns(c1, c2, c3, c4)
We assume pressure losses of 10 mbar on the air side, and 100 mbar on the
water side. Depending on our specifications we can calculate:
- What is the temperature of the water leaving the heat exchanger, or
- What is the required mass flow of water to heat up the air
Let's first assume, that a final pinch :code:`ttd_u` of 7.5 K is desired,
meaning, the water should leave the heat exchanger with a temperature
higher than the air by 7.5 K. With that, we will get the water flow, and
also the heat transfer coefficient :code:`kA` as a result.
.. note::
Note, that for specification of initial or final pinch temperature
differences, it is often important to start the calculation with a good
initial guess. In this case, we know that the water will be in liquid
state, therefore we can
- either specify the outlet enthalpy initial guess to be liquid,
- the state at the water outlet to be :code:`"l"`,
- or use :code:`"INCOMP::Water"` as fluid, since this uses liquid phase
only.
>>> he.set_attr(dp1=0.1, dp2=0.01, ttd_u=7.5)
>>> c1.set_attr(fluid={"INCOMP::Water": 1}, T=70, p=1.3)
>>> c3.set_attr(fluid={"air": 1}, T=10, p=1.02, v=2500)
>>> c4.set_attr(T=35)
>>> nw.solve("design")
>>> round(c1.v.val, 2)
0.7
>>> round(he.kA.val, 2)
3.13
Now, it might be interesting to see what happens under different operation
conditions after we have designed the system. For that, we can assume that
the heat transfer coefficient is constant. First we just fix the :code:`UA`
value instead of the final pinch and then resolve again.
>>> he.set_attr(design=["ttd_u"], offdesign=["UA"])
>>> design_state = nw.save(as_dict=True)
>>> nw.solve("offdesign", design_path=design_state)
>>> round(he.UA.val_SI / he.UA.design, 1)
1.0
Now, let's see what happens under different operating conditions. First
we change the air volumetric flow, then we change the air temperature to
check what happens to the outflow temperature of the water.
>>> c3.set_attr(v=2000)
>>> nw.solve("offdesign", design_path=design_state)
>>> round(c2.T.val, 2)
38.69
>>> c3.set_attr(v=2500, T=8)
>>> nw.solve("offdesign", design_path=design_state)
>>> round(c2.T.val, 2)
44.0
"""
[docs]
def get_parameters(self):
params = super().get_parameters()
del params["ttd_min"]
del params["eff_hot"]
del params["eff_cold"]
del params["eff_max"]
return params
def _calc_ttd_u(self):
return self.outl[0].T.val_SI - self.outl[1].T.val_SI
def _calc_ttd_l(self):
return self.inl[0].T.val_SI - self.inl[1].T.val_SI
[docs]
def ttd_l_func(self):
T_i1 = self.inl[0].calc_T()
T_i2 = self.inl[1].calc_T()
return self.ttd_l.val_SI - T_i1 + T_i2
[docs]
def ttd_l_dependents(self):
return [var for c in self.inl for var in [c.p, c.h]]
[docs]
def ttd_u_func(self):
T_o1 = self.outl[0].calc_T()
T_o2 = self.outl[1].calc_T()
return self.ttd_u.val_SI - T_o1 + T_o2
[docs]
def ttd_u_dependents(self):
return [var for c in self.outl for var in [c.p, c.h]]
[docs]
def calculate_td_log(self):
T_i1 = self.inl[0].calc_T()
T_i2 = self.inl[1].calc_T()
T_o1 = self.outl[0].calc_T()
T_o2 = self.outl[1].calc_T()
ttd_u = T_o1 - T_o2
ttd_l = T_i1 - T_i2
min_ttd = min(ttd_u, ttd_l)
if min_ttd <= 0:
return min_ttd
if round(ttd_u, 6) == round(ttd_l, 6):
return ttd_l
return (ttd_l - ttd_u) / math.log(ttd_l / ttd_u)