Source code for tespy.components.turbomachinery.base

# -*- coding: utf-8

"""Module of class Turbomachine.


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/turbomachinery/base.py

SPDX-License-Identifier: MIT
"""

import numpy as np

from tespy.components.component import Component
from tespy.components.component import component_registry
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.fluid_properties import h_mix_pT
from tespy.tools.fluid_properties import isentropic
from tespy.tools.fluid_properties import single_fluid
from tespy.tools.helpers import _numeric_deriv


[docs] @component_registry class Turbomachine(Component): _p_in_adj = 0.9 # factor relative to o.p for priority-2 i.p adjustment _p_out_adj = 1.1 # factor relative to i.p for priority-3 o.p adjustment _initial_pr_guess = 1.0 _initial_dh_fallback = 0.0 _initial_eta_guess = 0.9 r""" Parent class for compressor, pump and turbine. Ports ----- - Fluid inlets: in1 - Fluid outlets: out1 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>` Parameters ---------- 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. dp : float, dict Inlet to outlet absolute pressure change. Quantity: :code:`pressure_difference`. Equation: :py:meth:`dp_structure_matrix <tespy.components.component.Component.dp_structure_matrix>`. label : str The label of the component. 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). P : float, dict Power input/output of the component. Quantity: :code:`power`. Equation: :py:meth:`energy_balance_func <tespy.components.turbomachinery.base.Turbomachine.energy_balance_func>`. pr : float, dict 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. Example ------- For an example please refer to: - :class:`tespy.components.turbomachinery.compressor.Compressor` - :class:`tespy.components.turbomachinery.pump.Pump` - :class:`tespy.components.turbomachinery.turbine.Turbine` - :class:`tespy.components.turbomachinery.steam_turbine.SteamTurbine` """ def _calc_P(self): return self.inl[0].m.val_SI * (self.outl[0].h.val_SI - self.inl[0].h.val_SI)
[docs] def get_parameters(self): return { 'P': dc_cp( num_eq_sets=1, func=self.energy_balance_func, dependents=self.energy_balance_dependents, quantity="power", description="power input/output of the component", calc=self._calc_P ), 'pr': dc_cp( num_eq_sets=1, func_params={'pr': 'pr'}, structure_matrix=self.pr_structure_matrix, quantity="ratio", description="outlet to inlet pressure ratio", calc=self._calc_pr ), 'dp': dc_cp( num_eq_sets=1, structure_matrix=self.dp_structure_matrix, func_params={'dp': 'dp'}, quantity="pressure_difference", description="inlet to outlet absolute pressure change", calc=self._calc_dp ) }
[docs] def get_bypass_constraints(self): return { 'mass_flow_constraints': dc_cmc(**{ 'structure_matrix': self.variable_equality_structure_matrix, 'num_eq_sets': self.num_i, 'func_params': {'variable': 'm'} }), 'pressure_constraints': dc_cmc(**{ 'structure_matrix': self.variable_equality_structure_matrix, 'num_eq_sets': self.num_i, 'func_params': {'variable': 'p'} }), 'enthalpy_constraints': dc_cmc(**{ 'structure_matrix': self.variable_equality_structure_matrix, 'num_eq_sets': self.num_i, 'func_params': {'variable': 'h'} }), 'fluid_constraints': dc_cmc(**{ 'structure_matrix': self.variable_equality_structure_matrix, 'num_eq_sets': self.num_i, 'func_params': {'variable': 'fluid'} }) }
[docs] @staticmethod def inlets(): return ['in1']
[docs] @staticmethod def outlets(): return ['out1']
def _initial_affine_edges(self): # the state based enthalpy change is more trustworthy than the # constant guesses of e.g. heat exchangers, its weight makes those # absorb the closure residual of guess cycles return [ (self.inl[0].p, self.outl[0].p, self._initial_pr_guess, 0.0), (self.inl[0].h, self.outl[0].h, 1.0, self._initial_dh_guess(), 10.0), ] def _initial_temperature_edges(self): # the state based temperature change of the machine, consistent # with the enthalpy edge - for pumps this is close to zero, so the # temperature level carries across them i, o = self.inl[0], self.outl[0] try: h_in = i.h.val_SI # 0 is the placeholder of unset enthalpies during the starting # value assignment, evaluating the state there is meaningless if np.isnan(h_in) or h_in == 0: state = self.initial_state('in1') result = i._h_for_state(state) if state is not None else None if result is None: # mixtures and undeclared inlets: ambient temperature # as representative state h_in = h_mix_pT( i.p.val_SI, 293.15, i.fluid_data, i.mixing_rule ) else: h_in = result[0] p_out = o.p.val_SI if np.isnan(p_out) or p_out <= 0: p_out = i.p.val_SI * self._initial_pr_guess T_in = T_mix_ph(i.p.val_SI, h_in, i.fluid_data, i.mixing_rule) T_out = T_mix_ph( p_out, h_in + self._initial_dh_guess(), o.fluid_data, o.mixing_rule ) except (ValueError, KeyError, IndexError, NotImplementedError): T_in = T_out = float("nan") if np.isnan(T_in) or np.isnan(T_out): state = self.initial_state('in1') if state is not None and state.get("phase") == "liquid": # liquid compression is near isothermal regardless of the # unknown state, so the temperature level carries across # the machine even when no pressure exists yet to evaluate # the state based estimate return [(i, o, 0.0, 5.0)] return [] return [(i, o, T_out - T_in, 5.0)] def _initial_dh_guess(self): """Enthalpy change of an isentropic state change to the outlet pressure with a generic efficiency, evaluated at the inlet state or the inlet phase expectation of the component class.""" i, o = self.inl[0], self.outl[0] try: h_in = i.h.val_SI # 0 is the placeholder of unset enthalpies during the starting # value assignment, evaluating the state there is meaningless if np.isnan(h_in) or h_in == 0: state = self.initial_state('in1') result = i._h_for_state(state) if state is not None else None if result is None: # mixtures and undeclared inlets: ambient temperature # as representative state h_in = h_mix_pT( i.p.val_SI, 293.15, i.fluid_data, i.mixing_rule ) else: h_in = result[0] p_out = o.p.val_SI if np.isnan(p_out) or p_out <= 0: p_out = i.p.val_SI * self._initial_pr_guess dh_s = isentropic( i.p.val_SI, h_in, p_out, i.fluid_data, i.mixing_rule ) - h_in except (ValueError, KeyError, IndexError, NotImplementedError): return self._initial_dh_fallback if np.isnan(dh_s): return self._initial_dh_fallback if dh_s > 0: return dh_s / self._initial_eta_guess return dh_s * self._initial_eta_guess
[docs] def energy_balance_func(self): r""" Calculate energy balance of a turbomachine. Returns ------- residual : float Residual value of turbomachine energy balance .. math:: 0=\dot{m}_{in}\cdot\left(h_{out}-h_{in}\right)-P """ return self._calc_P() - self.P.val_SI
[docs] def energy_balance_dependents(self): return [ self.inl[0].m, self.inl[0].h, self.outl[0].h, ]
def _adjust_to_property_limits(self): if not self._isentropic_equation_is_set(): return i, o = self.inl[0], self.outl[0] fluid = single_fluid(i.fluid_data) if fluid is None: return wrapper = i.fluid.wrapper[fluid] try: s_in = wrapper.s_ph(i.p.val_SI, i.h.val_SI) wrapper.h_ps(o.p.val_SI, s_in) except ValueError: if i.h.is_var and self._p_out_adj > 1: s_max = wrapper.s_pT(o.p.val_SI, wrapper._T_max) i.h.set_reference_val_SI(wrapper.h_ps(i.p.val_SI, s_max) * 0.99) elif i.p.is_var: i.p.set_reference_val_SI(o.p.val_SI * self._p_in_adj) elif o.p.is_var: o.p.set_reference_val_SI(i.p.val_SI * self._p_out_adj)
[docs] def entropy_balance(self): r""" Calculate entropy balance of turbomachine. Note ---- The entropy balance makes the following parameter available: .. math:: \text{S\_irr}=\dot{m} \cdot \left(s_\text{out}-s_\text{in} \right)\\ """ self.S_irr = self.inl[0].m.val_SI * ( self.outl[0].s.val_SI - self.inl[0].s.val_SI )
[docs] def get_plotting_data(self): """Generate a dictionary containing FluProDia plotting information. Returns ------- data : dict A nested dictionary containing the keywords required by the :code:`calc_individual_isoline` method of the :code:`FluidPropertyDiagram` class. First level keys are the connection index ('in1' -> 'out1', therefore :code:`1` etc.). """ return { 1: { 'isoline_property': 's', 'isoline_value': self.inl[0].s.val, 'isoline_value_end': self.outl[0].s.val, 'starting_point_property': 'vol', 'starting_point_value': self.inl[0].vol.val, 'ending_point_property': 'vol', 'ending_point_value': self.outl[0].vol.val } }