{"action":"created","comment":{"url":"https://api.github.com/repos/Pyomo/pyomo/pulls/comments/181622275","pull_request_review_id":112279648,"id":181622275,"diff_hunk":"@@ -0,0 +1,3356 @@\n+#  ___________________________________________________________________________\n+#\n+#  Pyomo: Python Optimization Modeling Objects\n+#  Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC\n+#  Under the terms of Contract DE-NA0003525 with National Technology and \n+#  Engineering Solutions of Sandia, LLC, the U.S. Government retains certain \n+#  rights in this software.\n+#  This software is distributed under the 3-clause BSD License.\n+#  ___________________________________________________________________________\n+\n+from __future__ import division\n+\n+_using_chained_inequality = True\n+\n+#\n+# These symbols are part of pyomo.core.expr\n+#\n+__public__ = ['linear_expression', 'nonlinear_expression', 'inequality']\n+#\n+# These symbols are part of pyomo.core.expr.current\n+#\n+__all__ = (\n+'linear_expression',\n+'nonlinear_expression',\n+'inequality',\n+'mutable_sum_context',\n+'mutable_linear_context',\n+'decompose_term',\n+'clone_counter',\n+'clone_counter_context',\n+'clone_expression',\n+'evaluate_expression',\n+'identify_components',\n+'identify_variables',\n+'identify_mutable_parameters',\n+'expression_to_string',\n+'ExpressionBase',\n+'EqualityExpression',\n+'RangedExpression',\n+'InequalityExpression',\n+'ProductExpression',\n+'PowExpression',\n+'ExternalFunctionExpression',\n+'GetItemExpression',\n+'Expr_if',\n+'LinearExpression',\n+'ReciprocalExpression',\n+'NegationExpression',\n+'ViewSumExpression',\n+'UnaryFunctionExpression',\n+'AbsExpression',\n+'compress_expression',\n+'NPV_NegationExpression',\n+'NPV_ExternalFunctionExpression',\n+'NPV_PowExpression',\n+'NPV_ProductExpression',\n+'NPV_ReciprocalExpression',\n+'NPV_SumExpression',\n+'NPV_UnaryFunctionExpression',\n+'NPV_AbsExpression',\n+'SimpleExpressionVisitor',\n+'ExpressionValueVisitor',\n+'ExpressionReplacementVisitor',\n+'LinearDecompositionError',\n+'_SumExpression',               # This should not be referenced, except perhaps while testing code\n+'_MutableViewSumExpression',    # This should not be referenced, except perhaps while testing code\n+'_MutableLinearExpression',     # This should not be referenced, except perhaps while testing code\n+'_decompose_linear_terms',      # This should not be referenced, except perhaps while testing code\n+'_chainedInequality',           # This should not be referenced, except perhaps while testing code\n+'_using_chained_inequality',           # This should not be referenced, except perhaps while testing code\n+'_generate_sum_expression',                 # Only used within pyomo.core.expr\n+'_generate_mul_expression',                 # Only used within pyomo.core.expr\n+'_generate_other_expression',               # Only used within pyomo.core.expr\n+'_generate_intrinsic_function_expression',  # Only used within pyomo.core.expr\n+'_generate_relational_expression',          # Only used within pyomo.core.expr\n+)\n+\n+import math\n+import logging\n+import sys\n+import traceback\n+from copy import deepcopy\n+from collections import deque\n+from itertools import islice\n+from six import next, string_types, itervalues\n+from six.moves import xrange, builtins\n+from weakref import ref\n+\n+logger = logging.getLogger('pyomo.core')\n+\n+from pyutilib.misc.visitor import SimpleVisitor, ValueVisitor\n+from pyutilib.math.util import isclose\n+\n+from pyomo.util.deprecation import deprecation_warning\n+from pyomo.core.expr.symbol_map import SymbolMap\n+from pyomo.core.expr.numvalue import \\\n+    (NumericValue,\n+     NumericConstant,\n+     native_types,\n+     nonpyomo_leaf_types,\n+     native_numeric_types,\n+     as_numeric,\n+     value)\n+from pyomo.core.expr.expr_common import \\\n+    (_add, _sub, _mul, _div,\n+     _pow, _neg, _abs, _inplace,\n+     _unary, _radd, _rsub, _rmul,\n+     _rdiv, _rpow, _iadd, _isub,\n+     _imul, _idiv, _ipow, _lt, _le,\n+     _eq) \n+from pyomo.core.expr import expr_common as common\n+\n+\n+if _using_chained_inequality:               #pragma: no cover\n+    class _chainedInequality(object):\n+\n+        prev = None\n+        call_info = None\n+        cloned_from = []\n+\n+        @staticmethod\n+        def error_message(msg=None):\n+            if msg is None:\n+                msg = \"Relational expression used in an unexpected Boolean context.\"\n+            val = _chainedInequality.prev.to_string()\n+            # We are about to raise an exception, so it's OK to reset chainedInequality\n+            info = _chainedInequality.call_info\n+            _chainedInequality.call_info = None\n+            _chainedInequality.prev = None\n+\n+            args = ( str(msg).strip(), val.strip(), info[0], info[1],\n+                     ':\\n    %s' % info[3] if info[3] is not None else '.' )\n+            return \"\"\"%s\n+\n+        The inequality expression:\n+            %s\n+        contains non-constant terms (variables) that were evaluated in an\n+        unexpected Boolean context at\n+          File '%s', line %s%s\n+\n+        Evaluating Pyomo variables in a Boolean context, e.g.\n+            if expression <= 5:\n+        is generally invalid.  If you want to obtain the Boolean value of the\n+        expression based on the current variable values, explicitly evaluate the\n+        expression using the value() function:\n+            if value(expression) <= 5:\n+        or\n+            if value(expression <= 5):\n+        \"\"\" % args\n+\n+else:\n+    _chainedInequality = None\n+\n+\n+_ParamData = None\n+SimpleParam = None\n+TemplateExpressionError = None\n+def initialize_expression_data():\n+    \"\"\"\n+    A function used to initialize expression global data.\n+\n+    This function is necessary to avoid global imports.  It is executed\n+    when ``pyomo.environ`` is imported.\n+    \"\"\"\n+    global _ParamData\n+    global SimpleParam\n+    global TemplateExpressionError\n+    from pyomo.core.base.param import _ParamData, SimpleParam\n+    from pyomo.core.base.template_expr import TemplateExpressionError\n+\n+\n+def compress_expression(expr):\n+    \"\"\"\n+    Deprecated function that was used to compress deep Pyomo5\n+    expression trees.\n+    \"\"\"\n+    return expr\n+\n+\n+class clone_counter_context(object):\n+    \"\"\" Context manager for counting cloning events.\n+\n+    This context manager counts the number of times that the\n+    :func:`clone_expression <pyomo.core.expr.current.clone_expression>`\n+    function is executed.\n+    \"\"\"\n+\n+    _count = 0\n+\n+    def __enter__(self):\n+        return self\n+\n+    def __exit__(self, *args):\n+        pass\n+\n+    @property\n+    def count(self):\n+        \"\"\"A property that returns the clone count value.\n+        \"\"\"\n+        return clone_counter_context._count\n+\n+#: A clone counter context manager object that simplifies the\n+#: use of this context manager.  Specifically, different \n+#: instances of this context manger are not necessary.\n+clone_counter = clone_counter_context()\n+\n+\n+class mutable_sum_context(object):\n+    \"\"\" Context manager for mutable sums.\n+\n+    This context manager is used to compute a sum while\n+    treating the summation as a mutable object.\n+    \"\"\"\n+\n+    def __enter__(self):\n+        self.e = _MutableViewSumExpression([])\n+        return self.e\n+\n+    def __exit__(self, *args):\n+        if self.e.__class__ == _MutableViewSumExpression:\n+            self.e.__class__ = ViewSumExpression\n+\n+#: A context manager object for nonlinear expressions.\n+#: This is an instance of the :class:`mutable_sum_contex <pyomo.core.expr.current.mutable_sum_context>` context manager.\n+#: Different instances of this context manager are not necessary, unless these context managers are nested.\n+nonlinear_expression = mutable_sum_context()\n+\n+\n+class mutable_linear_context(object):\n+    \"\"\" Context manager for mutable linear sums.\n+\n+    This context manager is used to compute a linear sum while\n+    treating the summation as a mutable object.\n+    \"\"\"\n+\n+    def __enter__(self):\n+        \"\"\"\n+        The :class:`_MutableLinearExpression <pyomo.core.expr.current._MutableLinearExpression>`\n+        class is the context that is used to to\n+        hold the mutable linear sum.\n+        \"\"\"\n+        self.e = _MutableLinearExpression()\n+        return self.e\n+\n+    def __exit__(self, *args):\n+        \"\"\"\n+        The context is changed to the \n+        :class:`LinearExpression <pyomo.core.expr.current.LinearExpression>`\n+        class to transform the context into a nonmutable\n+        form.\n+        \"\"\"\n+        if self.e.__class__ == _MutableLinearExpression:\n+            self.e.__class__ = LinearExpression\n+\n+#: A context manager object for linear expressions.\n+#: This is an instance of the :class:`mutable_linear_contex <pyomo.core.expr.current.mutable_lienar_context>` context manager.\n+#: Different instances of this context manager are not necessary unless these context managers are nested.\n+linear_expression = mutable_linear_context()\n+\n+\n+#-------------------------------------------------------\n+#\n+# Visitor Logic\n+#\n+#-------------------------------------------------------\n+\n+class SimpleExpressionVisitor(object):\n+    \"\"\"\n+    Note:\n+        This class is a customization of the PyUtilib :class:`SimpleVisitor\n+        <pyutilib.misc.visitor.SimpleVisitor>` class that is tailored\n+        to efficiently walk Pyomo expression trees.  However, this class\n+        is not a subclass of the PyUtilib :class:`SimpleVisitor\n+        <pyutilib.misc.visitor.SimpleVisitor>` class because all key methods\n+        are reimplemented.\n+    \"\"\"\n+\n+    def visit(self, node):  #pragma: no cover\n+        \"\"\"\n+        Visit a node in an expression tree and perform some operation on\n+        it.\n+\n+        This method should be over-written by a user\n+        that is creating a sub-class.\n+\n+        Args:\n+            node: a node in an expression tree\n+\n+        Returns:\n+            nothing\n+        \"\"\"\n+        pass\n+\n+    def finalize(self):     #pragma: no cover\n+        \"\"\"\n+        Return the \"final value\" of the search.\n+\n+        The default implementation returns :const:`None`, because\n+        the traditional visitor pattern does not return a value.\n+\n+        Returns:\n+            The final value after the search.  Default is :const:`None`.\n+        \"\"\"\n+        pass\n+\n+    def xbfs(self, node):\n+        \"\"\"\n+        Breadth-first search of an expression tree, \n+        except that leaf nodes are immediately visited.\n+\n+        Note:\n+            This method has the same functionality as the \n+            PyUtilib :class:`SimpleVisitor.xbfs <pyutilib.misc.visitor.SimpleVisitor.xbfs>`\n+            method.  The difference is that this method\n+            is tailored to efficiently walk Pyomo expression trees.\n+\n+        Args:\n+            node: The root node of the expression tree that is searched.\n+\n+        Returns:\n+            The return value is determined by the :func:`finalize` function,\n+            which may be defined by the user.  Defaults to :const:`None`.\n+        \"\"\"\n+        dq = deque([node])\n+        while dq:\n+            current = dq.popleft()\n+            self.visit(current)\n+            #for c in self.children(current):\n+            for c in current.args:\n+                #if self.is_leaf(c):\n+                if c.__class__ in nonpyomo_leaf_types or not c.is_expression_type() or c.nargs() == 0:\n+                    self.visit(c)\n+                else:\n+                    dq.append(c)\n+        return self.finalize()\n+\n+    def xbfs_yield_leaves(self, node):\n+        \"\"\"\n+        Breadth-first search of an expression tree, except that \n+        leaf nodes are immediately visited.\n+\n+        Note:\n+            This method has the same functionality as the \n+            PyUtilib :class:`SimpleVisitor.xbfs_yield_leaves <pyutilib.misc.visitor.SimpleVisitor.xbfs_yield_leaves>`\n+            method.  The difference is that this method\n+            is tailored to efficiently walk Pyomo expression trees.\n+\n+        Args:\n+            node: The root node of the expression tree\n+                that is searched.\n+\n+        Returns:\n+            The return value is determined by the :func:`finalize` function,\n+            which may be defined by the user.  Defaults to :const:`None`.\n+        \"\"\"\n+        #\n+        # If we start with a leaf, then yield it and stop iteration\n+        #\n+        if node.__class__ in nonpyomo_leaf_types or not node.is_expression_type() or node.nargs() == 0:\n+            ans = self.visit(node)\n+            if not ans is None:\n+                yield ans\n+            raise StopIteration\n+        #\n+        # Iterate through the tree.\n+        #\n+        dq = deque([node])\n+        while dq:\n+            current = dq.popleft()\n+            #self.visit(current)\n+            #for c in self.children(current):\n+            for c in current.args:\n+                #if self.is_leaf(c):\n+                if c.__class__ in nonpyomo_leaf_types or not c.is_expression_type() or c.nargs() == 0:\n+                    ans = self.visit(c)\n+                    if not ans is None:\n+                        yield ans\n+                else:\n+                    dq.append(c)\n+\n+\n+class ExpressionValueVisitor(object):\n+    \"\"\"\n+    Note:\n+        This class is a customization of the PyUtilib :class:`ValueVisitor\n+        <pyutilib.misc.visitor.ValueVisitor>` class that is tailored\n+        to efficiently walk Pyomo expression trees.  However, this class\n+        is not a subclass of the PyUtilib :class:`ValueVisitor\n+        <pyutilib.misc.visitor.ValueVisitor>` class because all key methods\n+        are reimplemented.\n+    \"\"\"\n+\n+    def visit(self, node, values):  #pragma: no cover\n+        \"\"\"\n+        Visit a node in a tree and compute its value using\n+        the values of its children.\n+\n+        This method should be over-written by a user\n+        that is creating a sub-class.\n+\n+        Args:\n+            node: a node in a tree\n+            values: a list of values of this node's children\n+\n+        Returns:\n+            The *value* for this node, which is computed using :attr:`values`\n+        \"\"\"\n+        pass\n+\n+    def visiting_potential_leaf(self, node):    #pragma: no cover\n+        \"\"\" \n+        Visit a node and return its value if it is a leaf.\n+\n+        Note:\n+            This method needs to be over-written for a specific\n+            visitor application.\n+\n+        Args:\n+            node: a node in a tree\n+\n+        Returns:\n+            A tuple: ``(flag, value)``.   If ``flag`` is False,\n+            then the node is not a leaf and ``value`` is :const:`None`.  \n+            Otherwise, ``value`` is the computed value for this node.\n+        \"\"\"\n+        raise RuntimeError(\"The visiting_potential_leaf method needs to be defined.\")\n+\n+    def finalize(self, ans):    #pragma: no cover\n+        \"\"\"\n+        This method defines the return value for the search methods\n+        in this class.\n+\n+        The default implementation returns the value of the\n+        initial node (aka the root node), because\n+        this visitor pattern computes and returns value for each\n+        node to enable the computation of this value.\n+\n+        Args:\n+            ans: The final value computed by the search method.\n+\n+        Returns:\n+            The final value after the search. Defaults to simply\n+            returning :attr:`ans`.\n+        \"\"\"\n+        return ans\n+\n+    def dfs_postorder_stack(self, node):\n+        \"\"\"\n+        Perform a depth-first search in postorder using a stack\n+        implementation.\n+\n+        Note:\n+            This method has the same functionality as the \n+            PyUtilib :class:`ValueVisitor.dfs_postorder_stack <pyutilib.misc.visitor.ValueVisitor.dfs_postorder_stack>`\n+            method.  The difference is that this method\n+            is tailored to efficiently walk Pyomo expression trees.\n+\n+        Args:\n+            node: The root node of the expression tree\n+                that is searched.\n+\n+        Returns:\n+            The return value is determined by the :func:`finalize` function,\n+            which may be defined by the user.\n+        \"\"\"\n+        flag, value = self.visiting_potential_leaf(node)\n+        if flag:\n+            return value\n+        #_stack = [ (node, self.children(node), 0, len(self.children(node)), [])]\n+        _stack = [ (node, node._args_, 0, node.nargs(), [])]\n+        #\n+        # Iterate until the stack is empty\n+        #\n+        # Note: 1 is faster than True for Python 2.x\n+        #\n+        while 1:\n+            #\n+            # Get the top of the stack\n+            #   _obj        Current expression object\n+            #   _argList    The arguments for this expression objet\n+            #   _idx        The current argument being considered\n+            #   _len        The number of arguments\n+            #   _result     The return values\n+            #\n+            _obj, _argList, _idx, _len, _result = _stack.pop()\n+            #\n+            # Iterate through the arguments\n+            #\n+            while _idx < _len:\n+                _sub = _argList[_idx]\n+                _idx += 1\n+                flag, value = self.visiting_potential_leaf(_sub)\n+                if flag:\n+                    _result.append( value )\n+                else:\n+                    #\n+                    # Push an expression onto the stack\n+                    #\n+                    _stack.append( (_obj, _argList, _idx, _len, _result) )\n+                    _obj                    = _sub\n+                    #_argList                = self.children(_sub)\n+                    _argList                = _sub._args_\n+                    _idx                    = 0\n+                    _len                    = _sub.nargs()\n+                    _result                 = []\n+            #\n+            # Process the current node\n+            #\n+            ans = self.visit(_obj, _result)\n+            if _stack:\n+                #\n+                # \"return\" the recursion by putting the return value on the end of the results stack\n+                #\n+                _stack[-1][-1].append( ans )\n+            else:\n+                return self.finalize(ans)\n+\n+\n+class ExpressionReplacementVisitor(object):\n+    \"\"\"\n+    Note:\n+        This class is a customization of the PyUtilib :class:`ValueVisitor\n+        <pyutilib.misc.visitor.ValueVisitor>` class that is tailored\n+        to support replacement of sub-trees in a Pyomo expression\n+        tree.  However, this class is not a subclass of the PyUtilib\n+        :class:`ValueVisitor <pyutilib.misc.visitor.ValueVisitor>`\n+        class because all key methods are reimplemented.\n+    \"\"\"\n+\n+    def __init__(self, memo=None):\n+        \"\"\"\n+        Contruct a visitor that is tailored to support the\n+        replacement of sub-trees in a pyomo expression tree.\n+\n+        Args:\n+            memo (dict): A dictionary mapping object ids to \n+                objects.  This dictionary has the same semantics as\n+                the memo object used with ``copy.deepcopy``.  Defaults\n+                to None, which indicates that no user-defined\n+                dictionary is used.\n+        \"\"\"\n+        if memo is None:\n+            self.memo = {'__block_scope__': { id(None): False }}\n+        else:\n+            self.memo = memo        #pragma: no cover\n+\n+    def visit(self, node, values):\n+        \"\"\"\n+        Visit and clone nodes that have been expanded.\n+\n+        Note:\n+            This method normally does not need to be re-defined\n+            by a user.\n+\n+        Args:\n+            node: The node that will be cloned.\n+            values (list): The list of child nodes that have been\n+                cloned.  These values are used to define the \n+                cloned node.\n+\n+        Returns:\n+            The cloned node.  Default is to simply return the node.\n+        \"\"\"\n+        return node\n+\n+    def visiting_potential_leaf(self, node):    #pragma: no cover\n+        \"\"\" \n+        Visit a node and return a cloned node if it is a leaf.\n+\n+        Note:\n+            This method needs to be over-written for a specific\n+            visitor application.\n+\n+        Args:\n+            node: a node in a tree\n+\n+        Returns:\n+            A tuple: ``(flag, value)``.   If ``flag`` is False,\n+            then the node is not a leaf and ``value`` is :const:`None`.  \n+            Otherwise, ``value`` is a cloned node.\n+        \"\"\"\n+        raise RuntimeError(\"The visiting_potential_leaf method needs to be defined.\")\n+\n+    def finalize(self, ans):\n+        \"\"\"\n+        This method defines the return value for the search methods\n+        in this class.\n+\n+        The default implementation returns the value of the\n+        initial node (aka the root node), because\n+        this visitor pattern computes and returns value for each\n+        node to enable the computation of this value.\n+\n+        Args:\n+            ans: The final value computed by the search method.\n+\n+        Returns:\n+            The final value after the search. Defaults to simply\n+            returning :attr:`ans`.\n+        \"\"\"\n+        return ans\n+\n+    def construct_node(self, node, values):\n+        \"\"\"\n+        Call the expression construct_node() method.\n+        \"\"\"\n+        return node.construct_node( tuple(values), self.memo )\n+\n+    def dfs_postorder_stack(self, node):\n+        \"\"\"\n+        Perform a depth-first search in postorder using a stack\n+        implementation.\n+\n+        This method replaces subtrees.  This method detects if the\n+        :func:`visit` method returns a different object.  If so, then\n+        the node has been replaced and search process is adapted\n+        to replace all subsequent parent nodes in the tree.\n+\n+        Note:\n+            This method has the same functionality as the \n+            PyUtilib :class:`ValueVisitor.dfs_postorder_stack <pyutilib.misc.visitor.ValueVisitor.dfs_postorder_stack>`\n+            method that is tailored to support the\n+            replacement of sub-trees in a Pyomo expression tree.\n+\n+        Args:\n+            node: The root node of the expression tree\n+                that is searched.\n+\n+        Returns:\n+            The return value is determined by the :func:`finalize` function,\n+            which may be defined by the user.\n+        \"\"\"\n+        flag, value = self.visiting_potential_leaf(node)\n+        if flag:\n+            return value\n+        #_stack = [ (node, self.children(node), 0, len(self.children(node)), [])]\n+        _stack = [ (node, node._args_, 0, node.nargs(), [False])]\n+        #\n+        # Iterate until the stack is empty\n+        #\n+        # Note: 1 is faster than True for Python 2.x\n+        #\n+        while 1:\n+            #\n+            # Get the top of the stack\n+            #   _obj        Current expression object\n+            #   _argList    The arguments for this expression objet\n+            #   _idx        The current argument being considered\n+            #   _len        The number of arguments\n+            #   _result     The 'dirty' flag followed by return values\n+            #\n+            _obj, _argList, _idx, _len, _result = _stack.pop()\n+            #\n+            # Iterate through the arguments\n+            #\n+            while _idx < _len:\n+                _sub = _argList[_idx]\n+                _idx += 1\n+                flag, value = self.visiting_potential_leaf(_sub)\n+                if flag:\n+                    if id(value) != id(_sub):\n+                        _result[0] = True\n+                    _result.append( value )\n+                else:\n+                    #\n+                    # Push an expression onto the stack\n+                    #\n+                    _stack.append( (_obj, _argList, _idx, _len, _result) )\n+                    _obj                    = _sub\n+                    #_argList                = self.children(_sub)\n+                    _argList                = _sub._args_\n+                    _idx                    = 0\n+                    _len                    = _sub.nargs()\n+                    _result                 = [False]\n+            #\n+            # Process the current node\n+            #\n+            # If the user has defined a visit() function in a\n+            # subclass, then call that function.  But if the user\n+            # hasn't created a new class and we need to, then\n+            # call the ExpressionReplacementVisitor.visit() function.\n+            #\n+            ans = self.visit(_obj, _result[1:])\n+            if _result[0] and id(ans) == id(_obj):\n+                ans = self.construct_node(_obj, _result[1:])\n+            if _stack:\n+                if _result[0]:\n+                    _stack[-1][-1][0] = True\n+                #\n+                # \"return\" the recursion by putting the return value on the end of the results stack\n+                #\n+                _stack[-1][-1].append( ans )\n+            else:\n+                return self.finalize(ans)\n+\n+\n+\n+#-------------------------------------------------------\n+#\n+# Functions used to process expression trees\n+#\n+#-------------------------------------------------------\n+\n+# =====================================================\n+#  clone_expression\n+# =====================================================\n+\n+class _CloneVisitor(ExpressionValueVisitor):\n+\n+    def __init__(self, clone_leaves=False, memo=None):\n+        self.clone_leaves = clone_leaves\n+        self.memo = memo\n+\n+    def visit(self, node, values):\n+        \"\"\" Visit nodes that have been expanded \"\"\"\n+        return node.construct_node( tuple(values), self.memo )\n+\n+    def visiting_potential_leaf(self, node):\n+        \"\"\" \n+        Visiting a potential leaf.\n+\n+        Return True if the node is not expanded.\n+        \"\"\"\n+        if node.__class__ in nonpyomo_leaf_types:\n+            #\n+            # Store a native or numeric object\n+            #\n+            return True, deepcopy(node, self.memo)\n+\n+        if not node.is_expression_type():\n+            #\n+            # Store a leave object that is cloned\n+            #\n+            if self.clone_leaves:\n+                return True, deepcopy(node, self.memo)\n+            else:\n+                return True, node\n+\n+        if not self.clone_leaves and node.is_named_expression_type():\n+            #\n+            # If we are not cloning leaves, then \n+            # we don't copy the expression tree for a\n+            # named expression.\n+            #\n+            return True, node\n+\n+        return False, None\n+\n+\n+def clone_expression(expr, memo=None, clone_leaves=True):\n+    \"\"\"A function that is used to clone an expression.\n+\n+    Cloning is roughly equivalent to calling ``copy.deepcopy``.\n+    However, the :attr:`clone_leaves` argument can be used to \n+    clone only interior (i.e. non-leaf) nodes in the expression\n+    tree.   Note that named expression objects are treated as\n+    leaves when :attr:`clone_leaves` is :const:`True`, and hence\n+    those subexpressions are not cloned.\n+\n+    This function uses a non-recursive \n+    logic, which makes it more scalable than the logic in \n+    ``copy.deepcopy``.\n+\n+    Args:\n+        expr: The expression that will be cloned.\n+        memo (dict): A dictionary mapping object ids to \n+            objects.  This dictionary has the same semantics as\n+            the memo object used with ``copy.deepcopy``.  Defaults\n+            to None, which indicates that no user-defined\n+            dictionary is used.\n+        clone_leaves (bool): If True, then leaves are\n+            cloned along with the rest of the expression. \n+            Defaults to :const:`True`.\n+   \n+    Returns: \n+        The cloned expression.\n+    \"\"\"\n+    clone_counter_context._count += 1\n+    if not memo:\n+        memo = {'__block_scope__': { id(None): False }}\n+    #\n+    visitor = _CloneVisitor(clone_leaves=clone_leaves, memo=memo)\n+    return visitor.dfs_postorder_stack(expr)\n+\n+\n+# =====================================================\n+#  _sizeof_expression\n+# =====================================================\n+\n+class _SizeVisitor(SimpleExpressionVisitor):\n+\n+    def __init__(self):\n+        self.counter = 0\n+\n+    def visit(self, node):\n+        self.counter += 1\n+\n+    def finalize(self):\n+        return self.counter\n+\n+\n+def _sizeof_expression(expr):\n+    \"\"\"\n+    Return the number of nodes in the expression tree.\n+\n+    Args:\n+        expr: The root node of an expression tree.\n+\n+    Returns:\n+        A non-negative integer that is the number of \n+        interior and leaf nodes in the expression tree.\n+    \"\"\"\n+    visitor = _SizeVisitor()\n+    return visitor.xbfs(expr)\n+    \n+ \n+# =====================================================\n+#  evaluate_expression\n+# =====================================================\n+\n+class _EvaluationVisitor(ExpressionValueVisitor):\n+\n+    def visit(self, node, values):\n+        \"\"\" Visit nodes that have been expanded \"\"\"\n+        return node._apply_operation(values)\n+\n+    def visiting_potential_leaf(self, node):\n+        \"\"\" \n+        Visiting a potential leaf.\n+\n+        Return True if the node is not expanded.\n+        \"\"\"\n+        if node.__class__ in nonpyomo_leaf_types:\n+            return True, node\n+\n+        if node.is_variable_type():\n+            return True, value(node)\n+\n+        if not node.is_expression_type():\n+            return True, value(node)\n+\n+        return False, None\n+\n+\n+def evaluate_expression(exp, exception=True):\n+    \"\"\"\n+    Evaluate the value of the expression.\n+\n+    Args:\n+        expr: The root node of an expression tree.\n+        exception (bool): A flag that indicates whether \n+            exceptions are raised.  If this flag is\n+            :const:`False`, then an exception that\n+            occurs while evaluating the expression \n+            is caught and the return value is :const:`None`.\n+            Default is :const:`True`.\n+\n+    Returns:\n+        A floating point value if the expression evaluates\n+        normally, or :const:`None` if an exception occurs\n+        and is caught.\n+    \"\"\"\n+    try:\n+        visitor = _EvaluationVisitor()\n+        return visitor.dfs_postorder_stack(exp)\n+\n+    except TemplateExpressionError:     #pragma: no cover\n+        if exception:\n+            raise\n+        return None\n+    except ValueError:\n+        if exception:\n+            raise\n+        return None\n+\n+\n+# =====================================================\n+#  identify_components\n+# =====================================================\n+\n+class _ComponentVisitor(SimpleExpressionVisitor):\n+\n+    def __init__(self, types):\n+        self.seen = set()\n+        if types.__class__ is set:\n+            self.types = types\n+        else:\n+            self.types = set(types)\n+        \n+    def visit(self, node):\n+        if node.__class__ in self.types:\n+            if id(node) in self.seen:\n+                return\n+            self.seen.add(id(node))\n+            return node\n+\n+\n+def identify_components(expr, component_types):\n+    \"\"\"\n+    A generator that yields a sequence of nodes\n+    in an expression tree that belong to a specified set.\n+\n+    Args:\n+        expr: The root node of an expression tree.\n+        component_types (set or list): A set of class \n+            types that will be matched during the search.\n+\n+    Yields:\n+        Each node that is found.\n+    \"\"\"\n+    #\n+    # OPTIONS:\n+    # component_types - set (or list) if class types to find\n+    # in the expression.\n+    #\n+    visitor = _ComponentVisitor(component_types)\n+    for v in visitor.xbfs_yield_leaves(expr):\n+        yield v\n+\n+\n+# =====================================================\n+#  identify_variables\n+# =====================================================\n+\n+class _VariableVisitor(SimpleExpressionVisitor):\n+\n+    def __init__(self):\n+        self.seen = set()\n+        \n+    def visit(self, node):\n+        if node.__class__ in nonpyomo_leaf_types:\n+            return\n+\n+        if node.is_variable_type():\n+            if id(node) in self.seen:\n+                return\n+            self.seen.add(id(node))\n+            return node\n+\n+\n+def identify_variables(expr, include_fixed=True):\n+    \"\"\"\n+    A generator that yields a sequence of variables \n+    in an expression tree.\n+\n+    Args:\n+        expr: The root node of an expression tree.\n+        include_fixed (bool): If :const:`True`, then\n+            this generator will yield variables whose\n+            value is fixed.  Defaults to :const:`True`.\n+\n+    Yields:\n+        Each variable that is found.\n+    \"\"\"\n+    visitor = _VariableVisitor()\n+    if include_fixed:\n+        for v in visitor.xbfs_yield_leaves(expr):\n+            yield v\n+    else:\n+        for v in visitor.xbfs_yield_leaves(expr):\n+            if not v.is_fixed():\n+                yield v\n+\n+\n+# =====================================================\n+#  identify_mutable_parameters\n+# =====================================================\n+\n+class _MutableParamVisitor(SimpleExpressionVisitor):\n+\n+    def __init__(self):\n+        self.seen = set()\n+        \n+    def visit(self, node):\n+        if node.__class__ in nonpyomo_leaf_types:\n+            return\n+\n+        # TODO: Confirm that this has the right semantics\n+        if not node.is_variable_type() and node.is_fixed():\n+            if id(node) in self.seen:\n+                return\n+            self.seen.add(id(node))\n+            return node\n+\n+\n+def identify_mutable_parameters(expr):\n+    \"\"\"\n+    A generator that yields a sequence of mutable\n+    parameters in an expression tree.\n+\n+    Args:\n+        expr: The root node of an expression tree.\n+\n+    Yields:\n+        Each mutable parameter that is found.\n+    \"\"\"\n+    visitor = _MutableParamVisitor()\n+    for v in visitor.xbfs_yield_leaves(expr):\n+        yield v\n+\n+\n+# =====================================================\n+#  _polynomial_degree\n+# =====================================================\n+\n+class _PolyDegreeVisitor(ExpressionValueVisitor):\n+\n+    def visit(self, node, values):\n+        \"\"\" Visit nodes that have been expanded \"\"\"\n+        return node._compute_polynomial_degree(values)\n+\n+    def visiting_potential_leaf(self, node):\n+        \"\"\" \n+        Visiting a potential leaf.\n+\n+        Return True if the node is not expanded.\n+        \"\"\"\n+        if node.__class__ in nonpyomo_leaf_types or not node.is_potentially_variable():\n+            return True, 0\n+\n+        if not node.is_expression_type():\n+            return True, 0 if node.is_fixed() else 1\n+\n+        return False, None\n+\n+\n+def _polynomial_degree(node):\n+    \"\"\"\n+    Return the polynomial degree of the expression.\n+\n+    Args:\n+        node: The root node of an expression tree.\n+\n+    Returns:\n+        A non-negative integer that is the polynomial\n+        degree if the expression is polynomial, or :const:`None` otherwise.\n+    \"\"\"\n+    visitor = _PolyDegreeVisitor()\n+    return visitor.dfs_postorder_stack(node)\n+\n+\n+# =====================================================\n+#  _expression_is_fixed\n+# =====================================================\n+\n+class _IsFixedVisitor(ExpressionValueVisitor):\n+    \"\"\"\n+    NOTE: This doesn't check if combiner logic is \n+    all or any and short-circuit the test.  It's\n+    not clear that that is an important optimization.\n+    \"\"\"\n+\n+    def visit(self, node, values):\n+        \"\"\" Visit nodes that have been expanded \"\"\"\n+        return node._is_fixed(values)\n+\n+    def visiting_potential_leaf(self, node):\n+        \"\"\" \n+        Visiting a potential leaf.\n+\n+        Return True if the node is not expanded.\n+        \"\"\"\n+        if node.__class__ in nonpyomo_leaf_types or not node.is_potentially_variable():\n+            return True, True\n+\n+        elif not node.is_expression_type():\n+            return True, node.is_fixed()\n+\n+        return False, None\n+\n+\n+def _expression_is_fixed(node):\n+    \"\"\"\n+    Return the polynomial degree of the expression.\n+\n+    Args:\n+        node: The root node of an expression tree.\n+\n+    Returns:\n+        A non-negative integer that is the polynomial\n+        degree if the expression is polynomial, or :const:`None` otherwise.\n+    \"\"\"\n+    visitor = _IsFixedVisitor()\n+    return visitor.dfs_postorder_stack(node)\n+\n+\n+# =====================================================\n+#  expression_to_string\n+# =====================================================\n+\n+class _ToStringVisitor(ExpressionValueVisitor):\n+\n+    def __init__(self, verbose, smap, compute_values):\n+        super(_ToStringVisitor, self).__init__()\n+        self.verbose = verbose\n+        self.smap = smap\n+        self.compute_values = compute_values\n+\n+    def visit(self, node, values):\n+        \"\"\" Visit nodes that have been expanded \"\"\"\n+        tmp = []\n+        for i,val in enumerate(values):\n+            arg = node._args_[i]\n+\n+            if arg is None:\n+                tmp.append('Undefined')                 # TODO: coverage\n+            elif arg.__class__ in native_numeric_types:\n+                tmp.append(val)\n+            elif arg.__class__ in nonpyomo_leaf_types:\n+                tmp.append(\"'{0}'\".format(val))\n+            elif arg.is_variable_type():\n+                tmp.append(val)\n+            elif not self.verbose and arg.is_expression_type() and node._precedence() < arg._precedence():\n+                tmp.append(\"({0})\".format(val))\n+            else:\n+                tmp.append(val)\n+\n+        return node._to_string(tmp, self.verbose, self.smap, self.compute_values)\n+\n+    def visiting_potential_leaf(self, node):\n+        \"\"\" \n+        Visiting a potential leaf.\n+\n+        Return True if the node is not expanded.\n+        \"\"\"\n+        if node is None:\n+            return True, None                           # TODO: coverage\n+\n+        if node.__class__ in nonpyomo_leaf_types:\n+            return True, str(node)\n+\n+        if node.is_variable_type():\n+            if not node.fixed:\n+                return True, node.to_string(verbose=self.verbose, smap=self.smap, compute_values=False)\n+            return True, node.to_string(verbose=self.verbose, smap=self.smap, compute_values=self.compute_values)\n+\n+        if not node.is_expression_type():\n+            return True, node.to_string(verbose=self.verbose, smap=self.smap, compute_values=self.compute_values)\n+\n+        return False, None\n+\n+\n+def expression_to_string(expr, verbose=None, labeler=None, smap=None, compute_values=False, standardize=False):\n+    \"\"\"\n+    Return a string representation of an expression.\n+\n+    Args:\n+        expr: The root node of an expression tree.\n+        verbose (bool): If :const:`True`, then the output is\n+            a nested functional form.  Otherwise, the output\n+            is an algebraic expression.  Default is :const:`False`.\n+        labeler:  If specified, this labeler is used to label\n+            variables in the expression.\n+        smap:  If specified, this :class:`SymbolMap <pyomo.core.expr.symbol_map.SymbolMap>` is\n+            used to cache labels.\n+        compute_values (bool): If :const:`True`, then \n+            parameters and fixed variables are evaluated before the\n+            expression string is generated.  Default is :const:`False`.\n+        standardize (bool): If :const:`True` and :attr:`verbose` is :const:`False`, then the\n+            expression form is standardized to pull out constant and linear terms.\n+            Default is :const:`False`.\n+\n+    Returns:\n+        A string representation for the expression.\n+    \"\"\"\n+    verbose = common.TO_STRING_VERBOSE if verbose is None else verbose\n+    #\n+    # Standardize the output of expressions if requested (when verbose=False).\n+    # This involves constructing a standard representation and then creating \n+    # a string.\n+    #\n+    if standardize and not verbose:\n+        from pyomo.repn import generate_standard_repn\n+        try:\n+            if expr.__class__ is EqualityExpression:\n+                repn0 = generate_standard_repn(expr._args_[0], quadratic=True, compute_values=compute_values)\n+                repn1 = generate_standard_repn(expr._args_[1], quadratic=True, compute_values=compute_values)\n+                expr = EqualityExpression( (repn0.to_expression(), repn1.to_expression()) )\n+            elif expr.__class__ is InequalityExpression:\n+                repn0 = generate_standard_repn(expr._args_[0], quadratic=True, compute_values=compute_values)\n+                repn1 = generate_standard_repn(expr._args_[1], quadratic=True, compute_values=compute_values)\n+                expr = InequalityExpression( (repn0.to_expression(), repn1.to_expression()), strict=expr._strict )\n+            elif expr.__class__ is RangedExpression:\n+                repn0 = generate_standard_repn(expr._args_[0], quadratic=True, compute_values=compute_values)\n+                repn1 = generate_standard_repn(expr._args_[1], quadratic=True, compute_values=compute_values)\n+                repn2 = generate_standard_repn(expr._args_[2], quadratic=True, compute_values=compute_values)\n+                expr = RangedExpression( (repn0.to_expression(), repn1.to_expression(), repn2.to_expression()), strict=expr._strict )\n+            else:\n+                repn = generate_standard_repn(expr, quadratic=True, compute_values=compute_values)\n+                expr = repn.to_expression()\n+        except:     #pragma: no cover\n+            #\n+            # Generation of the standard repn will fail if the \n+            # expression is uninitialized.  Hence, we default to \n+            # using the non-standardized form.\n+            #\n+            # It might be smarter to raise errors for specific issues (e.g. uninitialized parameters).\n+            # Let's see if we start seeing errors that are masked here.\n+            #\n+            pass\n+    #\n+    # Setup the symbol map\n+    #\n+    if labeler is not None:\n+        if smap is None:\n+            smap = SymbolMap()\n+        smap.default_labeler = labeler\n+    #\n+    # Create and execute the visitor pattern\n+    #\n+    visitor = _ToStringVisitor(verbose, smap, compute_values)\n+    return visitor.dfs_postorder_stack(expr)\n+\n+\n+#-------------------------------------------------------\n+#\n+# Expression classes\n+#\n+#-------------------------------------------------------\n+\n+\n+class ExpressionBase(NumericValue):\n+    \"\"\"\n+    The base class for Pyomo expressions.\n+\n+    This class is used to define nodes in an expression\n+    tree.\n+\n+    Args:\n+        args (list or tuple): Children of this node.\n+    \"\"\"\n+\n+    __slots__ =  ('_args_',)\n+    PRECEDENCE = 0\n+\n+    def __init__(self, args):\n+        self._args_ = args\n+\n+    def nargs(self):\n+        \"\"\"\n+        Returns the number of child nodes.\n+        \n+        By default, Pyomo expressions represent binary operations\n+        with two arguments.\n+    \n+        Note:\n+            This function does not simply compute the length of\n+            :attr:`_args_` because some expression classes use\n+            a subset of the :attr:`_args_` array.  Thus, it\n+            is imperative that developers use this method!\n+\n+        Returns:\n+            A nonnegative integer that is the number of child nodes.\n+        \"\"\"\n+        return 2\n+\n+    def arg(self, i):\n+        \"\"\"\n+        Return the i-th child node.\n+\n+        Args:\n+            i (int): Nonnegative index of the child that is returned.\n+\n+        Returns:\n+            The i-th child node.\n+        \"\"\"\n+        if i >= self.nargs():\n+            raise KeyError(\"Invalid index for expression argument: %d\" % i)\n+        if i < 0:\n+            return self._args_[self.nargs()+i]    \n+        return self._args_[i]\n+\n+    @property\n+    def args(self):\n+        \"\"\"\n+        A generator that yields the child nodes.\n+\n+        Yields:\n+            Each child node in order.\n+        \"\"\"\n+        return islice(self._args_, self.nargs())\n+\n+    def __getstate__(self):\n+        \"\"\"\n+        Pickle the expression object\n+\n+        Returns:\n+            The pickled state.\n+        \"\"\"\n+        state = super(ExpressionBase, self).__getstate__()\n+        for i in ExpressionBase.__slots__:\n+           state[i] = getattr(self,i)\n+        return state\n+\n+    def __nonzero__(self):      #pragma: no cover\n+        \"\"\"\n+        Compute the value of the expression and convert it to\n+        a boolean.\n+\n+        Returns:\n+            A boolean value.\n+        \"\"\"\n+        return bool(self())\n+\n+    __bool__ = __nonzero__\n+\n+    def __call__(self, exception=True):\n+        \"\"\"\n+        Evaluate the value of the expression tree.\n+\n+        Args:\n+            exception (bool): If :const:`False`, then\n+                an exception raised while evaluating\n+                is captured, and the value returned is\n+                :const:`None`.  Default is :const:`True`.\n+\n+        Returns:\n+            The value of the expression or :const:`None`.\n+        \"\"\"\n+        return evaluate_expression(self, exception)\n+\n+    def __str__(self):\n+        \"\"\"\n+        Returns a string description of the expression.\n+\n+        Note:\n+            The value of ``pyomo.core.expr.expr_common.TO_STRING_VERBOSE``\n+            is used to configure the execution of this method.\n+            If this value is :const:`True`, then the string\n+            representation is a nested function description of the expression.\n+            The default is :const:`False`, which is an algebraic\n+            description of the expression.\n+\n+        Returns:\n+            A string.\n+        \"\"\"\n+        return expression_to_string(self, standardize=True)\n+\n+    def to_string(self, verbose=None, labeler=None, smap=None, compute_values=False):\n+        \"\"\"\n+        Return a string representation of the expression tree.\n+\n+        Args:\n+            verbose (bool): If :const:`True`, then the the string \n+                representation consists of nested functions.  Otherwise,\n+                the string representation is an algebraic equation.\n+                Defaults to :const:`False`.\n+            labeler: An object that generates string labels for \n+                variables in the expression tree.  Defaults to :const:`None`.\n+            smap:  If specified, this :class:`SymbolMap <pyomo.core.expr.symbol_map.SymbolMap>` is\n+                used to cache labels for variables.\n+            compute_values (bool): If :const:`True`, then \n+                parameters and fixed variables are evaluated before the\n+                expression string is generated.  Default is :const:`False`.\n+\n+        Returns:\n+            A string representation for the expression tree.\n+        \"\"\"\n+        return expression_to_string(self, verbose=verbose, labeler=labeler, smap=smap, compute_values=compute_values)\n+\n+    def _precedence(self):\n+        return ExpressionBase.PRECEDENCE\n+\n+    def _to_string(self, values, verbose, smap, compute_values):            #pragma: no cover\n+        \"\"\"\n+        Construct a string representation for this node, using the string\n+        representations of its children.\n+\n+        This method is called by the :class:`_ToStringVisitor\n+        <pyomo.core.expr.current._ToStringVisitor>` class.  It must\n+        must be defined in subclasses.\n+\n+        Args:\n+            values (list): The string representations of the children of this\n+                node.\n+            verbose (bool): If :const:`True`, then the the string \n+                representation consists of nested functions.  Otherwise,\n+                the string representation is an algebraic equation.\n+            smap:  If specified, this :class:`SymbolMap\n+                <pyomo.core.expr.symbol_map.SymbolMap>` is\n+                used to cache labels for variables.\n+            compute_values (bool): If :const:`True`, then \n+                parameters and fixed variables are evaluated before the\n+                expression string is generated.\n+\n+        Returns:\n+            A string representation for this node.\n+        \"\"\"\n+        pass\n+\n+    def getname(self, *args, **kwds):                       #pragma: no cover\n+        \"\"\"\n+        Return the text name of a function associated with this expression object.\n+\n+        In general, no arguments are passed to this function.\n+\n+        Args:\n+            *arg: a variable length list of arguments\n+            **kwds: keyword arguments\n+\n+        Returns:\n+            A string name for the function.\n+        \"\"\"\n+        raise NotImplementedError(\"Derived expression (%s) failed to \"\\\n+            \"implement getname()\" % ( str(self.__class__), ))\n+\n+    def clone(self, substitute=None):\n+        \"\"\"\n+        Return a clone of the expression tree.\n+\n+        Note:\n+            This method does not clone the leaves of the tree,\n+            which are numeric constants and variables.  It only \n+            clones the interior nodes, and expression leaf nodes\n+            like :class:`_MutableLinearExpression <pyomo.core.expr.current._MutableLinearExpression>`.  However, named expressions are treated \n+            like leaves, and they are not cloned.\n+            \n+        Args:\n+            substitute (dict): a dictionary that maps object ids to clone\n+                objects generated earlier during the cloning process.\n+\n+        Returns:\n+            A new expression tree.\n+        \"\"\"\n+        return clone_expression(self, memo=substitute, clone_leaves=False)\n+\n+    def __deepcopy__(self, memo):\n+        \"\"\"\n+        Return a clone of the expression tree.\n+\n+        Note:\n+            This method clones the leaves of the tree.\n+        Args:\n+            memo (dict): a dictionary that maps object ids to clone\n+                objects generated earlier during the cloning process.\n+\n+        Returns:\n+            A new expression tree.\n+        \"\"\"\n+        return clone_expression(self, memo=memo, clone_leaves=True)\n+\n+    def construct_node(self, args, memo):\n+        \"\"\"\n+        Construct a node using given arguments. \n+   \n+        This class provides a consistent interface for constructing a\n+        node, which is used in tree visitor scripts.  In the simplest\n+        case, this simply returns::\n+\n+            self.__class__(args))\n+\n+        But in general this constructs a copy of the current\n+        expression object using local data as well as arguments\n+        that represent the child nodes.   Thus, this method can be viewed\n+        as a node-level clone method.\n+\n+        Args:\n+            args (list): A list of child nodes for the new expression\n+                object\n+            memo (dict): A dictionary that maps object ids to clone\n+                objects generated earlier during a cloning process.\n+                This argument is needed to clone objects that are\n+                owned by a model, and it can be safely ignored for\n+                most expression classes.\n+\n+        Returns:\n+            A new expression object with the same type as the current\n+            class.\n+        \"\"\"\n+        return self.__class__(args)\n+\n+    def is_constant(self):\n+        \"\"\"Return True if this expression is an atomic constant\n+\n+        This method contrasts with the is_fixed() method.  This method\n+        returns True if the expression is an atomic constant, that is it\n+        is composed exclusively of constants and immutable parameters.\n+        NumericValue objects returning is_constant() == True may be\n+        simplified to their numeric value at any point without warning.\n+\n+        Note:  This defaults to False, but gets redefined in sub-classes.\n+        \"\"\"\n+        return False\n+\n+    def is_fixed(self):\n+        \"\"\"\n+        Return :const:`True` if this expression contains no free variables.\n+\n+        Returns:\n+            A boolean.\n+        \"\"\"\n+        return _expression_is_fixed(self)\n+\n+    def _is_fixed(self, values):\n+        \"\"\"\n+        Compute whether this expression is fixed given\n+        the fixed values of its children.\n+\n+        This method is called by the :class:`_IsFixedVisitor\n+        <pyomo.core.expr.current._IsFixedVisitor>` class.  It can\n+        be over-written by expression classes to customize this\n+        logic.\n+\n+        Args:\n+            values (list): A list of boolean values that indicate whether\n+                the children of this expression are fixed\n+\n+        Returns:\n+            A boolean that is :const:`True` if the fixed values of the\n+            children are all :const:`True`.\n+        \"\"\"\n+        return all(values)\n+\n+    def is_potentially_variable(self):\n+        \"\"\"\n+        Return :const:`True` if this expression contains variables.\n+\n+        This method returns :const:`True` if there are any variables\n+        within this expression.  This method returns :const:`True`\n+        even if there the variables in the expression are currently\n+        fixed.\n+\n+        Returns:\n+            A boolean.  Defaults to :const:`True` for expressions.\n+        \"\"\"\n+        return True\n+\n+    def is_named_expression_type(self):\n+        \"\"\"\n+        Return :const:`True` if this object is a named expression.\n+\n+        This method returns :const:`False` for this class, and it\n+        is included in other classes within Pyomo that are not named\n+        expressions, which allows for a check for named expressions \n+        without evaluating the class type.\n+\n+        Returns:\n+            A boolean.\n+        \"\"\"\n+        return False\n+\n+    def is_expression_type(self):\n+        \"\"\"\n+        Return :const:`True` if this object is an expression.\n+\n+        This method obviously returns :const:`True` for this class, but it\n+        is included in other classes within Pyomo that are not expressions,\n+        which allows for a check for expressions without\n+        evaluating the class type.\n+\n+        Returns:\n+            A boolean.\n+        \"\"\"\n+        return True\n+\n+    def size(self):\n+        \"\"\"\n+        Return the number of nodes in the expression tree.\n+\n+        Returns:\n+            A nonnegative integer that is the number of interior and leaf\n+            nodes in the expression tree.\n+        \"\"\"\n+        return _sizeof_expression(self)\n+\n+    def polynomial_degree(self):\n+        \"\"\"\n+        Return the polynomial degree of the expression.\n+\n+        Returns:\n+            A nonnegative integer that is the polynomial degree of\n+            the expression, if the expression is polynomial.  And\n+            :const:`None` otherwise.\n+        \"\"\"\n+        return _polynomial_degree(self)\n+\n+    def _compute_polynomial_degree(self, values):                          #pragma: no cover\n+        \"\"\"\n+        Compute the polynomial degree of this expression given\n+        the degree values of its children.\n+\n+        This method is called by the :class:`_PolyDegreeVisitor\n+        <pyomo.core.expr.current._PolyDegreeVisitor>` class.  It can\n+        be over-written by expression classes to customize this\n+        logic.\n+\n+        Args:\n+            values (list): A list of values that indicate the degree\n+                of the children expression.\n+\n+        Returns:\n+            A nonnegative integer that is the polynomial degree of the\n+            expression, or :const:`None`.  Default is :const:`None`.\n+        \"\"\"\n+        return None\n+\n+    def _apply_operation(self, result):     #pragma: no cover\n+        \"\"\"\n+        Compute the values of this node given the values of its children.\n+\n+        This method is called by the :class:`_EvaluationVisitor\n+        <pyomo.core.expr.current._EvaluationVisitor>` class.  It must\n+        be over-written by expression classes to customize this logic.\n+\n+        Note:\n+            This method applies the logical operation of the\n+            operator to the arguments.  It does *not* evaluate\n+            the arguments in the process, but assumes that they\n+            have been previously evaluated.  But noted that if\n+            this class contains auxilliary data (e.g. like the\n+            numeric coefficients in the :class:`LinearExpression\n+            <pyomo.core.expr.current.LinearExpression>` class, then\n+            those values *must* be evaluated as part of this\n+            function call.  An uninitialized parameter value\n+            encountered during the execution of this method is\n+            considered an error.\n+\n+        Args:\n+            values (list): A list of values that indicate the value\n+                of the children expressions.\n+\n+        Returns:\n+            A floating point value for this expression.\n+        \"\"\"\n+        raise NotImplementedError(\"Derived expression (%s) failed to \"\\\n+            \"implement _apply_operation()\" % ( str(self.__class__), ))\n+\n+\n+class NegationExpression(ExpressionBase):\n+    \"\"\"\n+    Negation expressions::\n+\n+        - x\n+    \"\"\"\n+\n+    __slots__ = ()\n+\n+    PRECEDENCE = 4\n+\n+    def nargs(self):\n+        return 1\n+\n+    def getname(self, *args, **kwds):\n+        return 'neg'\n+\n+    def _compute_polynomial_degree(self, result):\n+        return result[0]\n+\n+    def _precedence(self):\n+        return NegationExpression.PRECEDENCE\n+\n+    def _to_string(self, values, verbose, smap, compute_values):\n+        if verbose:\n+            return \"{0}({1})\".format(self.getname(), values[0])\n+        tmp = values[0]\n+        if tmp[0] == '-':\n+            i = 1\n+            while tmp[i] == ' ':\n+                i += 1\n+            return tmp[i:]\n+        return \"- \"+tmp\n+\n+    def _apply_operation(self, result):\n+        return -result[0]\n+\n+\n+class NPV_NegationExpression(NegationExpression):\n+    __slots__ = ()\n+\n+    def is_potentially_variable(self):\n+        return False\n+\n+\n+class ExternalFunctionExpression(ExpressionBase):\n+    \"\"\"\n+    External function expressions\n+\n+    Example::\n+\n+        model = ConcreteModel()\n+        model.a = Var()\n+        model.f = ExternalFunction(library='foo.so', function='bar')\n+        expr = model.f(model.a)\n+\n+    Args: \n+        args (tuple): children of this node\n+        fcn: a class that defines this external function\n+    \"\"\"\n+    __slots__ = ('_fcn',)\n+\n+    def __init__(self, args, fcn=None):\n+        self._args_ = args\n+        self._fcn = fcn\n+\n+    def nargs(self):\n+        return len(self._args_)\n+\n+    def construct_node(self, args, memo):\n+        return self.__class__(args, self._fcn)\n+\n+    def __getstate__(self):\n+        state = super(ExternalFunctionExpression, self).__getstate__()\n+        for i in ExternalFunctionExpression.__slots__:\n+            state[i] = getattr(self, i)\n+        return state\n+\n+    def getname(self, *args, **kwds):           #pragma: no cover\n+        return self._fcn.getname(*args, **kwds)\n+\n+    def _compute_polynomial_degree(self, result):\n+        # If the expression is constant, then \n+        # this is detected earlier.  Hence, we can safely\n+        # return None.\n+        return None\n+\n+    def _apply_operation(self, result):\n+        return self._fcn.evaluate( result )     #pragma: no cover\n+\n+    def _to_string(self, values, verbose, smap, compute_values):\n+        return \"{0}({1})\".format(self.getname(), \", \".join(values))\n+\n+\n+class NPV_ExternalFunctionExpression(ExternalFunctionExpression):\n+    __slots__ = ()\n+\n+    def is_potentially_variable(self):\n+        return False\n+\n+\n+class PowExpression(ExpressionBase):\n+    \"\"\"\n+    Power expressions::\n+\n+        x**y\n+    \"\"\"\n+\n+    __slots__ = ()\n+    PRECEDENCE = 2\n+\n+    def _compute_polynomial_degree(self, result):\n+        # PowExpression is a tricky thing.  In general, a**b is\n+        # nonpolynomial, however, if b == 0, it is a constant\n+        # expression, and if a is polynomial and b is a positive\n+        # integer, it is also polynomial.  While we would like to just\n+        # call this a non-polynomial expression, these exceptions occur\n+        # too frequently (and in particular, a**2)\n+        l,r = result\n+        if isclose(r, 0):\n+            if isclose(l, 0):\n+                return 0\n+            # NOTE: use value before int() so that we don't\n+            #       run into the disabled __int__ method on\n+            #       NumericValue\n+            exp = value(self._args_[1], exception=False)\n+            if exp is None:\n+                return None\n+            if exp == int(exp):\n+                if l is not None and exp > 0:\n+                    return l * exp\n+                elif exp == 0:\n+                    return 0\n+        return None\n+\n+    def _is_fixed(self, args):\n+        assert(len(args) == 2)\n+        if not args[1]:\n+            return False\n+        return args[0] or isclose(value(self._args_[1]), 0)\n+\n+    def _precedence(self):\n+        return PowExpression.PRECEDENCE\n+\n+    def _apply_operation(self, result):\n+        _l, _r = result\n+        return _l ** _r\n+\n+    def getname(self, *args, **kwds):\n+        return 'pow'\n+\n+    def _to_string(self, values, verbose, smap, compute_values):\n+        if verbose:\n+            return \"{0}({1}, {2})\".format(self.getname(), values[0], values[1])\n+        return \"{0}**{1}\".format(values[0], values[1])\n+\n+\n+class NPV_PowExpression(PowExpression):\n+    __slots__ = ()\n+\n+    def is_potentially_variable(self):\n+        return False\n+\n+\n+class ProductExpression(ExpressionBase):\n+    \"\"\"\n+    Product expressions::\n+\n+        x*y\n+    \"\"\"\n+\n+    __slots__ = ()\n+    PRECEDENCE = 4\n+\n+    def _precedence(self):\n+        return ProductExpression.PRECEDENCE\n+\n+    def _compute_polynomial_degree(self, result):\n+        # NB: We can't use sum() here because None (non-polynomial)\n+        # overrides a numeric value (and sum() just ignores it - or\n+        # errors in py3k)\n+        a, b = result\n+        if a is None or b is None:\n+            return None\n+        else:\n+            return a + b\n+\n+    def getname(self, *args, **kwds):\n+        return 'prod'\n+\n+    def _apply_operation(self, result):\n+        _l, _r = result\n+        return _l * _r\n+\n+    def _to_string(self, values, verbose, smap, compute_values):\n+        if verbose:\n+            return \"{0}({1}, {2})\".format(self.getname(), values[0], values[1])\n+        if values[0] == \"1\" or values[0] == \"1.0\":\n+            return values[1]\n+        if values[0] == \"-1\" or values[0] == \"-1.0\":\n+            return \"- {0}\".format(values[1])\n+        return \"{0}*{1}\".format(values[0],values[1])\n+\n+\n+class NPV_ProductExpression(ProductExpression):\n+    __slots__ = ()\n+\n+    def is_potentially_variable(self):\n+        return False\n+\n+\n+class ReciprocalExpression(ExpressionBase):\n+    \"\"\"\n+    Reciprocal expressions::\n+\n+        1/x\n+    \"\"\"\n+    __slots__ = ()\n+    PRECEDENCE = 3.5\n+\n+    def nargs(self):\n+        return 1\n+\n+    def _precedence(self):\n+        return ReciprocalExpression.PRECEDENCE\n+\n+    def _compute_polynomial_degree(self, result):\n+        if result[0] is 0:\n+            return 0\n+        return None\n+\n+    def getname(self, *args, **kwds):\n+        return 'recip'\n+\n+    def _to_string(self, values, verbose, smap, compute_values):\n+        if verbose:\n+            return \"{0}({1})\".format(self.getname(), values[0])\n+        return \"(1/{0})\".format(values[0])\n+\n+    def _apply_operation(self, result):\n+        return 1 / result[0]\n+\n+\n+class NPV_ReciprocalExpression(ReciprocalExpression):\n+    __slots__ = ()\n+\n+    def is_potentially_variable(self):\n+        return False\n+\n+\n+class _LinearOperatorExpression(ExpressionBase):\n+    \"\"\"\n+    An 'abstract' class that defines the polynomial degree for a simple\n+    linear operator\n+    \"\"\"\n+\n+    __slots__ = ()\n+\n+    def _compute_polynomial_degree(self, result):\n+        # NB: We can't use max() here because None (non-polynomial)\n+        # overrides a numeric value (and max() just ignores it)\n+        ans = 0\n+        for x in result:\n+            if x is None:\n+                return None\n+            elif ans < x:\n+                ans = x\n+        return ans\n+\n+\n+class RangedExpression(_LinearOperatorExpression):\n+    \"\"\"\n+    Ranged expressions, which define relations with a lower and upper bound::\n+\n+        x < y < z\n+        x <= y <= z\n+\n+    Args:\n+        args (tuple): child nodes\n+        strict (tuple): flags that indicates whether the inequalities are strict\n+    \"\"\"\n+\n+    __slots__ = ('_strict',)\n+    PRECEDENCE = 9\n+\n+    def __init__(self, args, strict):\n+        super(RangedExpression,self).__init__(args)\n+        self._strict = strict\n+\n+    def nargs(self):\n+        return 3\n+\n+    def construct_node(self, args, memo):\n+        return self.__class__(args, self._strict)\n+\n+    def __getstate__(self):\n+        state = super(RangedExpression, self).__getstate__()\n+        for i in RangedExpression.__slots__:\n+            state[i] = getattr(self, i)\n+        return state\n+\n+    def __nonzero__(self):\n+        return bool(self())\n+\n+    __bool__ = __nonzero__\n+\n+    def is_relational(self):\n+        return True\n+\n+    def _precedence(self):\n+        return RangedExpression.PRECEDENCE\n+\n+    def _apply_operation(self, result):\n+        _l, _b, _r = result\n+        if not self._strict[0]:\n+            if not self._strict[1]:\n+                return _l <= _b and _b <= _r\n+            else:\n+                return _l <= _b and _b < _r\n+        elif not self._strict[1]:\n+            return _l < _b and _b <= _r\n+        else:\n+            return _l < _b and _b < _r\n+\n+    def _to_string(self, values, verbose, smap, compute_values):\n+        return \"{0}  {1}  {2}  {3}  {4}\".format(values[0], '<' if self._strict[0] else '<=', values[1], '<' if self._strict[1] else '<=', values[2])\n+        \n+    def is_constant(self):\n+        return (self._args_[0].__class__ in native_numeric_types or self._args_[0].is_constant()) and \\\n+           ","path":"pyomo/core/expr/expr_pyomo5.py","position":null,"original_position":2479,"commit_id":"38da521de783f4fdc8ba26d79fa5cf87a4a0b573","original_commit_id":"de341c6373f217b7beb6ade31aa977231b6da67d","user":{"login":"whart222","id":728056,"avatar_url":"https://avatars2.githubusercontent.com/u/728056?v=4","gravatar_id":"","url":"https://api.github.com/users/whart222","html_url":"https://github.com/whart222","followers_url":"https://api.github.com/users/whart222/followers","following_url":"https://api.github.com/users/whart222/following{/other_user}","gists_url":"https://api.github.com/users/whart222/gists{/gist_id}","starred_url":"https://api.github.com/users/whart222/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/whart222/subscriptions","organizations_url":"https://api.github.com/users/whart222/orgs","repos_url":"https://api.github.com/users/whart222/repos","events_url":"https://api.github.com/users/whart222/events{/privacy}","received_events_url":"https://api.github.com/users/whart222/received_events","type":"User","site_admin":false},"body":"Done","created_at":"2018-04-16T04:57:11Z","updated_at":"2018-04-16T04:57:11Z","html_url":"https://github.com/Pyomo/pyomo/pull/272#discussion_r181622275","pull_request_url":"https://api.github.com/repos/Pyomo/pyomo/pulls/272","author_association":"OWNER","_links":{"self":{"href":"https://api.github.com/repos/Pyomo/pyomo/pulls/comments/181622275"},"html":{"href":"https://github.com/Pyomo/pyomo/pull/272#discussion_r181622275"},"pull_request":{"href":"https://api.github.com/repos/Pyomo/pyomo/pulls/272"}},"in_reply_to_id":174200335},"pull_request":{"url":"https://api.github.com/repos/Pyomo/pyomo/pulls/272","id":156353693,"html_url":"https://github.com/Pyomo/pyomo/pull/272","diff_url":"https://github.com/Pyomo/pyomo/pull/272.diff","patch_url":"https://github.com/Pyomo/pyomo/pull/272.patch","issue_url":"https://api.github.com/repos/Pyomo/pyomo/issues/272","number":272,"state":"open","locked":false,"title":"[WIP] Merge Expression Branch","user":{"login":"whart222","id":728056,"avatar_url":"https://avatars2.githubusercontent.com/u/728056?v=4","gravatar_id":"","url":"https://api.github.com/users/whart222","html_url":"https://github.com/whart222","followers_url":"https://api.github.com/users/whart222/followers","following_url":"https://api.github.com/users/whart222/following{/other_user}","gists_url":"https://api.github.com/users/whart222/gists{/gist_id}","starred_url":"https://api.github.com/users/whart222/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/whart222/subscriptions","organizations_url":"https://api.github.com/users/whart222/orgs","repos_url":"https://api.github.com/users/whart222/repos","events_url":"https://api.github.com/users/whart222/events{/privacy}","received_events_url":"https://api.github.com/users/whart222/received_events","type":"User","site_admin":false},"body":"This PR can be referenced to discuss issues that need to be resolved to merge the expressions branch:  expr_dev.\r\n\r\nDependencies:\r\n\r\n- [x] Resolve #276 (Kernel subclasses)\r\n- [x] Resolve #212 (GDP Rework) and merge into this branch","created_at":"2017-12-05T01:55:20Z","updated_at":"2018-04-16T04:57:11Z","closed_at":null,"merged_at":null,"merge_commit_sha":"590f2c81601c61ec8c58cc17b463af0a05dc43c8","assignee":{"login":"jsiirola","id":356359,"avatar_url":"https://avatars3.githubusercontent.com/u/356359?v=4","gravatar_id":"","url":"https://api.github.com/users/jsiirola","html_url":"https://github.com/jsiirola","followers_url":"https://api.github.com/users/jsiirola/followers","following_url":"https://api.github.com/users/jsiirola/following{/other_user}","gists_url":"https://api.github.com/users/jsiirola/gists{/gist_id}","starred_url":"https://api.github.com/users/jsiirola/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jsiirola/subscriptions","organizations_url":"https://api.github.com/users/jsiirola/orgs","repos_url":"https://api.github.com/users/jsiirola/repos","events_url":"https://api.github.com/users/jsiirola/events{/privacy}","received_events_url":"https://api.github.com/users/jsiirola/received_events","type":"User","site_admin":false},"assignees":[{"login":"jsiirola","id":356359,"avatar_url":"https://avatars3.githubusercontent.com/u/356359?v=4","gravatar_id":"","url":"https://api.github.com/users/jsiirola","html_url":"https://github.com/jsiirola","followers_url":"https://api.github.com/users/jsiirola/followers","following_url":"https://api.github.com/users/jsiirola/following{/other_user}","gists_url":"https://api.github.com/users/jsiirola/gists{/gist_id}","starred_url":"https://api.github.com/users/jsiirola/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jsiirola/subscriptions","organizations_url":"https://api.github.com/users/jsiirola/orgs","repos_url":"https://api.github.com/users/jsiirola/repos","events_url":"https://api.github.com/users/jsiirola/events{/privacy}","received_events_url":"https://api.github.com/users/jsiirola/received_events","type":"User","site_admin":false},{"login":"whart222","id":728056,"avatar_url":"https://avatars2.githubusercontent.com/u/728056?v=4","gravatar_id":"","url":"https://api.github.com/users/whart222","html_url":"https://github.com/whart222","followers_url":"https://api.github.com/users/whart222/followers","following_url":"https://api.github.com/users/whart222/following{/other_user}","gists_url":"https://api.github.com/users/whart222/gists{/gist_id}","starred_url":"https://api.github.com/users/whart222/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/whart222/subscriptions","organizations_url":"https://api.github.com/users/whart222/orgs","repos_url":"https://api.github.com/users/whart222/repos","events_url":"https://api.github.com/users/whart222/events{/privacy}","received_events_url":"https://api.github.com/users/whart222/received_events","type":"User","site_admin":false}],"requested_reviewers":[{"login":"blnicho","id":18425615,"avatar_url":"https://avatars1.githubusercontent.com/u/18425615?v=4","gravatar_id":"","url":"https://api.github.com/users/blnicho","html_url":"https://github.com/blnicho","followers_url":"https://api.github.com/users/blnicho/followers","following_url":"https://api.github.com/users/blnicho/following{/other_user}","gists_url":"https://api.github.com/users/blnicho/gists{/gist_id}","starred_url":"https://api.github.com/users/blnicho/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/blnicho/subscriptions","organizations_url":"https://api.github.com/users/blnicho/orgs","repos_url":"https://api.github.com/users/blnicho/repos","events_url":"https://api.github.com/users/blnicho/events{/privacy}","received_events_url":"https://api.github.com/users/blnicho/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[{"id":504976042,"url":"https://api.github.com/repos/Pyomo/pyomo/labels/IDAES%20related","name":"IDAES related","color":"00b3f7","default":false},{"id":838460434,"url":"https://api.github.com/repos/Pyomo/pyomo/labels/pyomo.core","name":"pyomo.core","color":"1d76db","default":false}],"milestone":null,"commits_url":"https://api.github.com/repos/Pyomo/pyomo/pulls/272/commits","review_comments_url":"https://api.github.com/repos/Pyomo/pyomo/pulls/272/comments","review_comment_url":"https://api.github.com/repos/Pyomo/pyomo/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Pyomo/pyomo/issues/272/comments","statuses_url":"https://api.github.com/repos/Pyomo/pyomo/statuses/38da521de783f4fdc8ba26d79fa5cf87a4a0b573","head":{"label":"Pyomo:expr_dev","ref":"expr_dev","sha":"38da521de783f4fdc8ba26d79fa5cf87a4a0b573","user":{"login":"Pyomo","id":10505959,"avatar_url":"https://avatars2.githubusercontent.com/u/10505959?v=4","gravatar_id":"","url":"https://api.github.com/users/Pyomo","html_url":"https://github.com/Pyomo","followers_url":"https://api.github.com/users/Pyomo/followers","following_url":"https://api.github.com/users/Pyomo/following{/other_user}","gists_url":"https://api.github.com/users/Pyomo/gists{/gist_id}","starred_url":"https://api.github.com/users/Pyomo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Pyomo/subscriptions","organizations_url":"https://api.github.com/users/Pyomo/orgs","repos_url":"https://api.github.com/users/Pyomo/repos","events_url":"https://api.github.com/users/Pyomo/events{/privacy}","received_events_url":"https://api.github.com/users/Pyomo/received_events","type":"Organization","site_admin":false},"repo":{"id":59858718,"name":"pyomo","full_name":"Pyomo/pyomo","owner":{"login":"Pyomo","id":10505959,"avatar_url":"https://avatars2.githubusercontent.com/u/10505959?v=4","gravatar_id":"","url":"https://api.github.com/users/Pyomo","html_url":"https://github.com/Pyomo","followers_url":"https://api.github.com/users/Pyomo/followers","following_url":"https://api.github.com/users/Pyomo/following{/other_user}","gists_url":"https://api.github.com/users/Pyomo/gists{/gist_id}","starred_url":"https://api.github.com/users/Pyomo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Pyomo/subscriptions","organizations_url":"https://api.github.com/users/Pyomo/orgs","repos_url":"https://api.github.com/users/Pyomo/repos","events_url":"https://api.github.com/users/Pyomo/events{/privacy}","received_events_url":"https://api.github.com/users/Pyomo/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Pyomo/pyomo","description":"The main repository for the Pyomo optimization modeling software.","fork":false,"url":"https://api.github.com/repos/Pyomo/pyomo","forks_url":"https://api.github.com/repos/Pyomo/pyomo/forks","keys_url":"https://api.github.com/repos/Pyomo/pyomo/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Pyomo/pyomo/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Pyomo/pyomo/teams","hooks_url":"https://api.github.com/repos/Pyomo/pyomo/hooks","issue_events_url":"https://api.github.com/repos/Pyomo/pyomo/issues/events{/number}","events_url":"https://api.github.com/repos/Pyomo/pyomo/events","assignees_url":"https://api.github.com/repos/Pyomo/pyomo/assignees{/user}","branches_url":"https://api.github.com/repos/Pyomo/pyomo/branches{/branch}","tags_url":"https://api.github.com/repos/Pyomo/pyomo/tags","blobs_url":"https://api.github.com/repos/Pyomo/pyomo/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Pyomo/pyomo/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Pyomo/pyomo/git/refs{/sha}","trees_url":"https://api.github.com/repos/Pyomo/pyomo/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Pyomo/pyomo/statuses/{sha}","languages_url":"https://api.github.com/repos/Pyomo/pyomo/languages","stargazers_url":"https://api.github.com/repos/Pyomo/pyomo/stargazers","contributors_url":"https://api.github.com/repos/Pyomo/pyomo/contributors","subscribers_url":"https://api.github.com/repos/Pyomo/pyomo/subscribers","subscription_url":"https://api.github.com/repos/Pyomo/pyomo/subscription","commits_url":"https://api.github.com/repos/Pyomo/pyomo/commits{/sha}","git_commits_url":"https://api.github.com/repos/Pyomo/pyomo/git/commits{/sha}","comments_url":"https://api.github.com/repos/Pyomo/pyomo/comments{/number}","issue_comment_url":"https://api.github.com/repos/Pyomo/pyomo/issues/comments{/number}","contents_url":"https://api.github.com/repos/Pyomo/pyomo/contents/{+path}","compare_url":"https://api.github.com/repos/Pyomo/pyomo/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Pyomo/pyomo/merges","archive_url":"https://api.github.com/repos/Pyomo/pyomo/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Pyomo/pyomo/downloads","issues_url":"https://api.github.com/repos/Pyomo/pyomo/issues{/number}","pulls_url":"https://api.github.com/repos/Pyomo/pyomo/pulls{/number}","milestones_url":"https://api.github.com/repos/Pyomo/pyomo/milestones{/number}","notifications_url":"https://api.github.com/repos/Pyomo/pyomo/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Pyomo/pyomo/labels{/name}","releases_url":"https://api.github.com/repos/Pyomo/pyomo/releases{/id}","deployments_url":"https://api.github.com/repos/Pyomo/pyomo/deployments","created_at":"2016-05-27T19:33:45Z","updated_at":"2018-04-14T20:51:15Z","pushed_at":"2018-04-16T04:25:36Z","git_url":"git://github.com/Pyomo/pyomo.git","ssh_url":"git@github.com:Pyomo/pyomo.git","clone_url":"https://github.com/Pyomo/pyomo.git","svn_url":"https://github.com/Pyomo/pyomo","homepage":null,"size":38387,"stargazers_count":226,"watchers_count":226,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":89,"mirror_url":null,"archived":false,"open_issues_count":155,"license":{"key":"other","name":"Other","spdx_id":null,"url":null},"forks":89,"open_issues":155,"watchers":226,"default_branch":"master"}},"base":{"label":"Pyomo:master","ref":"master","sha":"2e028339384925dd17b6ae2171484f29cfdcfb65","user":{"login":"Pyomo","id":10505959,"avatar_url":"https://avatars2.githubusercontent.com/u/10505959?v=4","gravatar_id":"","url":"https://api.github.com/users/Pyomo","html_url":"https://github.com/Pyomo","followers_url":"https://api.github.com/users/Pyomo/followers","following_url":"https://api.github.com/users/Pyomo/following{/other_user}","gists_url":"https://api.github.com/users/Pyomo/gists{/gist_id}","starred_url":"https://api.github.com/users/Pyomo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Pyomo/subscriptions","organizations_url":"https://api.github.com/users/Pyomo/orgs","repos_url":"https://api.github.com/users/Pyomo/repos","events_url":"https://api.github.com/users/Pyomo/events{/privacy}","received_events_url":"https://api.github.com/users/Pyomo/received_events","type":"Organization","site_admin":false},"repo":{"id":59858718,"name":"pyomo","full_name":"Pyomo/pyomo","owner":{"login":"Pyomo","id":10505959,"avatar_url":"https://avatars2.githubusercontent.com/u/10505959?v=4","gravatar_id":"","url":"https://api.github.com/users/Pyomo","html_url":"https://github.com/Pyomo","followers_url":"https://api.github.com/users/Pyomo/followers","following_url":"https://api.github.com/users/Pyomo/following{/other_user}","gists_url":"https://api.github.com/users/Pyomo/gists{/gist_id}","starred_url":"https://api.github.com/users/Pyomo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Pyomo/subscriptions","organizations_url":"https://api.github.com/users/Pyomo/orgs","repos_url":"https://api.github.com/users/Pyomo/repos","events_url":"https://api.github.com/users/Pyomo/events{/privacy}","received_events_url":"https://api.github.com/users/Pyomo/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Pyomo/pyomo","description":"The main repository for the Pyomo optimization modeling software.","fork":false,"url":"https://api.github.com/repos/Pyomo/pyomo","forks_url":"https://api.github.com/repos/Pyomo/pyomo/forks","keys_url":"https://api.github.com/repos/Pyomo/pyomo/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Pyomo/pyomo/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Pyomo/pyomo/teams","hooks_url":"https://api.github.com/repos/Pyomo/pyomo/hooks","issue_events_url":"https://api.github.com/repos/Pyomo/pyomo/issues/events{/number}","events_url":"https://api.github.com/repos/Pyomo/pyomo/events","assignees_url":"https://api.github.com/repos/Pyomo/pyomo/assignees{/user}","branches_url":"https://api.github.com/repos/Pyomo/pyomo/branches{/branch}","tags_url":"https://api.github.com/repos/Pyomo/pyomo/tags","blobs_url":"https://api.github.com/repos/Pyomo/pyomo/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Pyomo/pyomo/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Pyomo/pyomo/git/refs{/sha}","trees_url":"https://api.github.com/repos/Pyomo/pyomo/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Pyomo/pyomo/statuses/{sha}","languages_url":"https://api.github.com/repos/Pyomo/pyomo/languages","stargazers_url":"https://api.github.com/repos/Pyomo/pyomo/stargazers","contributors_url":"https://api.github.com/repos/Pyomo/pyomo/contributors","subscribers_url":"https://api.github.com/repos/Pyomo/pyomo/subscribers","subscription_url":"https://api.github.com/repos/Pyomo/pyomo/subscription","commits_url":"https://api.github.com/repos/Pyomo/pyomo/commits{/sha}","git_commits_url":"https://api.github.com/repos/Pyomo/pyomo/git/commits{/sha}","comments_url":"https://api.github.com/repos/Pyomo/pyomo/comments{/number}","issue_comment_url":"https://api.github.com/repos/Pyomo/pyomo/issues/comments{/number}","contents_url":"https://api.github.com/repos/Pyomo/pyomo/contents/{+path}","compare_url":"https://api.github.com/repos/Pyomo/pyomo/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Pyomo/pyomo/merges","archive_url":"https://api.github.com/repos/Pyomo/pyomo/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Pyomo/pyomo/downloads","issues_url":"https://api.github.com/repos/Pyomo/pyomo/issues{/number}","pulls_url":"https://api.github.com/repos/Pyomo/pyomo/pulls{/number}","milestones_url":"https://api.github.com/repos/Pyomo/pyomo/milestones{/number}","notifications_url":"https://api.github.com/repos/Pyomo/pyomo/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Pyomo/pyomo/labels{/name}","releases_url":"https://api.github.com/repos/Pyomo/pyomo/releases{/id}","deployments_url":"https://api.github.com/repos/Pyomo/pyomo/deployments","created_at":"2016-05-27T19:33:45Z","updated_at":"2018-04-14T20:51:15Z","pushed_at":"2018-04-16T04:25:36Z","git_url":"git://github.com/Pyomo/pyomo.git","ssh_url":"git@github.com:Pyomo/pyomo.git","clone_url":"https://github.com/Pyomo/pyomo.git","svn_url":"https://github.com/Pyomo/pyomo","homepage":null,"size":38387,"stargazers_count":226,"watchers_count":226,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":89,"mirror_url":null,"archived":false,"open_issues_count":155,"license":{"key":"other","name":"Other","spdx_id":null,"url":null},"forks":89,"open_issues":155,"watchers":226,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/Pyomo/pyomo/pulls/272"},"html":{"href":"https://github.com/Pyomo/pyomo/pull/272"},"issue":{"href":"https://api.github.com/repos/Pyomo/pyomo/issues/272"},"comments":{"href":"https://api.github.com/repos/Pyomo/pyomo/issues/272/comments"},"review_comments":{"href":"https://api.github.com/repos/Pyomo/pyomo/pulls/272/comments"},"review_comment":{"href":"https://api.github.com/repos/Pyomo/pyomo/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Pyomo/pyomo/pulls/272/commits"},"statuses":{"href":"https://api.github.com/repos/Pyomo/pyomo/statuses/38da521de783f4fdc8ba26d79fa5cf87a4a0b573"}},"author_association":"OWNER"}}
{"actor":{"display_login":"whart222"}}