Design Details

Warning

Pyomo expression trees are not composed of Python objects from a single class hierarchy. Consequently, Pyomo relies on duck typing to ensure that valid expression trees are created.

Most Pyomo expression trees have the following form

  1. Interior nodes are objects that inherit from the ExpressionBase class. These objects typically have one or more child nodes. Linear expression nodes do not have child nodes, but they are treated as interior nodes in the expression tree because they references other leaf nodes.

  2. Leaf nodes are numeric values, parameter components and variable components, which represent the inputs to the expression.

Expression Classes

Expression classes typically represent unary and binary operations. The following table describes the standard operators in Python and their associated Pyomo expression class:

Operation

Python Syntax

Pyomo Class

sum

x + y

SumExpression

product

x * y

ProductExpression

negation

- x

NegationExpression

division

x / y

DivisionExpression

power

x ** y

PowExpression

inequality

x <= y

InequalityExpression

equality

x == y

EqualityExpression

Additionally, there are a variety of other Pyomo expression classes that capture more general logical relationships, which are summarized in the following table:

Operation

Example

Pyomo Class

external function

myfunc(x,y,z)

ExternalFunctionExpression

logical if-then-else

Expr_if(IF=x, THEN=y, ELSE=z)

Expr_ifExpression

intrinsic function

sin(x)

UnaryFunctionExpression

absolute function

abs(x)

AbsExpression

Expression objects are immutable. Specifically, the list of arguments to an expression object (a.k.a. the list of child nodes in the tree) cannot be changed after an expression class is constructed. To enforce this property, expression objects have a standard API for accessing expression arguments:

  • args - a class property that returns a generator that yields the expression arguments

  • arg(i) - a function that returns the i-th argument

  • nargs() - a function that returns the number of expression arguments

Warning

Developers should never use the _args_ property directly! The semantics for the use of this data has changed since earlier versions of Pyomo. For example, in some expression classes the the value nargs() may not equal len(_args_)!

Expression trees can be categorized in four different ways:

  • constant expressions - expressions that do not contain numeric constants and immutable parameters.

  • mutable expressions - expressions that contain mutable parameters but no variables.

  • potentially variable expressions - expressions that contain variables, which may be fixed.

  • fixed expressions - expressions that contain variables, all of which are fixed.

These three categories are illustrated with the following example:

m = ConcreteModel()
m.p = Param(default=10, mutable=False)
m.q = Param(default=10, mutable=True)
m.x = Var()
m.y = Var(initialize=1)
m.y.fixed = True

The following table describes four different simple expressions that consist of a single model component, and it shows how they are categorized:

Category

m.p

m.q

m.x

m.y

constant

True

False

False

False

not potentially variable

True

True

False

False

potentially_variable

False

False

True

True

fixed

True

True

False

True

Expressions classes contain methods to test whether an expression tree is in each of these categories. Additionally, Pyomo includes custom expression classes for expression trees that are not potentially variable. These custom classes will not normally be used by developers, but they provide an optimization of the checks for potentially variability.

Special Expression Classes

The following classes are exceptions to the design principles describe above.

Named Expressions

Named expressions allow for changes to an expression after it has been constructed. For example, consider the expression f defined with the Expression component:

M = ConcreteModel()
M.v = Var()
M.w = Var()

M.e = Expression(expr=2 * M.v)
f = M.e + 3  # f == 2*v + 3
M.e += M.w  # f == 2*v + 3 + w

Although f is an immutable expression, whose definition is fixed, a sub-expressions is the named expression M.e. Named expressions have a mutable value. In other words, the expression that they point to can change. Thus, a change to the value of M.e changes the expression tree for any expression that includes the named expression.

Note

The named expression classes are not implemented as sub-classes of NumericExpression. This reflects design constraints related to the fact that these are modeling components that belong to class hierarchies other than the expression class hierarchy, and Pyomo’s design prohibits the use of multiple inheritance for these classes.

Linear Expressions

Pyomo includes a special expression class for linear expressions. The class LinearExpression provides a compact description of linear polynomials. Specifically, it includes a constant value constant and two lists for coefficients and variables: linear_coefs and linear_vars.

