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

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 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.document_models import generate_latex_eq
from tespy.tools.fluid_properties import v_mix_ph
from tespy.tools.global_vars import ERR
from tespy.tools.helpers import _numeric_deriv
from tespy.tools.helpers import bus_char_derivative
from tespy.tools.helpers import bus_char_evaluation
from tespy.tools.helpers import newton_with_kwargs


[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.component()}" ) 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.fkt_group = self.label # add container for components attributes self.parameters = self.get_parameters().copy() self.__dict__.update(self.parameters) self.set_attr(**kwargs)
[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) try: data.set_attr(is_var=False) except KeyError: pass continue try: float(kwargs[key]) is_numeric = True except (TypeError, ValueError): is_numeric = False # 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: data.set_attr(val=kwargs[key], is_set=True) if isinstance(data, dc_cp): data.set_attr(is_var=False) elif kwargs[key] == 'var' and isinstance(data, dc_cp): data.set_attr(is_set=True, is_var=True) elif isinstance(data, dc_simple): data.set_attr(val=kwargs[key], is_set=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']: 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" ]
[docs] @staticmethod def is_branch_source(): return False
[docs] def propagate_to_target(self, branch): inconn = branch["connections"][-1] conn_idx = self.inl.index(inconn) outconn = self.outl[conn_idx] branch["connections"] += [outconn] branch["components"] += [outconn.target] outconn.target.propagate_to_target(branch)
[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 preprocess(self, num_nw_vars): r""" Perform component initialization in network preprocessing. Parameters ---------- nw : tespy.networks.network.Network Network this component is integrated in. """ self.it = 0 self.num_eq = 0 self.vars = {} self.num_vars = 0 self.constraints = self.get_mandatory_constraints().copy() self.prop_specifications = {} self.var_specifications = {} self.group_specifications = {} self.char_specifications = {} self.__dict__.update(self.constraints) for constraint in self.constraints.values(): self.num_eq += constraint['num_eq'] for key, val in self.parameters.items(): data = self.get_attr(key) if isinstance(val, dc_cp): if data.is_var: data.J_col = num_nw_vars + self.num_vars self.num_vars += 1 self.vars[data] = key self.prop_specifications[key] = val.is_set self.var_specifications[key] = val.is_var # component characteristics elif isinstance(val, dc_cc): if data.func is not None: self.char_specifications[key] = val.is_set if data.char_func is None: try: data.char_func = ldc( self.component(), key, 'DEFAULT', CharLine) except KeyError: data.char_func = CharLine(x=[0, 1], y=[1, 1]) # component characteristics elif isinstance(val, dc_cm): if data.func is not None: self.char_specifications[key] = val.is_set if data.char_func is None: try: data.char_func = ldc( self.component(), key, 'DEFAULT', CharMap) except KeyError: data.char_func = CharLine(x=[0, 1], y=[1, 1]) # grouped component properties elif isinstance(val, dc_gcp): is_set = True for e in data.elements: if not self.get_attr(e).is_set: is_set = False if is_set: data.set_attr(is_set=True) elif data.is_set: start = ( 'All parameters of the component group have to be ' 'specified! This component group uses the following ' 'parameters: ' ) end = f" at {self.label}. Group will be set to False." logger.warning(start + ', '.join(val.elements) + end) val.set_attr(is_set=False) else: val.set_attr(is_set=False) self.group_specifications[key] = val.is_set # grouped component characteristics elif isinstance(val, dc_gcc): self.group_specifications[key] = val.is_set # component properties if data.is_set and data.func is not None: self.num_eq += data.num_eq self.jacobian = {} self.residual = np.zeros(self.num_eq) sum_eq = 0 for constraint in self.constraints.values(): num_eq = constraint['num_eq'] if constraint['constant_deriv']: constraint["deriv"](sum_eq) sum_eq += num_eq # done msg = f"The component {self.label} has {self.num_vars} variables." logger.debug(msg)
[docs] def get_parameters(self): return {}
[docs] def get_mandatory_constraints(self): return {}
[docs] @staticmethod def inlets(): return []
[docs] @staticmethod def outlets(): return []
[docs] @staticmethod def is_variable(var, increment_filter=None): if var.is_var: if increment_filter is None or not increment_filter[var.J_col]: return True return False
[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 * v_mix_ph( self.inl[inconn].p.val_SI, self.inl[inconn].h.val_SI, self.inl[inconn].fluid_data, self.inl[inconn].mixing_rule, T0=self.inl[inconn].T.val_SI ) 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 * v_mix_ph( self.inl[inconn].p.val_SI, self.inl[inconn].h.val_SI, self.inl[inconn].fluid_data, self.inl[inconn].mixing_rule, T0=self.inl[inconn].T.val_SI ) elif param == 'pr': return self.outl[outconn].p.val_SI / self.inl[inconn].p.val_SI else: return False
[docs] def get_char_expr_doc(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 : str LaTeX code for documentation """ if type == 'rel': if param == 'm': return ( r'\frac{\dot{m}_\mathrm{in,' + str(inconn + 1) + r'}}' r'{\dot{m}_\mathrm{in,' + str(inconn + 1) + r',design}}') elif param == 'm_out': return ( r'\frac{\dot{m}_\mathrm{out,' + str(outconn + 1) + r'}}{\dot{m}_\mathrm{out,' + str(outconn + 1) + r',design}}') elif param == 'v': return ( r'\frac{\dot{V}_\mathrm{in,' + str(inconn + 1) + r'}}' r'{\dot{V}_\mathrm{in,' + str(inconn + 1) + r',design}}') elif param == 'pr': return ( r'\frac{p_\mathrm{out,' + str(outconn + 1) + r'}\cdot p_\mathrm{in,' + str(inconn + 1) + r',design}}{p_\mathrm{out,' + str(outconn + 1) + r',design}\cdot p_\mathrm{in,' + str(inconn + 1) + r'}}') else: if param == 'm': return r'\dot{m}_\mathrm{in,' + str(inconn + 1) + r'}' elif param == 'm_out': return r'\dot{m}_\mathrm{out,' + str(outconn + 1) + r'}' elif param == 'v': return r'\dot{V}_\mathrm{in,' + str(inconn + 1) + r'}' elif param == 'pr': return ( r'\frac{p_\mathrm{out,' + str(outconn + 1) + r'}}{p_\mathrm{in,' + str(inconn + 1) + r'}}')
[docs] def solve(self, increment_filter): """ Solve equations and calculate partial derivatives of a component. Parameters ---------- increment_filter : ndarray Matrix for filtering non-changing variables. """ sum_eq = 0 for constraint in self.constraints.values(): num_eq = constraint['num_eq'] if num_eq > 0: self.residual[sum_eq:sum_eq + num_eq] = constraint['func']() if not constraint['constant_deriv']: constraint['deriv'](increment_filter, sum_eq) sum_eq += num_eq for data in self.parameters.values(): if data.is_set and data.func is not None: self.residual[sum_eq:sum_eq + data.num_eq] = data.func( **data.func_params ) data.deriv(increment_filter, sum_eq, **data.func_params) sum_eq += data.num_eq
[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
[docs] def set_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. """ 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)): self.get_attr(key).design = float(data[key]) 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.""" for p in self.parameters.keys(): data = self.get_attr(p) if isinstance(data, dc_cp): if data.val > data.max_val + ERR: msg = ( f"Invalid value for {p}: {p} = {data.val} above " f"maximum value ({data.max_val}) at component " f"{self.label}." ) logger.warning(msg) elif data.val < data.min_val - ERR: msg = ( f"Invalid value for {p}: {p} = {data.val} below " f"minimum value ({data.min_val}) at component " f"{self.label}." ) logger.warning(msg) elif isinstance(data, dc_cc) and data.is_set: 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)
[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 pressure_equality_func(self): r""" Equation for pressure equality. Returns ------- residual : float Residual value of equation. .. math:: 0 = p_{in,i} - p_{out,i} \;\forall i\in\text{inlets} """ residual = [] for i in range(self.num_i): residual += [self.inl[i].p.val_SI - self.outl[i].p.val_SI] return residual
[docs] def pressure_equality_func_doc(self, label): r""" Equation for pressure equality. Parameters ---------- label : str Label for equation. Returns ------- latex : str LaTeX code of equations applied. """ indices = list(range(1, self.num_i + 1)) if len(indices) > 1: indices = ', '.join(str(idx) for idx in indices) else: indices = str(indices[0]) latex = ( r'0=p_{\mathrm{in,}i}-p_{\mathrm{out,}i}' r'\; \forall i \in [' + indices + r']') return generate_latex_eq(self, latex, label)
[docs] def pressure_equality_deriv(self, k): r""" Calculate partial derivatives for all mass flow balance equations. Returns ------- deriv : ndarray Matrix with partial derivatives for the mass flow balance equations. """ for i in range(self.num_i): if self.inl[i].p.is_var: self.jacobian[k + i, self.inl[i].p.J_col] = 1 if self.outl[i].p.is_var: self.jacobian[k + i, self.outl[i].p.J_col] = -1
[docs] def enthalpy_equality_func(self): r""" Equation for enthalpy equality. Returns ------- residual : list Residual values of equations. .. math:: 0 = h_{in,i} - h_{out,i} \;\forall i\in\text{inlets} """ residual = [] for i in range(self.num_i): residual += [self.inl[i].h.val_SI - self.outl[i].h.val_SI] return residual
[docs] def enthalpy_equality_func_doc(self, label): r""" Equation for enthalpy equality. Parameters ---------- label : str Label for equation. Returns ------- latex : str LaTeX code of equations applied. """ indices = list(range(1, self.num_i + 1)) if len(indices) > 1: indices = ', '.join(str(idx) for idx in indices) else: indices = str(indices[0]) latex = ( r'0=h_{\mathrm{in,}i}-h_{\mathrm{out,}i}' r'\; \forall i \in [' + indices + r']' ) return generate_latex_eq(self, latex, label)
[docs] def enthalpy_equality_deriv(self, k): r""" Calculate partial derivatives for all mass flow balance equations. Returns ------- deriv : ndarray Matrix with partial derivatives for the mass flow balance equations. """ for i in range(self.num_i): if self.inl[i].h.is_var: self.jacobian[k + i, self.inl[i].h.J_col] = 1 if self.outl[i].h.is_var: self.jacobian[k + i, self.outl[i].h.J_col] = -1
[docs] def numeric_deriv(self, func, dx, conn=None, **kwargs): r""" Calculate partial derivative of the function func to dx. For details see :py:func:`tespy.tools.helpers._numeric_deriv` """ return _numeric_deriv(self, func, dx, conn, **kwargs)
[docs] def pr_func(self, pr='', inconn=0, outconn=0): r""" Calculate residual value of pressure ratio function. Parameters ---------- pr : str Component parameter to evaluate the pr_func on, e.g. :code:`pr1`. inconn : int Connection index of inlet. outconn : int Connection index of outlet. Returns ------- residual : float Residual value of function. .. math:: 0 = p_{in} \cdot pr - p_{out} """ pr = self.get_attr(pr) return self.inl[inconn].p.val_SI * pr.val - self.outl[outconn].p.val_SI
[docs] def pr_func_doc(self, label, pr='', inconn=0, outconn=0): r""" Calculate residual value of pressure ratio function. Parameters ---------- pr : str Component parameter to evaluate the pr_func on, e.g. :code:`pr1`. inconn : int Connection index of inlet. outconn : int Connection index of outlet. Returns ------- residual : float Residual value of function. """ latex = ( r'0=p_\mathrm{in,' + str(inconn + 1) + r'}\cdot ' + pr + r' - p_\mathrm{out,' + str(outconn + 1) + r'}' ) return generate_latex_eq(self, latex, label)
[docs] def pr_deriv(self, increment_filter, k, pr='', inconn=0, outconn=0): r""" Calculate residual value of pressure ratio function. Parameters ---------- increment_filter : ndarray Matrix for filtering non-changing variables. k : int Position of equation in Jacobian matrix. pr : str Component parameter to evaluate the pr_func on, 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[inconn] if i.p.is_var: self.jacobian[k, i.p.J_col] = pr.val if o.p.is_var: self.jacobian[k, o.p.J_col] = -1 if pr.is_var: self.jacobian[k, self.pr.J_col] = i.p.val_SI
[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='', 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 = v_mix_ph(i.p.val_SI, i.h.val_SI, i.fluid_data, i.mixing_rule, T0=i.T.val_SI) v_o = v_mix_ph(o.p.val_SI, o.h.val_SI, o.fluid_data, o.mixing_rule, T0=o.T.val_SI) return ( data.val - (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_func_doc(self, label, zeta='', 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. """ inl = r'_\mathrm{in,' + str(inconn + 1) + r'}' outl = r'_\mathrm{out,' + str(outconn + 1) + r'}' latex = ( r'0 = \begin{cases}' + '\n' + r'p' + inl + r'- p' + outl + r' & |\dot{m}' + inl + r'| < \unitfrac[0.0001]{kg}{s} \\' + '\n' + r'\frac{\zeta}{D^4}-\frac{(p' + inl + r'-p' + outl + r')' r'\cdot\pi^2}{8\cdot\dot{m}' + inl + r'\cdot|\dot{m}' + inl + r'|\cdot\frac{v' + inl + r' + v' + outl + r'}{2}}' + r'& |\dot{m}' + inl + r'| \geq \unitfrac[0.0001]{kg}{s}' + '\n' r'\end{cases}' ) return generate_latex_eq(self, latex, label)
[docs] def zeta_deriv(self, increment_filter, k, zeta='', inconn=0, outconn=0): r""" Calculate partial derivatives of zeta function. Parameters ---------- increment_filter : ndarray Matrix for filtering non-changing variables. k : int Position of equation in Jacobian matrix. 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. """ data = self.get_attr(zeta) f = self.zeta_func i = self.inl[inconn] o = self.outl[outconn] kwargs = dict(zeta=zeta, inconn=inconn, outconn=outconn) if self.is_variable(i.m, increment_filter): self.jacobian[k, i.m.J_col] = self.numeric_deriv(f, 'm', i, **kwargs) if self.is_variable(i.p, increment_filter): self.jacobian[k, i.p.J_col] = self.numeric_deriv(f, 'p', i, **kwargs) if self.is_variable(i.h, increment_filter): self.jacobian[k, i.h.J_col] = self.numeric_deriv(f, 'h', i, **kwargs) if self.is_variable(o.p, increment_filter): self.jacobian[k, o.p.J_col] = self.numeric_deriv(f, 'p', o, **kwargs) if self.is_variable(o.h, increment_filter): self.jacobian[k, o.h.J_col] = self.numeric_deriv(f, 'h', o, **kwargs) # custom variable zeta if data.is_var: self.jacobian[k, data.J_col] = self.numeric_deriv(f, zeta, None, **kwargs)
[docs] def dp_func(self, dp=None, inconn=None, outconn=None): """Calculate residual value of pressure difference function. Parameters ---------- dp : str Component parameter to evaluate the dp_func on, e.g. :code:`dp1`. inconn : int Connection index of inlet. outconn : int Connection index of outlet. Returns ------- residual : float Residual value of function. .. math:: 0 = p_{in} - p_{out} - dp """ inlet_conn = self.inl[inconn] outlet_conn = self.outl[outconn] dp_value = self.get_attr(dp).val_SI return inlet_conn.p.val_SI - outlet_conn.p.val_SI - dp_value
[docs] def dp_deriv(self, increment_filter, k, dp=None, inconn=None, outconn=None): r""" Calculate residual value of pressure difference function. Parameters ---------- increment_filter : ndarray Matrix for filtering non-changing variables. k : int Position of equation in Jacobian matrix. dp : str Component parameter to evaluate the dp_func on, 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] if inlet_conn.p.is_var: self.jacobian[k, inlet_conn.p.J_col] = 1 if outlet_conn.p.is_var: self.jacobian[k, outlet_conn.p.J_col] = -1