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