This expression object does not have arguments, and thus it is treated as a leaf node by Pyomo visitor classes. Further, the expression API functions described above do not work with this class. Thus, developers need to treat this class differently when walking an expression tree (e.g. when developing a problem transformation).

Sum Expressions

Pyomo does not have a binary sum expression class. Instead, it has an n-ary summation class, SumExpression. This expression class treats sums as n-ary sums for efficiency reasons; many large optimization models contain large sums. But note that this class maintains the immutability property described above. This class shares an underlying list of arguments with other SumExpression objects. A particular object owns the first n arguments in the shared list, but different objects may have different values of n.

This class acts like a normal immutable expression class, and the API described above works normally. But direct access to the shared list could have unexpected results.

Mutable Expressions

Finally, Pyomo includes several mutable expression classes that are private. These are not intended to be used by users, but they might be useful for developers in contexts where the developer can appropriately control how the classes are used. Specifically, immutability eliminates side-effects where changes to a sub-expression unexpectedly create changes to the expression tree. But within the context of model transformations, developers may be able to limit the use of expressions to avoid these side-effects. The following mutable private classes are available in Pyomo:

_MutableSumExpression

This class is used in the nonlinear_expression context manager to efficiently combine sums of nonlinear terms.

_MutableLinearExpression

This class is used in the linear_expression context manager to efficiently combine sums of linear terms.

Expression Semantics

Pyomo clear semantics regarding what is considered a valid leaf and interior node.

The following classes are valid interior nodes:

  • Subclasses of ExpressionBase

  • Classes that that are duck typed to match the API of the ExpressionBase class. For example, the named expression class Expression.

The following classes are valid leaf nodes:

  • Members of nonpyomo_leaf_types, which includes standard numeric data types like int, float and long, as well as numeric data types defined by numpy and other commonly used packages. This set also includes NonNumericValue, which is used to wrap non-numeric arguments to the ExternalFunctionExpression class.

  • Parameter component classes like ScalarParam and _ParamData, which arise in expression trees when the parameters are declared as mutable. (Immutable parameters are identified when generating expressions, and they are replaced with their associated numeric value.)

  • Variable component classes like ScalarVar and _GeneralVarData, which often arise in expression trees. <pyomo.core.expr.pyomo5_variable_types>`.

Note

In some contexts the LinearExpression class can be treated as an interior node, and sometimes it can be treated as a leaf. This expression object does not have any child arguments, so nargs() is zero. But this expression references variables and parameters in a linear expression, so in that sense it does not represent a leaf node in the tree.

Context Managers

Pyomo defines several context managers that can be used to declare the form of expressions, and to define a mutable expression object that efficiently manages sums.

The linear_expression object is a context manager that can be used to declare a linear sum. For example, consider the following two loops:

M = ConcreteModel()
M.x = Var(range(5))

s = 0
for i in range(5):
    s += M.x[i]

with linear_expression() as e:
    for i in range(5):
        e += M.x[i]

The first apparent difference in these loops is that the value of s is explicitly initialized while e is initialized when the context manager is entered. However, a more fundamental difference is that the expression representation for s differs from e. Each term added to s results in a new, immutable expression. By contrast, the context manager creates a mutable expression representation for e. This difference allows for both (a) a more efficient processing of each sum, and (b) a more compact representation for the expression.

The difference between linear_expression and nonlinear_expression is the underlying representation that each supports. Note that both of these are instances of context manager classes. In singled-threaded applications, these objects can be safely used to construct different expressions with different context declarations.

Finally, note that these context managers can be passed into the start method for the quicksum function. For example:

M = ConcreteModel()
M.x = Var(range(5))
M.y = Var(range(5))

with linear_expression() as e:
    quicksum((M.x[i] for i in M.x), start=e)
    quicksum((M.y[i] for i in M.y), start=e)

This sum contains terms for M.x[i] and M.y[i]. The syntax in this example is not intuitive because the sum is being stored in e.

Note

We do not generally expect users or developers to use these context managers. They are used by the quicksum and sum_product functions to accelerate expression generation, and there are few cases where the direct use of these context managers would provide additional utility to users and developers.