1# ___________________________________________________________________________ 2# 3# Pyomo: Python Optimization Modeling Objects 4# Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC 5# Under the terms of Contract DE-NA0003525 with National Technology and 6# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain 7# rights in this software. 8# This software is distributed under the 3-clause BSD License. 9# ___________________________________________________________________________ 10 11from __future__ import division 12 13import math 14import logging 15from itertools import islice 16 17logger = logging.getLogger('pyomo.core') 18 19from math import isclose 20from pyomo.common.deprecation import deprecated, deprecation_warning 21 22from .expr_common import ( 23 _add, _sub, _mul, _div, 24 _pow, _neg, _abs, _inplace, 25 _unary 26) 27from .numvalue import ( 28 NumericValue, 29 native_types, 30 nonpyomo_leaf_types, 31 native_numeric_types, 32 as_numeric, 33 value, 34) 35 36from .visitor import ( 37 evaluate_expression, expression_to_string, polynomial_degree, 38 clone_expression, sizeof_expression, _expression_is_fixed 39) 40 41 42class clone_counter(object): 43 """ Context manager for counting cloning events. 44 45 This context manager counts the number of times that the 46 :func:`clone_expression <pyomo.core.expr.current.clone_expression>` 47 function is executed. 48 """ 49 50 _count = 0 51 52 def __enter__(self): 53 return self 54 55 def __exit__(self, *args): 56 pass 57 58 @property 59 def count(self): 60 """A property that returns the clone count value. 61 """ 62 return clone_counter._count 63 64 65class nonlinear_expression(object): 66 """ Context manager for mutable sums. 67 68 This context manager is used to compute a sum while 69 treating the summation as a mutable object. 70 """ 71 72 def __enter__(self): 73 self.e = _MutableSumExpression([]) 74 return self.e 75 76 def __exit__(self, *args): 77 if self.e.__class__ == _MutableSumExpression: 78 self.e.__class__ = SumExpression 79 80 81class linear_expression(object): 82 """ Context manager for mutable linear sums. 83 84 This context manager is used to compute a linear sum while 85 treating the summation as a mutable object. 86 """ 87 88 def __enter__(self): 89 """ 90 The :class:`_MutableLinearExpression <pyomo.core.expr.current._MutableLinearExpression>` 91 class is the context that is used to to 92 hold the mutable linear sum. 93 """ 94 self.e = _MutableLinearExpression() 95 return self.e 96 97 def __exit__(self, *args): 98 """ 99 The context is changed to the 100 :class:`LinearExpression <pyomo.core.expr.current.LinearExpression>` 101 class to transform the context into a nonmutable 102 form. 103 """ 104 if self.e.__class__ == _MutableLinearExpression: 105 self.e.__class__ = LinearExpression 106 107 108#------------------------------------------------------- 109# 110# Expression classes 111# 112#------------------------------------------------------- 113 114 115class ExpressionBase(NumericValue): 116 """ 117 The base class for Pyomo expressions. 118 119 This class is used to define nodes in an expression 120 tree. 121 122 Args: 123 args (list or tuple): Children of this node. 124 """ 125 126 # Previously, we used _args to define expression class arguments. 127 # Here, we use _args_ to force errors for code that was referencing this 128 # data. There are now accessor methods, so in most cases users 129 # and developers should not directly access the _args_ data values. 130 __slots__ = ('_args_',) 131 PRECEDENCE = 0 132 133 def __init__(self, args): 134 self._args_ = args 135 136 def nargs(self): 137 """ 138 Returns the number of child nodes. 139 140 By default, Pyomo expressions represent binary operations 141 with two arguments. 142 143 Note: 144 This function does not simply compute the length of 145 :attr:`_args_` because some expression classes use 146 a subset of the :attr:`_args_` array. Thus, it 147 is imperative that developers use this method! 148 149 Returns: 150 A nonnegative integer that is the number of child nodes. 151 """ 152 return 2 153 154 def arg(self, i): 155 """ 156 Return the i-th child node. 157 158 Args: 159 i (int): Nonnegative index of the child that is returned. 160 161 Returns: 162 The i-th child node. 163 """ 164 if i >= self.nargs(): 165 raise KeyError("Invalid index for expression argument: %d" % i) 166 if i < 0: 167 return self._args_[self.nargs()+i] 168 return self._args_[i] 169 170 @property 171 def args(self): 172 """ 173 Return the child nodes 174 175 Returns: Either a list or tuple (depending on the node storage 176 model) containing only the child nodes of this node 177 """ 178 return self._args_[:self.nargs()] 179 180 181 def __getstate__(self): 182 """ 183 Pickle the expression object 184 185 Returns: 186 The pickled state. 187 """ 188 state = super(ExpressionBase, self).__getstate__() 189 for i in ExpressionBase.__slots__: 190 state[i] = getattr(self,i) 191 return state 192 193 def __nonzero__(self): #pragma: no cover 194 """ 195 Compute the value of the expression and convert it to 196 a boolean. 197 198 Returns: 199 A boolean value. 200 """ 201 return bool(self()) 202 203 __bool__ = __nonzero__ 204 205 def __call__(self, exception=True): 206 """ 207 Evaluate the value of the expression tree. 208 209 Args: 210 exception (bool): If :const:`False`, then 211 an exception raised while evaluating 212 is captured, and the value returned is 213 :const:`None`. Default is :const:`True`. 214 215 Returns: 216 The value of the expression or :const:`None`. 217 """ 218 return evaluate_expression(self, exception) 219 220 def __str__(self): 221 """ 222 Returns a string description of the expression. 223 224 Note: 225 The value of ``pyomo.core.expr.expr_common.TO_STRING_VERBOSE`` 226 is used to configure the execution of this method. 227 If this value is :const:`True`, then the string 228 representation is a nested function description of the expression. 229 The default is :const:`False`, which is an algebraic 230 description of the expression. 231 232 Returns: 233 A string. 234 """ 235 return expression_to_string(self) 236 237 def to_string(self, verbose=None, labeler=None, smap=None, compute_values=False): 238 """ 239 Return a string representation of the expression tree. 240 241 Args: 242 verbose (bool): If :const:`True`, then the the string 243 representation consists of nested functions. Otherwise, 244 the string representation is an algebraic equation. 245 Defaults to :const:`False`. 246 labeler: An object that generates string labels for 247 variables in the expression tree. Defaults to :const:`None`. 248 smap: If specified, this :class:`SymbolMap <pyomo.core.expr.symbol_map.SymbolMap>` is 249 used to cache labels for variables. 250 compute_values (bool): If :const:`True`, then 251 parameters and fixed variables are evaluated before the 252 expression string is generated. Default is :const:`False`. 253 254 Returns: 255 A string representation for the expression tree. 256 """ 257 return expression_to_string(self, verbose=verbose, labeler=labeler, smap=smap, compute_values=compute_values) 258 259 def _precedence(self): 260 return ExpressionBase.PRECEDENCE 261 262 def _associativity(self): 263 """Return the associativity of this operator. 264 265 Returns 1 if this operator is left-to-right associative or -1 if 266 it is right-to-left associative. Any other return value will be 267 interpreted as "not associative" (implying any arguments that 268 are at this operator's _precedence() will be enclosed in parens). 269 """ 270 # Most operators in Python are left-to-right associative 271 return 1 272 273 def _to_string(self, values, verbose, smap, compute_values): #pragma: no cover 274 """ 275 Construct a string representation for this node, using the string 276 representations of its children. 277 278 This method is called by the :class:`_ToStringVisitor 279 <pyomo.core.expr.current._ToStringVisitor>` class. It must 280 must be defined in subclasses. 281 282 Args: 283 values (list): The string representations of the children of this 284 node. 285 verbose (bool): If :const:`True`, then the the string 286 representation consists of nested functions. Otherwise, 287 the string representation is an algebraic equation. 288 smap: If specified, this :class:`SymbolMap 289 <pyomo.core.expr.symbol_map.SymbolMap>` is 290 used to cache labels for variables. 291 compute_values (bool): If :const:`True`, then 292 parameters and fixed variables are evaluated before the 293 expression string is generated. 294 295 Returns: 296 A string representation for this node. 297 """ 298 pass 299 300 def getname(self, *args, **kwds): #pragma: no cover 301 """ 302 Return the text name of a function associated with this expression object. 303 304 In general, no arguments are passed to this function. 305 306 Args: 307 *arg: a variable length list of arguments 308 **kwds: keyword arguments 309 310 Returns: 311 A string name for the function. 312 """ 313 raise NotImplementedError("Derived expression (%s) failed to "\ 314 "implement getname()" % ( str(self.__class__), )) 315 316 def clone(self, substitute=None): 317 """ 318 Return a clone of the expression tree. 319 320 Note: 321 This method does not clone the leaves of the 322 tree, which are numeric constants and variables. 323 It only clones the interior nodes, and 324 expression leaf nodes like 325 :class:`_MutableLinearExpression<pyomo.core.expr.current._MutableLinearExpression>`. 326 However, named expressions are treated like 327 leaves, and they are not cloned. 328 329 Args: 330 substitute (dict): a dictionary that maps object ids to clone 331 objects generated earlier during the cloning process. 332 333 Returns: 334 A new expression tree. 335 """ 336 return clone_expression(self, substitute=substitute) 337 338 def create_node_with_local_data(self, args): 339 """ 340 Construct a node using given arguments. 341 342 This method provides a consistent interface for constructing a 343 node, which is used in tree visitor scripts. In the simplest 344 case, this simply returns:: 345 346 self.__class__(args) 347 348 But in general this creates an expression object using local 349 data as well as arguments that represent the child nodes. 350 351 Args: 352 args (list): A list of child nodes for the new expression 353 object 354 memo (dict): A dictionary that maps object ids to clone 355 objects generated earlier during a cloning process. 356 This argument is needed to clone objects that are 357 owned by a model, and it can be safely ignored for 358 most expression classes. 359 360 Returns: 361 A new expression object with the same type as the current 362 class. 363 """ 364 return self.__class__(args) 365 366 def create_potentially_variable_object(self): 367 """ 368 Create a potentially variable version of this object. 369 370 This method returns an object that is a potentially variable 371 version of the current object. In the simplest 372 case, this simply sets the value of `__class__`: 373 374 self.__class__ = self.__class__.__mro__[1] 375 376 Note that this method is allowed to modify the current object 377 and return it. But in some cases it may create a new 378 potentially variable object. 379 380 Returns: 381 An object that is potentially variable. 382 """ 383 self.__class__ = self.__class__.__mro__[1] 384 return self 385 386 def is_constant(self): 387 """Return True if this expression is an atomic constant 388 389 This method contrasts with the is_fixed() method. This method 390 returns True if the expression is an atomic constant, that is it 391 is composed exclusively of constants and immutable parameters. 392 NumericValue objects returning is_constant() == True may be 393 simplified to their numeric value at any point without warning. 394 395 Note: This defaults to False, but gets redefined in sub-classes. 396 """ 397 return False 398 399 def is_fixed(self): 400 """ 401 Return :const:`True` if this expression contains no free variables. 402 403 Returns: 404 A boolean. 405 """ 406 return _expression_is_fixed(self) 407 408 def _is_fixed(self, values): 409 """ 410 Compute whether this expression is fixed given 411 the fixed values of its children. 412 413 This method is called by the :class:`_IsFixedVisitor 414 <pyomo.core.expr.current._IsFixedVisitor>` class. It can 415 be over-written by expression classes to customize this 416 logic. 417 418 Args: 419 values (list): A list of boolean values that indicate whether 420 the children of this expression are fixed 421 422 Returns: 423 A boolean that is :const:`True` if the fixed values of the 424 children are all :const:`True`. 425 """ 426 return all(values) 427 428 def is_potentially_variable(self): 429 """ 430 Return :const:`True` if this expression might represent 431 a variable expression. 432 433 This method returns :const:`True` when (a) the expression 434 tree contains one or more variables, or (b) the expression 435 tree contains a named expression. In both cases, the 436 expression cannot be treated as constant since (a) the variables 437 may not be fixed, or (b) the named expressions may be changed 438 at a later time to include non-fixed variables. 439 440 Returns: 441 A boolean. Defaults to :const:`True` for expressions. 442 """ 443 return True 444 445 def is_named_expression_type(self): 446 """ 447 Return :const:`True` if this object is a named expression. 448 449 This method returns :const:`False` for this class, and it 450 is included in other classes within Pyomo that are not named 451 expressions, which allows for a check for named expressions 452 without evaluating the class type. 453 454 Returns: 455 A boolean. 456 """ 457 return False 458 459 def is_expression_type(self): 460 """ 461 Return :const:`True` if this object is an expression. 462 463 This method obviously returns :const:`True` for this class, but it 464 is included in other classes within Pyomo that are not expressions, 465 which allows for a check for expressions without 466 evaluating the class type. 467 468 Returns: 469 A boolean. 470 """ 471 return True 472 473 def size(self): 474 """ 475 Return the number of nodes in the expression tree. 476 477 Returns: 478 A nonnegative integer that is the number of interior and leaf 479 nodes in the expression tree. 480 """ 481 return sizeof_expression(self) 482 483 def polynomial_degree(self): 484 """ 485 Return the polynomial degree of the expression. 486 487 Returns: 488 A non-negative integer that is the polynomial 489 degree if the expression is polynomial, or :const:`None` otherwise. 490 """ 491 return polynomial_degree(self) 492 493 def _compute_polynomial_degree(self, values): #pragma: no cover 494 """ 495 Compute the polynomial degree of this expression given 496 the degree values of its children. 497 498 This method is called by the :class:`_PolynomialDegreeVisitor 499 <pyomo.core.expr.current._PolynomialDegreeVisitor>` class. It can 500 be over-written by expression classes to customize this 501 logic. 502 503 Args: 504 values (list): A list of values that indicate the degree 505 of the children expression. 506 507 Returns: 508 A nonnegative integer that is the polynomial degree of the 509 expression, or :const:`None`. Default is :const:`None`. 510 """ 511 return None 512 513 def _apply_operation(self, result): #pragma: no cover 514 """ 515 Compute the values of this node given the values of its children. 516 517 This method is called by the :class:`_EvaluationVisitor 518 <pyomo.core.expr.current._EvaluationVisitor>` class. It must 519 be over-written by expression classes to customize this logic. 520 521 Note: 522 This method applies the logical operation of the 523 operator to the arguments. It does *not* evaluate 524 the arguments in the process, but assumes that they 525 have been previously evaluated. But noted that if 526 this class contains auxilliary data (e.g. like the 527 numeric coefficients in the :class:`LinearExpression 528 <pyomo.core.expr.current.LinearExpression>` class, then 529 those values *must* be evaluated as part of this 530 function call. An uninitialized parameter value 531 encountered during the execution of this method is 532 considered an error. 533 534 Args: 535 values (list): A list of values that indicate the value 536 of the children expressions. 537 538 Returns: 539 A floating point value for this expression. 540 """ 541 raise NotImplementedError("Derived expression (%s) failed to "\ 542 "implement _apply_operation()" % ( str(self.__class__), )) 543 544 545class NegationExpression(ExpressionBase): 546 """ 547 Negation expressions:: 548 549 - x 550 """ 551 552 __slots__ = () 553 554 PRECEDENCE = 4 555 556 def nargs(self): 557 return 1 558 559 def getname(self, *args, **kwds): 560 return 'neg' 561 562 def _compute_polynomial_degree(self, result): 563 return result[0] 564 565 def _precedence(self): 566 return NegationExpression.PRECEDENCE 567 568 def _to_string(self, values, verbose, smap, compute_values): 569 if verbose: 570 return "{0}({1})".format(self.getname(), values[0]) 571 tmp = values[0] 572 if tmp[0] == '-': 573 i = 1 574 while tmp[i] == ' ': 575 i += 1 576 return tmp[i:] 577 return "- "+tmp 578 579 def _apply_operation(self, result): 580 return -result[0] 581 582 583class NPV_NegationExpression(NegationExpression): 584 __slots__ = () 585 586 def is_potentially_variable(self): 587 return False 588 589 590class ExternalFunctionExpression(ExpressionBase): 591 """ 592 External function expressions 593 594 Example:: 595 596 model = ConcreteModel() 597 model.a = Var() 598 model.f = ExternalFunction(library='foo.so', function='bar') 599 expr = model.f(model.a) 600 601 Args: 602 args (tuple): children of this node 603 fcn: a class that defines this external function 604 """ 605 __slots__ = ('_fcn',) 606 607 def __init__(self, args, fcn=None): 608 self._args_ = args 609 self._fcn = fcn 610 611 def nargs(self): 612 return len(self._args_) 613 614 def create_node_with_local_data(self, args): 615 return self.__class__(args, self._fcn) 616 617 def __getstate__(self): 618 state = super(ExternalFunctionExpression, self).__getstate__() 619 for i in ExternalFunctionExpression.__slots__: 620 state[i] = getattr(self, i) 621 return state 622 623 def getname(self, *args, **kwds): #pragma: no cover 624 return self._fcn.getname(*args, **kwds) 625 626 def _compute_polynomial_degree(self, result): 627 return 0 if all(arg == 0 for arg in result) else None 628 629 def _apply_operation(self, result): 630 return self._fcn.evaluate( result ) 631 632 def _to_string(self, values, verbose, smap, compute_values): 633 return "{0}({1})".format(self.getname(), ", ".join(values)) 634 635 def get_arg_units(self): 636 """ Return the units for this external functions arguments """ 637 return self._fcn.get_arg_units() 638 639 def get_units(self): 640 """ Get the units of the return value for this external function """ 641 return self._fcn.get_units() 642 643class NPV_ExternalFunctionExpression(ExternalFunctionExpression): 644 __slots__ = () 645 646 def is_potentially_variable(self): 647 return False 648 649 650class PowExpression(ExpressionBase): 651 """ 652 Power expressions:: 653 654 x**y 655 """ 656 657 __slots__ = () 658 PRECEDENCE = 2 659 660 def _compute_polynomial_degree(self, result): 661 # PowExpression is a tricky thing. In general, a**b is 662 # nonpolynomial, however, if b == 0, it is a constant 663 # expression, and if a is polynomial and b is a positive 664 # integer, it is also polynomial. While we would like to just 665 # call this a non-polynomial expression, these exceptions occur 666 # too frequently (and in particular, a**2) 667 l,r = result 668 if r == 0: 669 if l == 0: 670 return 0 671 # NOTE: use value before int() so that we don't 672 # run into the disabled __int__ method on 673 # NumericValue 674 exp = value(self._args_[1], exception=False) 675 if exp is None: 676 return None 677 if exp == int(exp): 678 if l is not None and exp > 0: 679 return l * exp 680 elif exp == 0: 681 return 0 682 return None 683 684 def _is_fixed(self, args): 685 assert(len(args) == 2) 686 if not args[1]: 687 return False 688 return args[0] or value(self._args_[1]) == 0 689 690 def _precedence(self): 691 return PowExpression.PRECEDENCE 692 693 def _associativity(self): 694 # "**" is right-to-left associative in Python (so this should 695 # return -1), however, as this rule is not widely known and can 696 # confuse novice users, we will make our "**" operator 697 # non-associative (forcing parens) 698 return 0 699 700 def _apply_operation(self, result): 701 _l, _r = result 702 return _l ** _r 703 704 def getname(self, *args, **kwds): 705 return 'pow' 706 707 def _to_string(self, values, verbose, smap, compute_values): 708 if verbose: 709 return "{0}({1}, {2})".format(self.getname(), values[0], values[1]) 710 return "{0}**{1}".format(values[0], values[1]) 711 712 713class NPV_PowExpression(PowExpression): 714 __slots__ = () 715 716 def is_potentially_variable(self): 717 return False 718 719 720class ProductExpression(ExpressionBase): 721 """ 722 Product expressions:: 723 724 x*y 725 """ 726 727 __slots__ = () 728 PRECEDENCE = 4 729 730 def _precedence(self): 731 return ProductExpression.PRECEDENCE 732 733 def _compute_polynomial_degree(self, result): 734 # NB: We can't use sum() here because None (non-polynomial) 735 # overrides a numeric value (and sum() just ignores it - or 736 # errors in py3k) 737 a, b = result 738 if a is None or b is None: 739 return None 740 else: 741 return a + b 742 743 def getname(self, *args, **kwds): 744 return 'prod' 745 746 def _is_fixed(self, args): 747 # Anything times 0 equals 0, so one of the children is 748 # fixed and has a value of 0, then this expression is fixed 749 assert(len(args) == 2) 750 if all(args): 751 return True 752 for i in (0, 1): 753 if args[i] and value(self._args_[i]) == 0: 754 return True 755 return False 756 757 def _apply_operation(self, result): 758 _l, _r = result 759 return _l * _r 760 761 def _to_string(self, values, verbose, smap, compute_values): 762 if verbose: 763 return "{0}({1}, {2})".format(self.getname(), values[0], values[1]) 764 if values[0] in self._to_string.one: 765 return values[1] 766 if values[0] in self._to_string.minus_one: 767 return "- {0}".format(values[1]) 768 return "{0}*{1}".format(values[0],values[1]) 769 # Store these reference sets on the function for quick lookup 770 _to_string.one = {"1", "1.0", "(1)", "(1.0)"} 771 _to_string.minus_one = {"-1", "-1.0", "(-1)", "(-1.0)"} 772 773 774class NPV_ProductExpression(ProductExpression): 775 __slots__ = () 776 777 def is_potentially_variable(self): 778 return False 779 780 781class MonomialTermExpression(ProductExpression): 782 __slots__ = () 783 784 def getname(self, *args, **kwds): 785 return 'mon' 786 787class DivisionExpression(ExpressionBase): 788 """ 789 Division expressions:: 790 791 x/y 792 """ 793 __slots__ = () 794 PRECEDENCE = 4 795 796 def nargs(self): 797 return 2 798 799 def _precedence(self): 800 return DivisionExpression.PRECEDENCE 801 802 def _compute_polynomial_degree(self, result): 803 if result[1] == 0: 804 return result[0] 805 return None 806 807 def getname(self, *args, **kwds): 808 return 'div' 809 810 def _to_string(self, values, verbose, smap, compute_values): 811 if verbose: 812 return "{0}({1}, {2})".format(self.getname(), values[0], values[1]) 813 return "{0}/{1}".format(values[0], values[1]) 814 815 def _apply_operation(self, result): 816 return result[0] / result[1] 817 818 819class NPV_DivisionExpression(DivisionExpression): 820 __slots__ = () 821 822 def is_potentially_variable(self): 823 return False 824 825 826@deprecated("Use DivisionExpression", version='5.6.7') 827class ReciprocalExpression(ExpressionBase): 828 """ 829 Reciprocal expressions:: 830 831 1/x 832 """ 833 __slots__ = () 834 PRECEDENCE = 4 835 836 def __init__(self, args): 837 super(ReciprocalExpression, self).__init__(args) 838 839 def nargs(self): 840 return 1 841 842 def _precedence(self): 843 return ReciprocalExpression.PRECEDENCE 844 845 def _associativity(self): 846 return 0 847 848 def _compute_polynomial_degree(self, result): 849 if result[0] == 0: 850 return 0 851 return None 852 853 def getname(self, *args, **kwds): 854 return 'recip' 855 856 def _to_string(self, values, verbose, smap, compute_values): 857 if verbose: 858 return "{0}({1})".format(self.getname(), values[0]) 859 return "1/{0}".format(values[0]) 860 861 def _apply_operation(self, result): 862 return 1 / result[0] 863 864 865class NPV_ReciprocalExpression(ReciprocalExpression): 866 __slots__ = () 867 868 def is_potentially_variable(self): 869 return False 870 871 872class _LinearOperatorExpression(ExpressionBase): 873 """ 874 An 'abstract' class that defines the polynomial degree for a simple 875 linear operator 876 """ 877 878 __slots__ = () 879 880 def _compute_polynomial_degree(self, result): 881 # NB: We can't use max() here because None (non-polynomial) 882 # overrides a numeric value (and max() just ignores it) 883 ans = 0 884 for x in result: 885 if x is None: 886 return None 887 elif ans < x: 888 ans = x 889 return ans 890 891 892class SumExpressionBase(_LinearOperatorExpression): 893 """ 894 A base class for simple summation of expressions 895 896 The class hierarchy for summation is different than for other 897 expression types. For example, ProductExpression defines 898 the class for representing binary products, and sub-classes are 899 specializations of that class. 900 901 By contrast, the SumExpressionBase is not directly used to 902 represent expressions. Rather, this base class provides 903 commonly used methods and data. The reason is that some 904 subclasses of SumExpressionBase are binary while others 905 are n-ary. 906 907 Thus, developers will need to treat checks for summation 908 classes differently, depending on whether the binary/n-ary 909 operations are different. 910 """ 911 912 __slots__ = () 913 PRECEDENCE = 6 914 915 def _precedence(self): 916 return SumExpressionBase.PRECEDENCE 917 918 def getname(self, *args, **kwds): 919 return 'sum' 920 921 922class NPV_SumExpression(SumExpressionBase): 923 __slots__ = () 924 925 def create_potentially_variable_object(self): 926 return SumExpression( self._args_ ) 927 928 def _apply_operation(self, result): 929 l_, r_ = result 930 return l_ + r_ 931 932 def _to_string(self, values, verbose, smap, compute_values): 933 if verbose: 934 return "{0}({1}, {2})".format(self.getname(), values[0], values[1]) 935 if values[1][0] == '-': 936 return "{0} {1}".format(values[0],values[1]) 937 return "{0} + {1}".format(values[0],values[1]) 938 939 def is_potentially_variable(self): 940 return False 941 942 943class SumExpression(SumExpressionBase): 944 """ 945 Sum expression:: 946 947 x + y 948 949 Args: 950 args (list): Children nodes 951 """ 952 __slots__ = ('_nargs','_shared_args') 953 PRECEDENCE = 6 954 955 def __init__(self, args): 956 self._args_ = args 957 self._shared_args = False 958 self._nargs = len(self._args_) 959 960 def add(self, new_arg): 961 if new_arg.__class__ in native_numeric_types and new_arg == 0: 962 return self 963 # Clone 'self', because SumExpression are immutable 964 self._shared_args = True 965 self = self.__class__(self._args_) 966 # 967 if new_arg.__class__ is SumExpression or new_arg.__class__ is _MutableSumExpression: 968 self._args_.extend( islice(new_arg._args_, new_arg._nargs) ) 969 elif not new_arg is None: 970 self._args_.append(new_arg) 971 self._nargs = len(self._args_) 972 return self 973 974 def nargs(self): 975 return self._nargs 976 977 def _precedence(self): 978 return SumExpression.PRECEDENCE 979 980 def _apply_operation(self, result): 981 return sum(result) 982 983 def create_node_with_local_data(self, args): 984 return self.__class__(list(args)) 985 986 def __getstate__(self): 987 state = super(SumExpression, self).__getstate__() 988 for i in SumExpression.__slots__: 989 state[i] = getattr(self, i) 990 return state 991 992 def is_constant(self): 993 # 994 # In most normal contexts, a SumExpression is 995 # non-constant. When Forming expressions, constant 996 # parameters are turned into numbers, which are 997 # simply added. Mutable parameters, variables and 998 # expressions are not constant. 999 # 1000 return False 1001 1002 def is_potentially_variable(self): 1003 for v in islice(self._args_, self._nargs): 1004 if v.__class__ in nonpyomo_leaf_types: 1005 continue 1006 if v.is_variable_type() or v.is_potentially_variable(): 1007 return True 1008 return False 1009 1010 def _to_string(self, values, verbose, smap, compute_values): 1011 if verbose: 1012 tmp = [values[0]] 1013 for i in range(1,len(values)): 1014 tmp.append(", ") 1015 tmp.append(values[i]) 1016 return "{0}({1})".format(self.getname(), "".join(tmp)) 1017 1018 tmp = [values[0]] 1019 for i in range(1,len(values)): 1020 if values[i][0] == '-': 1021 tmp.append(' - ') 1022 tmp.append(values[i][1:].strip()) 1023 elif len(values[i]) > 3 and values[i][:2] == '(-' \ 1024 and values[i][-1] == ')' and _balanced_parens(values[i][1:-1]): 1025 tmp.append(' - ') 1026 tmp.append(values[i][2:-1].strip()) 1027 else: 1028 tmp.append(' + ') 1029 tmp.append(values[i]) 1030 return ''.join(tmp) 1031 1032 1033class _MutableSumExpression(SumExpression): 1034 """ 1035 A mutable SumExpression 1036 1037 The :func:`add` method is slightly different in that it 1038 does not create a new sum expression, but modifies the 1039 :attr:`_args_` data in place. 1040 """ 1041 1042 __slots__ = () 1043 1044 def add(self, new_arg): 1045 if new_arg.__class__ in native_numeric_types and new_arg == 0: 1046 return self 1047 # Do not clone 'self', because _MutableSumExpression are mutable 1048 #self._shared_args = True 1049 #self = self.__class__(list(self.args)) 1050 # 1051 if new_arg.__class__ is SumExpression or new_arg.__class__ is _MutableSumExpression: 1052 self._args_.extend( islice(new_arg._args_, new_arg._nargs) ) 1053 elif not new_arg is None: 1054 self._args_.append(new_arg) 1055 self._nargs = len(self._args_) 1056 return self 1057 1058 1059class Expr_ifExpression(ExpressionBase): 1060 """ 1061 A logical if-then-else expression:: 1062 1063 Expr_if(IF_=x, THEN_=y, ELSE_=z) 1064 1065 Args: 1066 IF_ (expression): A relational expression 1067 THEN_ (expression): An expression that is used if :attr:`IF_` is true. 1068 ELSE_ (expression): An expression that is used if :attr:`IF_` is false. 1069 """ 1070 __slots__ = ('_if','_then','_else') 1071 1072 # **NOTE**: This class evaluates the branching "_if" expression 1073 # on a number of occasions. It is important that 1074 # one uses __call__ for value() and NOT bool(). 1075 1076 def __init__(self, IF_=None, THEN_=None, ELSE_=None): 1077 if type(IF_) is tuple and THEN_==None and ELSE_==None: 1078 IF_, THEN_, ELSE_ = IF_ 1079 self._args_ = (IF_, THEN_, ELSE_) 1080 self._if = IF_ 1081 self._then = THEN_ 1082 self._else = ELSE_ 1083 if self._if.__class__ in native_numeric_types: 1084 self._if = as_numeric(self._if) 1085 1086 def nargs(self): 1087 return 3 1088 1089 def __getstate__(self): 1090 state = super(Expr_ifExpression, self).__getstate__() 1091 for i in Expr_ifExpression.__slots__: 1092 state[i] = getattr(self, i) 1093 return state 1094 1095 def getname(self, *args, **kwds): 1096 return "Expr_if" 1097 1098 def _is_fixed(self, args): 1099 assert(len(args) == 3) 1100 if args[0]: # self._if.is_fixed(): 1101 if args[1] and args[2]: 1102 return True 1103 if value(self._if): 1104 return args[1] # self._then.is_fixed() 1105 else: 1106 return args[2] # self._else.is_fixed() 1107 else: 1108 return False 1109 1110 def is_constant(self): 1111 if self._if.__class__ in native_numeric_types or self._if.is_constant(): 1112 if value(self._if): 1113 return (self._then.__class__ in native_numeric_types or self._then.is_constant()) 1114 else: 1115 return (self._else.__class__ in native_numeric_types or self._else.is_constant()) 1116 else: 1117 return (self._then.__class__ in native_numeric_types or self._then.is_constant()) and \ 1118 (self._else.__class__ in native_numeric_types or self._else.is_constant()) 1119 1120 def is_potentially_variable(self): 1121 return ((not self._if.__class__ in native_numeric_types) and self._if.is_potentially_variable()) or \ 1122 ((not self._then.__class__ in native_numeric_types) and self._then.is_potentially_variable()) or \ 1123 ((not self._else.__class__ in native_numeric_types) and self._else.is_potentially_variable()) 1124 1125 def _compute_polynomial_degree(self, result): 1126 _if, _then, _else = result 1127 if _if == 0: 1128 if _then == _else: 1129 return _then 1130 try: 1131 return _then if value(self._if) else _else 1132 except ValueError: 1133 pass 1134 return None 1135 1136 def _to_string(self, values, verbose, smap, compute_values): 1137 return '{0}( ( {1} ), then=( {2} ), else=( {3} ) )'.\ 1138 format(self.getname(), self._if, self._then, self._else) 1139 1140 def _apply_operation(self, result): 1141 _if, _then, _else = result 1142 return _then if _if else _else 1143 1144 1145class UnaryFunctionExpression(ExpressionBase): 1146 """ 1147 An expression object used to define intrinsic functions (e.g. sin, cos, tan). 1148 1149 Args: 1150 args (tuple): Children nodes 1151 name (string): The function name 1152 fcn: The function that is used to evaluate this expression 1153 """ 1154 __slots__ = ('_fcn', '_name') 1155 1156 def __init__(self, args, name=None, fcn=None): 1157 if not type(args) is tuple: 1158 args = (args,) 1159 self._args_ = args 1160 self._name = name 1161 self._fcn = fcn 1162 1163 def nargs(self): 1164 return 1 1165 1166 def create_node_with_local_data(self, args): 1167 return self.__class__(args, self._name, self._fcn) 1168 1169 def __getstate__(self): 1170 state = super(UnaryFunctionExpression, self).__getstate__() 1171 for i in UnaryFunctionExpression.__slots__: 1172 state[i] = getattr(self, i) 1173 return state 1174 1175 def getname(self, *args, **kwds): 1176 return self._name 1177 1178 def _to_string(self, values, verbose, smap, compute_values): 1179 if verbose: 1180 return "{0}({1})".format(self.getname(), values[0]) 1181 if values[0] and values[0][0] == '(' and values[0][-1] == ')' \ 1182 and _balanced_parens(values[0][1:-1]): 1183 return '{0}{1}'.format(self._name, values[0]) 1184 else: 1185 return '{0}({1})'.format(self._name, values[0]) 1186 1187 def _compute_polynomial_degree(self, result): 1188 if result[0] == 0: 1189 return 0 1190 else: 1191 return None 1192 1193 def _apply_operation(self, result): 1194 return self._fcn(result[0]) 1195 1196 1197class NPV_UnaryFunctionExpression(UnaryFunctionExpression): 1198 __slots__ = () 1199 1200 def is_potentially_variable(self): 1201 return False 1202 1203 1204# NOTE: This should be a special class, since the expression generation relies 1205# on the Python __abs__ method. 1206class AbsExpression(UnaryFunctionExpression): 1207 """ 1208 An expression object for the :func:`abs` function. 1209 1210 Args: 1211 args (tuple): Children nodes 1212 """ 1213 __slots__ = () 1214 1215 def __init__(self, arg): 1216 super(AbsExpression, self).__init__(arg, 'abs', abs) 1217 1218 def create_node_with_local_data(self, args): 1219 return self.__class__(args) 1220 1221 1222class NPV_AbsExpression(AbsExpression): 1223 __slots__ = () 1224 1225 def is_potentially_variable(self): 1226 return False 1227 1228 1229class LinearExpression(ExpressionBase): 1230 """ 1231 An expression object linear polynomials. 1232 1233 Args: 1234 args (tuple): Children nodes 1235 """ 1236 __slots__ = ('constant', # The constant term 1237 'linear_coefs', # Linear coefficients 1238 'linear_vars') # Linear variables 1239 1240 PRECEDENCE = 6 1241 1242 def __init__(self, args=None, constant=None, linear_coefs=None, linear_vars=None): 1243 """ 1244 Build a linear expression object that stores the constant, as well as 1245 coefficients and variables to represent const + sum_i(c_i*x_i) 1246 1247 You can specify args OR (constant, linear_coefs, and linear_vars) 1248 If args is provided, it should be a list that contains the constant, 1249 followed by the coefficients, followed by the variables. 1250 1251 Alternatively, you can specify the constant, the list of linear_coeffs 1252 and the list of linear_vars separately. Note that these lists are NOT 1253 copied. 1254 """ 1255 # I am not sure why LinearExpression allows omitting args, but 1256 # it does. If they are provided, they should be the constant 1257 # followed by the coefficients followed by the variables. 1258 if args: 1259 self.constant = args[0] 1260 n = (len(args)-1) // 2 1261 self.linear_coefs = args[1:n+1] 1262 self.linear_vars = args[n+1:] 1263 else: 1264 self.constant = constant if constant is not None else 0 1265 self.linear_coefs = linear_coefs if linear_coefs else [] 1266 self.linear_vars = linear_vars if linear_vars else [] 1267 1268 self._args_ = tuple() 1269 1270 def nargs(self): 1271 return 0 1272 1273 def _precedence(self): 1274 return LinearExpression.PRECEDENCE 1275 1276 def __getstate__(self): 1277 state = super(LinearExpression, self).__getstate__() 1278 for i in LinearExpression.__slots__: 1279 state[i] = getattr(self,i) 1280 return state 1281 1282 def create_node_with_local_data(self, args): 1283 return self.__class__(args) 1284 1285 def getname(self, *args, **kwds): 1286 return 'sum' 1287 1288 def _compute_polynomial_degree(self, result): 1289 return 1 if not self.is_fixed() else 0 1290 1291 def is_constant(self): 1292 return len(self.linear_vars) == 0 1293 1294 def _is_fixed(self, values=None): 1295 return all(v.fixed for v in self.linear_vars) 1296 1297 def is_fixed(self): 1298 return self._is_fixed() 1299 1300 def _to_string(self, values, verbose, smap, compute_values): 1301 tmp = [] 1302 if compute_values: 1303 const_ = value(self.constant) 1304 if not isclose(const_,0): 1305 tmp = [str(const_)] 1306 elif self.constant.__class__ in native_numeric_types: 1307 if not isclose(self.constant, 0): 1308 tmp = [str(self.constant)] 1309 else: 1310 tmp = [self.constant.to_string(compute_values=False)] 1311 if verbose: 1312 for c,v in zip(self.linear_coefs, self.linear_vars): 1313 if smap: # TODO: coverage 1314 v_ = smap.getSymbol(v) 1315 else: 1316 v_ = str(v) 1317 if c.__class__ in native_numeric_types or compute_values: 1318 c_ = value(c) 1319 if isclose(c_,1): 1320 tmp.append(str(v_)) 1321 elif isclose(c_,0): 1322 continue 1323 else: 1324 tmp.append("prod(%s, %s)" % (str(c_),str(v_))) 1325 else: 1326 tmp.append("prod(%s, %s)" % (str(c), v_)) 1327 return "{0}({1})".format(self.getname(), ', '.join(tmp)) 1328 for c,v in zip(self.linear_coefs, self.linear_vars): 1329 if smap: 1330 v_ = smap.getSymbol(v) 1331 else: 1332 v_ = str(v) 1333 if c.__class__ in native_numeric_types or compute_values: 1334 c_ = value(c) 1335 if isclose(c_,1): 1336 tmp.append(" + %s" % v_) 1337 elif isclose(c_,0): 1338 continue 1339 elif isclose(c_,-1): 1340 tmp.append(" - %s" % v_) 1341 elif c_ < 0: 1342 tmp.append(" - %s*%s" % (str(math.fabs(c_)), v_)) 1343 else: 1344 tmp.append(" + %s*%s" % (str(c_), v_)) 1345 else: 1346 c_str = str(c) 1347 if any(_ in c_str for _ in '+-*/'): 1348 c_str = '('+c_str+')' 1349 tmp.append(" + %s*%s" % (c_str, v_)) 1350 s = "".join(tmp) 1351 if len(s) == 0: #pragma: no cover 1352 return s 1353 if s[0] == " ": 1354 if s[1] == "+": 1355 return s[3:] 1356 return s[1:] 1357 return s 1358 1359 def is_potentially_variable(self): 1360 return len(self.linear_vars) > 0 1361 1362 def _apply_operation(self, result): 1363 return value(self.constant) + sum(value(c)*v.value for c,v in zip(self.linear_coefs, self.linear_vars)) 1364 1365 #@profile 1366 def _combine_expr(self, etype, _other): 1367 if etype == _add or etype == _sub or etype == -_add or etype == -_sub: 1368 # 1369 # if etype == _sub, then _MutableLinearExpression - VAL 1370 # if etype == -_sub, then VAL - _MutableLinearExpression 1371 # 1372 if etype == _sub: 1373 omult = -1 1374 else: 1375 omult = 1 1376 if etype == -_sub: 1377 self.constant *= -1 1378 for i,c in enumerate(self.linear_coefs): 1379 self.linear_coefs[i] = -c 1380 1381 if _other.__class__ in native_numeric_types or not _other.is_potentially_variable(): 1382 self.constant = self.constant + omult * _other 1383 # 1384 # WEH - These seem like uncommon cases, so I think we should defer processing them 1385 # until _decompose_linear_terms 1386 # 1387 #elif _other.__class__ is _MutableLinearExpression: 1388 # self.constant = self.constant + omult * _other.constant 1389 # for c,v in zip(_other.linear_coefs, _other.linear_vars): 1390 # self.linear_coefs.append(omult*c) 1391 # self.linear_vars.append(v) 1392 #elif _other.__class__ is SumExpression or _other.__class__ is _MutableSumExpression: 1393 # for e in _other._args_: 1394 # for c,v in _decompose_linear_terms(e, multiplier=omult): 1395 # if v is None: 1396 # self.constant += c 1397 # else: 1398 # self.linear_coefs.append(c) 1399 # self.linear_vars.append(v) 1400 else: 1401 for c,v in _decompose_linear_terms(_other, multiplier=omult): 1402 if v is None: 1403 self.constant += c 1404 else: 1405 self.linear_coefs.append(c) 1406 self.linear_vars.append(v) 1407 1408 elif etype == _mul or etype == -_mul: 1409 if _other.__class__ in native_numeric_types: 1410 multiplier = _other 1411 elif _other.is_potentially_variable(): 1412 if len(self.linear_vars) > 0: 1413 raise ValueError("Cannot multiply a linear expression with a variable expression") 1414 # 1415 # The linear expression is a constant, so re-initialize it with 1416 # a single term that multiplies the expression by the constant value. 1417 # 1418 c_ = self.constant 1419 self.constant = 0 1420 for c,v in _decompose_linear_terms(_other): 1421 if v is None: 1422 self.constant = c*c_ 1423 else: 1424 self.linear_vars.append(v) 1425 self.linear_coefs.append(c*c_) 1426 return self 1427 else: 1428 multiplier = _other 1429 1430 if multiplier.__class__ in native_numeric_types and multiplier == 0: 1431 self.constant = 0 1432 self.linear_vars = [] 1433 self.linear_coefs = [] 1434 else: 1435 self.constant *= multiplier 1436 for i,c in enumerate(self.linear_coefs): 1437 self.linear_coefs[i] = c*multiplier 1438 1439 elif etype == _div: 1440 if _other.__class__ in native_numeric_types: 1441 divisor = _other 1442 elif _other.is_potentially_variable(): 1443 raise ValueError("Unallowed operation on linear expression: division with a variable RHS") 1444 else: 1445 divisor = _other 1446 self.constant /= divisor 1447 for i,c in enumerate(self.linear_coefs): 1448 self.linear_coefs[i] = c/divisor 1449 1450 elif etype == -_div: 1451 if self.is_potentially_variable(): 1452 raise ValueError("Unallowed operation on linear expression: division with a variable RHS") 1453 return _other / self.constant 1454 1455 elif etype == _neg: 1456 self.constant *= -1 1457 for i,c in enumerate(self.linear_coefs): 1458 self.linear_coefs[i] = - c 1459 1460 else: 1461 raise ValueError("Unallowed operation on mutable linear expression: %d" % etype) #pragma: no cover 1462 1463 return self 1464 1465 1466class _MutableLinearExpression(LinearExpression): 1467 __slots__ = () 1468 1469 1470#------------------------------------------------------- 1471# 1472# Functions used to generate expressions 1473# 1474#------------------------------------------------------- 1475 1476def decompose_term(expr): 1477 """ 1478 A function that returns a tuple consisting of (1) a flag indicated 1479 whether the expression is linear, and (2) a list of tuples that 1480 represents the terms in the linear expression. 1481 1482 Args: 1483 expr (expression): The root node of an expression tree 1484 1485 Returns: 1486 A tuple with the form ``(flag, list)``. If :attr:`flag` is :const:`False`, then 1487 a nonlinear term has been found, and :const:`list` is :const:`None`. 1488 Otherwise, :const:`list` is a list of tuples: ``(coef, value)``. 1489 If :attr:`value` is :const:`None`, then this 1490 represents a constant term with value :attr:`coef`. Otherwise, 1491 :attr:`value` is a variable object, and :attr:`coef` is the 1492 numeric coefficient. 1493 """ 1494 if expr.__class__ in nonpyomo_leaf_types or not expr.is_potentially_variable(): 1495 return True, [(expr,None)] 1496 elif expr.is_variable_type(): 1497 return True, [(1,expr)] 1498 else: 1499 try: 1500 terms = [t_ for t_ in _decompose_linear_terms(expr)] 1501 return True, terms 1502 except LinearDecompositionError: 1503 return False, None 1504 1505class LinearDecompositionError(Exception): 1506 1507 def __init__(self, message): 1508 super(LinearDecompositionError, self).__init__(message) 1509 1510 1511def _decompose_linear_terms(expr, multiplier=1): 1512 """ 1513 A generator function that yields tuples for the linear terms 1514 in an expression. If nonlinear terms are encountered, this function 1515 raises the :class:`LinearDecompositionError` exception. 1516 1517 Args: 1518 expr (expression): The root node of an expression tree 1519 1520 Yields: 1521 Tuples: ``(coef, value)``. If :attr:`value` is :const:`None`, 1522 then this represents a constant term with value :attr:`coef`. 1523 Otherwise, :attr:`value` is a variable object, and :attr:`coef` 1524 is the numeric coefficient. 1525 1526 Raises: 1527 :class:`LinearDecompositionError` if a nonlinear term is encountered. 1528 """ 1529 if expr.__class__ in native_numeric_types or not expr.is_potentially_variable(): 1530 yield (multiplier*expr,None) 1531 elif expr.is_variable_type(): 1532 yield (multiplier,expr) 1533 elif expr.__class__ is MonomialTermExpression: 1534 yield (multiplier*expr._args_[0], expr._args_[1]) 1535 elif expr.__class__ is ProductExpression: 1536 if expr._args_[0].__class__ in native_numeric_types or not expr._args_[0].is_potentially_variable(): 1537 yield from _decompose_linear_terms(expr._args_[1], multiplier*expr._args_[0]) 1538 elif expr._args_[1].__class__ in native_numeric_types or not expr._args_[1].is_potentially_variable(): 1539 yield from _decompose_linear_terms(expr._args_[0], multiplier*expr._args_[1]) 1540 else: 1541 raise LinearDecompositionError("Quadratic terms exist in a product expression.") 1542 elif expr.__class__ is DivisionExpression: 1543 if expr._args_[1].__class__ in native_numeric_types or not expr._args_[1].is_potentially_variable(): 1544 yield from _decompose_linear_terms(expr._args_[0], multiplier/expr._args_[1]) 1545 else: 1546 raise LinearDecompositionError("Unexpected nonlinear term (division)") 1547 elif expr.__class__ is ReciprocalExpression: 1548 # The argument is potentially variable, so this represents a nonlinear term 1549 # 1550 # NOTE: We're ignoring possible simplifications 1551 raise LinearDecompositionError("Unexpected nonlinear term") 1552 elif expr.__class__ is SumExpression or expr.__class__ is _MutableSumExpression: 1553 for arg in expr.args: 1554 yield from _decompose_linear_terms(arg, multiplier) 1555 elif expr.__class__ is NegationExpression: 1556 yield from _decompose_linear_terms(expr._args_[0], -multiplier) 1557 elif expr.__class__ is LinearExpression or expr.__class__ is _MutableLinearExpression: 1558 if not (expr.constant.__class__ in native_numeric_types and expr.constant == 0): 1559 yield (multiplier*expr.constant,None) 1560 if len(expr.linear_coefs) > 0: 1561 for c,v in zip(expr.linear_coefs, expr.linear_vars): 1562 yield (multiplier*c,v) 1563 else: 1564 raise LinearDecompositionError("Unexpected nonlinear term") #pragma: no cover 1565 1566 1567def _process_arg(obj): 1568 # Note: caller is responsible for filtering out native types and 1569 # expressions. 1570 if not obj.is_numeric_type(): 1571 if hasattr(obj, 'as_binary'): 1572 # We assume non-numeric types that have an as_binary method 1573 # are instances of AutoLinkedBooleanVar. Calling as_binary 1574 # will return a valid Binary Var (and issue the appropriate 1575 # deprecation warning) 1576 obj = obj.as_binary() 1577 else: 1578 # User assistance: provide a helpful exception when using an 1579 # indexed object in an expression 1580 if obj.is_component_type() and obj.is_indexed(): 1581 raise TypeError( 1582 "Argument for expression is an indexed numeric " 1583 "value\nspecified without an index:\n\t%s\nIs this " 1584 "value defined over an index that you did not specify?" 1585 % (obj.name, ) ) 1586 1587 raise TypeError( 1588 "Attempting to use a non-numeric type (%s) in a " 1589 "numeric context" % (obj,)) 1590 1591 if obj.is_constant(): 1592 # Resolve constants (e.g., immutable scalar Params & NumericConstants) 1593 return value(obj) 1594 return obj 1595 1596 1597#@profile 1598def _generate_sum_expression(etype, _self, _other): 1599 1600 if etype > _inplace: 1601 etype -= _inplace 1602 1603 if _self.__class__ is _MutableLinearExpression: 1604 try: 1605 if etype >= _unary: 1606 return _self._combine_expr(etype, None) 1607 if _other.__class__ is not _MutableLinearExpression: 1608 if not (_other.__class__ in native_types or _other.is_expression_type()): 1609 _other = _process_arg(_other) 1610 return _self._combine_expr(etype, _other) 1611 except LinearDecompositionError: 1612 pass 1613 elif _other.__class__ is _MutableLinearExpression: 1614 try: 1615 if not (_self.__class__ in native_types or _self.is_expression_type()): 1616 _self = _process_arg(_self) 1617 return _other._combine_expr(-etype, _self) 1618 except LinearDecompositionError: 1619 pass 1620 1621 # 1622 # A mutable sum is used as a context manager, so we don't 1623 # need to process it to see if it's entangled. 1624 # 1625 if not (_self.__class__ in native_types or _self.is_expression_type()): 1626 _self = _process_arg(_self) 1627 1628 if etype == _neg: 1629 if _self.__class__ in native_numeric_types: 1630 return - _self 1631 elif _self.__class__ is MonomialTermExpression: 1632 tmp = _self._args_[0] 1633 if tmp.__class__ in native_numeric_types: 1634 return MonomialTermExpression((-tmp, _self._args_[1])) 1635 else: 1636 return MonomialTermExpression((NPV_NegationExpression((tmp,)), _self._args_[1])) 1637 elif _self.is_variable_type(): 1638 return MonomialTermExpression((-1, _self)) 1639 elif _self.is_potentially_variable(): 1640 return NegationExpression((_self,)) 1641 else: 1642 if _self.__class__ is NPV_NegationExpression: 1643 return _self._args_[0] 1644 return NPV_NegationExpression((_self,)) 1645 1646 if not (_other.__class__ in native_types or _other.is_expression_type()): 1647 _other = _process_arg(_other) 1648 1649 if etype < 0: 1650 # 1651 # This may seem obvious, but if we are performing an 1652 # "R"-operation (i.e. reverse operation), then simply reverse 1653 # self and other. This is legitimate as we are generating a 1654 # completely new expression here. 1655 # 1656 etype *= -1 1657 _self, _other = _other, _self 1658 1659 if etype == _add: 1660 # 1661 # x + y 1662 # 1663 if (_self.__class__ is SumExpression and not _self._shared_args) or \ 1664 _self.__class__ is _MutableSumExpression: 1665 return _self.add(_other) 1666 elif (_other.__class__ is SumExpression and not _other._shared_args) or \ 1667 _other.__class__ is _MutableSumExpression: 1668 return _other.add(_self) 1669 elif _other.__class__ in native_numeric_types: 1670 if _self.__class__ in native_numeric_types: 1671 return _self + _other 1672 elif _other == 0: 1673 return _self 1674 if _self.is_potentially_variable(): 1675 return SumExpression([_self, _other]) 1676 return NPV_SumExpression((_self, _other)) 1677 elif _self.__class__ in native_numeric_types: 1678 if _self == 0: 1679 return _other 1680 if _other.is_potentially_variable(): 1681 #return _LinearSumExpression((_self, _other)) 1682 return SumExpression([_self, _other]) 1683 return NPV_SumExpression((_self, _other)) 1684 elif _other.is_potentially_variable(): 1685 #return _LinearSumExpression((_self, _other)) 1686 return SumExpression([_self, _other]) 1687 elif _self.is_potentially_variable(): 1688 #return _LinearSumExpression((_other, _self)) 1689 #return SumExpression([_other, _self]) 1690 return SumExpression([_self, _other]) 1691 else: 1692 return NPV_SumExpression((_self, _other)) 1693 1694 elif etype == _sub: 1695 # 1696 # x - y 1697 # 1698 if (_self.__class__ is SumExpression and not _self._shared_args) or \ 1699 _self.__class__ is _MutableSumExpression: 1700 return _self.add(-_other) 1701 elif _other.__class__ in native_numeric_types: 1702 if _self.__class__ in native_numeric_types: 1703 return _self - _other 1704 elif _other == 0: 1705 return _self 1706 if _self.is_potentially_variable(): 1707 return SumExpression([_self, -_other]) 1708 return NPV_SumExpression((_self, -_other)) 1709 elif _self.__class__ in native_numeric_types: 1710 if _self == 0: 1711 if _other.__class__ is MonomialTermExpression: 1712 tmp = _other._args_[0] 1713 if tmp.__class__ in native_numeric_types: 1714 return MonomialTermExpression((-tmp, _other._args_[1])) 1715 return MonomialTermExpression((NPV_NegationExpression((_other._args_[0],)), _other._args_[1])) 1716 elif _other.is_variable_type(): 1717 return MonomialTermExpression((-1, _other)) 1718 elif _other.is_potentially_variable(): 1719 return NegationExpression((_other,)) 1720 return NPV_NegationExpression((_other,)) 1721 elif _other.__class__ is MonomialTermExpression: 1722 return SumExpression([_self, MonomialTermExpression((-_other._args_[0], _other._args_[1]))]) 1723 elif _other.is_variable_type(): 1724 return SumExpression([_self, MonomialTermExpression((-1,_other))]) 1725 elif _other.is_potentially_variable(): 1726 return SumExpression([_self, NegationExpression((_other,))]) 1727 return NPV_SumExpression((_self, NPV_NegationExpression((_other,)))) 1728 elif _other.__class__ is MonomialTermExpression: 1729 return SumExpression([_self, MonomialTermExpression((-_other._args_[0], _other._args_[1]))]) 1730 elif _other.is_variable_type(): 1731 return SumExpression([_self, MonomialTermExpression((-1,_other))]) 1732 elif _other.is_potentially_variable(): 1733 return SumExpression([_self, NegationExpression((_other,))]) 1734 elif _self.is_potentially_variable(): 1735 return SumExpression([_self, NPV_NegationExpression((_other,))]) 1736 else: 1737 return NPV_SumExpression((_self, NPV_NegationExpression((_other,)))) 1738 1739 raise RuntimeError("Unknown expression type '%s'" % etype) #pragma: no cover 1740 1741#@profile 1742def _generate_mul_expression(etype, _self, _other): 1743 1744 if etype > _inplace: 1745 etype -= _inplace 1746 1747 if _self.__class__ is _MutableLinearExpression: 1748 try: 1749 if _other.__class__ is not _MutableLinearExpression: 1750 if not (_other.__class__ in native_types or _other.is_expression_type()): 1751 _other = _process_arg(_other) 1752 return _self._combine_expr(etype, _other) 1753 except LinearDecompositionError: 1754 pass 1755 elif _other.__class__ is _MutableLinearExpression: 1756 try: 1757 if not (_self.__class__ in native_types or _self.is_expression_type()): 1758 _self = _process_arg(_self) 1759 return _other._combine_expr(-etype, _self) 1760 except LinearDecompositionError: 1761 pass 1762 1763 # 1764 # A mutable sum is used as a context manager, so we don't 1765 # need to process it to see if it's entangled. 1766 # 1767 if not (_self.__class__ in native_types or _self.is_expression_type()): 1768 _self = _process_arg(_self) 1769 1770 if not (_other.__class__ in native_types or _other.is_expression_type()): 1771 _other = _process_arg(_other) 1772 1773 if etype < 0: 1774 # 1775 # This may seem obvious, but if we are performing an 1776 # "R"-operation (i.e. reverse operation), then simply reverse 1777 # self and other. This is legitimate as we are generating a 1778 # completely new expression here. 1779 # 1780 etype *= -1 1781 _self, _other = _other, _self 1782 1783 if etype == _mul: 1784 # 1785 # x * y 1786 # 1787 if _other.__class__ in native_numeric_types: 1788 if _self.__class__ in native_numeric_types: 1789 return _self * _other 1790 elif _other == 0: 1791 return 0 1792 elif _other == 1: 1793 return _self 1794 if _self.is_variable_type(): 1795 return MonomialTermExpression((_other, _self)) 1796 elif _self.__class__ is MonomialTermExpression: 1797 tmp = _self._args_[0] 1798 if tmp.__class__ in native_numeric_types: 1799 return MonomialTermExpression((_other*tmp, _self._args_[1])) 1800 else: 1801 return MonomialTermExpression((NPV_ProductExpression((_other,tmp)), _self._args_[1])) 1802 elif _self.is_potentially_variable(): 1803 return ProductExpression((_self, _other)) 1804 return NPV_ProductExpression((_self, _other)) 1805 elif _self.__class__ in native_numeric_types: 1806 if _self == 0: 1807 return 0 1808 elif _self == 1: 1809 return _other 1810 if _other.is_variable_type(): 1811 return MonomialTermExpression((_self, _other)) 1812 elif _other.__class__ is MonomialTermExpression: 1813 tmp = _other._args_[0] 1814 if tmp.__class__ in native_numeric_types: 1815 return MonomialTermExpression((_self*tmp, _other._args_[1])) 1816 else: 1817 return MonomialTermExpression((NPV_ProductExpression((_self,tmp)), _other._args_[1])) 1818 elif _other.is_potentially_variable(): 1819 return ProductExpression((_self, _other)) 1820 return NPV_ProductExpression((_self, _other)) 1821 elif _other.is_variable_type(): 1822 if _self.is_potentially_variable(): 1823 return ProductExpression((_self, _other)) 1824 return MonomialTermExpression((_self, _other)) 1825 elif _other.is_potentially_variable(): 1826 return ProductExpression((_self, _other)) 1827 elif _self.is_variable_type(): 1828 return MonomialTermExpression((_other, _self)) 1829 elif _self.is_potentially_variable(): 1830 return ProductExpression((_self, _other)) 1831 else: 1832 return NPV_ProductExpression((_self, _other)) 1833 1834 elif etype == _div: 1835 # 1836 # x / y 1837 # 1838 if _other.__class__ in native_numeric_types: 1839 if _other == 1: 1840 return _self 1841 elif not _other: 1842 raise ZeroDivisionError() 1843 elif _self.__class__ in native_numeric_types: 1844 return _self / _other 1845 if _self.is_variable_type(): 1846 return MonomialTermExpression((1/_other, _self)) 1847 elif _self.__class__ is MonomialTermExpression: 1848 return MonomialTermExpression((_self._args_[0]/_other, _self._args_[1])) 1849 elif _self.is_potentially_variable(): 1850 return DivisionExpression((_self, _other)) 1851 return NPV_DivisionExpression((_self, _other)) 1852 elif _self.__class__ in native_numeric_types: 1853 if _self == 0: 1854 return 0 1855 elif _other.is_potentially_variable(): 1856 return DivisionExpression((_self, _other)) 1857 return NPV_DivisionExpression((_self, _other)) 1858 elif _other.is_potentially_variable(): 1859 return DivisionExpression((_self, _other)) 1860 elif _self.is_potentially_variable(): 1861 if _self.is_variable_type(): 1862 return MonomialTermExpression((NPV_DivisionExpression((1, _other)), _self)) 1863 return DivisionExpression((_self, _other)) 1864 else: 1865 return NPV_DivisionExpression((_self, _other)) 1866 1867 raise RuntimeError("Unknown expression type '%s'" % etype) #pragma: no cover 1868 1869 1870#@profile 1871def _generate_other_expression(etype, _self, _other): 1872 1873 if etype > _inplace: 1874 etype -= _inplace 1875 1876 # 1877 # A mutable sum is used as a context manager, so we don't 1878 # need to process it to see if it's entangled. 1879 # 1880 if not (_self.__class__ in native_types or _self.is_expression_type()): 1881 _self = _process_arg(_self) 1882 1883 # 1884 # abs(x) 1885 # 1886 if etype == _abs: 1887 if _self.__class__ in native_numeric_types: 1888 return abs(_self) 1889 elif _self.is_potentially_variable(): 1890 return AbsExpression(_self) 1891 else: 1892 return NPV_AbsExpression(_self) 1893 1894 if not (_other.__class__ in native_types or _other.is_expression_type()): 1895 _other = _process_arg(_other) 1896 1897 if etype < 0: 1898 # 1899 # This may seem obvious, but if we are performing an 1900 # "R"-operation (i.e. reverse operation), then simply reverse 1901 # self and other. This is legitimate as we are generating a 1902 # completely new expression here. 1903 # 1904 etype *= -1 1905 _self, _other = _other, _self 1906 1907 if etype == _pow: 1908 if _other.__class__ in native_numeric_types: 1909 if _other == 1: 1910 return _self 1911 elif not _other: 1912 return 1 1913 elif _self.__class__ in native_numeric_types: 1914 return _self ** _other 1915 elif _self.is_potentially_variable(): 1916 return PowExpression((_self, _other)) 1917 return NPV_PowExpression((_self, _other)) 1918 elif _self.__class__ in native_numeric_types: 1919 if _other.is_potentially_variable(): 1920 return PowExpression((_self, _other)) 1921 return NPV_PowExpression((_self, _other)) 1922 elif _self.is_potentially_variable() or _other.is_potentially_variable(): 1923 return PowExpression((_self, _other)) 1924 else: 1925 return NPV_PowExpression((_self, _other)) 1926 1927 raise RuntimeError("Unknown expression type '%s'" % etype) #pragma: no cover 1928 1929def _generate_intrinsic_function_expression(arg, name, fcn): 1930 if not (arg.__class__ in native_types or arg.is_expression_type()): 1931 arg = _process_arg(arg) 1932 1933 if arg.__class__ in native_types: 1934 return fcn(arg) 1935 elif arg.is_potentially_variable(): 1936 return UnaryFunctionExpression(arg, name, fcn) 1937 else: 1938 return NPV_UnaryFunctionExpression(arg, name, fcn) 1939 1940def _balanced_parens(arg): 1941 """Verify the string argument contains balanced parentheses. 1942 1943 This checks that every open paren is balanced by a closed paren. 1944 That is, the infix string expression is likely to be valid. This is 1945 primarily used to determine if a string that starts and ends with 1946 parens can have those parens removed. 1947 1948 Examples: 1949 >>> a = "(( x + y ) * ( z - w ))" 1950 >>> _balanced_parens(a[1:-1]) 1951 True 1952 >>> a = "( x + y ) * ( z - w )" 1953 >>> _balanced_parens(a[1:-1]) 1954 False 1955 """ 1956 _parenCount = 0 1957 for c in arg: 1958 if c == '(': 1959 _parenCount += 1 1960 elif c == ')': 1961 _parenCount -= 1 1962 if _parenCount < 0: 1963 return False 1964 return _parenCount == 0 1965 1966 1967NPV_expression_types = set( 1968 [NPV_NegationExpression, 1969 NPV_ExternalFunctionExpression, 1970 NPV_PowExpression, 1971 NPV_ProductExpression, 1972 NPV_DivisionExpression, 1973 NPV_ReciprocalExpression, 1974 NPV_SumExpression, 1975 NPV_UnaryFunctionExpression, 1976 NPV_AbsExpression]) 1977 1978