Source code for trnsystor.typevariable

"""TypeVariable module."""
import collections
import copy
import re

from bs4 import Tag

from trnsystor.linkstyle import LinkStyle
from trnsystor.utils import _parse_value, parse_type, standardize_name


[docs]class TypeVariable(object): """TypeVariable class. :class:`TypeVariable` is the main object class that handles storage of TRNSYS component parameters, inputs, outputs and derivatives. Parameters, Inputs, Outputs and Derivatives are all subclasses of TypeVariable. * See :class:`Parameter` for more details. * See :class:`Input` for more details. * See :class:`Output` for more details. * See :class:`Derivative` for more details. """ def __init__( self, val, order=None, name=None, role=None, dimension=None, unit=None, type=None, min=None, max=None, boundaries=None, default=None, symbol=None, definition=None, model=None, ): """Initialize a TypeVariable with the following attributes. Args: val (int, float, _Quantity): The actual value hold by this object. order (str): The order of the variable. name (str): This name will be seen by the user in the connections window and all other variable information windows. role (str): The role of the variables such as input, output, etc. Changing the role of a standard component requires reprogramming and recompiling the component. dimension (str): The dimension of the variable (power, temperature, etc.): This dimension must be already defined in the unit dictionary (refer to section 2.9) to be used. The pre-defined dimension ‘any’ allows to make a variable compatible with any other variable: no checks are performed on such variables if the user attempts to connect them to other variables. unit (str): The unit of the variable that the TRNSYS program requires for the specified dimension (C, F, K etc.) type (type or str): The type of the variable: Real, integer, Boolean, or string. min (int, float or pint._Quantity): The minimum value. The minimum and maximum can be "-INF" or "+INF" to indicate no limit (infinity). +/-INF is the default value. max (int, float or pint._Quantity): The maximum value. The minimum and maximum can be "-INF" or "+INF" to indicate no limit (infinity). +/-INF is the default value. boundaries (str): This setting determines if the minimum and maximum are included or not in the range. choices are "[;]", "[;[", "];]" ,"];[" default (int, float or pint._Quantity): the default value of the variable. The default value is replaced by the initial value for the inputs and derivatives and suppressed for the outputs. symbol (str): The symbol of the unit (not used). definition (str): A short description of the variable. model (TrnsysModel): the TrnsysModel this TypeVariable belongs to. """ super().__init__() self._is_question = False self._iscycle = False self._iscyclebase = False self.order = order self.name = name self.role = role self.dimension = dimension self.unit = ( unit if unit is None else re.sub(r"([\s\S\.]*)\/([\s\S\.]*)", r"(\1)/(\2)", unit) ) self.type = type self.min = min self.max = max self.boundaries = boundaries self.default = default self.symbol = symbol self.definition = ( definition if definition is None else " ".join(definition.split()) ) self.value = _parse_value( val, self.type, self.unit, (self.min, self.max), self.name ) self.model = model # the TrnsysModel this TypeVariable belongs to.
[docs] @classmethod def from_tag(cls, tag, model=None): """Class method to create a TypeVariable from an XML tag. Args: tag (Tag): The XML tag with its attributes and contents. model (TrnsysModel): The model. """ role = tag.find("role").text val = tag.find("default").text try: val = float(val) except ValueError: # could not convert string to float. if val == "STEP": val = 1 # Todo: figure out better logic when default value is 'STEP' elif val == "START": val = 1 elif val == "STOP": val = 8760 _type = parse_type(tag.find("type").text) attr = {attr.name: attr.text for attr in tag if isinstance(attr, Tag)} attr.update({"model": model}) if role == "parameter": return Parameter(_type(val), **attr) elif role == "input": return Input(_type(val), **attr) elif role == "output": return Output(_type(val), **attr) elif role == "derivative": return Derivative(_type(val), **attr) else: raise NotImplementedError( 'The role "{}" is not yet ' "supported.".format(role) )
def __float__(self): """Return magnitude of self.""" return self.value.m def __int__(self): """Return int(self).""" return int(self.value.m) def __mul__(self, other): """Return self * other.""" return float(self) * other def __add__(self, other): """Return self + other.""" return float(self) + other def __sub__(self, other): """Return self - other.""" return float(self) - other def _parse_types(self): for attr, value in self.__dict__.items(): if attr in ["default", "max", "min"]: parsed_value = _parse_value( value, self.type, self.unit, (self.min, self.max) ) self.__setattr__(attr, parsed_value) if attr in ["order"]: self.__setattr__(attr, int(value))
[docs] def copy(self): """TypeVariable: Make a copy of :attr:`self`.""" new_self = copy.copy(self) return new_self
@property def is_connected(self): """Whether or not this TypeVariable is connected to another TypeVariable. Checks if self is in any keys """ return self.predecessor is not None @property def predecessor(self): """Other TypeVariable from which this Input TypeVariable is connected. Predecessors """ if len(self.model.UNIT_GRAPH) == 0: return None predecessors = [] for pre in self.model.UNIT_GRAPH.predecessors(self.model): for key in self.model.UNIT_GRAPH[pre][self.model]: if self in key: u, v = key predecessors.append(u) if len(predecessors) > 1: raise Exception(f"An Input cannot have {predecessors} predecessors") elif predecessors: return next(iter(predecessors)) else: return None @property def idx(self): """Get the 0-based variable index of self.""" ordered_dict = collections.OrderedDict( ( standardize_name(self.model._meta.variables[attr].name), [self.model._meta.variables[attr], 0], ) for attr in sorted( filter( lambda kv: isinstance( self.model._meta.variables[kv], self.__class__ ) and self.model._meta.variables[kv]._iscyclebase is False, self.model._meta.variables, ), key=lambda key: self.model._meta.variables[key].order, ) if not self.model._meta.variables[attr]._iscyclebase ) i = 0 for key, value in ordered_dict.items(): value[1] = i i += 1 return ordered_dict[standardize_name(self.name)][1] @property def one_based_idx(self): """Get the 1-based variable index of self such as it appears in Trnsys.""" return self.idx + 1
[docs] def connect_to(self, other, link_style_kwargs=None): """Connect a single TypeVariable to TypeVariable `other`. Important: Keep in mind that since python traditionally uses 0-based indexing, the same logic is used in this package even though TRNSYS uses traditionally 1-based indexing. The package will internally handle the 1-based index in the output *.dck* file. Examples: Connect two :class:`TypeVariable` objects together >>> pipe_1.outputs['Outlet_Air_Temperature'].connect_to( >>> other=pipe2.intputs['Inlet_Air_Temperature'] >>> ) Args: other (TypeVariable): The other object. Raises: TypeError: When trying to connect to anything other than a :class:`TrnsysModel`. """ if link_style_kwargs is None: link_style_kwargs = {} if not isinstance(other, TypeVariable): raise TypeError("Only `TypeVariable` objects can be connected together") u = self v = other loc = link_style_kwargs.pop("loc", "best") self.model.UNIT_GRAPH.add_edge( u_for_edge=self.model, v_for_edge=other.model, key=(u, v), LinkStyle=LinkStyle(self.model, other.model, loc=loc, **link_style_kwargs), )
def __repr__(self): """Return repr(self).""" try: return ( f"{self.name}; units={self.unit}; " f"value={self.value:~P}\n{self.definition}" ) except Exception: return ( f"{self.name}; units={self.unit}; value={self.value}\n" f"{self.definition}" )
[docs]class Parameter(TypeVariable): """A subclass of :class:`TypeVariable` specific to parameters.""" def __init__(self, val, **kwargs): """A subclass of :class:`TypeVariable` specific to parameters.""" super().__init__(val, **kwargs) self._parse_types()
[docs]class Input(TypeVariable): """A subclass of :class:`TypeVariable` specific to inputs.""" def __init__(self, val, **kwargs): """A subclass of :class:`TypeVariable` specific to inputs.""" super().__init__(val, **kwargs) self._parse_types()
class InitialInputValue(TypeVariable): """A subclass of :class:`TypeVariable` specific to Initial Input Values.""" def __init__(self, val, **kwargs): """A subclass of :class:`TypeVariable` specific to inputs.""" super().__init__(val, **kwargs) self._parse_types()
[docs]class Output(TypeVariable): """A subclass of :class:`TypeVariable` specific to outputs.""" def __init__(self, val, **kwargs): """A subclass of :class:`TypeVariable` specific to outputs.""" super().__init__(val, **kwargs) self._parse_types() @property def is_connected(self) -> bool: """Return True of self has any successor.""" return len(self.successors) > 0 @property def successors(self): """Other TypeVariables to which this TypeVariable is connected. Successors.""" successors = [] for suc in self.model.UNIT_GRAPH.successors(self.model): for key in self.model.UNIT_GRAPH[self.model][suc]: if self in key: u, v = key successors.append(v) return successors
[docs]class Derivative(TypeVariable): """Derivatives class. the DERIVATIVES for a given :class:`TrnsysModel` specify initial values, such as the initial temperatures of various nodes in a thermal storage tank or the initial zone temperatures in a multi zone building. """ def __init__(self, val, **kwargs): """A subclass of :class:`TypeVariable` specific to derivatives.""" super().__init__(val, **kwargs) self._parse_types()