Skip to content

Commit

Permalink
Merge pull request #2909 from jsiirola/reorg-expr-imports
Browse files Browse the repository at this point in the history
Reorganize numeric expression modules
  • Loading branch information
jsiirola authored Jul 21, 2023
2 parents 25db781 + 5a3af38 commit ade5ac0
Show file tree
Hide file tree
Showing 30 changed files with 1,165 additions and 1,004 deletions.
16 changes: 16 additions & 0 deletions pyomo/common/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
141 changes: 141 additions & 0 deletions pyomo/common/numeric_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
2 changes: 1 addition & 1 deletion pyomo/core/base/PyomoModel.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@
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
from pyomo.core.base.var import Var
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
Expand Down
4 changes: 2 additions & 2 deletions pyomo/core/base/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
10 changes: 7 additions & 3 deletions pyomo/core/base/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,25 @@
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
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, 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.
Expand Down
5 changes: 3 additions & 2 deletions pyomo/core/base/indexed_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
4 changes: 2 additions & 2 deletions pyomo/core/base/param.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pyomo/core/base/piecewise.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@

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
from pyomo.core.base.constraint import Constraint, ConstraintList
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')
Expand Down
Loading

0 comments on commit ade5ac0

Please sign in to comment.