Source code for trnsystor.statement.equation

"""Equation module."""
import itertools

from sympy import Expr, Symbol

from trnsystor.statement.constant import Constant
from trnsystor.statement.statement import Statement
from trnsystor.typevariable import TypeVariable
from trnsystor.utils import TypeVariableSymbol, print_my_latex


[docs]class Equation(Statement, TypeVariable): """EQUATION Statement. The EQUATIONS statement allows variables to be defined as algebraic functions of constants, previously defined variables, and outputs from TRNSYS components. These variables can then be used in place of numbers in the TRNSYS input file to represent inputs to components; numerical values of parameters; and initial values of inputs and time-dependent variables. The capabilities of the EQUATIONS statement overlap but greatly exceed those of the CONSTANTS statement described in the previous section. Hint: In trnsystor, the Equation class works hand in hand with the :class:`EquationCollection` class. This class behaves a little bit like the equation component in the TRNSYS Studio, meaning that you can list equation in a block, give it a name, etc. See the :class:`EquationCollection` class for more details. """ _new_id = itertools.count(start=1) def __init__(self, name=None, equals_to=None, doc=None, model=None): """Initialize object. Args: name (str): The left hand side of the equation. equals_to (str, TypeVariable): The right hand side of the equation. doc (str, optional): A small description optionally printed in the deck file. model (Component): The TrnsysModel this Equation belongs to. """ super().__init__() self._n = next(self._new_id) self.name = name self.equals_to = equals_to self.doc = doc self.model = model # the TrnsysModel this Equation belongs to. def __repr__(self): """Return repr(self).""" return " = ".join([self.name, self._to_deck()]) def __str__(self): """Return repr(self).""" return repr(self)
[docs] @classmethod def from_expression(cls, expression, doc=None): """Create an equation from a string expression. Anything before the equal sign ("=") will become a Constant and anything after will become the equality statement. Example: Create a simple expression like so: >>> equa1 = Equation.from_expression("TdbAmb = [011,001]") Args: expression (str): A user-defined expression to parse. doc (str, optional): A small description optionally printed in the deck file. """ if "=" not in expression: raise ValueError( "The from_expression constructor must contain an expression " "with the equal sign" ) a, b = expression.split("=") return cls(a.strip(), b.strip(), doc=doc)
[docs] @classmethod def from_symbolic_expression(cls, name, exp, *args, doc=None): """Create an equation from symbolic expression. Crate an equation with a combination of a generic expression (with placeholder variables) and a list of arguments. The underlying engine will use Sympy and symbolic variables. You can use a mixture of :class:`TypeVariable` and :class:`Equation`, :class:`Constant` as well as the python default :class:`str`. .. Important:: If a `str` is passed in place of an expression argument ( :attr:`args`), make sure to declare that string as an Equation or a Constant later in the routine. Examples: In this example, we define a variable (var_a) and we want it to be equal to the 'Outlet Air Humidity Ratio' divided by 12 + log( Temperature to heat source). In a TRNSYS deck file one would have to manually determine the unit numbers and output numbers and write something like : '[1, 2]/12 + log([1, 1])'. With the :func:`~from_symbolic_expression`, we can do this very simply: 1. first, define the name of the variable: >>> name = "var_a" 2. then, define the expression as a string. Here, the variables `a` and `b` are symbols that represent the two type outputs. Note that their name has bee chosen arbitrarily. >>> exp = "log(a) + b / 12" >>> # would be also equivalent to >>> exp = "log(x) + y / 12" 3. here, we define the actual variables (the type outputs) after loading our model from its proforma: >>> from trnsystor import TrnsysModel >>> fan = TrnsysModel.from_xml("fan_type.xml") >>> vars = (fan.outputs[0], fan.outputs[1]) .. Important:: The order of the symbolic variable encountered in the string expression (step 2), from left to right, must be the same for the tuple of variables. For instance, `a` is followed by `b`, therefore `fan.outputs[0]` is followed by `fan.outputs[1]`. 4. finally, we create the Equation. Note that vars is passed with the '*' declaration to unpack the tuple. >>> from trnsystor.statement import Equation >>> eq = Equation.from_symbolic_expression(name, exp, *vars) >>> print(eq) [1, 1]/12 + log([1, 2]) Args: name (str): The name of the variable (left-hand side), of the equation. exp (str): The expression to evaluate. Use any variable name and mathematical expression. *args (tuple): A tuple of :class:`TypeVariable` that will replace the any variable name specified in the above expression. doc (str, optional): A small description optionally printed in the deck file. Returns: Equation: The Equation Statement object. """ from sympy.parsing.sympy_parser import parse_expr exp = parse_expr(exp) if len(exp.free_symbols) != len(args): raise AttributeError( "The expression does not have the same number of " "variables as arguments passed to the symbolic expression " "parser." ) for i, arg in enumerate(sorted(exp.free_symbols, key=lambda sym: sym.name)): new_symbol = args[i] if isinstance(new_symbol, TypeVariable): exp = exp.subs(arg, TypeVariableSymbol(new_symbol)) elif isinstance(new_symbol, (Equation, Constant)): exp = exp.subs(arg, Symbol(new_symbol.name)) else: exp = exp.subs(arg, Symbol(new_symbol)) return cls(name, exp, doc=doc)
@property def eq_number(self): """Return the equation number (unique).""" return self._n @property def idx(self): """Return the 0-based index of the Equation.""" ns = {e: i for i, e in enumerate(self.model)} return ns[self.name] @property def unit_number(self): """Return the unit number of the EquationCollection self belongs to.""" return self.model.unit_number def _to_deck(self): """Return deck representation of self.""" if isinstance(self.equals_to, TypeVariable): return "[{unit_number}, {output_id}]".format( unit_number=self.equals_to.model.unit_number, output_id=self.equals_to.one_based_idx, ) elif isinstance(self.equals_to, Expr): return print_my_latex(self.equals_to) else: return self.equals_to