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`. **Mandatory Equations** - fluid: :py:meth:`tespy.components.component.Component.variable_equality_structure_matrix` - mass flow: :py:meth:`tespy.components.component.Component.variable_equality_structure_matrix` - :py:meth:`tespy.components.heat_exchangers.base.HeatExchanger.energy_balance_func` **Optional Equations** - :py:meth:`tespy.components.heat_exchangers.base.HeatExchanger.energy_balance_hot_func` - :py:meth:`tespy.components.heat_exchangers.sectioned.SectionedHeatExchanger.UA_func` - :py:meth:`tespy.components.heat_exchangers.sectioned.SectionedHeatExchanger.td_pinch_func` - :py:meth:`tespy.components.heat_exchangers.sectioned.SectionedHeatExchanger.UA_cecchinato_func` - :py:meth:`tespy.components.heat_exchangers.base.HeatExchanger.ttd_u_func` - :py:meth:`tespy.components.heat_exchangers.base.HeatExchanger.ttd_l_func` - :py:meth:`tespy.components.heat_exchangers.base.HeatExchanger.ttd_min_func` - :py:meth:`tespy.components.heat_exchangers.base.HeatExchanger.eff_cold_func` - :py:meth:`tespy.components.heat_exchangers.base.HeatExchanger.eff_hot_func` - :py:meth:`tespy.components.heat_exchangers.base.HeatExchanger.eff_max_func` For hot and cold side individually: - :py:meth:`tespy.components.component.Component.pr_structure_matrix` - :py:meth:`tespy.components.component.Component.dp_structure_matrix` - :py:meth:`tespy.components.component.Component.zeta_func` Inlets/Outlets - in1, in2 (index 1: hot side, index 2: cold side) - out1, out2 (index 1: hot side, index 2: cold side) Image .. image:: /api/_images/HeatExchanger.svg :alt: flowsheet of the heat exchanger :align: center :class: only-light .. image:: /api/_images/HeatExchanger_darkmode.svg :alt: flowsheet of the heat exchanger :align: center :class: only-dark Parameters ---------- label : str The label of the component. design : list List containing design parameters (stated as String). offdesign : list List containing offdesign parameters (stated as String). design_path : str Path to the components design case. local_offdesign : boolean Treat this component in offdesign mode in a design calculation. local_design : boolean Treat this component in design mode in an offdesign calculation. char_warnings : boolean Ignore warnings on default characteristics usage for this component. printout : boolean Include this component in the network's results printout. Q : float, dict Heat transfer, :math:`Q/\text{W}`. pr1 : float, dict, :code:`"var"` Outlet to inlet pressure ratio at hot side, :math:`pr/1`. pr2 : float, dict, :code:`"var"` Outlet to inlet pressure ratio at cold side, :math:`pr/1`. dp1 : float, dict, :code:`"var"` Inlet to outlet pressure delta at hot side, unit is the network's pressure unit!. dp2 : float, dict, :code:`"var"` Inlet to outlet pressure delta at cold side, unit is the network's pressure unit!. zeta1 : float, dict, :code:`"var"` Geometry independent friction coefficient at hot side, :math:`\frac{\zeta}{D^4}/\frac{1}{\text{m}^4}`. zeta2 : float, dict, :code:`"var"` Geometry independent friction coefficient at cold side, :math:`\frac{\zeta}{D^4}/\frac{1}{\text{m}^4}`. ttd_l : float, dict Lower terminal temperature difference :math:`ttd_\text{l}/\text{K}`. ttd_u : float, dict Upper terminal temperature difference :math:`ttd_\text{u}/\text{K}`. ttd_min : float, dict Minimum terminal temperature difference :math:`ttd_\text{min}/\text{K}`. eff_cold : float, dict Cold side heat exchanger effectiveness :math:`eff_\text{cold}/\text{1}`. eff_hot : float, dict Hot side heat exchanger effectiveness :math:`eff_\text{hot}/\text{1}`. eff_max : float, dict Max value of hot and cold side heat exchanger effectiveness values :math:`eff_\text{max}/\text{1}`. UA : float, dict Sum of UA in all sections of the heat exchanger. td_pinch : float, dict Value of the lowest delta T between hot side and cold side at the different sections. UA_cecchinato : dict Group specification for partload UA modification according to :cite:`cecchinato2010`, for usage see details in the :py:meth:`tespy.components.heat_exchangers.movingboundary.MovingBoundaryHeatExchanger.UA_cecchinato_func`. This method can only be used in offdesign simulations! alpha_ration: float Secondary fluid to refrigerant heat transfer coefficient ratio. area_ration: float Secondary fluid to refrigerant heat transfer area ratio. re_exp_r: float Reynolds exponent for refrigerant side. re_exp_sf: float Reynolds exponent for secondary fluid side. refrigerant_index: int Connection index for the refrigerant side, 0 if refrigerant is on hot side, 1 if refrigerant is on cold side. 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", "temperature": "degC" ... }) >>> nw.set_attr(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) >>> import os >>> nw.save("design.json") >>> 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.json") 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.json") >>> 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 >>> os.remove("design.json") """
[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