Source code for tespy.components.displacementmachinery.polynomial_compressor_with_cooling

# -*- coding: utf-8

"""Module of class PolynomialCompressorWithCooling.


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/displacementmachinery/polynomial_compressor_with_cooling.py

SPDX-License-Identifier: MIT
"""
from tespy.components.component import component_registry
from tespy.components.displacementmachinery.polynomial_compressor import PolynomialCompressor
from tespy.tools.data_containers import ComponentMandatoryConstraints as dc_cmc
from tespy.tools.data_containers import ComponentProperties as dc_cp
from tespy.tools.fluid_properties import T_mix_ph
from tespy.tools.helpers import TESPyComponentError


[docs] @component_registry class PolynomialCompressorWithCooling(PolynomialCompressor): r""" Class for a compressor model following the EN12900 implementation of :cite:`cecchinato2010` and adding an inflow and and outflow for a cooling fluid. See the example for the intended use of the component. **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` - cooling energy balance: :py:meth:`tespy.components.displacementmachinery.polynomial_compressor_with_cooling.PolynomialCompressorWithCooling.cooling_energy_balance_func` **Optional Equations** - :py:meth:`tespy.components.component.Component.dp_structure_matrix` - :py:meth:`tespy.components.component.Component.pr_structure_matrix` - :py:meth:`tespy.components.displacementmachinery.polynomial_compressor.PolynomialCompressor.energy_balance_group_func` - :py:meth:`tespy.components.displacementmachinery.polynomial_compressor.PolynomialCompressor.eta_s_group_func` - :py:meth:`tespy.components.displacementmachinery.polynomial_compressor.PolynomialCompressor.eta_vol_group_func` - :py:meth:`tespy.components.component.Component.dp_structure_matrix` for cooling - :py:meth:`tespy.components.component.Component.pr_structure_matrix` for cooling Inlets/Outlets - in1, in2 (cooling) - out1, out2 (cooling) Optional inlets - power Image .. image:: /api/_images/PolynomialCompressorWithCooling.svg :alt: flowsheet of the compressor :align: center :class: only-light .. image:: /api/_images/PolynomialCompressorWithCooling_darkmode.svg :alt: flowsheet of the compressor :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. P : float, dict Compressor power, :math:`P/\text{W}` dissipation_ratio : float, dict Relative heat loss of compressor, :math:`Q_\text{diss,rel}/1` eta_recovery : float, dict Share of heat recovered in the cooling fluid of the heat loss of compressor, :math:`Q_\text{diss,rel}/1` eta_s : float, dict Isentropic efficiency, :math:`\eta_\text{s}/1` eta_s_poly : array, dict Polynomial coefficients for isentropic efficiency eta_vol : float, dict Volumetric efficiency, :math:`\eta_\text{vol}/1` eta_vol_poly : array, dict Polynomial coefficients for volumetric efficiency reference_state: dict Reference state for the polynomial and displacement. pr : float, dict Outlet to inlet pressure ratio, :math:`pr/1` dp : float, dict Inlet to outlet pressure difference, :math:`dp/\text{p}_\text{unit}` Is specified in the Network's pressure unit pr_cooling : float, dict Outlet to inlet pressure ratio for cooling, :math:`pr/1` dp_cooling : float, dict Inlet to outlet pressure difference for cooling, :math:`dp/\text{p}_\text{unit}` Is specified in the Network's pressure unit Example ------- The utilization of this component is intended to be done in two steps: 1. Calculate the reference state isentropic and volumetric efficiency polynomials based on the provided manufacturer data. 2. Set the resulting isentropic and volumetric efficiency polynomials. Under the assumption of isentropic and volumetric efficiency not being constant at variable compressor rpm, the outlet state will be determined with the volumetric flow at inlet. The volumetric flow at inlet scales linearly with the rpm of the compressor. >>> from tespy.components import Source, Sink, PolynomialCompressorWithCooling >>> from tespy.connections import Connection >>> from tespy.networks import Network >>> import pandas as pd >>> from CoolProp.CoolProp import PropsSI >>> nw = Network(iterinfo=False) >>> nw.units.set_defaults(**{ ... "pressure": "bar", "temperature": "degC" ... }) >>> so = Source("from evaporator") >>> si = Sink("to condenser") >>> compressor = PolynomialCompressorWithCooling("compressor") >>> c1 = Connection(so, "out1", compressor, "in1", label="c1") >>> c2 = Connection(compressor, "out1", si, "in1", label="c2") >>> nw.add_conns(c1, c2) Additionally, we add the cooling fluid connections. >>> so_cool = Source("cooling water inlet") >>> si_cool = Sink("cooling water outlet") >>> b1 = Connection(so_cool, "out1", compressor, "in2", label="b1") >>> b2 = Connection(compressor, "out2", si_cool, "in1", label="b2") >>> nw.add_conns(b1, b2) Now, we can either provide - a 10-coefficient polynomial for power and cooling or - provide the respective power and cooling energy from a datasheet of a compressor manufacturer to generate such a polynomial Then we can used a precalculation method, which transforms the polynomial or the data into two polynomials, one for the isentropic efficiency and one for the volumetric efficiency, both as a function of evaporation and condensation temperature. Additionally information on a reference state have to be provided, which include - superheating at suction - subcooling after the condensation - the rpm belonging to the original data - a displacement value (kg/h) with the respective rpm for this displacement .. tip:: The compressor data or the 10-coefficient polynomials can be retrieved from manufacturers. For example, Bitzer provides such data, which can be used to retrieve a polynomial. The data for this example have been retrieved from :cite:`bitzer2025_HSK`. >>> reference_state = { ... "T_sh": 20, # superheating ... "T_sc": 0, # subcooling ... "rpm_poly": 50 * 60, # rpm belonging to the polynomial data ... "rpm_displacement": 20 * 60, # rpm belonging to the displacement ... "displacement": 214 # kg / h ... } >>> power = pd.DataFrame( ... columns=[10,7.5,5,0,-5,-10], index=[30, 40, 50], dtype=float ... ) >>> cooling = power.copy() >>> cooling.loc[30] = [465600,424100,385500,316700,257900,208000] >>> cooling.loc[40] = [418900,380400,344800,281400,227400,181600] >>> cooling.loc[50] = [365900,331300,299200,242100,193700,152900] >>> power.loc[30] = [62.0,61.8,61.8,61.8,61.7,61.3] >>> power.loc[40] = [78.0,78.0,78.0,78.0,77.7,76.8] >>> power.loc[50] = [99.2,99.2,99.2,98.9,98.1,96.5] >>> power = power * 1000 .. attention:: The data or polynomial formulations must be in SI units! We can now use the inbuilt method to determine the isentropic and volumetric efficiency polynomials. For that we need to import the respective method. Apart from this method, there is also the :py:func:`tespy.components.displacementmachinery.polynomial_compressor.generate_eta_polys_from_power_and_cooling_polys` method, that can do the same step provided a polynomial for power and one for the cooling. >>> from tespy.components.displacementmachinery.polynomial_compressor import ( ... generate_eta_polys_from_data ... ) >>> eta_s_poly, eta_vol_poly = generate_eta_polys_from_data( ... power, cooling, "R134a", reference_state ... ) >>> eta_s_poly array([ 3.44223012e-03, -3.75139140e-02, 4.39204462e-02, -9.21644870e-04, 1.68576190e-03, -8.97540501e-04, -7.54781107e-06, 1.61377008e-05, -1.53820046e-05, 5.04818089e-06]) >>> eta_vol_poly array([ 5.81192914e-03, -7.18820053e-04, 7.41463587e-02, 2.84410052e-05, 6.51372426e-05, -1.89872495e-03, 7.84206012e-07, -1.90585865e-06, 4.52695494e-07, 1.51321175e-05]) We can take these polynomials and set them on the compressor instance together with the reference state and the assumption on heat dissipation. On top we need to specify the share of dissipated heat, that can be utilized by the cooling fluid. >>> compressor.set_attr( ... eta_s_poly=eta_s_poly, eta_vol_poly=eta_vol_poly, ... dissipation_ratio=0.05, eta_recovery=0.9, ... reference_state=reference_state ... ) First, we can impose the boundary conditions on "c1" that are equal to the displacement reference state. In that case, we should be able to get the same displacement value as inputted into the reference. >>> c1.set_attr(fluid={"R134a": 1}, T=0, td_dew=10) # T_evap=-10°C >>> compressor.set_attr(rpm=1200) >>> c2.set_attr(T_dew=50) >>> b1.set_attr(fluid={"water": 1}, T=20, p=1) >>> b2.set_attr(T=40) >>> compressor.set_attr(dp_cooling=0) >>> nw.solve("design") >>> round(c1.v.val * 3600 / compressor.eta_vol.val, 2) 214.0 >>> round(compressor.eta_s.val, 3) 0.5 >>> round(compressor.eta_vol.val, 3) 0.814 The mass flow of cooling water is a result of the dissipated heat: >>> round(compressor.Q_diss.val_SI * compressor.eta_recovery.val_SI) -1727 >>> round(b1.m.val, 3) 0.021 We can also double check our resulting isentropic and volumetric efficiency values with the evaluation of the polynomials. >>> from tespy.components.displacementmachinery.polynomial_compressor import ( ... calc_EN12900 ... ) >>> round(compressor.eta_s.val, 3) == round(calc_EN12900(eta_s_poly, -10, 50), 3) np.True_ >>> round(compressor.eta_vol.val, 3) == round(calc_EN12900(eta_vol_poly, -10, 50), 3) np.True_ .. tip:: You can also create polynomials for power and cooling from respective data. For that, import the :py:func:`tespy.components.displacementmachinery.polynomial_compressor.fit_EN12900` method and pass the respective data. We can also check the compressor power. It is higher than the power of an adiabatic compressor due to the heat dissipation. The compressor power plus heat dissipation will give the actual power required for isentropic compression. The heat dissipation is negative due to the heat leaving the component. >>> round(compressor.P.val) 38385 >>> round(compressor.Q_diss.val) -1919 >>> round(compressor.P.val + compressor.Q_diss.val) 36466 Now, let's see what happens, if evaporation or condensation temperature change: >>> c1.set_attr(T=20, td_dew=10) # T_evap=10°C >>> c2.set_attr(T_dew=40) >>> nw.solve("design") >>> round(compressor.eta_s.val, 3) 0.665 >>> round(compressor.eta_vol.val, 3) 0.924 It is also possible, to make the rpm a variable. This is useful, in case mass flow through the compressor is governed from external. Usually, this could be the case, if a specific heat transfer is required to be provided by the condenser or from the evaporator. In this case, we just fix the displacement to mimic that. >>> compressor.set_attr(rpm="var") >>> c1.set_attr(v=400/3600) >>> nw.solve("design") >>> round(compressor.rpm.val) 2427 As final remarks: You can also set fixed isentropic and fixed volumetric efficiencies for these components. """ def _preprocess(self, row_idx): if not self.eta_recovery.is_set: msg = ( f"The component {self.label} of type {self.__class__.__name__}" "requires you to specify the share of heat recovery " "eta_recovery." ) raise TESPyComponentError(msg) return super()._preprocess(row_idx)
[docs] @staticmethod def inlets(): return ['in1', 'in2']
[docs] @staticmethod def outlets(): return ['out1', 'out2']
[docs] def get_mandatory_constraints(self) -> dict: constraints = super().get_mandatory_constraints() # this is a dictionary constraints["cooling_energy_balance_constraints"] = dc_cmc( func=self.cooling_energy_balance_func, dependents=self.cooling_energy_balance_dependents, num_eq_sets=1, description="energy balance for the cooling ports" ) return constraints
[docs] def get_parameters(self): params = super().get_parameters() params["eta_recovery"] = dc_cp( quantity="efficiency", description="share of dissipated heat usable in cooling port" ) params["td_minimal"] = dc_cp( min_val=0, quantity="temperature_difference", is_result=True, description="theoretical minimal temperature difference between working and cooling fluid" ) params["dp_cooling"] = dc_cp( min_val=0, structure_matrix=self.dp_structure_matrix, func_params={"inconn": 1, "outconn": 1, "dp": "dp_cooling"}, quantity="pressure", num_eq_sets=1, description="cooling port inlet to outlet absolute pressure change" ) params["pr_cooling"] = dc_cp( min_val=0, structure_matrix=self.pr_structure_matrix, func_params={"inconn": 1, "outconn": 1, "pr": "pr_cooling"}, quantity="ratio", num_eq_sets=1, description="cooling port outlet to inlet pressure ratio" ) return params
[docs] def cooling_energy_balance_func(self): r"""Energy balance equation for the cooling port Returns ------- float residual of equation .. math:: 0 = \dot m_\text{in,2} \cdot \left( h_\text{out,2} - h_\text{in,2}\right) + \dot m_\text{in,1} \cdot \left( h_\text{out,1} - \frac{h_\text{out,1}}{1 - \text{diss_ratio}} + \frac{h_\text{in,1}\cdot\text{diss_ratio}}{1 - \text{diss_ratio}} \right) """ residual = ( self.inl[1].m.val_SI * (self.outl[1].h.val_SI - self.inl[1].h.val_SI) + self.inl[0].m.val_SI * ( self.outl[0].h.val_SI - self.outl[0].h.val_SI / (1 - self.dissipation_ratio.val_SI) + self.inl[0].h.val_SI * ( self.dissipation_ratio.val_SI / (1 - self.dissipation_ratio.val_SI) ) ) * self.eta_recovery.val_SI ) return residual
[docs] def cooling_energy_balance_dependents(self): return [ self.inl[0].m, self.inl[1].m, self.inl[0].h, self.inl[1].h, self.outl[0].h, self.outl[1].h ]
[docs] def calc_parameters(self): super().calc_parameters() i = self.inl[0] o = self.outl[0] h_2 = ( (o.h.val_SI - i.h.val_SI * self.dissipation_ratio.val_SI) / (1 - self.dissipation_ratio.val_SI) ) T_max_compressor_internal = T_mix_ph( self.outl[0].p.val_SI, h_2, self.outl[0].fluid_data, self.outl[0].mixing_rule, T0=self.outl[0].T.val_SI ) self.td_minimal.val_SI = ( T_max_compressor_internal - self.outl[1].T.val_SI ) self.dp_cooling.val_SI = self.inl[1].p.val_SI - self.outl[1].p.val_SI self.pr_cooling.val_SI = self.outl[1].p.val_SI / self.inl[1].p.val_SI