Source code for tespy.components.heat_exchangers.parallel

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

import numpy as np

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. **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.base.HeatExchanger.kA_func` - :py:meth:`tespy.components.heat_exchangers.base.HeatExchanger.kA_char_func` - :py:meth:`tespy.components.heat_exchangers.base.HeatExchanger.ttd_u_func` - :py:meth:`tespy.components.heat_exchangers.base.HeatExchanger.ttd_l_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 Outlet to inlet pressure ratio at hot side, :math:`pr/1`. pr2 : float, dict Outlet to inlet pressure ratio at cold side, :math:`pr/1`. dp1 : float, dict, Inlet to outlet pressure delta at hot side, :math:`dp/\text{Pa}` dp2 : float, dict Inlet to outlet pressure delta at cold side, :math:`dp\text{Pa}`. zeta1 : float, dict Geometry independent friction coefficient at hot side, :math:`\frac{\zeta}{D^4}/\frac{1}{\text{m}^4}`. zeta2 : float, dict Geometry independent friction coefficient at cold side, :math:`\frac{\zeta}{D^4}/\frac{1}{\text{m}^4}`. ttd_l : float, dict Initial terminal temperature difference, referring to the temperature difference between the two inlets of the heat exchanger, :math:`ttd_\mathrm{l}/\text{K}`. ttd_u : float, dict Final terminal temperature difference, referring to the temperature difference between the two outlets of the heat exchanger, :math:`ttd_\mathrm{u}/\text{K}`. kA : float, dict Area independent heat transfer coefficient, :math:`kA/\frac{\text{W}}{\text{K}}`. kA_char : dict Area independent heat transfer coefficient characteristic. kA_char1 : tespy.tools.characteristics.CharLine, dict Characteristic line for hot side heat transfer coefficient. kA_char2 : tespy.tools.characteristics.CharLine, dict Characteristic line for cold side heat transfer coefficient. 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 >>> import os >>> nw = Network(iterinfo=False) >>> nw.units.set_defaults(**{ ... "pressure": "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:`kA` value instead of the final pinch and then resolve again. >>> he.set_attr(design=["ttd_u"], offdesign=["kA"]) >>> nw.save("design.json") >>> nw.solve("offdesign", design_path="design.json") >>> round(he.kA.val_SI / he.kA.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.json") >>> round(c2.T.val, 2) 38.69 >>> c3.set_attr(v=2500, T=8) >>> nw.solve("offdesign", design_path="design.json") >>> round(c2.T.val, 2) 44.0 >>> os.remove("design.json") """
[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
[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): i1 = self.inl[0] i2 = self.inl[1] o1 = self.outl[0] o2 = self.outl[1] # temperature value manipulation for convergence stability T_i1 = i1.calc_T() T_i2 = i2.calc_T() T_o1 = o1.calc_T() T_o2 = o2.calc_T() if T_i1 <= T_i2: T_i1 = T_i2 + 0.01 if T_o1 <= T_o2: T_o2 = T_o1 - 0.01 ttd_u = T_o1 - T_o2 ttd_l = T_i1 - T_i2 if round(ttd_u, 6) == round(ttd_l, 6): td_log = ttd_l else: td_log = (ttd_l - ttd_u) / math.log((ttd_l) / (ttd_u)) return td_log
[docs] def calc_parameters(self): self.Q.val_SI = self.inl[0].m.val_SI * ( self.outl[0].h.val_SI - self.inl[0].h.val_SI ) self.ttd_u.val_SI = self.outl[0].T.val_SI - self.outl[1].T.val_SI self.ttd_l.val_SI = self.inl[0].T.val_SI - self.inl[1].T.val_SI # pr and zeta for i in range(2): self.get_attr(f'pr{i + 1}').val_SI = ( self.outl[i].p.val_SI / self.inl[i].p.val_SI ) self.get_attr(f'zeta{i + 1}').val_SI = self.calc_zeta( self.inl[i], self.outl[i] ) self.get_attr(f'dp{i + 1}').val_SI = ( self.inl[i].p.val_SI - self.outl[i].p.val_SI ) # kA and logarithmic temperature difference if self.ttd_u.val_SI < 0 or self.ttd_l.val_SI < 0: self.td_log.val_SI = np.nan elif round(self.ttd_l.val_SI, 6) == round(self.ttd_u.val_SI, 6): self.td_log.val_SI = self.ttd_l.val_SI elif round(self.ttd_l.val_SI, 6) == 0 or round(self.ttd_u.val_SI, 6) == 0: self.td_log.val_SI = np.nan else: self.td_log.val_SI = ( (self.ttd_l.val_SI - self.ttd_u.val_SI) / math.log(self.ttd_l.val_SI / self.ttd_u.val_SI) ) self.kA.val_SI = -self.Q.val_SI / self.td_log.val_SI