# -*- 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
"""
[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]
@staticmethod
def initialise_source(c, key):
r"""
Return a starting value for pressure and enthalpy at outlet.
Parameters
----------
c : tespy.connections.connection.Connection
Connection to perform initialisation on.
key : str
Fluid property to retrieve.
Returns
-------
val : float
Starting value for pressure/enthalpy in SI units.
"""
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
elif key == 'h':
fluid = single_fluid(c.fluid_data)
if fluid is not None:
if c.p.val_SI < c.fluid.wrapper[fluid]._p_crit:
return h_mix_pQ(c.p.val_SI, 1, c.fluid_data, c.mixing_rule) + 2e5
else:
temp = c.fluid.wrapper[fluid]._T_crit
return h_mix_pT(
c.p.val_SI, temp * 1.2, c.fluid_data, c.mixing_rule
) + 2e5
else:
temp = 450
return h_mix_pT(c.p.val_SI, temp, c.fluid_data, c.mixing_rule)
[docs]
@staticmethod
def initialise_target(c, key):
r"""
Return a starting value for pressure and enthalpy at inlet.
Parameters
----------
c : tespy.connections.connection.Connection
Connection to perform initialisation on.
key : str
Fluid property to retrieve.
Returns
-------
val : float
Starting value for pressure/enthalpy in SI units.
"""
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
elif key == 'h':
fluid = single_fluid(c.fluid_data)
if fluid is not None:
if c.p.val_SI < c.fluid.wrapper[fluid]._p_crit:
return h_mix_pQ(c.p.val_SI, 1, c.fluid_data, c.mixing_rule)
else:
temp = c.fluid.wrapper[fluid]._T_crit
return h_mix_pT(
c.p.val_SI, temp * 1.2, c.fluid_data, c.mixing_rule
)
else:
temp = 350
return h_mix_pT(c.p.val_SI, temp, c.fluid_data, c.mixing_rule)
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)