# ___________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright (c) 2008-2024
# 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.
# ___________________________________________________________________________
from pyomo.common.deprecation import deprecated
from pyomo.common.modeling import NOTSET
import pyomo.core.expr as EXPR
from pyomo.core.kernel.base import ICategorizedObject, _abstract_readwrite_property
from pyomo.core.kernel.container_utils import define_simple_containers
from pyomo.core.expr.numvalue import (
NumericValue,
is_fixed,
is_constant,
is_potentially_variable,
is_numeric_data,
value,
)
[docs]
class IIdentityExpression(NumericValue):
"""The interface for classes that simply wrap another
expression and perform no additional operations.
Derived classes should declare an _expr attribute or
override all implemented methods.
"""
__slots__ = ()
PRECEDENCE = 0
ASSOCIATIVITY = EXPR.OperatorAssociativity.NON_ASSOCIATIVE
@property
def expr(self):
return self._expr
#
# Implement the NumericValue abstract methods
#
def __call__(self, exception=False):
"""Compute the value of this expression.
Args:
exception (bool): Indicates if 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). Default is :const:`True`.
Returns:
numeric value or None
"""
return value(self._expr, exception=exception)
[docs]
def is_fixed(self):
"""A boolean indicating whether this expression is fixed."""
return is_fixed(self._expr)
[docs]
def is_parameter_type(self):
"""A boolean indicating whether this expression is a parameter object."""
return False
[docs]
def is_variable_type(self):
"""A boolean indicating whether this expression is a
variable object."""
return False
[docs]
def is_named_expression_type(self):
"""A boolean indicating whether this in a named expression."""
return True
[docs]
def is_expression_type(self, expression_system=None):
"""A boolean indicating whether this in an expression."""
return True
@property
def _args_(self):
"""A tuple of subexpressions involved in this expressions operation."""
return (self._expr,)
@property
def args(self):
"""A tuple of subexpressions involved in this expressions operation."""
return (self._expr,)
[docs]
def nargs(self):
"""Length of self._nargs()"""
return 1
def arg(self, i):
if i != 0:
raise KeyError("Unexpected argument id")
return self._expr
[docs]
def polynomial_degree(self):
"""The polynomial degree of the stored expression."""
if self.is_fixed():
return 0
return self._expr.polynomial_degree()
def _compute_polynomial_degree(self, values):
return values[0]
[docs]
def to_string(self, verbose=None, labeler=None, smap=None, compute_values=False):
"""Convert this expression into a string."""
return EXPR.expression_to_string(
self,
verbose=verbose,
labeler=labeler,
smap=smap,
compute_values=compute_values,
)
def _to_string(self, values, verbose, smap):
if verbose:
name = self.getname()
if name == None:
return "<%s>{%s}" % (self.__class__.__name__, values[0])
else:
if name[0] == '<':
name = ""
return "%s{%s}" % (name, values[0])
if self._expr is None:
return "%s{Undefined}" % str(self)
return values[0]
def _apply_operation(self, result):
return result[0]
def _is_fixed(self, values):
return values[0]
[docs]
def create_node_with_local_data(self, values):
"""
Construct an expression after constructing the
contained expression.
This class provides a consistent interface for constructing a
node, which is used in tree visitor scripts.
"""
return self.__class__(expr=values[0])
[docs]
def is_constant(self):
raise NotImplementedError # pragma:nocover
[docs]
def is_potentially_variable(self):
raise NotImplementedError # pragma:nocover
def clone(self):
raise NotImplementedError # pragma:nocover
[docs]
@deprecated(
"noclone() is deprecated and can be omitted: "
"Pyomo expressions natively support shared subexpressions.",
version='6.6.2',
)
def noclone(expr):
try:
if expr.is_potentially_variable():
return expression(expr)
except AttributeError:
pass
try:
if is_constant(expr):
return expr
except:
return expr
return data_expression(expr)
[docs]
class IExpression(ICategorizedObject, IIdentityExpression):
"""
The interface for mutable expressions.
"""
__slots__ = ()
#
# Implementations can choose to define these
# properties as using __slots__, __dict__, or
# by overriding the @property method
#
expr = _abstract_readwrite_property(doc="The stored expression")
#
# Override some of the NumericValue methods implemented
# by the base class
#
[docs]
def is_constant(self):
"""A boolean indicating whether this expression is constant."""
return False
[docs]
def is_potentially_variable(self):
"""A boolean indicating whether this expression can
reference variables."""
return True
[docs]
def clone(self):
"""Return a clone of this expression (no-op)."""
return self
[docs]
class expression(IExpression):
"""A named, mutable expression."""
_ctype = IExpression
__slots__ = ("_parent", "_storage_key", "_active", "_expr", "__weakref__")
[docs]
def __init__(self, expr=None):
self._parent = None
self._storage_key = None
self._active = True
self._expr = None
# call the setter
self.expr = expr
#
# Define the IExpression abstract methods
#
@property
def expr(self):
return self._expr
@expr.setter
def expr(self, expr):
self._expr = expr
[docs]
class data_expression(expression):
"""A named, mutable expression that is restricted to
storage of data expressions. An exception will be raised
if an expression is assigned that references (or is
allowed to reference) variables."""
__slots__ = ()
#
# Overload a few methods
#
[docs]
def is_potentially_variable(self):
"""A boolean indicating whether this expression can
reference variables."""
return False
[docs]
def polynomial_degree(self):
"""Always return zero because we always validate
that the stored expression can never reference
variables."""
return 0
@property
def expr(self):
return self._expr
@expr.setter
def expr(self, expr):
if (expr is not None) and (not is_numeric_data(expr)):
raise ValueError("Expression is not restricted to numeric data.")
self._expr = expr
# inserts class definitions for simple _tuple, _list, and
# _dict containers into this module
define_simple_containers(globals(), "expression", IExpression)