# -*- coding: utf-8
"""Module of class DisplacementMachine.
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/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
[docs]
@component_registry
class DisplacementMachine(Component):
r"""
Parent class for displacement machines
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 of the component. Quantity: :code:`power`.
Equation: :py:meth:`energy_balance_func <tespy.components.displacementmachinery.base.DisplacementMachine.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.displacementmachinery.polynomial_compressor.PolynomialCompressor`
- :class:`tespy.components.displacementmachinery.polynomial_compressor_with_cooling.PolynomialCompressorWithCooling`
"""
_initial_pr_guess = 3.0
_initial_dh_fallback = 5e4
_initial_eta_guess = 0.9
[docs]
def initial_state(self, port):
if port in ('in1', 'out1'):
return {"phase": "gas"}
return None
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
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 anchor 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
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 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']
[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,
]
[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
}
}