# -*- 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
----------
alpha1_g : float, dict
Hot-side heat transfer coefficient in superheated zone. Quantity:
:code:`heat_transfer_coefficient_per_area`.
alpha1_l : float, dict
Hot-side heat transfer coefficient in subcooled zone. Quantity:
:code:`heat_transfer_coefficient_per_area`.
alpha1_sc : float, dict
Hot-side heat transfer coefficient in supercritical zone. Quantity:
:code:`heat_transfer_coefficient_per_area`.
alpha1_tp : float, dict
Hot-side heat transfer coefficient in two-phase zone. Quantity:
:code:`heat_transfer_coefficient_per_area`.
alpha2_g : float, dict
Cold-side heat transfer coefficient in superheated zone. Quantity:
:code:`heat_transfer_coefficient_per_area`.
alpha2_l : float, dict
Cold-side heat transfer coefficient in subcooled zone. Quantity:
:code:`heat_transfer_coefficient_per_area`.
alpha2_sc : float, dict
Cold-side heat transfer coefficient in supercritical zone. Quantity:
:code:`heat_transfer_coefficient_per_area`.
alpha2_tp : float, dict
Cold-side heat transfer coefficient in two-phase zone. Quantity:
:code:`heat_transfer_coefficient_per_area`.
alpha_ratio : float, dict
Secondary to refrigerant side convective heat transfer coefficient
ratio. Quantity: :code:`ratio`.
area_hot : float, dict
Hot-side heat exchange area. Quantity: :code:`area`.
area_ratio : float, dict
Heat transfer area ratio; previously defined as secondary to refrigerant
side ratio, will be defined as hot to cold side ratio in a future
version. Quantity: :code:`ratio`.
area_zones : GroupedComponentProperties
Bell (2015) area-based heat exchanger constraint. All elements must be
set for the group to activate. For phases that do not occur in your
application set the corresponding alpha to any value - it only needs to
be set, as it will not be used. Elements: :code:`area_hot`,
:code:`area_ratio`, :code:`alpha1_l`, :code:`alpha1_tp`,
:code:`alpha1_g`, :code:`alpha1_sc`, :code:`alpha2_l`,
:code:`alpha2_tp`, :code:`alpha2_g`, :code:`alpha2_sc`, :code:`R_cond`.
Equation: :py:meth:`area_zones_func <tespy.components.heat_exchangers.sectioned.SectionedHeatExchanger.area_zones_func>`.
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 :code:`Q/UA`.
Quantity: :code:`temperature_difference`.
lmtd_per_section : numpy.ndarray
Logarithmic mean temperature difference in each section. Quantity:
:code:`temperature_difference`. Result only - populated by the network
after each solve.
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).
phase_cold_per_section : numpy.ndarray
Phase index per section on cold side (0=liquid, 1=two-phase, 2=gas,
3=supercritical). Result only - populated by the network after each
solve.
phase_hot_per_section : numpy.ndarray
Phase index per section on hot side (0=liquid, 1=two-phase, 2=gas,
3=supercritical). Result only - populated by the network after each
solve.
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>`.
Q_per_section : numpy.ndarray
Heat transferred from hot to cold side in each section. Quantity:
:code:`heat`. Result only - populated by the network after each solve.
Q_sections : numpy.ndarray
Cumulative heat transferred from hot to cold side up to each section
boundary. Quantity: :code:`heat`. Result only - populated by the network
after each solve.
R_cond : float, dict
Wall conduction thermal resistance. Quantity:
:code:`thermal_resistance`.
re_exp_cold : float, dict
Reynolds exponent for UA modification based on cold side mass flow.
re_exp_hot : float, dict
Reynolds exponent for UA modification based on hot side mass flow.
re_exp_r : float, dict
Deprecated - Reynolds exponent for refrigerant side mass flow; use
:code:`re_exp_hot` or :code:`re_exp_cold` depending on which side the
refrigerant flows on.
re_exp_sf : float, dict
Deprecated - Reynolds exponent for secondary fluid side mass flow; use
:code:`re_exp_hot` or :code:`re_exp_cold` depending on which side the
secondary fluid flows on.
refrigerant_index : int
Deprecated - side on which the refrigerant is flowing (0: hot, 1:cold).
T_cold_sections : numpy.ndarray
Cold side temperature at each section boundary. Quantity:
:code:`temperature`. Result only - populated by the network after each
solve.
T_hot_sections : numpy.ndarray
Hot side temperature at each section boundary. Quantity:
:code:`temperature`. Result only - populated by the network after each
solve.
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
Deprecated - equation for UA modification in offdesign using
refrigerant/secondary-fluid Reynolds exponents; use
:code:`UA_cecchinato_hc` with :code:`re_exp_hot` and :code:`re_exp_cold`
instead. Elements: :code:`re_exp_r`, :code:`re_exp_sf`,
:code:`alpha_ratio`, :code:`area_ratio`.
Equation: :py:meth:`UA_cecchinato_legacy_func <tespy.components.heat_exchangers.sectioned.SectionedHeatExchanger.UA_cecchinato_legacy_func>`.
UA_cecchinato_hc : GroupedComponentProperties
Temporary name - equation for UA modification in offdesign using
explicit hot/cold Reynolds exponents; in the next major version
:code:`UA_cecchinato` will adopt the hot/cold convention of
:code:`UA_cecchinato_hc`. Elements: :code:`re_exp_hot`,
:code:`re_exp_cold`, :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
After solving, section data is available directly via the component
attributes :code:`T_hot_sections`, :code:`T_cold_sections`,
:code:`Q_sections`, :code:`Q_per_section` and :code:`lmtd_per_section`.
Since the water vapor is cooled, condensed and then subcooled while the
air does not change phase, three sections will form:
>>> delta_T_between_sections = cd.T_hot_sections.val_SI - cd.T_cold_sections.val_SI
>>> delta_T_between_sections.round(2).tolist()
[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
>>> delta_T_between_sections = cd.T_hot_sections.val_SI - cd.T_cold_sections.val_SI
>>> delta_T_between_sections.round(2).tolist()
[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
- hot side Reynolds exponent (:code:`re_exp_hot`)
- cold side Reynolds exponent (:code:`re_exp_cold`)
- hot to cold side area ratio (:code:`area_ratio`)
- hot to cold side alpha (heat transfer coefficient) ratio (:code:`alpha_ratio`)
>>> 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_hot=0.8,
... re_exp_cold=0.55,
... design=["td_pinch"],
... offdesign=["UA_cecchinato_hc"]
... )
>>> 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, steps_hot=None, steps_cold=None):
"""Assign the sections of the heat exchanger
Parameters
----------
steps_hot : list, optional
Pre-computed phase-boundary steps for the hot side. Computed from
:py:meth:`_get_moving_steps` when not provided.
steps_cold : list, optional
Pre-computed phase-boundary steps for the cold side. Computed from
:py:meth:`_get_moving_steps` when not provided.
Returns
-------
list
List of cumulative sum of heat exchanged defining the heat exchanger
sections.
"""
if steps_hot is None:
steps_hot, _ = self._get_moving_steps(self.inl[0], self.outl[0])
if steps_cold is None:
steps_cold, _ = self._get_moving_steps(self.inl[1], self.outl[1])
return np.unique(np.r_[steps_hot, steps_cold])