Source code for wntr.sim.aml.expr

import abc
import itertools
import operator
import math
from wntr.utils.ordered_set import OrderedSet
import enum
from six import with_metaclass

if not hasattr(math, 'inf'):
    math.inf = float('inf')

native_numeric_types = {float, int}
native_integer_types = {int, bool}
native_boolean_types = {int, bool, str}


[docs] class OperationEnum(enum.IntEnum): add = -1 sub = -2 mul = -3 div = -4 pow = -5 abs = -6 sign = -7 if_else = -8 inequality = -9 exp = -10 log = -11 negation = -12 sin = -13 cos = -14 tan = -15 asin = -16 acos = -17 atan = -18
[docs] class Node(with_metaclass(abc.ABCMeta, object)): __slots__ = ()
[docs] @abc.abstractmethod def is_leaf(self): pass
[docs] class ExpressionBase(with_metaclass(abc.ABCMeta, Node)): """ A base class for expressions (including variables and params). """ __slots__ = ()
[docs] def is_relational(self): return False
[docs] @abc.abstractmethod def operators(self): pass
[docs] @abc.abstractmethod def last_node(self): pass
[docs] @abc.abstractmethod def evaluate(self): """ Evaluate the expression numerically. Returns ------- val: float The floating point value of the expression. """ pass
@abc.abstractmethod def _binary_operation_helper(self, other, cls): pass @abc.abstractmethod def _unary_operation_helper(self, cls): pass def __add__(self, other): if other == 0: return self return self._binary_operation_helper(other, AddOperator) def __sub__(self, other): if other == 0: return self return self._binary_operation_helper(other, SubtractOperator) def __mul__(self, other): if other == 0: return 0 elif other == 1: return self return self._binary_operation_helper(other, MultiplyOperator) def __truediv__(self, other): if other == 0: raise ValueError('Divide by 0') elif other == 1: return self return self._binary_operation_helper(other, DivideOperator) def __div__(self, other): if other == 0: raise ValueError('Divide by 0') elif other == 1: return self return self._binary_operation_helper(other, DivideOperator) def __pow__(self, other): if other == 0: return 1 elif other == 1: return self return self._binary_operation_helper(other, PowerOperator) def __radd__(self, other): assert type(other) in native_numeric_types if other == 0: return self return Float(other) + self def __rsub__(self, other): assert type(other) in native_numeric_types if other == 0: return -self return Float(other) - self def __rmul__(self, other): assert type(other) in native_numeric_types if other == 0: return 0 elif other == 1: return self return Float(other) * self def __rtruediv__(self, other): assert type(other) in native_numeric_types if other == 0: return 0 return Float(other) / self def __rdiv__(self, other): assert type(other) in native_numeric_types if other == 0: return 0 return Float(other) / self def __rpow__(self, other): assert type(other) in native_numeric_types if other == 0: return 0 elif other == 1: return 1 return Float(other) ** self def __neg__(self): return self._unary_operation_helper(NegationOperator)
[docs] @abc.abstractmethod def get_vars(self): pass
[docs] @abc.abstractmethod def get_params(self): pass
[docs] @abc.abstractmethod def get_floats(self): pass
[docs] @abc.abstractmethod def get_leaves(self): pass
[docs] @abc.abstractmethod def is_parameter_type(self): pass
[docs] @abc.abstractmethod def is_variable_type(self): pass
[docs] @abc.abstractmethod def is_float_type(self): pass
[docs] @abc.abstractmethod def is_expression_type(self): pass
[docs] @abc.abstractmethod def reverse_ad(self): pass
[docs] @abc.abstractmethod def reverse_sd(self): pass
[docs] @abc.abstractmethod def get_rpn(self, leaf_ndx_map): pass
def __repr__(self): return str(self)
[docs] class Leaf(with_metaclass(abc.ABCMeta, ExpressionBase)): __slots__ = ('_value', '_c_obj')
[docs] def is_leaf(self): return True
def _binary_operation_helper(self, other, cls): if type(other) in native_numeric_types: other = Float(other) new_operator = cls(self, other.last_node()) if other.is_leaf(): expr = expression() else: expr = expression(other) expr.append_operator(new_operator) return expr def _unary_operation_helper(self, cls): new_operator = cls(self) expr = expression() expr.append_operator(new_operator) return expr
[docs] def last_node(self): return self
[docs] def operators(self): return list()
@property def value(self): if self._c_obj is not None: return self._c_obj.value return self._value @value.setter def value(self, val): self._value = val if self._c_obj is not None: self._c_obj.value = val
[docs] def evaluate(self): return self._value
@abc.abstractmethod def _str(self): pass def __str__(self): return self._str()
[docs] def reverse_ad(self): return {self: 1}
[docs] def reverse_sd(self): return {self: 1}
[docs] def get_rpn(self, leaf_ndx_map): return [leaf_ndx_map[self]]
[docs] class Float(Leaf): __slots__ = ()
[docs] def __init__(self, val): self._value = val self._c_obj = None
[docs] def is_parameter_type(self): return False
[docs] def is_variable_type(self): return False
[docs] def is_float_type(self): return True
[docs] def is_expression_type(self): return False
def _str(self): return str(self.value)
[docs] def get_vars(self): return OrderedSet()
[docs] def get_params(self): return OrderedSet()
[docs] def get_floats(self): return OrderedSet([self])
[docs] def get_leaves(self): return OrderedSet([self])
def _binary_operation_helper(self, other, cls): if type(other) in native_numeric_types: return cls.operation(self.value, other) elif other.is_float_type(): return cls.operation(self.value, other.value) new_operator = cls(self, other.last_node()) if other.is_leaf(): expr = expression() else: expr = expression(other) expr.append_operator(new_operator) return expr def _unary_operation_helper(self, cls): return cls.operation(self.value)
[docs] class Var(Leaf): """ Variables Parameters ---------- val: float value of the variable """ __slots__ = ('_name',)
[docs] def __init__(self, val=0): self._value = val self._name = None self._c_obj = None
[docs] def is_parameter_type(self): return False
[docs] def is_variable_type(self): return True
[docs] def is_float_type(self): return False
[docs] def is_expression_type(self): return False
@property def name(self): return self._name @name.setter def name(self, val): self._name = val def _str(self): return str(self.name)
[docs] def get_vars(self): return OrderedSet([self])
[docs] def get_params(self): return OrderedSet()
[docs] def get_floats(self): return OrderedSet()
[docs] def get_leaves(self): return OrderedSet([self])
@property def index(self): if self._c_obj is None: return None else: return self._c_obj.index
[docs] class Param(Leaf): __slots__ = ('_name',)
[docs] def __init__(self, val=0): self._value = val self._name = None self._c_obj = None
[docs] def is_parameter_type(self): return True
[docs] def is_variable_type(self): return False
[docs] def is_float_type(self): return False
[docs] def is_expression_type(self): return False
@property def name(self): return self._name @name.setter def name(self, val): self._name = val def _str(self): return str(self.name)
[docs] def get_vars(self): return OrderedSet()
[docs] def get_params(self): return OrderedSet([self])
[docs] def get_floats(self): return OrderedSet()
[docs] def get_leaves(self): return OrderedSet([self])
[docs] class expression(ExpressionBase): __slots__ = ('_operators', '_n_opers', '_vars', '_params', '_floats')
[docs] def __init__(self, expr=None): """ Parameters ---------- expr: expression """ if expr is not None: if expr._operators[-1] is not expr.last_node(): self._operators = expr.list_of_operators() else: self._operators = expr._operators else: self._operators = [] self._n_opers = len(self._operators) self._vars = None self._params = None self._floats = None
[docs] def append_operator(self, oper): self._operators.append(oper) self._n_opers += 1
[docs] def last_node(self): """ Returns ------- last_node: Operator """ return self._operators[self._n_opers - 1]
[docs] def list_of_operators(self): return self._operators[:self._n_opers]
[docs] def is_leaf(self): return False
[docs] def operators(self): return itertools.islice(self._operators, 0, self._n_opers)
def _binary_operation_helper(self, other, cls): if type(other) in native_numeric_types: other = Float(other) new_operator = cls(self.last_node(), other.last_node()) expr = expression(self) for oper in other.operators(): expr.append_operator(oper) expr.append_operator(new_operator) return expr def _unary_operation_helper(self, cls): new_operator = cls(self.last_node()) expr = expression(self) expr.append_operator(new_operator) return expr
[docs] def evaluate(self): val_dict = dict() for oper in self.operators(): oper.evaluate(val_dict) return val_dict[self.last_node()]
[docs] def get_vars(self): if self._vars is None: self._collect_leaves() for i in self._vars: yield i
[docs] def get_params(self): if self._params is None: self._collect_leaves() for i in self._params: yield i
[docs] def get_floats(self): if self._floats is None: self._collect_leaves() for i in self._floats: yield i
def _collect_leaves(self): self._vars = OrderedSet() self._params = OrderedSet() self._floats = OrderedSet() for oper in self.operators(): for operand in oper.operands(): if operand.is_leaf(): if operand.is_variable_type(): self._vars.add(operand) elif operand.is_parameter_type(): self._params.add(operand) elif operand.is_float_type(): self._floats.add(operand) elif operand.is_expression_type(): self._vars.update(operand.get_vars()) self._params.update(operand.get_params()) self._floats.update(operand.get_floats()) else: raise ValueError('operand type not recognized: ' + str(operand))
[docs] def get_leaves(self): if self._vars is None: self._collect_leaves() for i in self._vars: yield i for i in self._params: yield i for i in self._floats: yield i
def _str(self): return str(self) def __str__(self): val_dict = dict() for oper in self.operators(): oper._str(val_dict) return val_dict[self.last_node()]
[docs] def is_variable_type(self): return False
[docs] def is_parameter_type(self): return False
[docs] def is_float_type(self): return False
[docs] def is_expression_type(self): return True
[docs] def reverse_ad(self): val_dict = dict() der_dict = dict() for oper in self.operators(): oper.diff_up(val_dict, der_dict) der_dict[self.last_node()] = 1 for oper in reversed(self.list_of_operators()): oper.diff_down(val_dict, der_dict) return der_dict
[docs] def reverse_sd(self): val_dict = dict() der_dict = dict() for oper in self.operators(): oper.diff_up_symbolic(val_dict, der_dict) der_dict[self.last_node()] = 1 for oper in reversed(self.list_of_operators()): oper.diff_down(val_dict, der_dict) return der_dict
[docs] def is_relational(self): if type(self.last_node()) in {InequalityOperator}: return True return False
[docs] def get_rpn(self, leaf_ndx_map): rpn_map = dict() for oper in self.operators(): oper.get_rpn(rpn_map, leaf_ndx_map) return rpn_map[self.last_node()]
[docs] class Operator(with_metaclass(abc.ABCMeta, Node)): __slots__ = ()
[docs] def is_leaf(self): return False
[docs] @abc.abstractmethod def evaluate(self, val_dict): pass
[docs] @abc.abstractmethod def operands(self): pass
[docs] @abc.abstractmethod def diff_up(self, val_dict, der_dict): pass
[docs] @abc.abstractmethod def diff_down(self, val_dict, der_dict): pass
[docs] @abc.abstractmethod def diff_up_symbolic(self, val_dict, der_dict): pass
[docs] @abc.abstractmethod def get_rpn(self, rpn_map, leaf_ndx_map): pass
[docs] class BinaryOperator(Operator): __slots__ = ('_operand1', '_operand2') operation = None str_repn = None operation_enum = None
[docs] def __init__(self, operand1, operand2): self._operand1 = operand1 self._operand2 = operand2
[docs] def evaluate(self, val_dict): if self._operand1.is_leaf(): val1 = self._operand1.value else: val1 = val_dict[self._operand1] if self._operand2.is_leaf(): val2 = self._operand2.value else: val2 = val_dict[self._operand2] val_dict[self] = self.operation(val1, val2)
def _str(self, val_dict): if self._operand1.is_leaf(): val1 = self._operand1._str() else: val1 = val_dict[self._operand1] if self._operand2.is_leaf(): val2 = self._operand2._str() else: val2 = val_dict[self._operand2] val_dict[self] = '(' + val1 + self.str_repn + val2 + ')'
[docs] def operands(self): yield self._operand1 yield self._operand2
[docs] def diff_up(self, val_dict, der_dict): if self._operand1.is_leaf(): val1 = self._operand1.value val_dict[self._operand1] = val1 if self._operand1 not in der_dict: der_dict[self._operand1] = 0 else: val1 = val_dict[self._operand1] der_dict[self._operand1] = 0 if self._operand2.is_leaf(): val2 = self._operand2.value val_dict[self._operand2] = val2 if self._operand2 not in der_dict: der_dict[self._operand2] = 0 else: val2 = val_dict[self._operand2] der_dict[self._operand2] = 0 val_dict[self] = self.operation(val1, val2)
[docs] def diff_up_symbolic(self, val_dict, der_dict): if self._operand1.is_leaf(): val1 = self._operand1 val_dict[self._operand1] = val1 if self._operand1 not in der_dict: der_dict[self._operand1] = 0 else: val1 = val_dict[self._operand1] der_dict[self._operand1] = 0 if self._operand2.is_leaf(): val2 = self._operand2 val_dict[self._operand2] = val2 if self._operand2 not in der_dict: der_dict[self._operand2] = 0 else: val2 = val_dict[self._operand2] der_dict[self._operand2] = 0 val_dict[self] = self.operation(val1, val2)
[docs] def get_rpn(self, rpn_map, leaf_ndx_map): if self._operand2.is_leaf(): if self._operand1.is_leaf(): rpn_map[self] = [leaf_ndx_map[self._operand1], leaf_ndx_map[self._operand2], self.operation_enum] else: rpn_map[self] = _rpn = rpn_map[self._operand1] _rpn.append(leaf_ndx_map[self._operand2]) _rpn.append(self.operation_enum) elif self._operand1.is_leaf(): rpn_map[self] = _rpn = rpn_map[self._operand2] _rpn.insert(0, leaf_ndx_map[self._operand1]) _rpn.append(self.operation_enum) else: rpn_map[self] = _rpn = rpn_map[self._operand1] _rpn.extend(rpn_map[self._operand2]) _rpn.append(self.operation_enum)
[docs] class AddOperator(BinaryOperator): __slots__ = () operation = operator.add str_repn = '+' operation_enum = OperationEnum.add.value
[docs] def diff_down(self, val_dict, der_dict): der = der_dict[self] der_dict[self._operand1] += der der_dict[self._operand2] += der
[docs] class SubtractOperator(BinaryOperator): __slots__ = () operation = operator.sub str_repn = '-' operation_enum = OperationEnum.sub.value
[docs] def diff_down(self, val_dict, der_dict): der = der_dict[self] der_dict[self._operand1] += der der_dict[self._operand2] -= der
[docs] class MultiplyOperator(BinaryOperator): __slots__ = () operation = operator.mul str_repn = '*' operation_enum = OperationEnum.mul.value
[docs] def diff_down(self, val_dict, der_dict): der = der_dict[self] der_dict[self._operand1] += der * val_dict[self._operand2] der_dict[self._operand2] += der * val_dict[self._operand1]
[docs] class DivideOperator(BinaryOperator): __slots__ = () operation = operator.truediv str_repn = '/' operation_enum = OperationEnum.div.value
[docs] def diff_down(self, val_dict, der_dict): der = der_dict[self] der_dict[self._operand1] += der / val_dict[self._operand2] der_dict[self._operand2] -= der * val_dict[self._operand1] / val_dict[self._operand2]**2
[docs] class PowerOperator(BinaryOperator): __slots__ = () operation = operator.pow str_repn = '**' operation_enum = OperationEnum.pow.value
[docs] def diff_down(self, val_dict, der_dict): der = der_dict[self] val1 = val_dict[self._operand1] val2 = val_dict[self._operand2] der_dict[self._operand1] += der * val2 * val1**(val2 - 1) if not self._operand2.is_leaf() or self._operand2.is_variable_type(): der_dict[self._operand2] += der * val1**val2 * log(val1)
[docs] class UnaryOperator(Operator): __slots__ = ('_operand',) str_repn = None operation_enum = None
[docs] def __init__(self, operand): """ Parameters ---------- operand: Node """ self._operand = operand
[docs] def evaluate(self, val_dict): if self._operand.is_leaf(): val = self._operand.value else: val = val_dict[self._operand] val_dict[self] = self.operation(val)
def _str(self, val_dict): if self._operand.is_leaf(): val = self._operand._str() else: val = val_dict[self._operand] val_dict[self] = '(' + self.str_repn + '(' + val + ')' + ')'
[docs] def operands(self): yield self._operand
[docs] def diff_up(self, val_dict, der_dict): if self._operand.is_leaf(): val = self._operand.value val_dict[self._operand] = val if self._operand not in der_dict: der_dict[self._operand] = 0 else: val = val_dict[self._operand] der_dict[self._operand] = 0 val_dict[self] = self.operation(val)
[docs] def diff_up_symbolic(self, val_dict, der_dict): if self._operand.is_leaf(): val = self._operand val_dict[self._operand] = val if self._operand not in der_dict: der_dict[self._operand] = 0 else: val = val_dict[self._operand] der_dict[self._operand] = 0 val_dict[self] = self.operation(val)
[docs] def get_rpn(self, rpn_map, leaf_ndx_map): if self._operand.is_leaf(): rpn_map[self] = [leaf_ndx_map[self._operand], self.operation_enum] else: rpn_map[self] = _rpn = rpn_map[self._operand] _rpn.append(self.operation_enum)
[docs] @staticmethod def operation(val): raise NotImplementedError('Subclasses should implement this.')
[docs] class NegationOperator(UnaryOperator): __slots__ = () str_repn = '-' operation_enum = OperationEnum.negation.value
[docs] def diff_down(self, val_dict, der_dict): der = der_dict[self] der_dict[self._operand] -= der
[docs] @staticmethod def operation(val): return -val
[docs] def exp(val): """ Parameters ---------- val: ExpressionBase Returns ------- expr: expression """ if type(val) in native_numeric_types: return math.exp(val) return val._unary_operation_helper(ExpOperator)
[docs] def log(val): """ Parameters ---------- val: ExpressionBase Returns ------- expr: expression """ if type(val) in native_numeric_types: return math.log(val) return val._unary_operation_helper(LogOperator)
[docs] def sin(val): """ Parameters ---------- val: ExpressionBase Returns ------- expr: expression """ if type(val) in native_numeric_types: return math.sin(val) return val._unary_operation_helper(SinOperator)
[docs] def cos(val): """ Parameters ---------- val: ExpressionBase Returns ------- expr: expression """ if type(val) in native_numeric_types: return math.cos(val) return val._unary_operation_helper(CosOperator)
[docs] def tan(val): """ Parameters ---------- val: ExpressionBase Returns ------- expr: expression """ if type(val) in native_numeric_types: return math.tan(val) return val._unary_operation_helper(TanOperator)
[docs] def asin(val): """ Parameters ---------- val: ExpressionBase Returns ------- expr: expression """ if type(val) in native_numeric_types: return math.asin(val) return val._unary_operation_helper(AsinOperator)
[docs] def acos(val): """ Parameters ---------- val: ExpressionBase Returns ------- expr: expression """ if type(val) in native_numeric_types: return math.acos(val) return val._unary_operation_helper(AcosOperator)
[docs] def atan(val): """ Parameters ---------- val: ExpressionBase Returns ------- expr: expression """ if type(val) in native_numeric_types: return math.atan(val) return val._unary_operation_helper(AtanOperator)
[docs] def if_else(if_statement, then_statement, else_statement): """ Parameters ---------- if_statement: ExpressionBase then_statement: ExpressionBase else_statement: ExpressionBase Returns ------- expr: ExpressionBase """ if type(if_statement) in native_numeric_types or type(if_statement) in native_boolean_types: if if_statement: return then_statement else: return else_statement if type(then_statement) in native_numeric_types: then_statement = Float(then_statement) if type(else_statement) in native_numeric_types: else_statement = Float(else_statement) assert if_statement.is_relational() expr = expression(if_statement) new_operator = IfElseOperator(if_statement.last_node(), then_statement.last_node(), else_statement.last_node()) for oper in then_statement.operators(): expr.append_operator(oper) for oper in else_statement.operators(): expr.append_operator(oper) expr.append_operator(new_operator) return expr
[docs] def inequality(body, lb=None, ub=None): """ Parameters ---------- body: ExpressionBase or float lb: float ub: float Returns ------- expr: ExpressionBase """ if lb is not None and ub is not None: if type(lb) not in native_numeric_types or type(ub) not in native_numeric_types: raise ValueError('inequality can only accept both lb and ub arguments if both are floats or ints.') if lb is None: lb = -math.inf if ub is None: ub = math.inf if type(lb) in native_numeric_types: lb = Float(lb) else: body -= lb lb = Float(0) if type(ub) in native_numeric_types: ub = Float(ub) else: body -= ub ub = Float(0) if type(body) in native_numeric_types: return lb.value <= body <= ub.value if body.is_leaf(): expr = expression() else: expr = expression(body) new_operator = InequalityOperator(body.last_node(), lb=lb, ub=ub) expr.append_operator(new_operator) return expr
[docs] def sign(val): if type(val) in native_numeric_types: if val >= 0: return 1 else: return -1 return val._unary_operation_helper(SignOperator)
[docs] def abs(val): if type(val) in native_numeric_types: return math.fabs(val) return val._unary_operation_helper(AbsOperator)
[docs] class IfElseOperator(Operator): __slots__ = ('_if_arg', '_then_arg', '_else_arg')
[docs] def __init__(self, if_arg, then_arg, else_arg): self._if_arg = if_arg self._then_arg = then_arg self._else_arg = else_arg
[docs] def evaluate(self, val_dict): if_val = val_dict[self._if_arg] if if_val: if self._then_arg.is_leaf(): res = self._then_arg.value else: res = val_dict[self._then_arg] else: if self._else_arg.is_leaf(): res = self._else_arg.value else: res = val_dict[self._else_arg] val_dict[self] = res
[docs] def operands(self): yield self._if_arg yield self._then_arg yield self._else_arg
def _str(self, val_dict): if_val = val_dict[self._if_arg] if self._then_arg.is_leaf(): then_str = self._then_arg._str() else: then_str = val_dict[self._then_arg] if self._else_arg.is_leaf(): else_str = self._else_arg._str() else: else_str = val_dict[self._else_arg] s = '\n (\n' s += ' if ' + if_val + ':\n' s += ' ' + then_str.replace('\n', '\n ') + '\n' s += ' else:\n' s += ' ' + else_str.replace('\n', '\n ') + '\n' s += ' )\n' val_dict[self] = s
[docs] def diff_up(self, val_dict, der_dict): if_val = val_dict[self._if_arg] der_dict[self._if_arg] = 0 if if_val: if self._then_arg.is_leaf(): val = self._then_arg.value val_dict[self._then_arg] = val if self._then_arg not in der_dict: der_dict[self._then_arg] = 0 else: val = val_dict[self._then_arg] der_dict[self._then_arg] = 0 if self._else_arg.is_leaf(): _val = self._else_arg.value val_dict[self._else_arg] = _val if self._else_arg not in der_dict: der_dict[self._else_arg] = 0 else: der_dict[self._else_arg] = 0 else: if self._else_arg.is_leaf(): val = self._else_arg.value val_dict[self._else_arg] = val if self._else_arg not in der_dict: der_dict[self._else_arg] = 0 else: val = val_dict[self._else_arg] der_dict[self._else_arg] = 0 if self._then_arg.is_leaf(): _val = self._then_arg.value val_dict[self._then_arg] = _val if self._then_arg not in der_dict: der_dict[self._then_arg] = 0 else: der_dict[self._then_arg] = 0 val_dict[self] = val
[docs] def diff_up_symbolic(self, val_dict, der_dict): der_dict[self._if_arg] = 0 if_val = val_dict[self._if_arg] if self._then_arg.is_leaf(): then_val = self._then_arg val_dict[self._then_arg] = then_val if self._then_arg not in der_dict: der_dict[self._then_arg] = 0 else: then_val = val_dict[self._then_arg] der_dict[self._then_arg] = 0 if self._else_arg.is_leaf(): else_val = self._else_arg val_dict[self._else_arg] = else_val if self._else_arg not in der_dict: der_dict[self._else_arg] = 0 else: else_val = val_dict[self._else_arg] der_dict[self._else_arg] = 0 val_dict[self] = if_else(if_val, then_val, else_val)
[docs] def diff_down(self, val_dict, der_dict): der = der_dict[self] der_dict[self._then_arg] += if_else(val_dict[self._if_arg], der, 0) der_dict[self._else_arg] += if_else(val_dict[self._if_arg], 0, der)
[docs] def get_rpn(self, rpn_map, leaf_ndx_map): if self._if_arg.is_leaf(): rpn_map[self] = _rpn = [leaf_ndx_map[self._if_arg]] else: rpn_map[self] = _rpn = rpn_map[self._if_arg] if self._then_arg.is_leaf(): _rpn.append(leaf_ndx_map[self._then_arg]) else: _rpn.extend(rpn_map[self._then_arg]) if self._else_arg.is_leaf(): _rpn.append(leaf_ndx_map[self._else_arg]) else: _rpn.extend(rpn_map[self._else_arg]) _rpn.append(OperationEnum.if_else.value)
[docs] class InequalityOperator(Operator): __slots__ = ('_lb', '_ub', '_body')
[docs] def __init__(self, body, lb, ub): self._body = body self._lb = lb self._ub = ub
[docs] def evaluate(self, val_dict): if self._body.is_leaf(): body_val = self._body.value else: body_val = val_dict[self._body] val_dict[self] = (self._lb.value <= body_val <= self._ub.value)
[docs] def operands(self): yield self._body yield self._lb yield self._ub
def _str(self, val_dict): if self._body.is_leaf(): body_val = self._body._str() else: body_val = val_dict[self._body] val_dict[self] = '(' + self._lb._str() + ' <= ' + body_val + ' <= ' + self._ub._str() + ')'
[docs] def diff_up(self, val_dict, der_dict): if self._body.is_leaf(): body_val = self._body.value val_dict[self._body] = body_val if self._body not in der_dict: der_dict[self._body] = 0 else: body_val = val_dict[self._body] der_dict[self._body] = 0 val_dict[self] = (self._lb.value <= body_val <= self._ub.value)
[docs] def diff_up_symbolic(self, val_dict, der_dict): if self._body.is_leaf(): body_val = self._body val_dict[self._body] = body_val if self._body not in der_dict: der_dict[self._body] = 0 else: body_val = val_dict[self._body] der_dict[self._body] = 0 val_dict[self] = inequality(body_val, self._lb.value, self._ub.value)
[docs] def diff_down(self, val_dict, der_dict): pass
[docs] def get_rpn(self, rpn_map, leaf_ndx_map): if self._body.is_leaf(): rpn_map[self] = _rpn = [leaf_ndx_map[self._body]] else: rpn_map[self] = _rpn = rpn_map[self._body] _rpn.append(leaf_ndx_map[self._lb]) _rpn.append(leaf_ndx_map[self._ub]) _rpn.append(OperationEnum.inequality.value)
[docs] class SignOperator(UnaryOperator): __slots__ = () str_repn = 'sign' operation_enum = OperationEnum.sign.value
[docs] def diff_down(self, val_dict, der_dict): pass
[docs] @staticmethod def operation(val): return sign(val)
[docs] class AbsOperator(UnaryOperator): __slots__ = () str_repn = 'abs' operation_enum = OperationEnum.abs.value
[docs] def diff_down(self, val_dict, der_dict): der = der_dict[self] der_dict[self._operand] += der * if_else(if_statement=inequality(body=val_dict[self._operand], lb=0), then_statement=Float(1), else_statement=Float(-1))
[docs] @staticmethod def operation(val): return abs(val)
[docs] class ExpOperator(UnaryOperator): __slots__ = () str_repn = 'exp' operation_enum = OperationEnum.exp.value
[docs] def diff_down(self, val_dict, der_dict): der = der_dict[self] der_dict[self._operand] += der * exp(val_dict[self._operand])
[docs] @staticmethod def operation(val): return exp(val)
[docs] class LogOperator(UnaryOperator): __slots__ = () str_repn = 'log' operation_enum = OperationEnum.log.value
[docs] def diff_down(self, val_dict, der_dict): der = der_dict[self] der_dict[self._operand] += der / val_dict[self._operand]
[docs] @staticmethod def operation(val): return log(val)
[docs] class SinOperator(UnaryOperator): __slots__ = () str_repn = 'sin' operation_enum = OperationEnum.sin.value
[docs] def diff_down(self, val_dict, der_dict): der = der_dict[self] der_dict[self._operand] += der * cos(val_dict[self._operand])
[docs] @staticmethod def operation(val): return sin(val)
[docs] class CosOperator(UnaryOperator): __slots__ = () str_repn = 'cos' operation_enum = OperationEnum.cos.value
[docs] def diff_down(self, val_dict, der_dict): der = der_dict[self] der_dict[self._operand] -= der * sin(val_dict[self._operand])
[docs] @staticmethod def operation(val): return cos(val)
[docs] class TanOperator(UnaryOperator): __slots__ = () str_repn = 'tan' operation_enum = OperationEnum.tan.value
[docs] def diff_down(self, val_dict, der_dict): der = der_dict[self] der_dict[self._operand] += der / (cos(val_dict[self._operand])**2)
[docs] @staticmethod def operation(val): return tan(val)
[docs] class AsinOperator(UnaryOperator): __slots__ = () str_repn = 'asin' operation_enum = OperationEnum.asin.value
[docs] def diff_down(self, val_dict, der_dict): der = der_dict[self] der_dict[self._operand] += der / (1 - val_dict[self._operand]**2)**0.5
[docs] @staticmethod def operation(val): return asin(val)
[docs] class AcosOperator(UnaryOperator): __slots__ = () str_repn = 'acos' operation_enum = OperationEnum.acos.value
[docs] def diff_down(self, val_dict, der_dict): der = der_dict[self] der_dict[self._operand] -= der / (1 - val_dict[self._operand]**2)**0.5
[docs] @staticmethod def operation(val): return acos(val)
[docs] class AtanOperator(UnaryOperator): __slots__ = () str_repn = 'atan' operation_enum = OperationEnum.atan.value
[docs] def diff_down(self, val_dict, der_dict): der = der_dict[self] der_dict[self._operand] += der / (1 + val_dict[self._operand]**2)
[docs] @staticmethod def operation(val): return atan(val)
[docs] def value(obj): if type(obj) in native_numeric_types: return obj return obj.evaluate()
[docs] def is_variable_type(obj): """ Returns True if the object is a variable. Parameters ---------- obj: ExpressionBase Also accepts floats and ints Returns ------- bool """ if type(obj) in native_numeric_types: return False return obj.is_variable_type()
[docs] class ConditionalExpression(object):
[docs] def __init__(self): self._conditions = list() self._exprs = list()
[docs] def add_condition(self, condition, expr): assert condition.is_relational() self._conditions.append(condition) self._exprs.append(expr)
[docs] def add_final_expr(self, expr): self._conditions.append(Float(1)) self._exprs.append(expr)
[docs] def evaluate(self): for i, cond in enumerate(self._conditions): if cond.evaluate(): return self._exprs[i].evaluate()
[docs] def reverse_ad(self): for i, cond in enumerate(self._conditions): if cond.evaluate(): return self._exprs[i].reverse_ad()
def __str__(self): i = 0 s = '\n' for _cond, _expr in zip(self._conditions, self._exprs): if i == 0: s += 'if ' else: s += 'elif ' s += str(_cond) s += ':\n ' s += str(_expr) s += '\n' i += 1 return s def __repr__(self): return self.__str__()