# -*- coding: utf-8
"""Module of class MovingBoundaryHeatExchanger.
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/movingboundary.py
SPDX-License-Identifier: MIT
"""
import numpy as np
from tespy.components.component import component_registry
from tespy.components.heat_exchangers.sectioned import SectionedHeatExchanger
[docs]
@component_registry
class MovingBoundaryHeatExchanger(SectionedHeatExchanger):
r"""
Class for counter flow heat exchanger with UA sections.
The heat exchanger is internally discretized into multiple sections, which
are defined by phase changes. The component assumes, that a pressure drop
is linear to the change in enthalpy, meaning the phase boundary
identification is done iteratively. In principle the implementations
follows :cite:`bell2015`.
.. image:: /api/_images/components/HeatExchanger.svg
:alt: flowsheet of the movingboundaryheatexchanger
:align: center
:class: only-light
.. image:: /api/_images/components/HeatExchanger_darkmode.svg
:alt: flowsheet of the movingboundaryheatexchanger
: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
----------
alpha_ratio : float, dict
Secondary to refrigerant side convective heat transfer coefficient
ratio. Quantity: :code:`ratio`.
area_ratio : float, dict
Secondary to refrigerant side heat transfer area ratio. Quantity:
:code:`ratio`.
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>`.
eff_cold : float, dict
Heat exchanger effectiveness for cold side. Quantity:
:code:`efficiency`.
Equation: :py:meth:`eff_cold_func <tespy.components.heat_exchangers.base.HeatExchanger.eff_cold_func>`.
eff_hot : float, dict
Heat exchanger effectiveness for hot side. Quantity: :code:`efficiency`.
Equation: :py:meth:`eff_hot_func <tespy.components.heat_exchangers.base.HeatExchanger.eff_hot_func>`.
eff_max : float, dict
Maximum heat exchanger effectiveness. Quantity: :code:`efficiency`.
Equation: :py:meth:`eff_max_func <tespy.components.heat_exchangers.base.HeatExchanger.eff_max_func>`.
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>`.
re_exp_r : float, dict
Reynolds exponent for UA modification based on refrigerant side mass
flow.
re_exp_sf : float, dict
Reynolds exponent for UA modification based on secondary fluid side mass
flow.
refrigerant_index : int
Side on which the refrigerant is flowing (0: hot, 1:cold).
td_log : float, dict
Deprecated, use :code:`lmtd` instead. Quantity:
:code:`temperature_difference`.
td_pinch : float, dict
Equation for minimum pinch. Quantity: :code:`temperature_difference`.
Equation: :py:meth:`td_pinch_func <tespy.components.heat_exchangers.sectioned.SectionedHeatExchanger.td_pinch_func>`.
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.base.HeatExchanger.ttd_l_func>`.
ttd_min : float, dict
Minimum terminal temperature difference. Quantity:
:code:`temperature_difference`.
Equation: :py:meth:`ttd_min_func <tespy.components.heat_exchangers.base.HeatExchanger.ttd_min_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.base.HeatExchanger.ttd_u_func>`.
UA : float, dict
Sum of UA values of all sections of heat exchanger. Quantity:
:code:`heat_transfer_coefficient`.
Equation: :py:meth:`UA_func <tespy.components.heat_exchangers.sectioned.SectionedHeatExchanger.UA_func>`.
UA_cecchinato : GroupedComponentProperties
Equation for UA modification in offdesign. Elements: :code:`re_exp_r`,
:code:`re_exp_sf`, :code:`alpha_ratio`, :code:`area_ratio`.
Equation: :py:meth:`UA_cecchinato_func <tespy.components.heat_exchangers.sectioned.SectionedHeatExchanger.UA_cecchinato_func>`.
UA_char : GroupedComponentCharacteristics
Equation for sectioned UA modification based on characteristic lines.
Elements: :code:`UA_char1`, :code:`UA_char2`.
Equation: :py:meth:`UA_char_func <tespy.components.heat_exchangers.sectioned.SectionedHeatExchanger.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 equations only apply to counter-current heat exchangers.
Example
-------
Water vapor should be cooled down, condensed and then further subcooled. For
this air is heated up from 15 °C to 25 °C.
>>> from tespy.components import Source, Sink, MovingBoundaryHeatExchanger
>>> from tespy.connections import Connection
>>> from tespy.networks import Network
>>> import numpy as np
>>> nw = Network()
>>> nw.units.set_defaults(**{
... "pressure": "bar", "pressure_difference": "bar",
... "temperature": "degC"
... })
>>> nw.iterinfo = False
>>> so1 = Source("vapor source")
>>> so2 = Source("air source")
>>> cd = MovingBoundaryHeatExchanger("condenser")
>>> si1 = Sink("water sink")
>>> si2 = Sink("air sink")
>>> c1 = Connection(so1, "out1", cd, "in1", label="1")
>>> c2 = Connection(cd, "out1", si1, "in1", label="2")
>>> c11 = Connection(so2, "out1", cd, "in2", label="11")
>>> c12 = Connection(cd, "out2", si2, "in1", label="12")
>>> nw.add_conns(c1, c2, c11, c12)
To generate good guess values, first we run the simulation with fixed
pressure on the water side. The water enters at superheated vapor state
with 15 °C superheating and leaves it with 10 °C subcooling.
>>> c1.set_attr(fluid={"Water": 1}, p=1, td_dew=15, m=1)
>>> c2.set_attr(td_bubble=15)
>>> c11.set_attr(fluid={"Air": 1}, p=1, T=15)
>>> c12.set_attr(T=25)
>>> cd.set_attr(pr1=1, pr2=1)
>>> nw.solve("design")
Now we can remove the pressure specifications on the air side and impose
the minimum pinch instead, which will determine the actual water
condensation pressure.
>>> c1.set_attr(p=None)
>>> cd.set_attr(td_pinch=5)
>>> nw.solve("design")
>>> round(c1.p.val, 3)
0.056
>>> round(c1.T.val, 1)
50.0
We can also see the temperature differences in all sections of the heat
exchanger. Since the water vapor is cooled, condensed and then subcooled,
while the air does not change phase, three sections will form:
>>> Q_sections, T_steps_hot, T_steps_cold, Q_per_section, td_log_per_section = cd.calc_sections()
>>> delta_T_between_sections = T_steps_hot - T_steps_cold
>>> [round(float(dT), 2) for dT in delta_T_between_sections]
[5.0, 19.75, 10.11, 25.0]
We can see that the lowest delta T is the first one. This is the delta T
between the hot side outlet and the cold side inlet, which can also be seen
if we have a look at the network's results.
>>> ();nw.print_results();() # doctest: +ELLIPSIS
(...)
If we change the subcooling degree at the water outlet, the condensation
pressure and pinch will move.
>>> c2.set_attr(td_bubble=5)
>>> nw.solve("design")
>>> round(c1.p.val, 3)
0.042
>>> Q_sections, T_steps_hot, T_steps_cold, Q_per_section, td_log_per_section = cd.calc_sections()
>>> delta_T_between_sections = T_steps_hot - T_steps_cold
>>> [round(float(dT), 2) for dT in delta_T_between_sections]
[9.88, 14.8, 5.0, 19.88]
Finally, in contrast to the baseclass :code:`HeatExchanger` `kA` value, the
`UA` value takes into account the heat transfer per section and calculates
the heat transfer coefficient as the sum of all sections, while the `kA`
value only takes into account the inlet and outlet temperatures and the
total heat transfer.
>>> round(cd.kA.val)
173307
>>> round(cd.UA.val)
273449
It is also possible to apply a partload modification to UA following the
implementation of :cite:`cecchinato2010`. For this you have to specify
:code:`UA_cecchinato` as offdesign parameter and along with it, values for
- refrigerant side Reynolds exponent
- secondary fluid side Reynolds exponent
- secondary fluid to refrigerant area ratio
- secondary fluid to refrigerant alpha (heat transfer coefficient) ratio
- the refrigerant index (which side of the heat exchanger is passed by the
refrigerant)
>>> design_state = nw.save(as_dict=True)
>>> cd.set_attr(
... area_ratio=20, # typical for a finned heat exchanger
... alpha_ratio=1e-2, # alpha for water side is higher
... re_exp_r=0.8,
... re_exp_sf=0.55,
... refrigerant_index=0, # water is refrigerant in this case
... design=["td_pinch"],
... offdesign=["UA_cecchinato"]
... )
>>> nw.solve("offdesign", design_path=design_state)
Without modifying any parameter, pinch and UA should be identical to
design conditions.
>>> round(cd.td_pinch.val, 2)
5.0
>>> round(cd.UA.val)
273449
With change in operating conditions, e.g. reduction of heat transfer
we'd typically observe lower pinch, if the heat transfer reduces faster
than the UA value does.
>>> c1.set_attr(m=0.8)
>>> nw.solve("offdesign", design_path=design_state)
>>> round(cd.Q.val_SI / cd.Q.design, 2)
0.8
>>> round(cd.UA.val_SI / cd.UA.design, 2)
0.88
>>> round(cd.td_pinch.val, 2)
4.3
"""
[docs]
def get_parameters(self):
params = super().get_parameters()
del params["num_sections"]
return params
def _assign_steps(self):
"""Assign the sections of the heat exchanger
Returns
-------
list
List of cumulative sum of heat exchanged defining the heat exchanger
sections.
"""
steps_hot = self._get_moving_steps(self.inl[0], self.outl[0])
steps_cold = self._get_moving_steps(self.inl[1], self.outl[1])
# unique throws out duplicates and sorts at the same time
steps = np.unique(np.r_[steps_hot, steps_cold])
return steps