# -*- coding: utf-8
"""Module of class Compressor.
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/compressor.py
SPDX-License-Identifier: MIT
"""
import warnings
import numpy as np
from tespy.components.component import component_registry
from tespy.components.turbomachinery.base import Turbomachine
from tespy.tools import logger
from tespy.tools.data_containers import ComponentCharacteristicMaps as dc_cm
from tespy.tools.data_containers import ComponentCharacteristics as dc_cc
from tespy.tools.data_containers import ComponentMandatoryConstraints as dc_cmc
from tespy.tools.data_containers import ComponentProperties as dc_cp
from tespy.tools.data_containers import GroupedComponentProperties as dc_gcp
from tespy.tools.fluid_properties import h_mix_pQ
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 _get_dependents
[docs]
@component_registry
class Compressor(Turbomachine):
r"""
Class for a basic compressor.
.. image:: /api/_images/components/Compressor.svg
:alt: flowsheet of the compressor
:align: center
:class: only-light
.. image:: /api/_images/components/Compressor_darkmode.svg
:alt: flowsheet of the compressor
:align: center
:class: only-dark
Ports
-----
- Fluid inlets: in1
- Fluid outlets: out1
- Power inlets: power
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>`
When a power or heat connector is attached:
- energy_connector_balance: :py:meth:`energy_connector_balance_func <tespy.components.turbomachinery.compressor.Compressor.energy_connector_balance_func>`
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>`.
eta_s : float, dict
Isentropic efficiency. Quantity: :code:`efficiency`.
Equation: :py:meth:`eta_s_func <tespy.components.turbomachinery.compressor.Compressor.eta_s_func>`.
eta_s_char : tespy.tools.characteristics.CharLine, dict
Isentropic efficiency lookup table for offdesign.
Equation: :py:meth:`eta_s_char_func <tespy.components.turbomachinery.compressor.Compressor.eta_s_char_func>`.
igva : float, dict, :code:`"var"`
Inlet guide vane angle. Quantity: :code:`angle`. Can be set as a system
variable by passing :code:`"var"` as its value.
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
-------
Create an air compressor model and calculate the power required for
compression of 50 l/s of ambient air to 5 bars.
>>> from tespy.components import Sink, Source, Compressor
>>> from tespy.connections import Connection
>>> from tespy.networks import Network
>>> nw = Network(iterinfo=False)
>>> nw.units.set_defaults(**{
... "pressure": "bar", "pressure_difference": "bar",
... "temperature": "degC", "volumetric_flow": "l/s", "enthalpy": "kJ/kg"
... })
>>> si = Sink('sink')
>>> so = Source('source')
>>> comp = Compressor('compressor')
>>> inc = Connection(so, 'out1', comp, 'in1')
>>> outg = Connection(comp, 'out1', si, 'in1')
>>> nw.add_conns(inc, outg)
Specify the compressor parameters: nominal efficiency and pressure ratio.
For offdesign mode the efficiency characteristic line is selected instead
of the isentropic efficiency.
>>> comp.set_attr(pr=5, eta_s=0.8, design=['eta_s'], offdesign=['eta_s_char'])
>>> inc.set_attr(fluid={'air': 1}, p=1, T=20, v=50)
>>> nw.solve('design')
>>> design_state = nw.save(as_dict=True)
>>> round(comp.P.val, 0)
12772.0
>>> round(comp.eta_s.val, 2)
0.8
>>> inc.set_attr(v=45)
>>> nw.solve('offdesign', design_path=design_state)
>>> round(comp.eta_s.val, 2)
0.79
"""
_initial_pr_guess = 3.0
_initial_dh_fallback = 5e4
[docs]
def initial_state(self, port):
if port in ('in1', 'out1'):
return {"phase": "gas"}
return None
[docs]
@staticmethod
def powerinlets():
return ["power"]
[docs]
def get_mandatory_constraints(self):
constraints = super().get_mandatory_constraints()
if len(self.power_inl) > 0:
constraints["energy_connector_balance"] = dc_cmc(**{
"func": self.energy_connector_balance_func,
"dependents": self.energy_connector_dependents,
"num_eq_sets": 1
})
return constraints
[docs]
def get_parameters(self):
parameters = super().get_parameters()
parameters["P"].min_val = 0
parameters["pr"].min_val = 1
parameters["dp"].max_val = 0
parameters.update({
'eta_s': dc_cp(
min_val=0, max_val=1, num_eq_sets=1,
func=self.eta_s_func,
deriv=self.eta_s_deriv,
dependents=self.eta_s_dependents,
quantity="efficiency",
description="isentropic efficiency",
calc=self._calc_eta_s
),
'eta_s_char': dc_cc(
param='m', num_eq_sets=1,
func=self.eta_s_char_func,
dependents=self.eta_s_char_dependents,
description="isentropic efficiency lookup table for offdesign"
),
'igva': dc_cp(
min_val=-90, max_val=90, val=0, quantity="angle",
description="inlet guide vane angle", _allows_var=True
),
})
return parameters
[docs]
def energy_connector_balance_func(self):
r"""
(optional) energy balance equation connecting the power connector to
the component's power
Returns
-------
residual : float
Residual value of equation
.. math::
0=\dot E - \dot{m}_{in}\cdot\left(h_{out}-h_{in}\right)
"""
return self.power_inl[0].E.val_SI - self.inl[0].m.val_SI * (
self.outl[0].h.val_SI - self.inl[0].h.val_SI
)
[docs]
def energy_connector_dependents(self):
return [
self.power_inl[0].E, self.inl[0].m, self.outl[0].h, self.inl[0].h
]
[docs]
def eta_s_func(self):
r"""
Equation for given isentropic efficiency of a compressor.
Returns
-------
residual : float
Residual value of equation.
.. math::
0 = -\left( h_{out} - h_{in} \right) \cdot \eta_{s} +
\left( h_{out,s} - h_{in} \right)
"""
i = self.inl[0]
o = self.outl[0]
return (
(o.h.val_SI - i.h.val_SI) * self.eta_s.val_SI - (
isentropic(
i.p.val_SI,
i.h.val_SI,
o.p.val_SI,
i.fluid_data,
i.mixing_rule,
T0=i.T.val_SI,
T0_out=o.T.val_SI
) - self.inl[0].h.val_SI
)
)
[docs]
def eta_s_deriv(self, increment_filter, k, dependents=None):
r"""
Partial derivatives for isentropic efficiency.
Parameters
----------
increment_filter : ndarray
Matrix for filtering non-changing variables.
k : int
Position of derivatives in Jacobian matrix (k-th equation).
"""
dependents = dependents["scalars"][0]
i = self.inl[0]
o = self.outl[0]
f = self.eta_s_func
if o.h.is_var and not i.h.is_var:
self._partial_derivative(o.h, k, self.eta_s.val_SI, increment_filter)
# remove o.h from the dependents
dependents = dependents.difference(_get_dependents([o.h])[0])
for dependent in dependents:
self._partial_derivative(dependent, k, f, increment_filter)
[docs]
def eta_s_dependents(self):
return [
self.inl[0].p,
self.inl[0].h,
self.outl[0].p,
self.outl[0].h,
]
[docs]
def eta_s_char_func(self):
r"""
Equation for given isentropic efficiency characteristic.
Returns
-------
residual : float
Residual value of equation.
.. math::
0 = \left(h_{out}-h_{in}\right) \cdot \eta_{s,design}
\cdot f\left( expr \right) -\left( h_{out,s} - h_{in} \right)
"""
p = self.eta_s_char.param
expr = self.get_char_expr(p, **self.eta_s_char.char_params)
if not expr:
msg = (
'Please choose a valid parameter, you want to link the '
f'isentropic efficiency to at component {self.label}.'
)
logger.error(msg)
raise ValueError(msg)
i = self.inl[0]
o = self.outl[0]
return (
(o.h.val_SI - i.h.val_SI)
* self.eta_s.design * self.eta_s_char.char_func.evaluate(expr)
- (
isentropic(
i.p.val_SI,
i.h.val_SI,
o.p.val_SI,
i.fluid_data,
i.mixing_rule,
T0=i.T.val_SI,
T0_out=o.T.val_SI
) - i.h.val_SI
)
)
[docs]
def eta_s_char_dependents(self):
return [
self.inl[0].m,
self.inl[0].p,
self.inl[0].h,
self.outl[0].p,
self.outl[0].h,
]
def _isentropic_equation_is_set(self):
return self.eta_s.is_set or self.eta_s_char.is_set
[docs]
def convergence_check(self):
r"""
Perform a convergence check.
Note
----
Manipulate enthalpies/pressure at inlet and outlet if not specified by
user to match physically feasible constraints.
"""
i, o = self.inl[0], self.outl[0]
if o.p.is_var and o.p.val_SI < i.p.val_SI:
o.p.set_reference_val_SI(i.p.val_SI * 1.5)
if o.h.is_var and o.h.val_SI < i.h.val_SI:
o.h.set_reference_val_SI(i.h.val_SI + 100e3)
if i.p.is_var and o.p.val_SI < i.p.val_SI:
i.p.set_reference_val_SI(o.p.val_SI * 2 / 3)
i.p.val_SI = o.p.val_SI * 0.9
if i.h.is_var and o.h.val_SI < i.h.val_SI:
i.h.set_reference_val_SI(o.h.val_SI - 100e3)
[docs]
def initialise_source(self, c, key):
r"""
Return a generic pressure starting value at the outlet.
Only consulted as the last fallback tier when neither user
values, propagation nor the temperature field anchors covered
the pressure. Enthalpy starting values are generated by the
automatic starting value machinery instead.
Parameters
----------
c : tespy.connections.connection.Connection
Connection to perform initialisation on.
key : str
Fluid property to retrieve.
Returns
-------
val : float
Starting value for pressure in SI units, 0 for no information.
"""
if key == 'p':
fluid = single_fluid(c.fluid_data)
if fluid is not None:
return c.fluid.wrapper[fluid]._p_crit / 2
else:
return 10e5
return 0
[docs]
@staticmethod
def initialise_target(c, key):
r"""
Return a generic pressure starting value at the inlet.
Only consulted as the last fallback tier when neither user
values, propagation nor the temperature field anchors covered
the pressure. Enthalpy starting values are generated by the
automatic starting value machinery instead.
Parameters
----------
c : tespy.connections.connection.Connection
Connection to perform initialisation on.
key : str
Fluid property to retrieve.
Returns
-------
val : float
Starting value for pressure in SI units, 0 for no information.
"""
if key == 'p':
fluid = single_fluid(c.fluid_data)
if fluid is not None:
return c.fluid.wrapper[fluid]._p_crit / 3
else:
return 1e5
return 0
def _calc_eta_s(self):
i, o = self.inl[0], self.outl[0]
return (
isentropic(
i.p.val_SI, i.h.val_SI, o.p.val_SI,
i.fluid_data, i.mixing_rule,
T0=i.T.val_SI, T0_out=o.T.val_SI
) - i.h.val_SI
) / (o.h.val_SI - i.h.val_SI)