Source code for tespy.components.heat_exchangers.movingboundary

# -*- 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