Source code for pyomo.core.expr.numvalue

#  ___________________________________________________________________________
#
#  Pyomo: Python Optimization Modeling Objects
#  Copyright (c) 2008-2025
#  National Technology and Engineering Solutions of Sandia, LLC
#  Under the terms of Contract DE-NA0003525 with National Technology and
#  Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
#  rights in this software.
#  This software is distributed under the 3-clause BSD License.
#  ___________________________________________________________________________

import sys
import logging

from pyomo.common.deprecation import (
    deprecated,
    deprecation_warning,
    relocated_module_attribute,
)
from pyomo.common.modeling import NOTSET
from pyomo.core.expr.expr_common import ExpressionType, _type_check_exception_arg
from pyomo.core.expr.numeric_expr import NumericValue

# TODO: update Pyomo to import these objects from common.numeric_types
#   (and not from here)
from pyomo.common.numeric_types import (
    nonpyomo_leaf_types,
    native_types,
    native_numeric_types,
    native_integer_types,
    native_logical_types,
    _pyomo_constant_types,
    check_if_numeric_type,
    value,
)
from pyomo.core.pyomoobject import PyomoObject

relocated_module_attribute(
    'native_boolean_types',
    'pyomo.common.numeric_types._native_boolean_types',
    version='6.6.0',
    f_globals=globals(),
    msg="The native_boolean_types set will be removed in the future: the set "
    "contains types that were convertible to bool, and not types that should "
    "be treated as if they were bool (as was the case for the other "
    "native_*_types sets).  Users likely should use native_logical_types.",
)
relocated_module_attribute(
    'pyomo_constant_types',
    'pyomo.common.numeric_types._pyomo_constant_types',
    version='6.7.2',
    f_globals=globals(),
    msg="The pyomo_constant_types set will be removed in the future: the set "
    "contained only NumericConstant and _PythonCallbackFunctionID, and provided "
    "no meaningful value to clients or walkers.  Users should likely handle "
    "these types in the same manner as immutable Params.",
)
relocated_module_attribute(
    'RegisterNumericType',
    'pyomo.common.numeric_types.RegisterNumericType',
    version='6.6.0',
    f_globals=globals(),
)
relocated_module_attribute(
    'RegisterIntegerType',
    'pyomo.common.numeric_types.RegisterIntegerType',
    version='6.6.0',
    f_globals=globals(),
)
relocated_module_attribute(
    'RegisterBooleanType',
    'pyomo.common.numeric_types.RegisterBooleanType',
    version='6.6.0',
    f_globals=globals(),
)
relocated_module_attribute(
    'NumericValue',
    'pyomo.core.expr.numeric_expr.NumericValue',
    version='6.6.2',
    f_globals=globals(),
)
relocated_module_attribute(
    'NumericNDArray',
    'pyomo.core.expr.numeric_expr.NumericNDArray',
    version='6.6.2',
    f_globals=globals(),
)

logger = logging.getLogger('pyomo.core')


##------------------------------------------------------------------------
##
## Standard types of expressions
##
##------------------------------------------------------------------------


