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
from collections import deque

import numpy as np
import pandas as pd

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.global_vars import ERR
from tespy.tools.helpers import TESPyNetworkError
from tespy.tools.helpers import _get_dependents
from tespy.tools.helpers import _get_vector_dependents
from tespy.tools.helpers import _partial_derivative
from tespy.tools.helpers import _partial_derivative_vecvar
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 = {} def _topological_sort(calc_items): """Return keys of calc_items in topological order based on calc_deps. Raises ValueError if a dependency cycle is detected. """ successors = {k: [] for k in calc_items} in_degree = {k: 0 for k in calc_items} for k, dc in calc_items.items(): for dep in dc.calc_deps: if dep in calc_items: successors[dep].append(k) in_degree[k] += 1 queue = deque(k for k in calc_items if in_degree[k] == 0) ordered = [] while queue: k = queue.popleft() ordered.append(k) for succ in successors[k]: in_degree[succ] -= 1 if in_degree[succ] == 0: queue.append(succ) if len(ordered) != len(calc_items): cycle = set(calc_items) - set(ordered) raise ValueError(f"Cycle detected in calc_deps: {cycle}") return ordered
[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): if not isinstance(label, str): msg = 'Component label must be of type str!' 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 self._local_connection_design_state = {} # 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()) self.num_heat_i = len(self.heatinlets()) self.num_heat_o = len(self.heatoutlets())
[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. """ for key, value in kwargs.items(): if key in self.parameters: self._set_parameter(key, value) elif key in ('design', 'offdesign'): self._set_design_list(key, value) elif key in ('local_design', 'local_offdesign', 'printout', 'char_warnings', 'bypass'): self._set_bool_attr(key, value) elif key in ('design_path', 'fkt_group'): self._set_path_attr(key, value) else: msg = f"Component {self.label} has no attribute {key}." logger.error(msg) raise KeyError(msg)
def _set_parameter(self, key, value): try: self.parameters[key].accept(value) except TypeError as e: msg = ( f"Bad datatype for keyword argument '{key}' on " f"component {self.label}: {e}" ) logger.error(msg) raise TypeError(msg) from e def _set_design_list(self, key, value): if not isinstance(value, list): msg = ( f"Please provide the {key} parameters as list for " f"component {self.label}." ) logger.error(msg) raise TypeError(msg) if not set(value).issubset(self.parameters.keys()): 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) self.__dict__[key] = value def _set_bool_attr(self, key, value): if not isinstance(value, bool): msg = ( f"Please provide the {key} parameter as bool for " f"component {self.label}." ) logger.error(msg) raise TypeError(msg) self.__dict__[key] = value if key == 'local_offdesign' and not value: self._local_connection_design_state = {} def _set_path_attr(self, key, value): self.__dict__[key] = value self.new_design = True if key == 'design_path' and value is None: self._local_connection_design_state = {}
[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() # 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 = CharMap() # 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 = ( f"Not all parameters of the component group {key} " f"of component {self.label} have to been specified, " "the group equation will not be applied. This " "component group uses the following " f"parameters: {', '.join(data.elements)}." ) logger.debug(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 []
[docs] @staticmethod def heatinlets(): return []
[docs] @staticmethod def heatoutlets(): return []
@property def all_connections(self): return self.all_inlets + self.all_outlets @property def all_inlets(self): return self.inl + self.power_inl + self.heat_inl @property def all_outlets(self): return self.outl + self.power_outl + self.heat_outl def _validate_connections(self): if len(self.outl) != self.num_o: msg = ( f"The component {self.label} is missing " f"{self.num_o - len(self.outl)} outgoing connections. " "Make sure all outlets are connected and all connections " "have been added to the network." ) logger.error(msg) raise TESPyNetworkError(msg) if len(self.inl) != self.num_i: msg = ( f"The component {self.label} is missing " f"{self.num_i - len(self.inl)} incoming connections. " "Make sure all inlets are connected and all connections " "have been added to the network." ) logger.error(msg) raise TESPyNetworkError(msg) 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._conn_design(self.inl[inconn], 'm') elif param == 'm_out': return self.outl[outconn].m.val_SI / self._conn_design(self.outl[outconn], 'm') elif param == 'v': v = self.inl[inconn].m.val_SI * self.inl[inconn].calc_vol() return v / self._conn_design(self.inl[inconn], 'v') elif param == 'pr': return ( (self.outl[outconn].p.val_SI * self._conn_design(self.inl[inconn], 'p')) / (self.inl[inconn].p.val_SI * self._conn_design(self.outl[outconn], 'p')) ) 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
def _conn_design(self, conn, param): r""" Return the design point value of a connection parameter. When a component has an individual :code:`design_path` (either because it has :code:`local_offdesign=True` in a design-mode solve, or because it carries its own :code:`design_path` in an offdesign solve), the adjacent connection design values are stored in :code:`_local_connection_design_state` during preprocessing. This method returns those local values when available and falls back to the connection's own :code:`.design` attribute otherwise. Parameters ---------- conn : tespy.connections.connection.Connection Adjacent connection object. param : str Connection parameter name, e.g. :code:`'m'`, :code:`'p'`, :code:`'h'`, :code:`'T'`, :code:`'v'`, :code:`'vol'`. Returns ------- float Design point value in SI units. """ if self._local_connection_design_state: local_state = self._local_connection_design_state.get(conn.label) if local_state is not None and param in local_state: return local_state[param] return getattr(conn, param).design
[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.get(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 def _calc_pr(self, inconn=0, outconn=0): return self.outl[outconn].p.val_SI / self.inl[inconn].p.val_SI def _calc_dp(self, inconn=0, outconn=0): return self.inl[inconn].p.val_SI - self.outl[outconn].p.val_SI
[docs] def calc_parameters(self): r"""Postprocessing parameter calculation. Each :py:class:`~tespy.tools.data_containers.ComponentProperties` whose :code:`calc` attribute is set is called here in topological order (respecting :code:`calc_deps` dependencies). .. note:: Two patterns exist for :code:`calc` methods, and it is important to keep them distinct: **Pattern A - solver variables only** (p, h, m, fluid composition, connection energies E): methods like :py:meth:`_calc_P` read only quantities that are unknowns of the solver, therefore these methods can also be used in the residual calculations during iterations. **Pattern B - derived properties** (T, v, x, ...): methods like :code:`_calc_ttd_u` or :code:`_calc_td_log` call helpers such as :code:`calc_T()` which rely on values that are computed during connection postprocessing (e.g. :code:`connection.T.val_SI`). These methods must **only** be called in postprocessing, never during iteration, because the derived values are not yet available. When adding a new :code:`calc` method, choose Pattern A if the result depends solely on solver variables; choose Pattern B otherwise, and make sure no caller invokes it during iteration. """ calc_items = { k: dc for k, dc in self.parameters.items() if isinstance(dc, dc_cp) and dc.calc is not None } for k in _topological_sort(calc_items): dc = calc_items[k] dc.val_SI = dc.calc(**dc.calc_params)
[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 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
def _calc_zeta(self, inconn=0, outconn=0): i, o = self.inl[inconn], self.outl[outconn] 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 calculated 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, ]
[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