Source code for tespy.components.component

# -*- coding: utf-8

"""Module class component.

All tespy components inherit from this class.


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/components.py

SPDX-License-Identifier: MIT
"""

import math

import numpy as np
import pandas as pd
import pint

from tespy.tools import logger
from tespy.tools.characteristics import CharLine
from tespy.tools.characteristics import CharMap
from tespy.tools.characteristics import load_default_char as ldc
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 GroupedComponentCharacteristics as dc_gcc
from tespy.tools.data_containers import GroupedComponentProperties as dc_gcp
from tespy.tools.data_containers import SimpleDataContainer as dc_simple
from tespy.tools.global_vars import ERR
from tespy.tools.helpers import _get_dependents
from tespy.tools.helpers import _get_vector_dependents
from tespy.tools.helpers import _is_numeric
from tespy.tools.helpers import _partial_derivative
from tespy.tools.helpers import _partial_derivative_vecvar
from tespy.tools.helpers import bus_char_derivative
from tespy.tools.helpers import bus_char_evaluation
from tespy.tools.helpers import newton_with_kwargs
from tespy.tools.units import _UNITS
from tespy.tools.units import SI_UNITS


[docs] def component_registry(type): component_registry.items[type.__name__] = type return type
component_registry.items = {}
[docs] @component_registry class Component: r""" Class Component is the base class of all TESPy components. 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. **kwargs : See the class documentation of desired component for available keywords. Note ---- The initialisation method (__init__), setter method (set_attr) and getter method (get_attr) are used for instances of class component and its children. Allowed keywords in kwargs are 'design_path', 'design' and 'offdesign'. Additional keywords depend on the type of component you want to create. Example ------- Basic example for a setting up a :py:class:`tespy.components.component.Component` object. This example does not run a tespy calculation. >>> from tespy.components.component import Component >>> comp = Component('myComponent') >>> type(comp) <class 'tespy.components.component.Component'> """ def __init__(self, label, **kwargs): # check if components label is of type str and for prohibited chars _forbidden = [';', ',', '.'] if not isinstance(label, str): msg = 'Component label must be of type str!' logger.error(msg) raise ValueError(msg) elif any([True for x in _forbidden if x in label]): msg = ( f"You cannot use any of {', '.join(_forbidden)} in a " f"component label ({self.__class__.__name__})" ) logger.error(msg) raise ValueError(msg) else: self.label = label # defaults self.new_design = True self.design_path = None self.design = [] self.offdesign = [] self.local_design = False self.local_offdesign = False self.char_warnings = True self.printout = True self.bypass = False self.fkt_group = self.label # add container for components attributes self.parameters = self.get_parameters().copy() self.__dict__.update(self.parameters) self.set_attr(**kwargs) self.num_i = len(self.inlets()) self.num_o = len(self.outlets()) self.num_power_i = len(self.powerinlets()) self.num_power_o = len(self.poweroutlets())
[docs] def set_attr(self, **kwargs): r""" Set, reset or unset attributes of a component for provided arguments. Parameters ---------- 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. **kwargs : See the class documentation of desired component for available keywords. Note ---- Allowed keywords in kwargs are obtained from class documentation as all components share the :py:meth:`tespy.components.component.Component.set_attr` method. """ # set specified values for key in kwargs: if key in self.parameters: data = self.get_attr(key) if kwargs[key] is None: data.set_attr(is_set=False) if hasattr(data, "is_var"): data.set_attr(is_var=False) continue is_numeric = False is_quantity = False if isinstance(kwargs[key], pint.Quantity): is_quantity = True else: is_numeric = _is_numeric(kwargs[key]) # dict specification if (isinstance(kwargs[key], dict) and not isinstance(data, dc_simple)): data.set_attr(**kwargs[key]) # value specification for component properties elif isinstance(data, dc_cp) or isinstance(data, dc_simple): if is_numeric or is_quantity: data.set_attr(val=kwargs[key], is_set=True) if isinstance(data, dc_cp): data.set_attr(is_var=False) elif isinstance(data, dc_simple): data.set_attr(val=kwargs[key], is_set=True) elif kwargs[key] == 'var' and isinstance(data, dc_cp): data.set_attr(is_set=True, is_var=True) # invalid datatype for keyword else: msg = ( f"Bad datatype for keyword argument {key} for " f"component {self.label}." ) logger.error(msg) raise TypeError(msg) elif isinstance(data, dc_cc) or isinstance(data, dc_cm): # value specification for characteristics if (isinstance(kwargs[key], CharLine) or isinstance(kwargs[key], CharMap)): data.char_func = kwargs[key] # invalid datatype for keyword else: msg = ( f"Bad datatype for keyword argument {key} for " f"component {self.label}." ) logger.error(msg) raise TypeError(msg) elif key in ['design', 'offdesign']: if not isinstance(kwargs[key], list): msg = ( f"Please provide the {key} parameters as list for " f"component {self.label}." ) logger.error(msg) raise TypeError(msg) if set(kwargs[key]).issubset(list(self.parameters.keys())): self.__dict__.update({key: kwargs[key]}) else: keys = ", ".join(self.parameters.keys()) msg = ( "Available parameters for (off-)design specification " f"of component {self.label} are: {keys}." ) logger.error(msg) raise ValueError(msg) elif key in ['local_design', 'local_offdesign', 'printout', 'char_warnings', 'bypass']: if not isinstance(kwargs[key], bool): msg = ( f"Please provide the {key} parameters as bool for " f"component {self.label}." ) logger.error(msg) raise TypeError(msg) else: self.__dict__.update({key: kwargs[key]}) elif key == 'design_path' or key == 'fkt_group': self.__dict__.update({key: kwargs[key]}) self.new_design = True # invalid keyword else: msg = f"Component {self.label} has no attribute {key}." logger.error(msg) raise KeyError(msg)
[docs] def get_attr(self, key): r""" Get the value of a component's attribute. Parameters ---------- key : str The attribute you want to retrieve. Returns ------- out : Value of specified attribute. """ if key in self.__dict__: return self.__dict__[key] else: msg = f"Component {self.label} has no attribute {key}." logger.error(msg) raise KeyError(msg)
def _serialize(self): export = {} for k in self._serializable(): export.update({k: self.get_attr(k)}) for k in self.parameters: data = self.get_attr(k) export.update({k: data._serialize()}) return {self.label: export} @staticmethod def _serializable(): return [ "design", "offdesign", "local_design", "local_offdesign", "design_path", "printout", "fkt_group", "char_warnings", "bypass" ] def _get_result_attributes(self): return [ key for key, p in self.parameters.items() if isinstance(p, dc_cp) ]
[docs] def collect_results(self): result = {} for key in self._get_result_attributes(): p = self.get_attr(key) if (p.func is not None or (p.func is None and p.is_set) or p.is_result or p.structure_matrix is not None): result[key] = p.val else: result[key] = np.nan result[f"{key}_unit"] = p.unit return pd.Series(result)
[docs] def propagate_wrapper_to_target(self, branch): inconn = branch["connections"][-1] conn_idx = self.inl.index(inconn) outconn = self.outl[conn_idx] branch["connections"] += [outconn] branch["components"] += [self] outconn.target.propagate_wrapper_to_target(branch)
[docs] def get_variables(self): variables = {} for key, data in self.parameters.items(): if isinstance(data, dc_cp): if data.is_var: variables.update({key: data}) return variables
def _preprocess(self, row_idx): r""" Perform component initialization in network preprocessing. Parameters ---------- nw : tespy.networks.network.Network Network this component is integrated in. """ self.num_vars = 0 if self.bypass: self.constraints = self.get_bypass_constraints().copy() else: self.constraints = self.get_mandatory_constraints().copy() self.parameters self.prop_specifications = {} self.var_specifications = {} self.group_specifications = {} self.char_specifications = {} # ??? self.__dict__.update(self.constraints) self._structure_matrix = {} self._rhs = {} self._equation_set_lookup = {} sum_eq = 0 for name, constraint in self.constraints.items(): for i in range(sum_eq, sum_eq + constraint.num_eq_sets): self._rhs[i + row_idx] = 0 self._equation_set_lookup[i + row_idx] = name if constraint.structure_matrix is not None: constraint.structure_matrix(row_idx + sum_eq, **constraint.func_params) sum_eq += constraint.num_eq_sets if not self.bypass: sum_eq = self._setup_user_imposed_constraints(row_idx, sum_eq) self.num_eq = sum_eq def _setup_user_imposed_constraints(self, row_idx, sum_eq): for key, data in self.parameters.items(): if isinstance(data, dc_cp): if data.is_var: self.num_vars += 1 data._potential_var = True else: data._potential_var = False self.prop_specifications[key] = data.is_set self.var_specifications[key] = data.is_var # component characteristics elif isinstance(data, dc_cc): if data.func is not None: self.char_specifications[key] = data.is_set if data.char_func is None: try: data.char_func = ldc( self.__class__.__name__, key, 'DEFAULT', CharLine ) except KeyError: data.char_func = CharLine(x=[0, 1], y=[1, 1]) # component characteristics elif isinstance(data, dc_cm): if data.func is not None: self.char_specifications[key] = data.is_set if data.char_func is None: try: data.char_func = ldc( self.__class__.__name__, key, 'DEFAULT', CharMap ) except KeyError: data.char_func = CharLine(x=[0, 1], y=[1, 1]) # grouped component properties elif type(data) == dc_gcp: is_set = True for e in data.elements: if not self.get_attr(e).is_set: is_set = False if is_set: if self._mode == "design" and key not in self.offdesign: data.set_attr(is_set=True) elif self._mode == "offdesign" and key not in self.design: data.set_attr(is_set=True) elif data.is_set: msg = ( 'All parameters of the component group have to be ' 'specified! This component group uses the following ' f'parameters: {", ".join(data.elements)} at ' f'{self.label}. Group will be set to False.' ) logger.warning(msg) data.set_attr(is_set=False) else: data.set_attr(is_set=False) self.group_specifications[key] = data.is_set # grouped component characteristics elif type(data) == dc_gcc: self.group_specifications[key] = data.is_set # add equations to structure matrix structure_matrix = data.structure_matrix is not None func = data.func is not None if data.is_set and (structure_matrix or func): for i in range(sum_eq, sum_eq + data.num_eq_sets): self._rhs[i + row_idx] = 0 self._equation_set_lookup[i + row_idx] = key if structure_matrix: data.structure_matrix(row_idx + sum_eq, **data.func_params) sum_eq += data.num_eq_sets return sum_eq def _update_num_eq(self): pass def _check_dependents_implemented(self, deriv, dependents): if deriv is None and len(dependents) > 1: msg = ( "Retrieving the derivatives of component parameters " "associated with more than one equation is not yet " "supported. For these equations, you have to implement " "a separate derivate calculation method yourself and " "specify it in the component's parameter dictionaries." ) raise NotImplementedError(msg) def _assign_dependents_and_eq_mapping(self, value, data, eq_dict, eq_counter): if data.dependents is None: scalar_dependents = [[] for _ in range(data.num_eq)] vector_dependents = [{} for _ in range(data.num_eq)] else: dependents = data.dependents(**data.func_params) if type(dependents) == list: scalar_dependents = _get_dependents(dependents) vector_dependents = [{} for _ in range(data.num_eq)] else: scalar_dependents = _get_dependents(dependents["scalars"]) vector_dependents = _get_vector_dependents(dependents["vectors"]) # this is a temporary fix if len(vector_dependents) < data.num_eq: vector_dependents = [{} for _ in range(data.num_eq)] self._check_dependents_implemented(data.deriv, scalar_dependents) eq_dict[value]._scalar_dependents = scalar_dependents eq_dict[value]._vector_dependents = vector_dependents eq_dict[value]._first_eq_index = eq_counter for i in range(data.num_eq): self._equation_lookup[eq_counter + i] = (value, i) self._equation_scalar_dependents_lookup[eq_counter + i] = scalar_dependents[i] self._equation_vector_dependents_lookup[eq_counter + i] = vector_dependents[i] def _prepare_for_solver(self, system_dependencies, eq_counter): r""" Perform component initialization in network preprocessing. Parameters ---------- nw : tespy.networks.network.Network Network this component is integrated in. """ self.num_eq = 0 self.it = 0 self.equations = {} self._equation_lookup = {} self._equation_scalar_dependents_lookup = {} self._equation_vector_dependents_lookup = {} self._update_num_eq() for eq_num, value in self._equation_set_lookup.items(): if eq_num in system_dependencies: continue eq_dict = self.equations if value in self.constraints: data = self.constraints[value] elif value in self.parameters: data = self.parameters[value] if data.num_eq == 0: continue if value not in eq_dict: eq_dict.update({value: data}) self._assign_dependents_and_eq_mapping( value, data, eq_dict, eq_counter ) self.num_eq += data.num_eq eq_counter += data.num_eq self.jacobian = {} self.residual = {} # this could in principle apply for all equations! for constraint in self.equations.values(): eq_num = constraint._first_eq_index if constraint.constant_deriv: constraint.deriv(None, eq_num) return eq_counter
[docs] def get_parameters(self): return {}
[docs] def get_mandatory_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'}, 'description': "mass flow equality constraint(s)" }), 'fluid_constraints': dc_cmc(**{ 'structure_matrix': self.variable_equality_structure_matrix, 'num_eq_sets': self.num_i, 'func_params': {'variable': 'fluid'}, 'description': "fluid composition equality constraint(s)" }) }
[docs] def get_bypass_constraints(self): msg = ( f"The component {self.label} of type {self.__class__.__name__} " "does not have bypassing functionality yet." ) logger.exception(msg) raise NotImplementedError(msg)
[docs] @staticmethod def inlets(): return []
[docs] @staticmethod def outlets(): return []
[docs] @staticmethod def powerinlets(): return []
[docs] @staticmethod def poweroutlets(): return []
def _partial_derivative(self, var, eq_num, value, increment_filter=None, **kwargs): result = _partial_derivative(var, value, increment_filter, **kwargs) if result is not None: self.jacobian[eq_num, var.J_col] = result def _partial_derivative_fluid(self, var, eq_num, value, dx, increment_filter=None, **kwargs): result = _partial_derivative_vecvar(var, value, dx, increment_filter, **kwargs) if result is not None: self.jacobian[eq_num, var.J_col[dx]] = result def _add_missing_fluids(self, connections): return []
[docs] def get_char_expr(self, param, type='rel', inconn=0, outconn=0): r""" Generic method to access characteristic function parameters. Parameters ---------- param : str Parameter for characteristic function evaluation. type : str Type of expression: - :code:`rel`: relative to design value - :code:`abs`: absolute value inconn : int Index of inlet connection. outconn : int Index of outlet connection. Returns ------- expr : float Value of expression """ if type == 'rel': if param == 'm': return self.inl[inconn].m.val_SI / self.inl[inconn].m.design elif param == 'm_out': return self.outl[outconn].m.val_SI / self.outl[outconn].m.design elif param == 'v': v = self.inl[inconn].m.val_SI * self.inl[inconn].calc_vol() return v / self.inl[inconn].v.design elif param == 'pr': return ( (self.outl[outconn].p.val_SI * self.inl[inconn].p.design) / (self.inl[inconn].p.val_SI * self.outl[outconn].p.design) ) else: msg = ( f"The parameter {param} is not available for " "characteristic function evaluation." ) logger.error(msg) raise ValueError(msg) else: if param == 'm': return self.inl[inconn].m.val_SI elif param == 'm_out': return self.outl[outconn].m.val_SI elif param == 'v': return self.inl[inconn].m.val_SI * self.inl[inconn].calc_vol() elif param == 'pr': return self.outl[outconn].p.val_SI / self.inl[inconn].p.val_SI else: return False
[docs] def bus_func(self, bus): r""" Base method for calculation of the value of the bus function. Parameters ---------- bus : tespy.connections.bus.Bus TESPy bus object. Returns ------- residual : float Residual value of bus equation. """ return 0
[docs] def calc_bus_expr(self, bus): r""" Return the busses' characteristic line input expression. Parameters ---------- bus : tespy.connections.bus.Bus Bus to calculate the characteristic function expression for. Returns ------- expr : float Ratio of power to power design depending on the bus base specification. """ b = bus.comps.loc[self] if np.isnan(b['P_ref']) or b['P_ref'] == 0: return 1 else: comp_val = self.bus_func(b) if b['base'] == 'component': return abs(comp_val / b['P_ref']) else: kwargs = { "function": bus_char_evaluation, "parameter": "bus_value", "component_value": comp_val, "reference_value": b["P_ref"], "char_func": b["char"] } bus_value = newton_with_kwargs( derivative=bus_char_derivative, target_value=0, val0=b['P_ref'], valmin=-1e15, valmax=1e15, **kwargs ) return bus_value / b['P_ref']
[docs] def calc_bus_efficiency(self, bus): r""" Return the busses' efficiency. Parameters ---------- bus : tespy.connections.bus.Bus Bus to calculate the efficiency value on. Returns ------- efficiency : float Efficiency value of the bus. .. math:: \eta_\mathrm{bus} = \begin{cases} \eta\left( \frac{\dot{E}_\mathrm{bus}}{\dot{E}_\mathrm{bus,ref}}\right) & \text{bus base = 'bus'}\\ \eta\left( \frac{\dot{E}_\mathrm{component}} {\dot{E}_\mathrm{component,ref}}\right) & \text{bus base = 'component'} \end{cases} Note ---- If the base value of the bus is the bus value itself, a newton iteration is used to find the bus value satisfying the corresponding equation (case 1). """ return bus.comps.loc[self, 'char'].evaluate(self.calc_bus_expr(bus))
[docs] def calc_bus_value(self, bus): r""" Return the busses' value of the component's energy transfer. Parameters ---------- bus : tespy.connections.bus.Bus Bus to calculate energy transfer on. Returns ------- bus_value : float Value of the energy transfer on the specified bus. .. math:: \dot{E}_\mathrm{bus} = \begin{cases} \frac{\dot{E}_\mathrm{component}}{f\left( \frac{\dot{E}_\mathrm{bus}}{\dot{E}_\mathrm{bus,ref}}\right)} & \text{bus base = 'bus'}\\ \dot{E}_\mathrm{component} \cdot f\left( \frac{\dot{E}_\mathrm{component}} {\dot{E}_\mathrm{component,ref}}\right) & \text{bus base = 'component'} \end{cases} Note ---- If the base value of the bus is the bus value itself, a newton iteration is used to find the bus value satisfying the corresponding equation (case 1). """ b = bus.comps.loc[self] comp_val = self.bus_func(b) expr = self.calc_bus_expr(bus) if b['base'] == 'component': return comp_val * b['char'].evaluate(expr) else: return comp_val / b['char'].evaluate(expr)
[docs] def initialise_source(self, 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. .. math:: val = \begin{cases} 0 & \text{key = 'p'}\\ 0 & \text{key = 'h'} \end{cases} """ return 0
[docs] def initialise_target(self, 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. .. math:: val = \begin{cases} 0 & \text{key = 'p'}\\ 0 & \text{key = 'h'} \end{cases} """ return 0
def _set_design_parameters(self, mode, data): r""" Set or unset design values of component parameters. Parameters ---------- mode : str Setting component design values for :code:`mode='offdesign'` and unsetting them for :code:`mode='design'`. df : pandas.core.series.Series Series containing the component parameters. """ self._mode = mode if mode == 'design' or self.local_design: self.new_design = True for key, dc in self.parameters.items(): if isinstance(dc, dc_cp): if ( ((mode == 'offdesign' and not self.local_design) or (mode == 'design' and self.local_offdesign)) and (data[key] is not None) ): if f"{key}_unit" in data: value = _UNITS.ureg.Quantity( data[key], data[f"{key}_unit"] ).to(SI_UNITS[dc.quantity]).magnitude else: value = data[key] self.get_attr(key).design = float(value) else: self.get_attr(key).design = np.nan
[docs] def calc_parameters(self): r"""Postprocessing parameter calculation.""" return
[docs] def check_parameter_bounds(self): r"""Check parameter value limits.""" _no_limit_violated = True for p in self.parameters.keys(): data = self.get_attr(p) if isinstance(data, dc_cp): if data.val_SI > data.max_val + ERR: msg = ( f"Invalid value for {p}: {p} = {data.val_SI} above " f"maximum value ({data.max_val}) at component " f"{self.label}." ) logger.warning(msg) _no_limit_violated = False elif data.val_SI < data.min_val - ERR: msg = ( f"Invalid value for {p}: {p} = {data.val_SI} below " f"minimum value ({data.min_val}) at component " f"{self.label}." ) logger.warning(msg) _no_limit_violated = False elif isinstance(data, dc_cc) and data.is_set: if data.param is not None: expr = self.get_char_expr(data.param, **data.char_params) data.char_func.get_domain_errors(expr, self.label) elif isinstance(data, dc_gcc) and data.is_set: for char in data.elements: char_data = self.get_attr(char) expr = self.get_char_expr( char_data.param, **char_data.char_params) char_data.char_func.get_domain_errors(expr, self.label) return _no_limit_violated
[docs] def convergence_check(self): return
[docs] def entropy_balance(self): r"""Entropy balance calculation method.""" return
[docs] def exergy_balance(self, T0): r""" Exergy balance calculation method. Parameters ---------- T0 : float Ambient temperature T0 / K. """ self.E_P = np.nan self.E_F = np.nan self.E_bus = { "chemical": np.nan, "physical": np.nan, "massless": np.nan } self.E_D = np.nan self.epsilon = self._calc_epsilon()
def _calc_epsilon(self): if self.E_F == 0: return np.nan else: return self.E_P / self.E_F
[docs] def get_plotting_data(self): return
[docs] def pr_structure_matrix(self, k, pr=None, inconn=0, outconn=0): r""" Create linear relationship between inflow and outflow pressure .. math:: p_{in} \cdot pr = p_{out} Parameters ---------- k : int equation number in systems of equations pr : str Component parameter, e.g. :code:`pr1`. inconn : int Connection index of inlet. outconn : int Connection index of outlet. """ pr = self.get_attr(pr) i = self.inl[inconn] o = self.outl[outconn] self._structure_matrix[k, i.p.sm_col] = pr.val_SI self._structure_matrix[k, o.p.sm_col] = -1
[docs] def variable_equality_structure_matrix(self, k, **kwargs): r""" Create pairwise linear relationship between two variables :code:`var` for all inlets and the respective outlets. This usually is applied to mass flow, pressure, enthalpy and fluid composition. .. math:: var_\text{in,i} = var_\text{out,i} Parameters ---------- k : int equation number in systems of equations variable : str Connection variable name, e.g. :code:`h`. """ variable = kwargs.get("variable") for count, (i, o) in enumerate(zip(self.inl, self.outl)): self._structure_matrix[k + count, i.get_attr(variable).sm_col] = 1 self._structure_matrix[k + count, o.get_attr(variable).sm_col] = -1
[docs] def calc_zeta(self, i, o): if abs(i.m.val_SI) <= 1e-4: return 0 else: return ( (i.p.val_SI - o.p.val_SI) * math.pi ** 2 / (4 * i.m.val_SI ** 2 * (i.vol.val_SI + o.vol.val_SI)) )
[docs] def zeta_func(self, zeta=None, inconn=0, outconn=0): r""" Calculate residual value of :math:`\zeta`-function. Parameters ---------- zeta : str Component parameter to evaluate the zeta_func on, e.g. :code:`zeta1`. inconn : int Connection index of inlet. outconn : int Connection index of outlet. Returns ------- residual : float Residual value of function. .. math:: 0 = \begin{cases} p_{in} - p_{out} & |\dot{m}| < \epsilon \\ \frac{\zeta}{D^4} - \frac{(p_{in} - p_{out}) \cdot \pi^2} {8 \cdot \dot{m}_{in} \cdot |\dot{m}_{in}| \cdot \frac{v_{in} + v_{out}}{2}} & |\dot{m}| > \epsilon \end{cases} Note ---- The zeta value is caluclated on the basis of a given pressure loss at a given flow rate in the design case. As the cross sectional area A will not change, it is possible to handle the equation in this way: .. math:: \frac{\zeta}{D^4} = \frac{\Delta p \cdot \pi^2} {8 \cdot \dot{m}^2 \cdot v} """ data = self.get_attr(zeta) i = self.inl[inconn] o = self.outl[outconn] if abs(i.m.val_SI) < 1e-4: return i.p.val_SI - o.p.val_SI else: v_i = i.calc_vol(T0=i.T.val_SI) v_o = o.calc_vol(T0=o.T.val_SI) return ( data.val_SI - (i.p.val_SI - o.p.val_SI) * math.pi ** 2 / (8 * abs(i.m.val_SI) * i.m.val_SI * (v_i + v_o) / 2) )
[docs] def zeta_dependents(self, zeta=None, inconn=0, outconn=0): return [ self.inl[inconn].m, self.inl[inconn].p, self.inl[inconn].h, self.outl[outconn].p, self.outl[outconn].h, self.get_attr(zeta) ]
[docs] def dp_structure_matrix(self, k, dp=None, inconn=0, outconn=0): r""" Create linear relationship between inflow and outflow pressure .. math:: p_{in} - dp = p_{out} Parameters ---------- k : int equation number in systems of equations dp : str Component parameter, e.g. :code:`dp1`. inconn : int Connection index of inlet. outconn : int Connection index of outlet. """ inlet_conn = self.inl[inconn] outlet_conn = self.outl[outconn] self._structure_matrix[k, inlet_conn.p.sm_col] = 1 self._structure_matrix[k, outlet_conn.p.sm_col] = -1 self._rhs[k] = self.get_attr(dp).val_SI