diff --git a/pyomo/common/errors.py b/pyomo/common/errors.py index 744dbf76f59..050221c4a5e 100644 --- a/pyomo/common/errors.py +++ b/pyomo/common/errors.py @@ -217,3 +217,19 @@ class TempfileContextError(PyomoException, IndexError): """ pass + + +class TemplateExpressionError(ValueError): + """Special ValueError raised by getitem for template arguments + + This exception is triggered by the Pyomo expression system when + attempting to get a member of an IndexedComponent using either a + TemplateIndex, or an expression containing a TemplateIndex. + + Users should never see this exception. + + """ + + def __init__(self, template, *args, **kwds): + self.template = template + super(TemplateExpressionError, self).__init__(*args, **kwds) diff --git a/pyomo/common/numeric_types.py b/pyomo/common/numeric_types.py index 27eec3a96f2..dbad3ef0853 100644 --- a/pyomo/common/numeric_types.py +++ b/pyomo/common/numeric_types.py @@ -9,7 +9,13 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ +import logging +import sys + from pyomo.common.deprecation import deprecated, relocated_module_attribute +from pyomo.common.errors import TemplateExpressionError + +logger = logging.getLogger(__name__) #: Python set used to identify numeric constants, boolean values, strings #: and instances of @@ -123,3 +129,138 @@ def RegisterLogicalType(new_type): native_logical_types.add(new_type) native_types.add(new_type) nonpyomo_leaf_types.add(new_type) + + +def check_if_numeric_type(obj): + """Test if the argument behaves like a numeric type. + + We check for "numeric types" by checking if we can add zero to it + without changing the object's type. If that works, then we register + the type in native_numeric_types. + + """ + obj_class = obj.__class__ + # Do not re-evaluate known native types + if obj_class in native_types: + return obj_class in native_numeric_types + + try: + obj_plus_0 = obj + 0 + obj_p0_class = obj_plus_0.__class__ + # ensure that the object is comparable to 0 in a meaningful way + # (among other things, this prevents numpy.ndarray objects from + # being added to native_numeric_types) + if not ((obj < 0) ^ (obj >= 0)): + return False + # Native types *must* be hashable + hash(obj) + except: + return False + if obj_p0_class is obj_class or obj_p0_class in native_numeric_types: + # + # If we get here, this is a reasonably well-behaving + # numeric type: add it to the native numeric types + # so that future lookups will be faster. + # + RegisterNumericType(obj_class) + # + # Generate a warning, since Pyomo's management of third-party + # numeric types is more robust when registering explicitly. + # + logger.warning( + f"""Dynamically registering the following numeric type: + {obj_class.__module__}.{obj_class.__name__} +Dynamic registration is supported for convenience, but there are known +limitations to this approach. We recommend explicitly registering +numeric types using RegisterNumericType() or RegisterIntegerType().""" + ) + return True + else: + return False + + +def value(obj, exception=True): + """ + A utility function that returns the value of a Pyomo object or + expression. + + Args: + obj: The argument to evaluate. If it is None, a + string, or any other primitive numeric type, + then this function simply returns the argument. + Otherwise, if the argument is a NumericValue + then the __call__ method is executed. + exception (bool): If :const:`True`, then an exception should + be raised when instances of NumericValue fail to + s evaluate due to one or more objects not being + initialized to a numeric value (e.g, one or more + variables in an algebraic expression having the + value None). If :const:`False`, then the function + returns :const:`None` when an exception occurs. + Default is True. + + Returns: A numeric value or None. + """ + if obj.__class__ in native_types: + return obj + if obj.__class__ in pyomo_constant_types: + # + # I'm commenting this out for now, but I think we should never expect + # to see a numeric constant with value None. + # + # if exception and obj.value is None: + # raise ValueError( + # "No value for uninitialized NumericConstant object %s" + # % (obj.name,)) + return obj.value + # + # Test if we have a duck typed Pyomo expression + # + try: + obj.is_numeric_type() + except AttributeError: + # + # TODO: Historically we checked for new *numeric* types and + # raised exceptions for anything else. That is inconsistent + # with allowing native_types like None/str/bool to be returned + # from value(). We should revisit if that is worthwhile to do + # here. + # + if check_if_numeric_type(obj): + return obj + else: + if not exception: + return None + raise TypeError( + "Cannot evaluate object with unknown type: %s" % obj.__class__.__name__ + ) from None + # + # Evaluate the expression object + # + if exception: + # + # Here, we try to catch the exception + # + try: + tmp = obj(exception=True) + if tmp is None: + raise ValueError( + "No value for uninitialized NumericValue object %s" % (obj.name,) + ) + return tmp + except TemplateExpressionError: + # Template expressions work by catching this error type. So + # we should defer this error handling and not log an error + # message. + raise + except: + logger.error( + "evaluating object as numeric value: %s\n (object: %s)\n%s" + % (obj, type(obj), sys.exc_info()[1]) + ) + raise + else: + # + # Here, we do not try to catch the exception + # + return obj(exception=False) diff --git a/pyomo/core/base/PyomoModel.py b/pyomo/core/base/PyomoModel.py index f6f0713ab1f..f8b2710b9f2 100644 --- a/pyomo/core/base/PyomoModel.py +++ b/pyomo/core/base/PyomoModel.py @@ -23,6 +23,7 @@ from pyomo.common.deprecation import deprecated, deprecation_warning from pyomo.common.gc_manager import PauseGC from pyomo.common.log import is_debug_set +from pyomo.common.numeric_types import value from pyomo.core.staleflag import StaleFlagManager from pyomo.core.expr.symbol_map import SymbolMap from pyomo.core.base.component import ModelComponentFactory @@ -30,7 +31,6 @@ from pyomo.core.base.constraint import Constraint from pyomo.core.base.objective import Objective from pyomo.core.base.suffix import active_import_suffix_generator -from pyomo.core.base.numvalue import value from pyomo.core.base.block import ScalarBlock from pyomo.core.base.set import Set from pyomo.core.base.componentuid import ComponentUID diff --git a/pyomo/core/base/connector.py b/pyomo/core/base/connector.py index 63513bc1765..f3d4833b837 100644 --- a/pyomo/core/base/connector.py +++ b/pyomo/core/base/connector.py @@ -19,13 +19,13 @@ from pyomo.common.formatting import tabular_writer from pyomo.common.log import is_debug_set from pyomo.common.modeling import NOTSET +from pyomo.common.numeric_types import value from pyomo.common.timing import ConstructionTimer - +from pyomo.core.expr.numvalue import NumericValue from pyomo.core.base.component import ComponentData, ModelComponentFactory from pyomo.core.base.global_set import UnindexedComponent_index from pyomo.core.base.indexed_component import IndexedComponent from pyomo.core.base.misc import apply_indexed_rule -from pyomo.core.base.numvalue import NumericValue, value from pyomo.core.base.transformation import TransformationFactory logger = logging.getLogger('pyomo.core') diff --git a/pyomo/core/base/expression.py b/pyomo/core/base/expression.py index a724932ecc7..320d759a9db 100644 --- a/pyomo/core/base/expression.py +++ b/pyomo/core/base/expression.py @@ -21,7 +21,11 @@ from pyomo.common.modeling import NOTSET from pyomo.common.formatting import tabular_writer from pyomo.common.timing import ConstructionTimer -from pyomo.common.numeric_types import native_types, native_numeric_types +from pyomo.common.numeric_types import ( + native_types, + native_numeric_types, + check_if_numeric_type, +) from pyomo.core.expr import current as EXPR import pyomo.core.expr.numeric_expr as numeric_expr @@ -29,13 +33,13 @@ from pyomo.core.base.global_set import UnindexedComponent_index from pyomo.core.base.indexed_component import IndexedComponent, UnindexedComponent_set from pyomo.core.base.misc import apply_indexed_rule -from pyomo.core.base.numvalue import NumericValue, as_numeric +from pyomo.core.expr.numvalue import as_numeric from pyomo.core.base.initializer import Initializer logger = logging.getLogger('pyomo.core') -class _ExpressionData(NumericValue): +class _ExpressionData(numeric_expr.NumericValue): """ An object that defines a named expression. diff --git a/pyomo/core/base/indexed_component.py b/pyomo/core/base/indexed_component.py index 80e04731e91..8d7f991a0c0 100644 --- a/pyomo/core/base/indexed_component.py +++ b/pyomo/core/base/indexed_component.py @@ -20,8 +20,8 @@ from copy import deepcopy from pyomo.core.expr import current as EXPR -from pyomo.core.expr.expr_errors import TemplateExpressionError -from pyomo.core.expr.numvalue import native_types, NumericNDArray +from pyomo.core.expr.numeric_expr import NumericNDArray +from pyomo.core.expr.numvalue import native_types from pyomo.core.base.indexed_component_slice import IndexedComponent_slice from pyomo.core.base.initializer import Initializer from pyomo.core.base.component import Component, ActiveComponent @@ -33,6 +33,7 @@ from pyomo.common.autoslots import fast_deepcopy from pyomo.common.dependencies import numpy as np, numpy_available from pyomo.common.deprecation import deprecated, deprecation_warning +from pyomo.common.errors import DeveloperError, TemplateExpressionError from pyomo.common.modeling import NOTSET from pyomo.common.sorting import sorted_robust diff --git a/pyomo/core/base/param.py b/pyomo/core/base/param.py index 273deda390f..288be2c7b4f 100644 --- a/pyomo/core/base/param.py +++ b/pyomo/core/base/param.py @@ -21,8 +21,9 @@ from pyomo.common.deprecation import deprecation_warning, RenamedClass from pyomo.common.log import is_debug_set from pyomo.common.modeling import NOTSET +from pyomo.common.numeric_types import native_types, value as expr_value from pyomo.common.timing import ConstructionTimer - +from pyomo.core.expr.numvalue import NumericValue from pyomo.core.base.component import ComponentData, ModelComponentFactory from pyomo.core.base.global_set import UnindexedComponent_index from pyomo.core.base.indexed_component import ( @@ -32,7 +33,6 @@ ) from pyomo.core.base.initializer import Initializer from pyomo.core.base.misc import apply_indexed_rule, apply_parameterized_indexed_rule -from pyomo.core.base.numvalue import NumericValue, native_types, value as expr_value from pyomo.core.base.set import Reals, _AnySet from pyomo.core.base.units_container import units from pyomo.core.expr.current import GetItemExpression diff --git a/pyomo/core/base/piecewise.py b/pyomo/core/base/piecewise.py index b77041ee0ae..8ab6ce38ca5 100644 --- a/pyomo/core/base/piecewise.py +++ b/pyomo/core/base/piecewise.py @@ -48,6 +48,7 @@ from pyomo.common.log import is_debug_set from pyomo.common.deprecation import deprecation_warning +from pyomo.common.numeric_types import value from pyomo.common.timing import ConstructionTimer from pyomo.core.base.block import Block, _BlockData from pyomo.core.base.component import ModelComponentFactory @@ -55,7 +56,6 @@ from pyomo.core.base.sos import SOSConstraint from pyomo.core.base.var import Var, _VarData, IndexedVar from pyomo.core.base.set_types import PositiveReals, NonNegativeReals, Binary -from pyomo.core.base.numvalue import value from pyomo.core.base.util import flatten_tuple logger = logging.getLogger('pyomo.core') diff --git a/pyomo/core/expr/__init__.py b/pyomo/core/expr/__init__.py index 634f6f0b0f1..04ab1967175 100644 --- a/pyomo/core/expr/__init__.py +++ b/pyomo/core/expr/__init__.py @@ -24,48 +24,53 @@ boolean_value, logical_expr, relational_expr, - current, ) -# FIXME: remove circular dependencies between numvalue and numeric_expr # -# We unfortunately have circular dependencies between the numvalue -# module (which defines the base class for all numeric expression -# components, and implements the operator overloading methods) and the -# numeric_expr module (the dispatchers and the expression node -# definitions) -numvalue._add_dispatcher = numeric_expr._add_dispatcher -numvalue._neg_dispatcher = numeric_expr._neg_dispatcher -numvalue._mul_dispatcher = numeric_expr._mul_dispatcher -numvalue._div_dispatcher = numeric_expr._div_dispatcher -numvalue._abs_dispatcher = numeric_expr._abs_dispatcher -numvalue._pow_dispatcher = numeric_expr._pow_dispatcher +# FIXME: remove circular dependencies between relational_expr and numeric_expr +# -# Initialize numvalue functions -numvalue._generate_relational_expression = ( +# Initialize relational expression functions +numeric_expr._generate_relational_expression = ( relational_expr._generate_relational_expression ) # Initialize logicalvalue functions boolean_value._generate_logical_proposition = logical_expr._generate_logical_proposition -from .numvalue import ( + +from pyomo.common.numeric_types import ( value, - is_constant, - is_fixed, - is_variable_type, - is_potentially_variable, - NumericValue, - ZeroConstant, native_numeric_types, native_types, - polynomial_degree, + nonpyomo_leaf_types, ) +from pyomo.common.errors import TemplateExpressionError +from .base import ExpressionBase from .boolean_value import BooleanValue - -from .numeric_expr import linear_expression, nonlinear_expression, mutable_expression +from .expr_common import ExpressionType, Mode from .logical_expr import ( + native_logical_types, + special_boolean_atom_types, + # + BooleanValue, + BooleanConstant, + BooleanExpressionBase, + # + UnaryBooleanExpression, + NotExpression, + BinaryBooleanExpression, + EquivalenceExpression, + XorExpression, + ImplicationExpression, + NaryBooleanExpression, + AndExpression, + OrExpression, + ExactlyExpression, + AtMostExpression, + AtLeastExpression, + # land, lnot, lor, @@ -76,8 +81,34 @@ atmost, implies, ) -from .relational_expr import inequality -from .current import ( +from .numeric_expr import ( + NumericValue, + NumericExpression, + # operators: + AbsExpression, + DivisionExpression, + Expr_ifExpression, + ExternalFunctionExpression, + LinearExpression, + MonomialTermExpression, + NegationExpression, + PowExpression, + ProductExpression, + SumExpressionBase, # TODO: deprecate / remove + SumExpression, + UnaryFunctionExpression, + # TBD: remove export of NPV classes here? + NPV_AbsExpression, + NPV_DivisionExpression, + NPV_Expr_ifExpression, + NPV_ExternalFunctionExpression, + NPV_NegationExpression, + NPV_PowExpression, + NPV_ProductExpression, + NPV_SumExpression, + NPV_UnaryFunctionExpression, + # functions to generate expressions + Expr_if, log, log10, sin, @@ -96,8 +127,76 @@ atanh, ceil, floor, - Expr_if, + # Lgacy utilities + NPV_expression_types, # TODO: remove + LinearDecompositionError, # TODO: move to common.errors + decompose_term, + linear_expression, + nonlinear_expression, + mutable_expression, +) +from .numvalue import ( + as_numeric, + is_constant, + is_fixed, + is_variable_type, + is_potentially_variable, + ZeroConstant, + polynomial_degree, +) +from .relational_expr import ( + RelationalExpression, + RangedExpression, + InequalityExpression, + EqualityExpression, + inequality, +) +from .symbol_map import SymbolMap +from .template_expr import ( + GetItemExpression, + Numeric_GetItemExpression, + Boolean_GetItemExpression, + Structural_GetItemExpression, + GetAttrExpression, + Numeric_GetAttrExpression, + Boolean_GetAttrExpression, + Structural_GetAttrExpression, + CallExpression, + TemplateSumExpression, + # + NPV_Numeric_GetItemExpression, + NPV_Boolean_GetItemExpression, + NPV_Structural_GetItemExpression, + NPV_Numeric_GetAttrExpression, + NPV_Boolean_GetAttrExpression, + NPV_Structural_GetAttrExpression, + # + IndexTemplate, + resolve_template, + ReplaceTemplateExpression, + substitute_template_expression, + substitute_getitem_with_param, + substitute_template_with_value, + templatize_rule, + templatize_constraint, +) +from .visitor import ( + StreamBasedExpressionVisitor, + SimpleExpressionVisitor, + ExpressionValueVisitor, + ExpressionReplacementVisitor, + FixedExpressionError, + NonConstantExpressionError, + identify_components, + identify_variables, + identify_mutable_parameters, + clone_expression, + evaluate_expression, + expression_to_string, + polynomial_degree, + replace_expressions, + sizeof_expression, ) -from pyomo.core.expr.calculus.derivatives import differentiate -from pyomo.core.expr.taylor_series import taylor_series_expansion +from .calculus.derivatives import differentiate +from .taylor_series import taylor_series_expansion diff --git a/pyomo/core/expr/base.py b/pyomo/core/expr/base.py index 2939250ce02..b74bbff4e3c 100644 --- a/pyomo/core/expr/base.py +++ b/pyomo/core/expr/base.py @@ -11,16 +11,12 @@ import enum +from pyomo.common.dependencies import attempt_import from pyomo.common.numeric_types import native_types from pyomo.core.pyomoobject import PyomoObject -from . import expr_common as common -from .visitor import ( - expression_to_string, - evaluate_expression, - clone_expression, - _expression_is_fixed, - sizeof_expression, -) +from pyomo.core.expr.expr_common import OperatorAssociativity + +visitor, _ = attempt_import('pyomo.core.expr.visitor') class ExpressionBase(PyomoObject): @@ -44,7 +40,7 @@ class in with their fundamental base data type (NumericValue, interpreted as "not associative" (implying any arguments that are at this operator's PRECEDENCE will be enclosed in parens). """ - ASSOCIATIVITY = common.OperatorAssociativity.LEFT_TO_RIGHT + ASSOCIATIVITY = OperatorAssociativity.LEFT_TO_RIGHT def nargs(self): """Returns the number of child nodes. @@ -119,7 +115,7 @@ def __call__(self, exception=True): The value of the expression or :const:`None`. """ - return evaluate_expression(self, exception) + return visitor.evaluate_expression(self, exception) def __str__(self): """Returns a string description of the expression. @@ -137,7 +133,7 @@ def __str__(self): ------- str """ - return expression_to_string(self) + return visitor.expression_to_string(self) def to_string(self, verbose=None, labeler=None, smap=None, compute_values=False): """Return a string representation of the expression tree. @@ -168,7 +164,7 @@ def to_string(self, verbose=None, labeler=None, smap=None, compute_values=False) A string representation for the expression tree. """ - return expression_to_string( + return visitor.expression_to_string( self, verbose=verbose, labeler=labeler, @@ -240,7 +236,7 @@ def clone(self, substitute=None): Returns: A new expression tree. """ - return clone_expression(self, substitute=substitute) + return visitor.clone_expression(self, substitute=substitute) def create_node_with_local_data(self, args, classtype=None): """ @@ -287,7 +283,7 @@ def is_fixed(self): Returns: A boolean. """ - return _expression_is_fixed(self) + return visitor._expression_is_fixed(self) def _is_fixed(self, values): """ @@ -362,7 +358,7 @@ def size(self): A nonnegative integer that is the number of interior and leaf nodes in the expression tree. """ - return sizeof_expression(self) + return visitor.sizeof_expression(self) def _apply_operation(self, result): # pragma: no cover """ diff --git a/pyomo/core/expr/current.py b/pyomo/core/expr/current.py index 1eb3f8a35b9..b0405324580 100644 --- a/pyomo/core/expr/current.py +++ b/pyomo/core/expr/current.py @@ -15,69 +15,26 @@ # # Common intrinsic functions # -from pyomo.core.expr import expr_common as common - - -# -# Provide a global value that indicates which expression system is being used -# -class Mode(enum.IntEnum): - # coopr: Original Coopr/Pyomo expression system - coopr_trees = 1 - # coopr3: leverage reference counts to reduce the amount of required - # expression cloning to ensure independent expression trees. - coopr3_trees = 3 - # pyomo4: rework the expression system to remove reliance on - # reference counting. This enables pypy support (which doesn't have - # reference counting). This version never became the default. - pyomo4_trees = 4 - # pyomo5: refinement of pyomo4. Expressions are now immutable by - # contract, which tolerates "entangled" expression trees. Added - # specialized classes for NPV expressions and LinearExpressions. - pyomo5_trees = 5 - # pyomo6: refinement of pyomo5 expression generation to leverage - # multiple dispatch. Standardized expression storage and argument - # handling (significant rework of the LinearExpression structure). - pyomo6_trees = 6 - - -_mode = Mode.pyomo6_trees -# We no longer support concurrent expression systems. _mode is left -# primarily so we can support expression system-specific baselines -assert _mode == Mode.pyomo6_trees - -# -# Pull symbols from the appropriate expression system -# -from pyomo.core.expr import numvalue as _numvalue -from pyomo.core.expr import boolean_value as _logicalvalue - -from pyomo.core.expr import numeric_expr as _numeric_expr -from .base import ExpressionBase -from pyomo.core.expr.numeric_expr import ( - _add, - _sub, - _mul, - _div, - _pow, - _neg, - _abs, - _inplace, - _unary, +import pyomo.core.expr.expr_common as common +from pyomo.core.expr.expr_common import clone_counter, _mode + +from pyomo.core.expr import ( + Mode, + # from pyomo.core.expr.base + ExpressionBase, + # pyomo.core.expr.visitor + evaluate_expression, + expression_to_string, + polynomial_degree, + clone_expression, + sizeof_expression, + # pyomo.core.expr.numeric_expr NumericExpression, NumericValue, native_types, nonpyomo_leaf_types, native_numeric_types, - as_numeric, value, - evaluate_expression, - expression_to_string, - polynomial_degree, - clone_expression, - sizeof_expression, - _expression_is_fixed, - clone_counter, nonlinear_expression, linear_expression, NegationExpression, @@ -94,7 +51,6 @@ class Mode(enum.IntEnum): SumExpressionBase, NPV_SumExpression, SumExpression, - _MutableSumExpression, Expr_ifExpression, NPV_Expr_ifExpression, UnaryFunctionExpression, @@ -102,32 +58,39 @@ class Mode(enum.IntEnum): AbsExpression, NPV_AbsExpression, LinearExpression, - _MutableLinearExpression, decompose_term, LinearDecompositionError, - _decompose_linear_terms, - _balanced_parens, NPV_expression_types, - _fcn_dispatcher, -) -from pyomo.core.expr import logical_expr as _logical_expr -from pyomo.core.expr.logical_expr import ( + Expr_if, + ceil, + floor, + exp, + log, + log10, + sqrt, + sin, + cos, + tan, + sinh, + cosh, + tanh, + asin, + acos, + atan, + asinh, + acosh, + atanh, + # pyomo.core.expr.numvalue + as_numeric, + # pyomo.core.expr.logical_expr native_logical_types, BooleanValue, BooleanConstant, - _and, - _or, - _equiv, - _inv, - _xor, - _impl, - _generate_logical_proposition, BooleanExpressionBase, lnot, equivalent, xor, implies, - _flattened, land, lor, exactly, @@ -140,24 +103,20 @@ class Mode(enum.IntEnum): XorExpression, ImplicationExpression, NaryBooleanExpression, - _add_to_and_or_expression, AndExpression, OrExpression, ExactlyExpression, AtMostExpression, AtLeastExpression, special_boolean_atom_types, -) -from pyomo.core.expr.relational_expr import ( + # pyomo.core.expr.relational_expr RelationalExpression, RangedExpression, InequalityExpression, EqualityExpression, inequality, -) -from pyomo.core.expr.template_expr import ( + # pyomo.core.expr.template_expr TemplateExpressionError, - _NotSpecified, GetItemExpression, Numeric_GetItemExpression, Boolean_GetItemExpression, @@ -173,130 +132,25 @@ class Mode(enum.IntEnum): NPV_Boolean_GetAttrExpression, NPV_Structural_GetAttrExpression, CallExpression, - _TemplateSumExpression_argList, TemplateSumExpression, IndexTemplate, resolve_template, ReplaceTemplateExpression, substitute_template_expression, - _GetItemIndexer, substitute_getitem_with_param, substitute_template_with_value, - _set_iterator_template_generator, - _template_iter_context, templatize_rule, templatize_constraint, -) -from pyomo.core.expr import visitor as _visitor -from pyomo.core.expr.visitor import ( + # pyomo.core.expr.visitor SymbolMap, StreamBasedExpressionVisitor, SimpleExpressionVisitor, ExpressionValueVisitor, replace_expressions, ExpressionReplacementVisitor, - _EvaluationVisitor, FixedExpressionError, NonConstantExpressionError, - _EvaluateConstantExpressionVisitor, - _ComponentVisitor, identify_components, - _VariableVisitor, identify_variables, - _MutableParamVisitor, identify_mutable_parameters, - _PolynomialDegreeVisitor, - _IsFixedVisitor, - _ToStringVisitor, ) - - -def Expr_if(IF=None, THEN=None, ELSE=None): - """ - Function used to construct a logical conditional expression. - """ - if _numvalue.is_constant(IF): - return THEN if value(IF) else ELSE - if not any(map(_numvalue.is_potentially_variable, (IF, THEN, ELSE))): - return NPV_Expr_ifExpression((IF, THEN, ELSE)) - return Expr_ifExpression((IF, THEN, ELSE)) - - -# -# NOTE: abs() and pow() are not defined here, because they are -# Python operators. -# -def ceil(arg): - return _fcn_dispatcher[arg.__class__](arg, 'ceil', math.ceil) - - -def floor(arg): - return _fcn_dispatcher[arg.__class__](arg, 'floor', math.floor) - - -# e ** x -def exp(arg): - return _fcn_dispatcher[arg.__class__](arg, 'exp', math.exp) - - -def log(arg): - return _fcn_dispatcher[arg.__class__](arg, 'log', math.log) - - -def log10(arg): - return _fcn_dispatcher[arg.__class__](arg, 'log10', math.log10) - - -# FIXME: this is nominally the same as x ** 0.5, but follows a different -# path and produces a different NL file! -def sqrt(arg): - return _fcn_dispatcher[arg.__class__](arg, 'sqrt', math.sqrt) - # return _pow_dispatcher[arg.__class__, float](arg, 0.5) - - -def sin(arg): - return _fcn_dispatcher[arg.__class__](arg, 'sin', math.sin) - - -def cos(arg): - return _fcn_dispatcher[arg.__class__](arg, 'cos', math.cos) - - -def tan(arg): - return _fcn_dispatcher[arg.__class__](arg, 'tan', math.tan) - - -def sinh(arg): - return _fcn_dispatcher[arg.__class__](arg, 'sinh', math.sinh) - - -def cosh(arg): - return _fcn_dispatcher[arg.__class__](arg, 'cosh', math.cosh) - - -def tanh(arg): - return _fcn_dispatcher[arg.__class__](arg, 'tanh', math.tanh) - - -def asin(arg): - return _fcn_dispatcher[arg.__class__](arg, 'asin', math.asin) - - -def acos(arg): - return _fcn_dispatcher[arg.__class__](arg, 'acos', math.acos) - - -def atan(arg): - return _fcn_dispatcher[arg.__class__](arg, 'atan', math.atan) - - -def asinh(arg): - return _fcn_dispatcher[arg.__class__](arg, 'asinh', math.asinh) - - -def acosh(arg): - return _fcn_dispatcher[arg.__class__](arg, 'acosh', math.acosh) - - -def atanh(arg): - return _fcn_dispatcher[arg.__class__](arg, 'atanh', math.atanh) diff --git a/pyomo/core/expr/expr_common.py b/pyomo/core/expr/expr_common.py index 6292a5c08f8..98c9a433994 100644 --- a/pyomo/core/expr/expr_common.py +++ b/pyomo/core/expr/expr_common.py @@ -16,27 +16,6 @@ TO_STRING_VERBOSE = False -_add = 1 -_sub = 2 -_mul = 3 -_div = 4 -_pow = 5 -_neg = 6 -_abs = 7 -_inplace = 10 -_unary = _neg - -_radd = -_add -_iadd = _inplace + _add -_rsub = -_sub -_isub = _inplace + _sub -_rmul = -_mul -_imul = _inplace + _mul -_rdiv = -_div -_idiv = _inplace + _div -_rpow = -_pow -_ipow = _inplace + _pow - _eq = 0 _le = 1 _lt = 2 @@ -50,6 +29,37 @@ _impl = 5 +# +# Provide a global value that indicates which expression system is being used +# +class Mode(enum.IntEnum): + # coopr: Original Coopr/Pyomo expression system + coopr_trees = 1 + # coopr3: leverage reference counts to reduce the amount of required + # expression cloning to ensure independent expression trees. + coopr3_trees = 3 + # pyomo4: rework the expression system to remove reliance on + # reference counting. This enables pypy support (which doesn't have + # reference counting). This version never became the default. + pyomo4_trees = 4 + # pyomo5: refinement of pyomo4. Expressions are now immutable by + # contract, which tolerates "entangled" expression trees. Added + # specialized classes for NPV expressions and LinearExpressions. + pyomo5_trees = 5 + # pyomo6: refinement of pyomo5 expression generation to leverage + # multiple dispatch. Standardized expression storage and argument + # handling (significant rework of the LinearExpression structure). + pyomo6_trees = 6 + # + CURRENT = pyomo6_trees + + +_mode = Mode.CURRENT +# We no longer support concurrent expression systems. _mode is left +# primarily so we can support expression system-specific baselines +assert _mode == Mode.pyomo6_trees + + class OperatorAssociativity(enum.IntEnum): """Enum for indicating the associativity of an operator. diff --git a/pyomo/core/expr/expr_errors.py b/pyomo/core/expr/expr_errors.py index 95c982b3679..cc540c69b47 100644 --- a/pyomo/core/expr/expr_errors.py +++ b/pyomo/core/expr/expr_errors.py @@ -9,8 +9,11 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ +from pyomo.common.deprecation import relocated_module_attribute -class TemplateExpressionError(ValueError): - def __init__(self, template, *args, **kwds): - self.template = template - super(TemplateExpressionError, self).__init__(*args, **kwds) +relocated_module_attribute( + 'TemplateExpressionError', + 'pyomo.common.errors.TemplateExpressionError', + version='6.6.2.dev0', + f_globals=globals(), +) diff --git a/pyomo/core/expr/numeric_expr.py b/pyomo/core/expr/numeric_expr.py index 9bb089e7337..b0f2cf380ff 100644 --- a/pyomo/core/expr/numeric_expr.py +++ b/pyomo/core/expr/numeric_expr.py @@ -18,47 +18,92 @@ logger = logging.getLogger('pyomo.core') from math import isclose -from pyomo.common.deprecation import deprecated, deprecation_warning -from pyomo.common.formatting import tostr -from .expr_common import ( - OperatorAssociativity, - ExpressionType, - clone_counter, - _add, - _sub, - _mul, - _div, - _pow, - _neg, - _abs, - _inplace, - _unary, +from pyomo.common.dependencies import numpy as np, numpy_available +from pyomo.common.deprecation import ( + deprecated, + deprecation_warning, + relocated_module_attribute, ) -from .base import ExpressionBase, NPV_Mixin -from .numvalue import ( - NumericValue, +from pyomo.common.errors import PyomoException, DeveloperError +from pyomo.common.formatting import tostr +from pyomo.common.numeric_types import ( native_types, nonpyomo_leaf_types, native_numeric_types, - as_numeric, - value, - is_potentially_variable, check_if_numeric_type, + value, ) -from .visitor import ( - evaluate_expression, - expression_to_string, - polynomial_degree, - clone_expression, - sizeof_expression, - _expression_is_fixed, +from pyomo.core.pyomoobject import PyomoObject +from pyomo.core.expr.expr_common import ( + OperatorAssociativity, + ExpressionType, + _lt, + _le, + _eq, +) + +# Note: pyggyback on expr.base's use of attempt_import(visitor) +from pyomo.core.expr.base import ExpressionBase, NPV_Mixin, visitor + +relocated_module_attribute( + 'is_potentially_variable', + 'pyomo.core.expr.numvalue.is_potentially_variable', + version='6.6.2.dev0', + f_globals=globals(), +) +relocated_module_attribute( + 'as_numeric', + 'pyomo.core.expr.numvalue.as_numeric', + version='6.6.2.dev0', + f_globals=globals(), +) +relocated_module_attribute( + 'clone_counter', + 'pyomo.core.expr.expr_common.clone_counter', + version='6.6.2.dev0', + f_globals=globals(), +) +relocated_module_attribute( + 'evaluate_expression', + 'pyomo.core.expr.visitor.evaluate_expression', + version='6.6.2.dev0', + f_globals=globals(), +) +relocated_module_attribute( + 'expression_to_string', + 'pyomo.core.expr.visitor.expression_to_string', + version='6.6.2.dev0', + f_globals=globals(), +) +relocated_module_attribute( + 'polynomial_degree', + 'pyomo.core.expr.visitor.polynomial_degree', + version='6.6.2.dev0', + f_globals=globals(), +) +relocated_module_attribute( + 'clone_expression', + 'pyomo.core.expr.visitor.clone_expression', + version='6.6.2.dev0', + f_globals=globals(), +) +relocated_module_attribute( + 'sizeof_expression', + 'pyomo.core.expr.visitor.sizeof_expression', + version='6.6.2.dev0', + f_globals=globals(), ) _zero_one_optimizations = {1} +# Stub in the dispatchers +def _generate_relational_expression(etype, lhs, rhs): + raise RuntimeError("incomplete import of Pyomo expression system") + + def enable_expression_optimizations(zero=None, one=None): """Enable(disable) expression generation optimizations @@ -164,6 +209,497 @@ class linear_expression(mutable_expression): """ +class NumericValue(PyomoObject): + """ + This is the base class for numeric values used in Pyomo. + """ + + __slots__ = () + + # This is required because we define __eq__ + __hash__ = None + + def getname(self, fully_qualified=False, name_buffer=None): + """ + If this is a component, return the component's name on the owning + block; otherwise return the value converted to a string + """ + _base = super(NumericValue, self) + if hasattr(_base, 'getname'): + return _base.getname(fully_qualified, name_buffer) + else: + return str(type(self)) + + @property + def name(self): + return self.getname(fully_qualified=True) + + @property + def local_name(self): + return self.getname(fully_qualified=False) + + def is_numeric_type(self): + """Return True if this class is a Pyomo numeric object""" + return True + + def is_constant(self): + """Return True if this numeric value is a constant value""" + return False + + def is_fixed(self): + """Return True if this is a non-constant value that has been fixed""" + return False + + def is_potentially_variable(self): + """Return True if variables can appear in this expression""" + return False + + @deprecated( + "is_relational() is deprecated in favor of " + "is_expression_type(ExpressionType.RELATIONAL)", + version='6.4.3', + ) + def is_relational(self): + """ + Return True if this numeric value represents a relational expression. + """ + return False + + def is_indexed(self): + """Return True if this numeric value is an indexed object""" + return False + + def polynomial_degree(self): + """ + Return the polynomial degree of the expression. + + Returns: + :const:`None` + """ + return self._compute_polynomial_degree(None) + + def _compute_polynomial_degree(self, values): + """ + Compute the polynomial degree of this expression given + the degree values of its children. + + Args: + values (list): A list of values that indicate the degree + of the children expression. + + Returns: + :const:`None` + """ + return None + + def __bool__(self): + """Coerce the value to a bool + + Numeric values can be coerced to bool only if the value / + expression is constant. Fixed (but non-constant) or variable + values will raise an exception. + + Raises: + PyomoException + + """ + # Note that we want to implement __bool__, as scalar numeric + # components (e.g., Param, Var) implement __len__ (since they + # are implicit containers), and Python falls back on __len__ if + # __bool__ is not defined. + if self.is_constant(): + return bool(self()) + raise PyomoException( + """ +Cannot convert non-constant Pyomo numeric value (%s) to bool. +This error is usually caused by using a Var, unit, or mutable Param in a +Boolean context such as an "if" statement. For example, + >>> m.x = Var() + >>> if not m.x: + ... pass +would cause this exception.""".strip() + % (self,) + ) + + def __float__(self): + """Coerce the value to a floating point + + Numeric values can be coerced to float only if the value / + expression is constant. Fixed (but non-constant) or variable + values will raise an exception. + + Raises: + TypeError + + """ + if self.is_constant(): + return float(self()) + raise TypeError( + """ +Implicit conversion of Pyomo numeric value (%s) to float is disabled. +This error is often the result of using Pyomo components as arguments to +one of the Python built-in math module functions when defining +expressions. Avoid this error by using Pyomo-provided math functions or +explicitly resolving the numeric value using the Pyomo value() function. +""".strip() + % (self,) + ) + + def __int__(self): + """Coerce the value to an integer + + Numeric values can be coerced to int only if the value / + expression is constant. Fixed (but non-constant) or variable + values will raise an exception. + + Raises: + TypeError + + """ + if self.is_constant(): + return int(self()) + raise TypeError( + """ +Implicit conversion of Pyomo numeric value (%s) to int is disabled. +This error is often the result of using Pyomo components as arguments to +one of the Python built-in math module functions when defining +expressions. Avoid this error by using Pyomo-provided math functions or +explicitly resolving the numeric value using the Pyomo value() function. +""".strip() + % (self,) + ) + + def __lt__(self, other): + """ + Less than operator + + This method is called when Python processes statements of the form:: + + self < other + other > self + """ + return _generate_relational_expression(_lt, self, other) + + def __gt__(self, other): + """ + Greater than operator + + This method is called when Python processes statements of the form:: + + self > other + other < self + """ + return _generate_relational_expression(_lt, other, self) + + def __le__(self, other): + """ + Less than or equal operator + + This method is called when Python processes statements of the form:: + + self <= other + other >= self + """ + return _generate_relational_expression(_le, self, other) + + def __ge__(self, other): + """ + Greater than or equal operator + + This method is called when Python processes statements of the form:: + + self >= other + other <= self + """ + return _generate_relational_expression(_le, other, self) + + def __eq__(self, other): + """ + Equal to operator + + This method is called when Python processes the statement:: + + self == other + """ + return _generate_relational_expression(_eq, self, other) + + def __add__(self, other): + """ + Binary addition + + This method is called when Python processes the statement:: + + self + other + """ + return _add_dispatcher[self.__class__, other.__class__](self, other) + + def __sub__(self, other): + """ + Binary subtraction + + This method is called when Python processes the statement:: + + self - other + """ + return self.__add__(-other) + + def __mul__(self, other): + """ + Binary multiplication + + This method is called when Python processes the statement:: + + self * other + """ + return _mul_dispatcher[self.__class__, other.__class__](self, other) + + def __div__(self, other): + """ + Binary division + + This method is called when Python processes the statement:: + + self / other + """ + return _div_dispatcher[self.__class__, other.__class__](self, other) + + def __truediv__(self, other): + """ + Binary division (when __future__.division is in effect) + + This method is called when Python processes the statement:: + + self / other + """ + return _div_dispatcher[self.__class__, other.__class__](self, other) + + def __pow__(self, other): + """ + Binary power + + This method is called when Python processes the statement:: + + self ** other + """ + return _pow_dispatcher[self.__class__, other.__class__](self, other) + + def __radd__(self, other): + """ + Binary addition + + This method is called when Python processes the statement:: + + other + self + """ + return _add_dispatcher[other.__class__, self.__class__](other, self) + + def __rsub__(self, other): + """ + Binary subtraction + + This method is called when Python processes the statement:: + + other - self + """ + return other + (-self) + + def __rmul__(self, other): + """ + Binary multiplication + + This method is called when Python processes the statement:: + + other * self + + when other is not a :class:`NumericValue ` object. + """ + return _mul_dispatcher[other.__class__, self.__class__](other, self) + + def __rdiv__(self, other): + """Binary division + + This method is called when Python processes the statement:: + + other / self + """ + return _div_dispatcher[other.__class__, self.__class__](other, self) + + def __rtruediv__(self, other): + """ + Binary division (when __future__.division is in effect) + + This method is called when Python processes the statement:: + + other / self + """ + return _div_dispatcher[other.__class__, self.__class__](other, self) + + def __rpow__(self, other): + """ + Binary power + + This method is called when Python processes the statement:: + + other ** self + """ + return _pow_dispatcher[other.__class__, self.__class__](other, self) + + def __iadd__(self, other): + """ + Binary addition + + This method is called when Python processes the statement:: + + self += other + """ + return _add_dispatcher[self.__class__, other.__class__](self, other) + + def __isub__(self, other): + """ + Binary subtraction + + This method is called when Python processes the statement:: + + self -= other + """ + return self.__iadd__(-other) + + def __imul__(self, other): + """ + Binary multiplication + + This method is called when Python processes the statement:: + + self *= other + """ + return _mul_dispatcher[self.__class__, other.__class__](self, other) + + def __idiv__(self, other): + """ + Binary division + + This method is called when Python processes the statement:: + + self /= other + """ + return _div_dispatcher[self.__class__, other.__class__](self, other) + + def __itruediv__(self, other): + """ + Binary division (when __future__.division is in effect) + + This method is called when Python processes the statement:: + + self /= other + """ + return _div_dispatcher[self.__class__, other.__class__](self, other) + + def __ipow__(self, other): + """ + Binary power + + This method is called when Python processes the statement:: + + self **= other + """ + return _pow_dispatcher[self.__class__, other.__class__](self, other) + + def __neg__(self): + """ + Negation + + This method is called when Python processes the statement:: + + - self + """ + return _neg_dispatcher[self.__class__](self) + + def __pos__(self): + """ + Positive expression + + This method is called when Python processes the statement:: + + + self + """ + return self + + def __abs__(self): + """Absolute value + + This method is called when Python processes the statement:: + + abs(self) + """ + return _abs_dispatcher[self.__class__](self) + + def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): + return NumericNDArray.__array_ufunc__(None, ufunc, method, *inputs, **kwargs) + + def to_string(self, verbose=None, labeler=None, smap=None, compute_values=False): + """Return a string representation of the expression tree. + + Args: + verbose (bool): If :const:`True`, then the string + representation consists of nested functions. Otherwise, + the string representation is an infix algebraic equation. + Defaults to :const:`False`. + labeler: An object that generates string labels for + non-constant in the expression tree. Defaults to + :const:`None`. + smap: A SymbolMap instance that stores string labels for + non-constant nodes in the expression tree. Defaults to + :const:`None`. + compute_values (bool): If :const:`True`, then fixed + expressions are evaluated and the string representation + of the resulting value is returned. + + Returns: + A string representation for the expression tree. + + """ + if compute_values and self.is_fixed(): + try: + return str(self()) + except: + pass + if not self.is_constant(): + if smap is not None: + return smap.getSymbol(self, labeler) + elif labeler is not None: + return labeler(self) + return str(self) + + +# +# Note: the "if numpy_available" in the class definition also ensures +# that the numpy types are registered if numpy is in fact available +# +# TODO: Move this to a separate module to support avoiding the numpy +# import if numpy is not actually used. +class NumericNDArray(np.ndarray if numpy_available else object): + """An ndarray subclass that stores Pyomo numeric expressions""" + + def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): + if method == '__call__': + # Convert all incoming types to ndarray (to prevent recursion) + args = [np.asarray(i) for i in inputs] + # Set the return type to be an 'object'. This prevents the + # logical operators from casting the result to a bool. This + # requires numpy >= 1.6 + kwargs['dtype'] = object + + # Delegate to the base ufunc, but return an instance of this + # class so that additional operators hit this method. + ans = getattr(ufunc, method)(*args, **kwargs) + if isinstance(ans, np.ndarray): + if ans.size == 1: + return ans[0] + return ans.view(NumericNDArray) + else: + return ans + + # ------------------------------------------------------- # # Expression classes @@ -254,7 +790,7 @@ def polynomial_degree(self): A non-negative integer that is the polynomial degree if the expression is polynomial, or :const:`None` otherwise. """ - return polynomial_degree(self) + return visitor.polynomial_degree(self) def _compute_polynomial_degree(self, values): """ @@ -1108,7 +1644,7 @@ def _decompose_linear_terms(expr, multiplier=1): # ------------------------------------------------------- -class ARG_TYPE(enum.Enum): +class ARG_TYPE(enum.IntEnum): MUTABLE = -2 ASNUMERIC = -1 INVALID = 0 @@ -3369,6 +3905,7 @@ def _fcn_mutable(a, name, fcn): def _fcn_invalid(a, name, fcn): fcn(a) + # returns None def _fcn_native(a, name, fcn): @@ -3418,6 +3955,150 @@ def _register_new_fcn_dispatcher(a, name, fcn): } +# +# NOTE: abs() and pow() are not defined here, because they are +# Python operators. +# +def ceil(arg): + return _fcn_dispatcher[arg.__class__](arg, 'ceil', math.ceil) + + +def floor(arg): + return _fcn_dispatcher[arg.__class__](arg, 'floor', math.floor) + + +# e ** x +def exp(arg): + return _fcn_dispatcher[arg.__class__](arg, 'exp', math.exp) + + +def log(arg): + return _fcn_dispatcher[arg.__class__](arg, 'log', math.log) + + +def log10(arg): + return _fcn_dispatcher[arg.__class__](arg, 'log10', math.log10) + + +# FIXME: this is nominally the same as x ** 0.5, but follows a different +# path and produces a different NL file! +def sqrt(arg): + return _fcn_dispatcher[arg.__class__](arg, 'sqrt', math.sqrt) + # return _pow_dispatcher[arg.__class__, float](arg, 0.5) + + +def sin(arg): + return _fcn_dispatcher[arg.__class__](arg, 'sin', math.sin) + + +def cos(arg): + return _fcn_dispatcher[arg.__class__](arg, 'cos', math.cos) + + +def tan(arg): + return _fcn_dispatcher[arg.__class__](arg, 'tan', math.tan) + + +def sinh(arg): + return _fcn_dispatcher[arg.__class__](arg, 'sinh', math.sinh) + + +def cosh(arg): + return _fcn_dispatcher[arg.__class__](arg, 'cosh', math.cosh) + + +def tanh(arg): + return _fcn_dispatcher[arg.__class__](arg, 'tanh', math.tanh) + + +def asin(arg): + return _fcn_dispatcher[arg.__class__](arg, 'asin', math.asin) + + +def acos(arg): + return _fcn_dispatcher[arg.__class__](arg, 'acos', math.acos) + + +def atan(arg): + return _fcn_dispatcher[arg.__class__](arg, 'atan', math.atan) + + +def asinh(arg): + return _fcn_dispatcher[arg.__class__](arg, 'asinh', math.asinh) + + +def acosh(arg): + return _fcn_dispatcher[arg.__class__](arg, 'acosh', math.acosh) + + +def atanh(arg): + return _fcn_dispatcher[arg.__class__](arg, 'atanh', math.atanh) + + +# +# Function interface to Expr_ifExpression +# + + +def _process_expr_if_arg(arg, kwargs, name): + alt = kwargs.pop(name, None) + if alt is not None: + if arg is not None: + raise ValueError(f'Cannot specify both {name}_ and {name}') + arg = alt + _type = _categorize_arg_type(arg) + # Note that relational expressions get mapped to INVALID + while _type < ARG_TYPE.INVALID: + if _type is ARG_TYPE.MUTABLE: + arg = _recast_mutable(arg) + elif _type is ARG_TYPE.ASNUMERIC: + arg = arg.as_numeric() + else: + raise DeveloperError('_categorize_arg_type() returned unexpected ARG_TYPE') + _type = _categorize_arg_type(arg) + return arg, _type + + +def Expr_if(IF_=None, THEN_=None, ELSE_=None, **kwargs): + """ + Function used to construct a conditional numeric expression. + + This function accepts either of the following signatures: + + - Expr_if(IF={expr}, THEN={expr}, ELSE={expr}) + - Expr_if(IF_={expr}, THEN_={expr}, ELSE_={expr}) + + (the former is historical, and the latter is required to support Cythonization) + """ + _pv = False + ELSE_, _type = _process_expr_if_arg(ELSE_, kwargs, 'ELSE') + _pv |= _type >= ARG_TYPE.VAR or _type == ARG_TYPE.INVALID + THEN_, _type = _process_expr_if_arg(THEN_, kwargs, 'THEN') + _pv |= _type >= ARG_TYPE.VAR or _type == ARG_TYPE.INVALID + IF_, _type = _process_expr_if_arg(IF_, kwargs, 'IF') + _pv |= _type >= ARG_TYPE.VAR or _type == ARG_TYPE.INVALID + if kwargs: + raise ValueError('Unrecognized arguments: ' + ', '.join(kwargs)) + # Notes: + # - side effect: IF is the last iteration, so _type == _categorize_arg_type(IF) + # - we do NO error checking as to the actual arg types. That is + # left to the writer (and as of writing [Jul 2023], the NL writer + # is the only writer that recognized Expr_if) + if _type is ARG_TYPE.NATIVE: + return THEN_ if IF_ else ELSE_ + elif _type is ARG_TYPE.PARAM and IF_.is_constant(): + return THEN_ if IF_.value else ELSE_ + elif _pv: + return Expr_ifExpression((IF_, THEN_, ELSE_)) + else: + return NPV_Expr_ifExpression((IF_, THEN_, ELSE_)) + + +# +# Misc (legacy) functions +# + + def _balanced_parens(arg): """Verify the string argument contains balanced parentheses. diff --git a/pyomo/core/expr/numvalue.py b/pyomo/core/expr/numvalue.py index 1df9de777f0..ba008475b86 100644 --- a/pyomo/core/expr/numvalue.py +++ b/pyomo/core/expr/numvalue.py @@ -27,36 +27,13 @@ import sys import logging -from pyomo.common.dependencies import numpy as np, numpy_available from pyomo.common.deprecation import ( deprecated, deprecation_warning, relocated_module_attribute, ) -from pyomo.common.errors import PyomoException -from pyomo.core.expr.expr_common import ( - _add, - _sub, - _mul, - _div, - _pow, - _neg, - _abs, - _radd, - _rsub, - _rmul, - _rdiv, - _rpow, - _iadd, - _isub, - _imul, - _idiv, - _ipow, - _lt, - _le, - _eq, - ExpressionType, -) +from pyomo.core.expr.expr_common import ExpressionType +from pyomo.core.expr.numeric_expr import NumericValue import pyomo.common.numeric_types as _numeric_types # TODO: update Pyomo to import these objects from common.numeric_types @@ -68,9 +45,10 @@ native_integer_types, native_logical_types, pyomo_constant_types, + check_if_numeric_type, + value, ) from pyomo.core.pyomoobject import PyomoObject -from pyomo.core.expr.expr_errors import TemplateExpressionError relocated_module_attribute( 'native_boolean_types', @@ -100,27 +78,22 @@ version='6.6.0', f_globals=globals(), ) +relocated_module_attribute( + 'NumericValue', + 'pyomo.core.expr.numeric_expr.NumericValue', + version='6.6.2.dev0', + f_globals=globals(), +) +relocated_module_attribute( + 'NumericNDArray', + 'pyomo.core.expr.numeric_expr.NumericNDArray', + version='6.6.2.dev0', + f_globals=globals(), +) logger = logging.getLogger('pyomo.core') -# Stub in the dispatchers -def _incomplete_import(*args): - raise RuntimeError("incomplete import of Pyomo expression system") - - -_add_dispatcher = collections.defaultdict(_incomplete_import) -_mul_dispatcher = collections.defaultdict(_incomplete_import) -_div_dispatcher = collections.defaultdict(_incomplete_import) -_pow_dispatcher = collections.defaultdict(_incomplete_import) -_neg_dispatcher = collections.defaultdict(_incomplete_import) -_abs_dispatcher = collections.defaultdict(_incomplete_import) - - -def _generate_relational_expression(etype, lhs, rhs): - raise RuntimeError("incomplete import of Pyomo expression system") - - ##------------------------------------------------------------------------ ## ## Standard types of expressions @@ -147,93 +120,6 @@ def __str__(self): nonpyomo_leaf_types.add(NonNumericValue) -def value(obj, exception=True): - """ - A utility function that returns the value of a Pyomo object or - expression. - - Args: - obj: The argument to evaluate. If it is None, a - string, or any other primitive numeric type, - then this function simply returns the argument. - Otherwise, if the argument is a NumericValue - then the __call__ method is executed. - exception (bool): If :const:`True`, then an exception should - be raised when instances of NumericValue fail to - evaluate due to one or more objects not being - initialized to a numeric value (e.g, one or more - variables in an algebraic expression having the - value None). If :const:`False`, then the function - returns :const:`None` when an exception occurs. - Default is True. - - Returns: A numeric value or None. - """ - if obj.__class__ in native_types: - return obj - if obj.__class__ in pyomo_constant_types: - # - # I'm commenting this out for now, but I think we should never expect - # to see a numeric constant with value None. - # - # if exception and obj.value is None: - # raise ValueError( - # "No value for uninitialized NumericConstant object %s" - # % (obj.name,)) - return obj.value - # - # Test if we have a duck typed Pyomo expression - # - try: - obj.is_numeric_type() - except AttributeError: - # - # TODO: Historically we checked for new *numeric* types and - # raised exceptions for anything else. That is inconsistent - # with allowing native_types like None/str/bool to be returned - # from value(). We should revisit if that is worthwhile to do - # here. - # - if check_if_numeric_type(obj): - return obj - else: - if not exception: - return None - raise TypeError( - "Cannot evaluate object with unknown type: %s" % obj.__class__.__name__ - ) from None - # - # Evaluate the expression object - # - if exception: - # - # Here, we try to catch the exception - # - try: - tmp = obj(exception=True) - if tmp is None: - raise ValueError( - "No value for uninitialized NumericValue object %s" % (obj.name,) - ) - return tmp - except TemplateExpressionError: - # Template expressions work by catching this error type. So - # we should defer this error handling and not log an error - # message. - raise - except: - logger.error( - "evaluating object as numeric value: %s\n (object: %s)\n%s" - % (obj, type(obj), sys.exc_info()[1]) - ) - raise - else: - # - # Here, we do not try to catch the exception - # - return obj(exception=False) - - def is_constant(obj): """ A utility function that returns a boolean that indicates @@ -490,54 +376,6 @@ def as_numeric(obj): ) -def check_if_numeric_type(obj): - """Test if the argument behaves like a numeric type. - - We check for "numeric types" by checking if we can add zero to it - without changing the object's type. If that works, then we register - the type in native_numeric_types. - - """ - obj_class = obj.__class__ - # Do not re-evaluate known native types - if obj_class in native_types: - return obj_class in native_numeric_types - - try: - obj_plus_0 = obj + 0 - obj_p0_class = obj_plus_0.__class__ - # ensure that the object is comparable to 0 in a meaningful way - # (among other things, this prevents numpy.ndarray objects from - # being added to native_numeric_types) - if not ((obj < 0) ^ (obj >= 0)): - return False - # Native types *must* be hashable - hash(obj) - except: - return False - if obj_p0_class is obj_class or obj_p0_class in native_numeric_types: - # - # If we get here, this is a reasonably well-behaving - # numeric type: add it to the native numeric types - # so that future lookups will be faster. - # - _numeric_types.RegisterNumericType(obj_class) - # - # Generate a warning, since Pyomo's management of third-party - # numeric types is more robust when registering explicitly. - # - logger.warning( - f"""Dynamically registering the following numeric type: - {obj_class.__module__}.{obj_class.__name__} -Dynamic registration is supported for convenience, but there are known -limitations to this approach. We recommend explicitly registering -numeric types using RegisterNumericType() or RegisterIntegerType().""" - ) - return True - else: - return False - - @deprecated( "check_if_numeric_type_and_cache() has been deprecated in " "favor of just calling as_numeric()", @@ -555,468 +393,6 @@ def check_if_numeric_type_and_cache(obj): return obj -class NumericValue(PyomoObject): - """ - This is the base class for numeric values used in Pyomo. - """ - - __slots__ = () - - # This is required because we define __eq__ - __hash__ = None - - def getname(self, fully_qualified=False, name_buffer=None): - """ - If this is a component, return the component's name on the owning - block; otherwise return the value converted to a string - """ - _base = super(NumericValue, self) - if hasattr(_base, 'getname'): - return _base.getname(fully_qualified, name_buffer) - else: - return str(type(self)) - - @property - def name(self): - return self.getname(fully_qualified=True) - - @property - def local_name(self): - return self.getname(fully_qualified=False) - - def is_numeric_type(self): - """Return True if this class is a Pyomo numeric object""" - return True - - def is_constant(self): - """Return True if this numeric value is a constant value""" - return False - - def is_fixed(self): - """Return True if this is a non-constant value that has been fixed""" - return False - - def is_potentially_variable(self): - """Return True if variables can appear in this expression""" - return False - - @deprecated( - "is_relational() is deprecated in favor of " - "is_expression_type(ExpressionType.RELATIONAL)", - version='6.4.3', - ) - def is_relational(self): - """ - Return True if this numeric value represents a relational expression. - """ - return False - - def is_indexed(self): - """Return True if this numeric value is an indexed object""" - return False - - def polynomial_degree(self): - """ - Return the polynomial degree of the expression. - - Returns: - :const:`None` - """ - return self._compute_polynomial_degree(None) - - def _compute_polynomial_degree(self, values): - """ - Compute the polynomial degree of this expression given - the degree values of its children. - - Args: - values (list): A list of values that indicate the degree - of the children expression. - - Returns: - :const:`None` - """ - return None - - def __bool__(self): - """Coerce the value to a bool - - Numeric values can be coerced to bool only if the value / - expression is constant. Fixed (but non-constant) or variable - values will raise an exception. - - Raises: - PyomoException - - """ - # Note that we want to implement __bool__, as scalar numeric - # components (e.g., Param, Var) implement __len__ (since they - # are implicit containers), and Python falls back on __len__ if - # __bool__ is not defined. - if self.is_constant(): - return bool(self()) - raise PyomoException( - """ -Cannot convert non-constant Pyomo numeric value (%s) to bool. -This error is usually caused by using a Var, unit, or mutable Param in a -Boolean context such as an "if" statement. For example, - >>> m.x = Var() - >>> if not m.x: - ... pass -would cause this exception.""".strip() - % (self,) - ) - - def __float__(self): - """Coerce the value to a floating point - - Numeric values can be coerced to float only if the value / - expression is constant. Fixed (but non-constant) or variable - values will raise an exception. - - Raises: - TypeError - - """ - if self.is_constant(): - return float(self()) - raise TypeError( - """ -Implicit conversion of Pyomo numeric value (%s) to float is disabled. -This error is often the result of using Pyomo components as arguments to -one of the Python built-in math module functions when defining -expressions. Avoid this error by using Pyomo-provided math functions or -explicitly resolving the numeric value using the Pyomo value() function. -""".strip() - % (self,) - ) - - def __int__(self): - """Coerce the value to an integer - - Numeric values can be coerced to int only if the value / - expression is constant. Fixed (but non-constant) or variable - values will raise an exception. - - Raises: - TypeError - - """ - if self.is_constant(): - return int(self()) - raise TypeError( - """ -Implicit conversion of Pyomo numeric value (%s) to int is disabled. -This error is often the result of using Pyomo components as arguments to -one of the Python built-in math module functions when defining -expressions. Avoid this error by using Pyomo-provided math functions or -explicitly resolving the numeric value using the Pyomo value() function. -""".strip() - % (self,) - ) - - def __lt__(self, other): - """ - Less than operator - - This method is called when Python processes statements of the form:: - - self < other - other > self - """ - return _generate_relational_expression(_lt, self, other) - - def __gt__(self, other): - """ - Greater than operator - - This method is called when Python processes statements of the form:: - - self > other - other < self - """ - return _generate_relational_expression(_lt, other, self) - - def __le__(self, other): - """ - Less than or equal operator - - This method is called when Python processes statements of the form:: - - self <= other - other >= self - """ - return _generate_relational_expression(_le, self, other) - - def __ge__(self, other): - """ - Greater than or equal operator - - This method is called when Python processes statements of the form:: - - self >= other - other <= self - """ - return _generate_relational_expression(_le, other, self) - - def __eq__(self, other): - """ - Equal to operator - - This method is called when Python processes the statement:: - - self == other - """ - return _generate_relational_expression(_eq, self, other) - - def __add__(self, other): - """ - Binary addition - - This method is called when Python processes the statement:: - - self + other - """ - return _add_dispatcher[self.__class__, other.__class__](self, other) - - def __sub__(self, other): - """ - Binary subtraction - - This method is called when Python processes the statement:: - - self - other - """ - return self.__add__(-other) - - def __mul__(self, other): - """ - Binary multiplication - - This method is called when Python processes the statement:: - - self * other - """ - return _mul_dispatcher[self.__class__, other.__class__](self, other) - - def __div__(self, other): - """ - Binary division - - This method is called when Python processes the statement:: - - self / other - """ - return _div_dispatcher[self.__class__, other.__class__](self, other) - - def __truediv__(self, other): - """ - Binary division (when __future__.division is in effect) - - This method is called when Python processes the statement:: - - self / other - """ - return _div_dispatcher[self.__class__, other.__class__](self, other) - - def __pow__(self, other): - """ - Binary power - - This method is called when Python processes the statement:: - - self ** other - """ - return _pow_dispatcher[self.__class__, other.__class__](self, other) - - def __radd__(self, other): - """ - Binary addition - - This method is called when Python processes the statement:: - - other + self - """ - return _add_dispatcher[other.__class__, self.__class__](other, self) - - def __rsub__(self, other): - """ - Binary subtraction - - This method is called when Python processes the statement:: - - other - self - """ - return other + (-self) - - def __rmul__(self, other): - """ - Binary multiplication - - This method is called when Python processes the statement:: - - other * self - - when other is not a :class:`NumericValue ` object. - """ - return _mul_dispatcher[other.__class__, self.__class__](other, self) - - def __rdiv__(self, other): - """Binary division - - This method is called when Python processes the statement:: - - other / self - """ - return _div_dispatcher[other.__class__, self.__class__](other, self) - - def __rtruediv__(self, other): - """ - Binary division (when __future__.division is in effect) - - This method is called when Python processes the statement:: - - other / self - """ - return _div_dispatcher[other.__class__, self.__class__](other, self) - - def __rpow__(self, other): - """ - Binary power - - This method is called when Python processes the statement:: - - other ** self - """ - return _pow_dispatcher[other.__class__, self.__class__](other, self) - - def __iadd__(self, other): - """ - Binary addition - - This method is called when Python processes the statement:: - - self += other - """ - return _add_dispatcher[self.__class__, other.__class__](self, other) - - def __isub__(self, other): - """ - Binary subtraction - - This method is called when Python processes the statement:: - - self -= other - """ - return self.__iadd__(-other) - - def __imul__(self, other): - """ - Binary multiplication - - This method is called when Python processes the statement:: - - self *= other - """ - return _mul_dispatcher[self.__class__, other.__class__](self, other) - - def __idiv__(self, other): - """ - Binary division - - This method is called when Python processes the statement:: - - self /= other - """ - return _div_dispatcher[self.__class__, other.__class__](self, other) - - def __itruediv__(self, other): - """ - Binary division (when __future__.division is in effect) - - This method is called when Python processes the statement:: - - self /= other - """ - return _div_dispatcher[self.__class__, other.__class__](self, other) - - def __ipow__(self, other): - """ - Binary power - - This method is called when Python processes the statement:: - - self **= other - """ - return _pow_dispatcher[self.__class__, other.__class__](self, other) - - def __neg__(self): - """ - Negation - - This method is called when Python processes the statement:: - - - self - """ - return _neg_dispatcher[self.__class__](self) - - def __pos__(self): - """ - Positive expression - - This method is called when Python processes the statement:: - - + self - """ - return self - - def __abs__(self): - """Absolute value - - This method is called when Python processes the statement:: - - abs(self) - """ - return _abs_dispatcher[self.__class__](self) - - def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): - return NumericNDArray.__array_ufunc__(None, ufunc, method, *inputs, **kwargs) - - def to_string(self, verbose=None, labeler=None, smap=None, compute_values=False): - """Return a string representation of the expression tree. - - Args: - verbose (bool): If :const:`True`, then the string - representation consists of nested functions. Otherwise, - the string representation is an infix algebraic equation. - Defaults to :const:`False`. - labeler: An object that generates string labels for - non-constant in the expression tree. Defaults to - :const:`None`. - smap: A SymbolMap instance that stores string labels for - non-constant nodes in the expression tree. Defaults to - :const:`None`. - compute_values (bool): If :const:`True`, then fixed - expressions are evaluated and the string representation - of the resulting value is returned. - - Returns: - A string representation for the expression tree. - - """ - if compute_values and self.is_fixed(): - try: - return str(self()) - except: - pass - if not self.is_constant(): - if smap is not None: - return smap.getSymbol(self, labeler) - elif labeler is not None: - return labeler(self) - return str(self) - - class NumericConstant(NumericValue): """An object that contains a constant numeric value. @@ -1055,32 +431,3 @@ def pprint(self, ostream=None, verbose=False): # We use as_numeric() so that the constant is also in the cache ZeroConstant = as_numeric(0) - - -# -# Note: the "if numpy_available" in the class definition also ensures -# that the numpy types are registered if numpy is in fact available -# - - -class NumericNDArray(np.ndarray if numpy_available else object): - """An ndarray subclass that stores Pyomo numeric expressions""" - - def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): - if method == '__call__': - # Convert all incoming types to ndarray (to prevent recursion) - args = [np.asarray(i) for i in inputs] - # Set the return type to be an 'object'. This prevents the - # logical operators from casting the result to a bool. This - # requires numpy >= 1.6 - kwargs['dtype'] = object - - # Delegate to the base ufunc, but return an instance of this - # class so that additional operators hit this method. - ans = getattr(ufunc, method)(*args, **kwargs) - if isinstance(ans, np.ndarray): - if ans.size == 1: - return ans[0] - return ans.view(NumericNDArray) - else: - return ans diff --git a/pyomo/core/expr/relational_expr.py b/pyomo/core/expr/relational_expr.py index 89bcdcc0411..2909be95c5a 100644 --- a/pyomo/core/expr/relational_expr.py +++ b/pyomo/core/expr/relational_expr.py @@ -14,17 +14,16 @@ from pyomo.common.deprecation import deprecated from pyomo.common.errors import PyomoException, DeveloperError +from pyomo.common.numeric_types import ( + native_numeric_types, + check_if_numeric_type, + value, +) from .base import ExpressionBase from .boolean_value import BooleanValue from .expr_common import _lt, _le, _eq, ExpressionType -from .numvalue import ( - native_numeric_types, - is_potentially_variable, - is_constant, - value, - check_if_numeric_type, -) +from .numvalue import is_potentially_variable, is_constant from .visitor import polynomial_degree # ------------------------------------------------------- diff --git a/pyomo/core/expr/template_expr.py b/pyomo/core/expr/template_expr.py index 97b0bfa0912..4682844fef0 100644 --- a/pyomo/core/expr/template_expr.py +++ b/pyomo/core/expr/template_expr.py @@ -16,8 +16,8 @@ import builtins from pyomo.common.backports import nullcontext +from pyomo.common.errors import TemplateExpressionError from pyomo.core.expr.base import ExpressionBase, ExpressionArgs_Mixin, NPV_Mixin -from pyomo.core.expr.expr_errors import TemplateExpressionError from pyomo.core.expr.logical_expr import BooleanExpression from pyomo.core.expr.numeric_expr import ( NumericExpression, diff --git a/pyomo/core/expr/visitor.py b/pyomo/core/expr/visitor.py index 2cdf3a1a39c..c8f22ba1d3a 100644 --- a/pyomo/core/expr/visitor.py +++ b/pyomo/core/expr/visitor.py @@ -19,17 +19,16 @@ logger = logging.getLogger('pyomo.core') -from .symbol_map import SymbolMap -from . import expr_common as common -from .expr_errors import TemplateExpressionError from pyomo.common.deprecation import deprecated, deprecation_warning -from pyomo.common.errors import DeveloperError -from pyomo.core.expr.numvalue import ( +from pyomo.common.errors import DeveloperError, TemplateExpressionError +from pyomo.common.numeric_types import ( nonpyomo_leaf_types, native_types, native_numeric_types, value, ) +import pyomo.core.expr.expr_common as common +from pyomo.core.expr.symbol_map import SymbolMap try: # sys._getframe is slightly faster than inspect's currentframe, but diff --git a/pyomo/core/plugins/transform/radix_linearization.py b/pyomo/core/plugins/transform/radix_linearization.py index 697be203f2a..98f00573974 100644 --- a/pyomo/core/plugins/transform/radix_linearization.py +++ b/pyomo/core/plugins/transform/radix_linearization.py @@ -10,6 +10,7 @@ # ___________________________________________________________________________ from pyomo.core.expr.current import ProductExpression, PowExpression +from pyomo.core.expr.numvalue import as_numeric from pyomo.core import Binary, value from pyomo.core.base import ( Transformation, @@ -20,7 +21,6 @@ Block, RangeSet, ) -from pyomo.core.base.numvalue import as_numeric from pyomo.core.base.var import _VarData import logging diff --git a/pyomo/core/tests/unit/kernel/test_dict_container.py b/pyomo/core/tests/unit/kernel/test_dict_container.py index ea419136053..e6b6f8d7aab 100644 --- a/pyomo/core/tests/unit/kernel/test_dict_container.py +++ b/pyomo/core/tests/unit/kernel/test_dict_container.py @@ -16,6 +16,7 @@ import pyomo.common.unittest as unittest import pyomo.kernel as pmo from pyomo.common.log import LoggingIntercept +from pyomo.common.tee import capture_output from pyomo.core.kernel.base import ICategorizedObject, ICategorizedObjectContainer from pyomo.core.kernel.homogeneous_container import IHomogeneousContainer from pyomo.core.kernel.dict_container import DictContainer @@ -72,17 +73,18 @@ def test_pprint(self): # Not really testing what the output is, just that # an error does not occur. The pprint functionality # is still in the early stages. - cdict = self._container_type({None: self._ctype_factory()}) - pyomo.kernel.pprint(cdict) - b = block() - b.cdict = cdict - pyomo.kernel.pprint(cdict) - pyomo.kernel.pprint(b) - m = block() - m.b = b - pyomo.kernel.pprint(cdict) - pyomo.kernel.pprint(b) - pyomo.kernel.pprint(m) + with capture_output() as OUT: + cdict = self._container_type({None: self._ctype_factory()}) + pyomo.kernel.pprint(cdict) + b = block() + b.cdict = cdict + pyomo.kernel.pprint(cdict) + pyomo.kernel.pprint(b) + m = block() + m.b = b + pyomo.kernel.pprint(cdict) + pyomo.kernel.pprint(b) + pyomo.kernel.pprint(m) def test_ctype(self): c = self._container_type() @@ -863,7 +865,7 @@ def descend(x): return not x._is_heterogeneous_container descend.seen = [] - pmo.pprint(cdict) + # pmo.pprint(cdict) order = list(pmo.preorder_traversal(cdict, active=True, descend=descend)) self.assertEqual([None, '[0]', '[2]'], [c.name for c in order]) self.assertEqual( diff --git a/pyomo/core/tests/unit/test_numeric_expr.py b/pyomo/core/tests/unit/test_numeric_expr.py index 156dba33214..179ebed16ac 100644 --- a/pyomo/core/tests/unit/test_numeric_expr.py +++ b/pyomo/core/tests/unit/test_numeric_expr.py @@ -67,7 +67,7 @@ ) from pyomo.kernel import variable, expression, objective -from pyomo.core.expr.expr_common import ExpressionType +from pyomo.core.expr.expr_common import ExpressionType, clone_counter from pyomo.core.expr.numvalue import ( NumericConstant, as_numeric, @@ -94,7 +94,6 @@ NPV_DivisionExpression, NPV_SumExpression, decompose_term, - clone_counter, nonlinear_expression, _MutableLinearExpression, _MutableSumExpression, @@ -2453,7 +2452,7 @@ def test_expr_if(self): m = ConcreteModel() m.a = Var() m.b = Var() - expr = Expr_if(IF=m.a + m.b < 20, THEN=m.a, ELSE=m.b) + expr = Expr_if(IF_=m.a + m.b < 20, THEN_=m.a, ELSE_=m.b) self.assertEqual( "Expr_if( ( a + b < 20 ), then=( a ), else=( b ) )", str(expr) ) @@ -2461,6 +2460,10 @@ def test_expr_if(self): self.assertEqual( "Expr_if( ( a + b < 20 ), then=( 1 ), else=( b ) )", str(expr) ) + with self.assertRaisesRegex(ValueError, "Cannot specify both THEN_ and THEN"): + Expr_if(IF_=m.a + m.b < 20, THEN_=1, ELSE_=m.b, THEN=2) + with self.assertRaisesRegex(ValueError, "Unrecognized arguments: _THEN_"): + Expr_if(IF_=m.a + m.b < 20, _THEN_=1, ELSE_=m.b) def test_getitem(self): m = ConcreteModel() diff --git a/pyomo/core/tests/unit/test_numpy_expr.py b/pyomo/core/tests/unit/test_numpy_expr.py index 60ccda5fd57..e9afdc9b2a8 100644 --- a/pyomo/core/tests/unit/test_numpy_expr.py +++ b/pyomo/core/tests/unit/test_numpy_expr.py @@ -29,7 +29,8 @@ Reals, ) from pyomo.core.expr.current import MonomialTermExpression -from pyomo.core.expr.numvalue import NumericNDArray, as_numeric +from pyomo.core.expr.numeric_expr import NumericNDArray +from pyomo.core.expr.numvalue import as_numeric from pyomo.core.expr.compare import compare_expressions from pyomo.core.expr.relational_expr import InequalityExpression diff --git a/pyomo/core/tests/unit/test_visitor.py b/pyomo/core/tests/unit/test_visitor.py index 0dc7e6ebc08..b70996a13dc 100644 --- a/pyomo/core/tests/unit/test_visitor.py +++ b/pyomo/core/tests/unit/test_visitor.py @@ -74,8 +74,8 @@ ) from pyomo.core.base.param import _ParamData, ScalarParam from pyomo.core.expr.template_expr import IndexTemplate -from pyomo.core.expr.expr_errors import TemplateExpressionError from pyomo.common.collections import ComponentSet +from pyomo.common.errors import TemplateExpressionError from pyomo.common.log import LoggingIntercept from io import StringIO from pyomo.core.expr.compare import assertExpressionsEqual diff --git a/pyomo/dae/contset.py b/pyomo/dae/contset.py index f9d052dbf9b..ee4c9f79e89 100644 --- a/pyomo/dae/contset.py +++ b/pyomo/dae/contset.py @@ -11,10 +11,10 @@ import logging import bisect +from pyomo.common.numeric_types import native_numeric_types from pyomo.common.timing import ConstructionTimer from pyomo.core.base.set import SortedScalarSet from pyomo.core.base.component import ModelComponentFactory -from pyomo.core.base.numvalue import native_numeric_types logger = logging.getLogger('pyomo.dae') __all__ = ['ContinuousSet'] diff --git a/pyomo/network/port.py b/pyomo/network/port.py index e68ee4a7930..dfe9cd54441 100644 --- a/pyomo/network/port.py +++ b/pyomo/network/port.py @@ -20,6 +20,7 @@ from pyomo.common.formatting import tabular_writer from pyomo.common.log import is_debug_set from pyomo.common.modeling import unique_component_name, NOTSET +from pyomo.common.numeric_types import value from pyomo.common.timing import ConstructionTimer from pyomo.core.base.var import Var @@ -28,7 +29,7 @@ from pyomo.core.base.global_set import UnindexedComponent_index from pyomo.core.base.indexed_component import IndexedComponent, UnindexedComponent_set from pyomo.core.base.misc import apply_indexed_rule -from pyomo.core.base.numvalue import as_numeric, value +from pyomo.core.expr.numvalue import as_numeric from pyomo.core.expr.current import identify_variables from pyomo.core.base.label import alphanum_label_from_name diff --git a/pyomo/repn/beta/matrix.py b/pyomo/repn/beta/matrix.py index 76a344ecb45..ff2d6857bd6 100644 --- a/pyomo/repn/beta/matrix.py +++ b/pyomo/repn/beta/matrix.py @@ -21,9 +21,10 @@ from weakref import ref as weakref_ref from pyomo.common.log import is_debug_set +from pyomo.common.numeric_types import value +from pyomo.core.expr.numvalue import is_fixed, ZeroConstant from pyomo.core.base.set_types import Any from pyomo.core.base import SortComponents, Var -from pyomo.core.base.numvalue import is_fixed, value, ZeroConstant from pyomo.core.base.component import ModelComponentFactory from pyomo.core.base.constraint import ( Constraint, diff --git a/pyomo/repn/plugins/baron_writer.py b/pyomo/repn/plugins/baron_writer.py index 72802fbc6bc..63c5f44d4bd 100644 --- a/pyomo/repn/plugins/baron_writer.py +++ b/pyomo/repn/plugins/baron_writer.py @@ -28,6 +28,7 @@ native_types, nonpyomo_leaf_types, ) +from pyomo.core.expr.visitor import _ToStringVisitor from pyomo.core.expr import current as EXPR from pyomo.core.base import ( SortComponents, @@ -106,7 +107,7 @@ def _handle_AbsExpression(visitor, node, values): # A visitor pattern that creates a string for an expression # that is compatible with the BARON syntax. # -class ToBaronVisitor(EXPR._ToStringVisitor): +class ToBaronVisitor(_ToStringVisitor): _expression_handlers = { EXPR.PowExpression: _handle_PowExpression, EXPR.UnaryFunctionExpression: _handle_UnaryFunctionExpression, diff --git a/pyomo/repn/plugins/gams_writer.py b/pyomo/repn/plugins/gams_writer.py index 7dc827f0aed..064c2857d23 100644 --- a/pyomo/repn/plugins/gams_writer.py +++ b/pyomo/repn/plugins/gams_writer.py @@ -24,6 +24,7 @@ native_numeric_types, nonpyomo_leaf_types, ) +from pyomo.core.expr.visitor import _ToStringVisitor from pyomo.core.base import ( SymbolMap, ShortNameLabeler, @@ -99,7 +100,7 @@ def _handle_AbsExpression(visitor, node, values): # A visitor pattern that creates a string for an expression # that is compatible with the GAMS syntax. # -class ToGamsVisitor(EXPR._ToStringVisitor): +class ToGamsVisitor(_ToStringVisitor): _expression_handlers = { EXPR.PowExpression: _handle_PowExpression, EXPR.UnaryFunctionExpression: _handle_UnaryFunctionExpression, diff --git a/pyomo/repn/standard_repn.py b/pyomo/repn/standard_repn.py index 66585a13869..1ad99ff29a2 100644 --- a/pyomo/repn/standard_repn.py +++ b/pyomo/repn/standard_repn.py @@ -18,15 +18,16 @@ import logging import itertools +from pyomo.common.numeric_types import native_types, native_numeric_types from pyomo.core.base import Constraint, Objective, ComponentMap from pyomo.core.expr import current as EXPR +from pyomo.core.expr.numvalue import NumericConstant from pyomo.core.base.objective import _GeneralObjectiveData, ScalarObjective from pyomo.core.base import _ExpressionData, Expression from pyomo.core.base.expression import ScalarExpression, _GeneralExpressionData from pyomo.core.base.var import ScalarVar, Var, _GeneralVarData, value from pyomo.core.base.param import ScalarParam, _ParamData -from pyomo.core.base.numvalue import NumericConstant, native_numeric_types from pyomo.core.kernel.expression import expression, noclone from pyomo.core.kernel.variable import IVariable, variable from pyomo.core.kernel.objective import objective @@ -969,7 +970,7 @@ def _collect_division(exp, multiplier, idMap, compute_values, verbose, quadratic def _collect_branching_expr(exp, multiplier, idMap, compute_values, verbose, quadratic): _if, _then, _else = exp.args - if _if.__class__ in native_numeric_types: # TODO: coverage? + if _if.__class__ in native_types: if_val = _if elif not _if.is_potentially_variable(): if compute_values: diff --git a/pyomo/repn/tests/diffutils.py b/pyomo/repn/tests/diffutils.py index d86f15aa711..d4edc026bd6 100644 --- a/pyomo/repn/tests/diffutils.py +++ b/pyomo/repn/tests/diffutils.py @@ -42,7 +42,7 @@ def load_baseline(baseline, testfile, extension, version): _tmp = [baseline[:-3]] else: _tmp = baseline.split(f'.{extension}.', 1) - _tmp.insert(1, f'expr{int(EXPR._mode)}') + _tmp.insert(1, f'expr{int(EXPR.Mode.CURRENT)}') _tmp.insert(2, version) if not os.path.exists('.'.join(_tmp)): _tmp.pop(1)