# -*- coding: utf-8
"""Module of class Valve.
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/piping.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 import logger
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
[docs]
@component_registry
class Valve(Component):
r"""
The Valve throttles a fluid without changing enthalpy.
**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`
**Optional Equations**
- :py:meth:`tespy.components.component.Component.dp_structure_matrix`
- :py:meth:`tespy.components.component.Component.pr_structure_matrix`
- :py:meth:`tespy.components.component.Component.zeta_func`
- :py:meth:`tespy.components.piping.valve.Valve.dp_char_func`
Inlets/Outlets
- in1
- out1
Image
.. image:: /api/_images/Valve.svg
:alt: flowsheet of the valve
:align: center
:class: only-light
.. image:: /api/_images/Valve_darkmode.svg
:alt: flowsheet of the valve
: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.
pr : float, dict, :code:`"var"`
Outlet to inlet pressure ratio, :math:`pr/1`
zeta : float, dict, :code:`"var"`
Geometry independent friction coefficient,
:math:`\frac{\zeta}{D^4}/\frac{1}{\text{m}^4}`.
dp_char : tespy.tools.characteristics.CharLine, dict
Characteristic line for difference pressure to mass flow.
Example
-------
A mass flow of 1 kg/s methane is throttled from 80 bar to 15 bar in a
valve. The inlet temperature is at 50 °C. It is possible to determine the
outlet temperature as the throttling does not change enthalpy.
>>> from tespy.components import Sink, Source, Valve
>>> from tespy.connections import Connection
>>> from tespy.networks import Network
>>> import os
>>> nw = Network(iterinfo=False)
>>> nw.units.set_defaults(**{
... "pressure": "bar", "temperature": "degC"
... })
>>> so = Source('source')
>>> si = Sink('sink')
>>> v = Valve('valve')
>>> so_v = Connection(so, 'out1', v, 'in1')
>>> v_si = Connection(v, 'out1', si, 'in1')
>>> nw.add_conns(so_v, v_si)
>>> v.set_attr(offdesign=['zeta'])
>>> so_v.set_attr(fluid={'CH4': 1}, m=1, T=50, p=80, design=['m'])
>>> v_si.set_attr(p=15)
>>> nw.solve('design')
>>> nw.save('tmp.json')
>>> round(v_si.T.val, 1)
26.3
>>> round(v.pr.val, 3)
0.188
The simulation determined the area independent zeta value
:math:`\frac{\zeta}{D^4}`. This zeta remains constant if the cross
sectional area of the valve opening does not change. Using the zeta value
we can determine the pressure ratio at a different feed pressure.
>>> so_v.set_attr(p=70)
>>> nw.solve('offdesign', design_path='tmp.json')
>>> round(so_v.m.val, 1)
0.9
>>> round(v_si.T.val, 1)
30.0
>>> os.remove('tmp.json')
"""
[docs]
def get_parameters(self):
return {
'pr': dc_cp(
min_val=1e-4, max_val=1, num_eq_sets=1,
structure_matrix=self.pr_structure_matrix,
func_params={'pr': 'pr'},
quantity="ratio"
),
'dp': dc_cp(
min_val=0,
num_eq_sets=1,
structure_matrix=self.dp_structure_matrix,
func_params={"inconn": 0, "outconn": 0, "dp": "dp"},
quantity="pressure",
description="inlet to outlet absolute pressure change"
),
'zeta': dc_cp(
min_val=0, max_val=1e15, num_eq_sets=1,
func=self.zeta_func,
dependents=self.zeta_dependents,
func_params={'zeta': 'zeta'}
),
'dp_char': dc_cc(
param='m', num_eq_sets=1,
dependents=self.dp_char_dependents,
func=self.dp_char_func,
char_params={'type': 'abs'},
description="inlet to outlet absolute pressure change as function of mass flow lookup table"
)
}
[docs]
def get_mandatory_constraints(self):
constraints = super().get_mandatory_constraints()
constraints.update({
'enthalpy_constraints': dc_cmc(**{
'structure_matrix': self.variable_equality_structure_matrix,
'num_eq_sets': 1,
'func_params': {'variable': 'h'},
"description": "equation for enthalpy equality"
})
})
return constraints
[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 dp_char_func(self):
r"""
Equation for characteristic line of difference pressure to mass flow.
Returns
-------
residual : ndarray
Residual value of equation.
.. math::
0=p_\mathrm{in}-p_\mathrm{out}-f\left( expr \right)
"""
p = self.dp_char.param
expr = self.get_char_expr(p, **self.dp_char.char_params)
if not expr:
msg = ('Please choose a valid parameter, you want to link the '
'pressure drop to at component ' + self.label + '.')
logger.error(msg)
raise ValueError(msg)
return (
self.inl[0].p.val_SI - self.outl[0].p.val_SI
- self.dp_char.char_func.evaluate(expr)
)
[docs]
def dp_char_dependents(self):
dependents = [
self.inl[0].m,
self.inl[0].p,
self.outl[0].p,
]
if self.dp_char.param == 'v':
dependents += [self.inl[0].h]
return dependents
[docs]
def calc_parameters(self):
r"""Postprocessing parameter calculation."""
i = self.inl[0]
o = self.outl[0]
self.pr.val_SI = o.p.val_SI / i.p.val_SI
self.dp.val_SI = i.p.val_SI - o.p.val_SI
self.zeta.val_SI = self.calc_zeta(i, o)
[docs]
def entropy_balance(self):
r"""
Calculate entropy balance of a valve.
Note
----
The entropy balance makes the follwing parameter available:
.. math::
\text{S\_irr}=\dot{m} \cdot \left(s_\mathrm{out}-s_\mathrm{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 exergy_balance(self, T0):
r"""
Calculate exergy balance of a valve.
Parameters
----------
T0 : float
Ambient temperature T0 / K.
Note
----
.. math::
\dot{E}_\mathrm{P} =
\begin{cases}
\text{not defined (nan)} & T_\mathrm{in}, T_\mathrm{out} \geq T_0\\
\dot{E}_\mathrm{out}^\mathrm{T}
& T_\mathrm{in} > T_0 \geq T_\mathrm{out}\\
\dot{E}_\mathrm{out}^\mathrm{T} - \dot{E}_\mathrm{in}^\mathrm{T}
& T_0 \geq T_\mathrm{in}, T_\mathrm{out}\\
\end{cases}
\dot{E}_\mathrm{F} =
\begin{cases}
\dot{E}_\mathrm{in}^\mathrm{PH} - \dot{E}_\mathrm{out}^\mathrm{PH}
& T_\mathrm{in}, T_\mathrm{out} \geq T_0\\
\dot{E}_\mathrm{in}^\mathrm{T} + \dot{E}_\mathrm{in}^\mathrm{M}-
\dot{E}_\mathrm{out}^\mathrm{M}
& T_\mathrm{in} > T_0 \geq T_\mathrm{out}\\
\dot{E}_\mathrm{in}^\mathrm{M} - \dot{E}_\mathrm{out}^\mathrm{M}
& T_0 \geq T_\mathrm{in}, T_\mathrm{out}\\
\end{cases}
"""
if self.inl[0].T.val_SI > T0 and self.outl[0].T.val_SI > T0:
self.E_P = np.nan
self.E_F = self.inl[0].Ex_physical - self.outl[0].Ex_physical
elif self.outl[0].T.val_SI <= T0 and self.inl[0].T.val_SI > T0:
self.E_P = self.outl[0].Ex_therm
self.E_F = self.inl[0].Ex_therm + (
self.inl[0].Ex_mech - self.outl[0].Ex_mech)
elif self.inl[0].T.val_SI <= T0 and self.outl[0].T.val_SI <= T0:
self.E_P = self.outl[0].Ex_therm - self.inl[0].Ex_therm
self.E_F = self.inl[0].Ex_mech - self.outl[0].Ex_mech
else:
msg = ('Exergy balance of a valve, where outlet temperature is '
'larger than inlet temperature is not implmented.')
logger.warning(msg)
self.E_P = np.nan
self.E_F = np.nan
self.E_bus = {
"chemical": np.nan, "physical": np.nan, "massless": np.nan
}
if np.isnan(self.E_P):
self.E_D = self.E_F
else:
self.E_D = self.E_F - self.E_P
self.epsilon = self._calc_epsilon()
[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': 'h',
'isoline_value': self.inl[0].h.val,
'isoline_value_end': self.outl[0].h.val,
'starting_point_property': 'v',
'starting_point_value': self.inl[0].vol.val,
'ending_point_property': 'v',
'ending_point_value': self.outl[0].vol.val
}
}