Source code for tespy.connections.connection

# -*- coding: utf-8

"""Module of class Connection and class Ref.


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/connections/connection.py

SPDX-License-Identifier: MIT
"""

import logging

import numpy as np

from tespy.components.component import Component
from tespy.tools import fluid_properties as fp
from tespy.tools.data_containers import DataContainerSimple as dc_simple
from tespy.tools.data_containers import FluidComposition as dc_flu
from tespy.tools.data_containers import FluidProperties as dc_prop
from tespy.tools.helpers import TESPyConnectionError

# pass the warning messages to the logger
logging.captureWarnings(True)


[docs]class Connection: r""" Class connection is the container for fluid properties between components. Parameters ---------- m : float, tespy.connections.connection.Ref Mass flow specification. m0 : float Starting value specification for mass flow. p : float, tespy.connections.connection.Ref Pressure specification. p0 : float Starting value specification for pressure. h : float, tespy.connections.connection.Ref Enthalpy specification. h0 : float Starting value specification for enthalpy. fluid : dict Fluid compostition specification. fluid0 : dict Starting value specification for fluid compostition. fluid_balance : boolean Fluid balance equation specification. x : float Gas phase mass fraction specification. T : float, tespy.connections.connection.Ref Temperature specification. Td_bp : float Temperature difference to boiling point at pressure corresponding pressure of this connection in K. v : float Volumetric flow specification. state : str State of the pure fluid on this connection: liquid ('l') or gaseous ('g'). design : list List containing design parameters (stated as string). offdesign : list List containing offdesign parameters (stated as string). design_path : str Path to individual design case for this connection. local_offdesign : boolean Treat this connection in offdesign mode in a design calculation. local_design : boolean Treat this connection in design mode in an offdesign calculation. printout : boolean Include this connection in the network's results printout. label : str Label of the connection. The default value is: :code:`'source:source_id_target:target_id'`. Note ---- - The fluid balance parameter applies a balancing of the fluid vector on the specified conntion to 100 %. For example, you have four fluid components (a, b, c and d) in your vector, you set two of them (a and b) and want the other two (components c and d) to be a result of your calculation. If you set this parameter to True, the equation (0 = 1 - a - b - c - d) will be applied. - The specification of values for design and/or offdesign is used for automatic switch from design to offdesign calculation: All parameters given in 'design', e.g. :code:`design=['T', 'p']`, are unset in any offdesign calculation, parameters given in 'offdesign' are set for offdesign calculation. Example ------- This example shows how to create connections and specify parameters. First create the required components and connect them in the next step. After that, it is possible specify parameters with the :code:`set_attr` method. >>> from tespy.components import Sink, Source >>> from tespy.connections import Connection, Ref >>> from tespy.tools import FluidComposition as dc_flu >>> from tespy.tools import FluidProperties as dc_prop >>> import numpy as np >>> so1 = Source('source1') >>> so2 = Source('source2') >>> si1 = Sink('sink1') >>> si2 = Sink('sink2') >>> so_si1 = Connection(so1, 'out1', si1, 'in1', label='connection 1') >>> so_si2 = Connection(so2, 'out1', si2, 'in1') >>> so_si1.label 'connection 1' >>> so_si2.label 'source2:out1_sink2:in1' There are different ways of setting parameters on connections: Specify - a numeric value (for attributes mass flow, pressure and enthalpy) - a numeric starting value (for attributes mass flow, pressure and enthalpy) - a dictionary (for attributes fluid and fluid0) - a boolean value (for attributes fluid_balance, local_design, local_offdesign). - a referenced value (mass flow, pressure, temperature, enthalpy). - numpy.nan or None (unsetting a value). - a string (for attributes design_path and state). - a list (for attributes design and offdesign). >>> so_si1.set_attr(v=0.012, m0=10, p=5, h=400, fluid={'H2O': 1, 'N2': 0}) >>> so_si2.set_attr(m=Ref(so_si1, 2, -5), h0=700, T=200, ... fluid={'N2': 1}, fluid_balance=True, ... design=['T'], offdesign=['m', 'v']) The set_attr method automatically converts your input in data_container information. >>> type(so_si1.v) <class 'tespy.tools.data_containers.FluidProperties'> >>> type(so_si1.fluid) <class 'tespy.tools.data_containers.FluidComposition'> If you want get a spcific value use the logic: connection.property.*. Aditionally, it is possible to use the :code:`get_attr` method. >>> so_si1.m.val0 10 >>> so_si1.m.val_set False >>> so_si1.m.get_attr('val_set') False >>> type(so_si2.m.ref) <class 'tespy.connections.connection.Ref'> >>> so_si2.fluid.get_attr('balance') True >>> so_si2.m.ref.get_attr('delta') -5 >>> so_si2.m.ref_set True >>> type(so_si2.m.ref.get_attr('obj')) <class 'tespy.connections.connection.Connection'> Unset the specified temperature and specify temperature difference to boiling point instead. >>> so_si2.T.val_set True >>> so_si2.set_attr(Td_bp=5, T=np.nan) >>> so_si2.T.val_set False >>> so_si2.Td_bp.val 5 >>> so_si2.set_attr(Td_bp=None) >>> so_si2.Td_bp.val_set False Specify the state keyword: The fluid will be forced to liquid or gaseous state in this case. >>> so_si2.set_attr(state='l') >>> so_si2.state.is_set True >>> so_si2.set_attr(state=np.nan) >>> so_si2.state.is_set False >>> so_si2.set_attr(state='g') >>> so_si2.state.is_set True >>> so_si2.set_attr(state=None) >>> so_si2.state.is_set False """ def __init__(self, comp1, outlet_id, comp2, inlet_id, label=None, **kwargs): # check input parameters if not (isinstance(comp1, Component) and isinstance(comp2, Component)): msg = ('Error creating connection. Check if comp1, comp2 are of ' 'type component.') logging.error(msg) raise TypeError(msg) if comp1 == comp2: msg = ('Error creating connection. Cannot connect component ' + comp1.label + ' to itself.') logging.error(msg) raise TESPyConnectionError(msg) if outlet_id not in comp1.outlets(): msg = ('Error creating connection. Specified oulet_id (' + outlet_id + ') is not valid for component ' + comp1.component() + '. Valid ids are: ' + str(comp1.outlets()) + '.') logging.error(msg) raise ValueError(msg) if inlet_id not in comp2.inlets(): msg = ( 'Error creating connection. Specified inlet_id (' + inlet_id + ') is not valid for component ' + comp2.component() + '. Valid ids are: ' + str(comp2.inlets()) + '.') logging.error(msg) raise ValueError(msg) if label is None: self.label = ( comp1.label + ':' + outlet_id + '_' + comp2.label + ':' + inlet_id) else: self.label = label if not isinstance(self.label, str): msg = 'Please provide the label as string.' logging.error(msg) raise TypeError(msg) # set specified values self.source = comp1 self.source_id = outlet_id self.target = comp2 self.target_id = inlet_id # defaults self.new_design = True self.design_path = None self.design = [] self.offdesign = [] self.local_design = False self.local_offdesign = False self.printout = True # set default values for kwargs self.variables = self.attr() self.variables0 = [x + '0' for x in self.variables.keys()] self.__dict__.update(self.variables) self.set_attr(**kwargs) msg = ( 'Created connection ' + self.source.label + ' (' + self.source_id + ') -> ' + self.target.label + ' (' + self.target_id + ').') logging.debug(msg)
[docs] def set_attr(self, **kwargs): r""" Set, reset or unset attributes of a connection. Parameters ---------- m : float, tespy.connections.connection.Ref Mass flow specification. m0 : float Starting value specification for mass flow. p : float, tespy.connections.connection.Ref Pressure specification. p0 : float Starting value specification for pressure. h : float, tespy.connections.connection.Ref Enthalpy specification. h0 : float Starting value specification for enthalpy. fluid : dict Fluid compostition specification. fluid0 : dict Starting value specification for fluid compostition. fluid_balance : boolean Fluid balance equation specification. x : float Gas phase mass fraction specification. T : float, tespy.connections.connection.Ref Temperature specification. Td_bp : float Temperature difference to boiling point at pressure corresponding pressure of this connection in K. v : float Volumetric flow specification. state : str State of the pure fluid on this connection: liquid ('l') or gaseous ('g'). design : list List containing design parameters (stated as string). offdesign : list List containing offdesign parameters (stated as string). design_path : str Path to individual design case for this connection. local_offdesign : boolean Treat this connection in offdesign mode in a design calculation. local_design : boolean Treat this connection in design mode in an offdesign calculation. printout : boolean Include this connection in the network's results printout. Note ---- - The fluid balance parameter applies a balancing of the fluid vector on the specified conntion to 100 %. For example, you have four fluid components (a, b, c and d) in your vector, you set two of them (a and b) and want the other two (components c and d) to be a result of your calculation. If you set this parameter to True, the equation (0 = 1 - a - b - c - d) will be applied. - The specification of values for design and/or offdesign is used for automatic switch from design to offdesign calculation: All parameters given in 'design', e.g. :code:`design=['T', 'p']`, are unset in any offdesign calculation, parameters given in 'offdesign' are set for offdesign calculation. - The property state is applied on pure fluids only. If you specify the desired state of the fluid at a connection the convergence check will adjust the enthalpy values of that connection for the first iterations in order to meet the state requirement. """ # set specified values for key in kwargs: if key == 'label': # bad datatype msg = 'Label can only be specified on instance creation.' logging.error(msg) raise TESPyConnectionError(msg) elif key in self.variables or key in self.variables0: # fluid specification try: float(kwargs[key]) is_numeric = True except (TypeError, ValueError): is_numeric = False if 'fluid' in key and key != 'fluid_balance': if isinstance(kwargs[key], dict): # starting values if key in self.variables0: self.fluid.set_attr(val0=kwargs[key].copy()) # specified parameters else: self.fluid.set_attr(val=kwargs[key].copy()) self.fluid.set_attr( val_set={f: True for f in kwargs[key].keys()}) else: # bad datatype msg = ( 'Datatype for fluid vector specification must be ' 'dict.') logging.error(msg) raise TypeError(msg) elif key == 'state': if kwargs[key] in ['l', 'g']: self.state.set_attr(val=kwargs[key], is_set=True) elif kwargs[key] is None: self.state.set_attr(is_set=False) elif is_numeric: if np.isnan(kwargs[key]): self.get_attr(key).set_attr(is_set=False) else: msg = ( 'To unset the state specification either use ' 'np.nan or None.') logging.error(msg) raise ValueError(msg) else: msg = ( 'Keyword argument "state" must either be ' '"l" or "g" or be None or np.nan.') logging.error(msg) raise TypeError(msg) elif kwargs[key] is None: self.get_attr(key).set_attr(val_set=False) self.get_attr(key).set_attr(ref_set=False) elif is_numeric: if np.isnan(kwargs[key]): self.get_attr(key).set_attr(val_set=False) self.get_attr(key).set_attr(ref_set=False) else: # value specification if key in self.variables: self.get_attr(key).set_attr( val_set=True, val=kwargs[key]) # starting value specification else: self.get_attr(key.replace('0', '')).set_attr( val0=kwargs[key]) # reference object elif isinstance(kwargs[key], Ref): if key in ['x', 'Td_bp']: msg = ( 'References for vapor mass fraction and ' 'subcooling/superheating are not implemented.' ) logging.error(msg) raise NotImplementedError(msg) else: self.get_attr(key).set_attr(ref=kwargs[key]) self.get_attr(key).set_attr(ref_set=True) # invalid datatype for keyword else: msg = 'Bad datatype for keyword argument ' + key + '.' logging.error(msg) raise TypeError(msg) # fluid balance elif key == 'fluid_balance': if isinstance(kwargs[key], bool): self.get_attr('fluid').set_attr(balance=kwargs[key]) else: msg = ( 'Datatype for keyword argument fluid_balance must be ' 'boolean.') logging.error(msg) raise TypeError(msg) # design/offdesign parameter list elif key == 'design' or key == 'offdesign': if not isinstance(kwargs[key], list): msg = 'Please provide the ' + key + ' parameters as list!' logging.error(msg) raise TypeError(msg) elif set(kwargs[key]).issubset(self.variables.keys()): self.__dict__.update({key: kwargs[key]}) else: params = ', '.join(self.variables.keys()) msg = ( 'Available parameters for (off-)design specification ' 'are: ' + params + '.') logging.error(msg) raise ValueError(msg) # design path elif key == 'design_path': if isinstance(kwargs[key], str): self.__dict__.update({key: kwargs[key]}) elif np.isnan(kwargs[key]): self.design_path = None else: msg = ( 'Please provide the design_path parameter as string ' 'or as nan.') logging.error(msg) raise TypeError(msg) self.new_design = True # other boolean keywords elif key in ['printout', 'local_design', 'local_offdesign']: if not isinstance(kwargs[key], bool): msg = ('Please provide the ' + key + ' as boolean.') logging.error(msg) raise TypeError(msg) else: self.__dict__.update({key: kwargs[key]}) # invalid keyword else: msg = 'Connection has no attribute ' + key + '.' logging.error(msg) raise KeyError(msg)
[docs] def get_attr(self, key): r""" Get the value of a connection's attribute. Parameters ---------- key : str The attribute you want to retrieve. Returns ------- out : Specified attribute. """ if key in self.__dict__: return self.__dict__[key] else: msg = 'Connection has no attribute \"' + key + '\".' logging.error(msg) raise KeyError(msg)
[docs] @staticmethod def attr(): r""" Return available attributes of a connection. Returns ------- out : list List of available attributes of a connection. """ return {'m': dc_prop(), 'p': dc_prop(), 'h': dc_prop(), 'T': dc_prop(), 'x': dc_prop(), 'v': dc_prop(), 'vol': dc_prop(), 's': dc_prop(), 'fluid': dc_flu(), 'Td_bp': dc_prop(), 'state': dc_simple()}
[docs] def get_flow(self): r""" Return the SI-values for the network variables. Returns ------- out : list List of mass flow and fluid property information. """ return [self.m.val_SI, self.p.val_SI, self.h.val_SI, self.fluid.val]
[docs] def get_physical_exergy(self, p0, T0): r""" Get the value of a connection's specific physical exergy. Calcute physical exergy of connection Parameters ---------- p0 : float Ambient pressure p0 / Pa. T0 : float Ambient temperature T0 / K. Note ---- .. math:: e^\mathrm{PH} = e^\mathrm{T} + e^\mathrm{M}\\ E^\mathrm{T} = \dot{m} \cdot e^\mathrm{T}\\ E^\mathrm{M} = \dot{m} \cdot e^\mathrm{M}\\ E^\mathrm{PH} = \dot{m} \cdot e^\mathrm{PH} """ self.ex_therm, self.ex_mech = fp.calc_physical_exergy(self, p0, T0) self.Ex_therm = self.ex_therm * self.m.val_SI self.Ex_mech = self.ex_mech * self.m.val_SI self.ex_physical = self.ex_therm + self.ex_mech self.Ex_physical = self.m.val_SI * self.ex_physical
[docs]class Ref: r""" A bus is used to connect different energy flows. For example, reference the mass flow of one connection :math:`\dot{m}` to another mass flow :math:`\dot{m}_{ref}`: .. math:: \dot{m} = \dot{m}_\mathrm{ref} \cdot \mathrm{factor} + \mathrm{delta} Parameters ---------- obj : tespy.connections.connection.Connection Connection to be referenced. factor : float Factor to multiply specified property with. delta : float Delta to add after multiplication. """ def __init__(self, ref_obj, factor, delta): if not isinstance(ref_obj, Connection): msg = 'First parameter must be object of type connection.' logging.error(msg) raise TypeError(msg) if not (isinstance(factor, int) or isinstance(factor, float)): msg = 'Second parameter must be of type int or float.' logging.error(msg) raise TypeError(msg) if not (isinstance(delta, int) or isinstance(delta, float)): msg = 'Thrid parameter must be of type int or float.' logging.error(msg) raise TypeError(msg) self.obj = ref_obj self.factor = factor self.delta = delta self.delta_SI = None msg = ('Created reference object with factor ' + str(self.factor) + ' and delta ' + str(self.delta) + ' referring to connection ' + ref_obj.source.label + ' (' + ref_obj.source_id + ') -> ' + ref_obj.target.label + ' (' + ref_obj.target_id + ').') logging.debug(msg)
[docs] def get_attr(self, key): r""" Get the value of a reference attribute. Parameters ---------- key : str The attribute you want to retrieve. Returns ------- out : Specified attribute. """ if key in self.__dict__: return self.__dict__[key] else: msg = 'Reference has no attribute \"' + key + '\".' logging.error(msg) raise KeyError(msg)