[docs] class NonNumericValue(PyomoObject): """An object that contains a non-numeric value Constructor Arguments: value The initial value. """ __slots__ = ('value',)
[docs] def __init__(self, value): self.value = value
def __str__(self): return str(self.value) def __repr__(self): return repr(self.value) def __call__(self, exception=NOTSET): exception = _type_check_exception_arg(self, exception) return self.value def is_constant(self): return True def is_fixed(self): return True
nonpyomo_leaf_types.add(NonNumericValue)
[docs] def is_constant(obj): """ A utility function that returns a boolean that indicates whether the object is a constant. """ # JDS: NB: I am not sure why we allow str to be a constant, but # since we have historically done so, we check for type membership # in native_types and not in native_numeric_types. # if obj.__class__ in native_types: return True try: return obj.is_constant() except AttributeError: pass # Now we need to confirm that we have an unknown numeric type # # As this branch is only hit for previously unknown (to Pyomo) # types that behave reasonably like numbers, we know they *must* # be constant. if check_if_numeric_type(obj): return True else: raise TypeError( "Cannot assess properties of object with unknown type: %s" % (type(obj).__name__,) )
[docs] def is_fixed(obj): """ A utility function that returns a boolean that indicates whether the input object's value is fixed. """ # JDS: NB: I am not sure why we allow str to be a constant, but # since we have historically done so, we check for type membership # in native_types and not in native_numeric_types. # if obj.__class__ in native_types: return True try: return obj.is_fixed() except AttributeError: pass # Now we need to confirm that we have an unknown numeric type # # As this branch is only hit for previously unknown (to Pyomo) # types that behave reasonably like numbers, we know they *must* # be fixed. if check_if_numeric_type(obj): return True else: raise TypeError( "Cannot assess properties of object with unknown type: %s" % (type(obj).__name__,) )
[docs] def is_variable_type(obj): """ A utility function that returns a boolean indicating whether the input object is a variable. """ if obj.__class__ in native_types: return False try: return obj.is_variable_type() except AttributeError: return False
[docs] def is_potentially_variable(obj): """ A utility function that returns a boolean indicating whether the input object can reference variables. """ if obj.__class__ in native_types: return False try: return obj.is_potentially_variable() except AttributeError: return False
[docs] def is_numeric_data(obj): """ A utility function that returns a boolean indicating whether the input object is numeric and not potentially variable. """ if obj.__class__ in native_numeric_types: return True elif obj.__class__ in native_types: # this likely means it is a string return False try: # Test if this is an expression object that # is not potentially variable return not obj.is_potentially_variable() except AttributeError: pass # Now we need to confirm that we have an unknown numeric type # # As this branch is only hit for previously unknown (to Pyomo) # types that behave reasonably like numbers, we know they *must* # be numeric data (unless an exception is raised). return check_if_numeric_type(obj)
[docs] def polynomial_degree(obj): """ A utility function that returns an integer that indicates the polynomial degree for an object. boolean indicating """ if obj.__class__ in native_numeric_types: return 0 elif obj.__class__ in native_types: raise TypeError( "Cannot evaluate the polynomial degree of a non-numeric type: %s" % (type(obj).__name__,) ) try: return obj.polynomial_degree() except AttributeError: pass # Now we need to confirm that we have an unknown numeric type if check_if_numeric_type(obj): # As this branch is only hit for previously unknown (to Pyomo) # types that behave reasonably like numbers, we know they *must* # be a numeric constant. return 0 else: raise TypeError( "Cannot assess properties of object with unknown type: %s" % (type(obj).__name__,) )
# Note: # For now, all constants are coerced to floats. This avoids integer # division in Python 2.x. (At least some of the time.) # # When we eliminate support for Python 2.x, we will not need this # coercion. The main difference in the following code is that we will # need to index KnownConstants by both the class type and value, since # INT, FLOAT and LONG values sometimes hash the same. # # It is very common to have only a few constants in a model, but those # constants get repeated many times. KnownConstants lets us re-use / # share constants we have seen before. # _KnownConstants = {}
[docs] def as_numeric(obj): """ A function that creates a NumericConstant object that wraps Python numeric values. This function also manages a cache of constants. NOTE: This function is only intended for use when data is added to a component. Args: obj: The numeric value that may be wrapped. Raises: TypeError if the object is in native_types and not in native_numeric_types Returns: A NumericConstant object or the original object. """ if obj.__class__ in native_numeric_types: val = _KnownConstants.get(obj, None) if val is not None: return val # # Coerce the value to a float, if possible # try: obj = float(obj) except: pass # # Create the numeric constant. This really # should be the only place in the code # where these objects are constructed. # retval = NumericConstant(obj) # # Cache the numeric constants. We used a bounded cache size # to avoid unexpectedly large lists of constants. There are # typically a small number of constants that need to be cached. # # NOTE: A LFU policy might be more sensible here, but that # requires a more complex cache. It's not clear that that # is worth the extra cost. # if len(_KnownConstants) < 1024: # obj may (or may not) be hashable, so we need this try # block so that things proceed normally for non-hashable # "numeric" types try: _KnownConstants[obj] = retval except: pass # return retval # # Ignore objects that are duck typed to work with Pyomo expressions # try: if obj.is_numeric_type(): return obj elif obj.is_expression_type(ExpressionType.RELATIONAL): deprecation_warning( "returning a relational expression from as_numeric(). " "Relational expressions are no longer numeric types. " "In the future this will raise a TypeError.", version='6.4.3', ) return obj else: try: _name = obj.name except AttributeError: _name = str(obj) raise TypeError( "The '%s' object '%s' is not a valid type for Pyomo " "numeric expressions" % (type(obj).__name__, _name) ) except AttributeError: pass # # Test if the object looks like a number. If so, re-call as_numeric # (this type will have been added to native_numeric_types). # if check_if_numeric_type(obj): return as_numeric(obj) # # Generate errors # if obj.__class__ in native_types: raise TypeError( "%s values ('%s') are not allowed in Pyomo " "numeric expressions" % (type(obj).__name__, str(obj)) ) raise TypeError( "Cannot treat the value '%s' as a numeric value because it has " "unknown type '%s'" % (str(obj), type(obj).__name__) )
[docs] @deprecated( "check_if_numeric_type_and_cache() has been deprecated in " "favor of just calling as_numeric()", version='6.4.3', ) def check_if_numeric_type_and_cache(obj): """Test if the argument is a numeric type by checking if we can add zero to it. If that works, then we cache the value and return a NumericConstant object. """ if check_if_numeric_type(obj): return as_numeric(obj) else: return obj
[docs] class NumericConstant(NumericValue): """An object that contains a constant numeric value. Constructor Arguments: value The initial value. """ __slots__ = ('value',)
[docs] def __init__(self, value): self.value = value
[docs] def is_constant(self): return True
[docs] def is_fixed(self): return True
def _compute_polynomial_degree(self, result): return 0 def __str__(self): return str(self.value) def __call__(self, exception=NOTSET): """Return the constant value""" exception = _type_check_exception_arg(self, exception) return self.value def pprint(self, ostream=None, verbose=False): if ostream is None: # pragma:nocover ostream = sys.stdout ostream.write(str(self))
_pyomo_constant_types.add(NumericConstant) # We use as_numeric() so that the constant is also in the cache ZeroConstant = as_numeric(0)