Pyomo Models¶
PAO can be used to express linear and quadratic problems in Pyomo using a SubModel
component, which was previously introduced in the pyomo.bilevel
package [PyomoBookII]. Pyomo
represents optimization models using an model objects that are
annotated with modeling component objects. Thus, SubModel
component is a simple extension of the modeling concepts in Pyomo.
Hint
Advanced Pyomo users will realize that the PAO SubModel
component
is a special case of the Pyomo Block
component, which is used to
structure the expression of Pyomo models.
A SubModel
component creates a context for expressing the
objective and constraints in a lower-level model. Pyomo models can
include nested and parallel SubModel
components to express
complex multilevel problems.
Bilevel Examples¶
Consider the following bilevel problem:
This problem has has linear upper- and lower-level problems with different objectives in each level.
>>> M = pe.ConcreteModel()
>>> M.x = pe.Var(bounds=(2,6))
>>> M.y = pe.Var()
>>> M.L = pao.pyomo.SubModel(fixed=[M.x,M.y])
>>> M.L.z = pe.Var(bounds=(0,None))
>>> M.o = pe.Objective(expr=M.x + 3*M.L.z, sense=pe.minimize)
>>> M.c = pe.Constraint(expr= M.x + M.y == 10)
>>> M.L.o = pe.Objective(expr=M.L.z, sense=pe.maximize)
>>> M.L.c1 = pe.Constraint(expr= M.x + M.L.z <= 8)
>>> M.L.c2 = pe.Constraint(expr= M.x + 4*M.L.z >= 8)
>>> M.L.c3 = pe.Constraint(expr= M.x + 2*M.L.z <= 13)
>>> opt = pao.Solver("pao.pyomo.FA")
>>> results = opt.solve(M)
>>> print(M.x.value, M.y.value, M.L.z.value)
6.0 4.0 2.0
This example illustrates the flexibility of Pyomo representations in PAO:
- Each level can express different objectives with different senses
- Variables can be bounded or unbounded
- Equality and inequality constraints can be expressed
The SubModel
component is used to define a logically separate
optimization model that includes variables that are dynamically fixed
by upper-level problems. All of the Pyomo objective and constraint
declarations contained in the SubModel
declaration are included
in the sub-problem that it defines, even if they are nested in Pyomo
Block
components. The SubModel
component also declares
which variables are fixed in a lower-level problem. The value of the
fixed argument is a Pyomo variable or a list of variables. For example,
the following model expresses the upper-level variables with a single
variable, M.x, which is fixed in the SubModel
declaration:
>>> M = pe.ConcreteModel()
>>> M.x = pe.Var([0,1])
>>> M.x[0].setlb(2)
>>> M.x[0].setub(6)
>>> M.L = pao.pyomo.SubModel(fixed=M.x)
>>> M.L.z = pe.Var(bounds=(0,None))
>>> M.o = pe.Objective(expr=M.x[0] + 3*M.L.z, sense=pe.minimize)
>>> M.c = pe.Constraint(expr= M.x[0] + M.x[1] == 10)
>>> M.L.o = pe.Objective(expr=M.L.z, sense=pe.maximize)
>>> M.L.c1 = pe.Constraint(expr= M.x[0] + M.L.z <= 8)
>>> M.L.c2 = pe.Constraint(expr= M.x[0] + 4*M.L.z >= 8)
>>> M.L.c3 = pe.Constraint(expr= M.x[0] + 2*M.L.z <= 13)
>>> opt = pao.Solver("pao.pyomo.FA")
>>> results = opt.solve(M)
>>> print(M.x[0].value, M.x[1].value, M.L.z.value)
6.0 4.0 2.0
Although a lower-level problem is logically a separate optimization model,
you cannot use a SubModel
that is defined with a separate Pyomo
model object. Pyomo implicitly requires that all variables used in
objective and constraint expressions are attributes of the same Pyomo model.
However, the location of variable declarations in a Pyomo model does not denote their
use in upper- or lower-level problems. For example, consider the following
model that re-expresses the previous problem:
>>> M = pe.ConcreteModel()
>>> M.x = pe.Var(bounds=(2,6))
>>> M.y = pe.Var()
>>> M.z = pe.Var(bounds=(0,None))
>>> M.o = pe.Objective(expr=M.x + 3*M.z, sense=pe.minimize)
>>> M.c = pe.Constraint(expr= M.x + M.y == 10)
>>> M.L = pao.pyomo.SubModel(fixed=[M.x,M.y])
>>> M.L.o = pe.Objective(expr=M.z, sense=pe.maximize)
>>> M.L.c1 = pe.Constraint(expr= M.x + M.z <= 8)
>>> M.L.c2 = pe.Constraint(expr= M.x + 4*M.z >= 8)
>>> M.L.c3 = pe.Constraint(expr= M.x + 2*M.z <= 13)
>>> opt = pao.Solver("pao.pyomo.FA")
>>> results = opt.solve(M)
>>> print(M.x.value, M.y.value, M.z.value)
6.0 4.0 2.0
Note that all of the decision variables are declared outside of the
SubModel
component, even though the variable M.z
is a
lower-level variable. The declarations of SubModel
components
defines the mathematical role of all decision variables in a Pyomo model.
As this example illustrates, the specification of a bilevel problem can
be simplified if all variables are expressed at once.
Finally, we observe that PAO’s Pyomo representation only works with a subset of the many different modeling components that are supported in Pyomo:
Set
- Set declarationsParam
- Parameter declarationsVar
- Variable declarationsBlock
- Defines a subset of a modelObjective
- Define a model objectiveConstraint
- Define model constraints
Additional Pyomo modeling components will be added to PAO as motivating applications arise and as suitable solvers become available.
Multilevel Examples¶
Multilevel problems can be easily expressed with Pyomo using multiple declarations
of SubModel
.
Multiple Lower Levels¶
Consider the following bilevel problem that extends the PAO1 model to include two equivalent lower-levels:
The PAO2 model can be expressed in Pyomo as follows:
>>> M = pe.ConcreteModel()
>>> M.x = pe.Var(bounds=(2,6))
>>> M.y = pe.Var()
>>> M.z = pe.Var([1,2], bounds=(0,None))
>>> M.o = pe.Objective(expr=M.x + 3*M.z[1]+3*M.z[2], sense=pe.minimize)
>>> M.c = pe.Constraint(expr= M.x + M.y == 10)
>>> M.L1 = pao.pyomo.SubModel(fixed=[M.x])
>>> M.L1.o = pe.Objective(expr=M.z[1], sense=pe.maximize)
>>> M.L1.c1 = pe.Constraint(expr= M.x + M.z[1] <= 8)
>>> M.L1.c2 = pe.Constraint(expr= M.x + 4*M.z[1] >= 8)
>>> M.L1.c3 = pe.Constraint(expr= M.x + 2*M.z[1] <= 13)
>>> M.L2 = pao.pyomo.SubModel(fixed=[M.y])
>>> M.L2.o = pe.Objective(expr=M.z[2], sense=pe.maximize)
>>> M.L2.c1 = pe.Constraint(expr= M.y + M.z[2] <= 8)
>>> M.L2.c2 = pe.Constraint(expr= M.y + 4*M.z[2] >= 8)
>>> M.L2.c3 = pe.Constraint(expr= M.y + 2*M.z[2] <= 13)
>>> opt = pao.Solver("pao.pyomo.FA")
>>> results = opt.solve(M)
>>> print(M.x.value, M.y.value, M.z[1].value, M.z[2].value)
2.0 8.0 5.5 0.0
Trilevel Problems¶
Trilevel problems can be described with nested declarations of
SubModel
components. Consider the following trilevel continuous
linear problem described by Anadalingam [Anadalingam]:
This model can be expressed in Pyomo as follows:
>>> M = pe.ConcreteModel()
>>> M.x1 = pe.Var(bounds=(0,None))
>>> M.x2 = pe.Var(bounds=(0,None))
>>> M.x3 = pe.Var(bounds=(0,0.5))
>>> M.L = pao.pyomo.SubModel(fixed=M.x1)
>>> M.L.B = pao.pyomo.SubModel(fixed=M.x2)
>>> M.o = pe.Objective(expr=-7*M.x1 - 3*M.x2 + 4*M.x3)
>>> M.L.o = pe.Objective(expr=-M.x2)
>>> M.L.B.o = pe.Objective(expr=-M.x3)
>>> M.L.B.c1 = pe.Constraint(expr= M.x1 + M.x2 + M.x3 <= 3)
>>> M.L.B.c2 = pe.Constraint(expr= M.x1 + M.x2 - M.x3 <= 1)
>>> M.L.B.c3 = pe.Constraint(expr= M.x1 + M.x2 + M.x3 >= 1)
>>> M.L.B.c4 = pe.Constraint(expr= - M.x1 + M.x2 + M.x3 <= 1)
Note
PAO solvers cannot currently solve trilevel problems like this, but an issue has been submitted to add this functionality.
Bilinear Problems¶
PAO models using Pyomo represent general quadratic problems with quadratic terms in the objective and constraints at each level. The special case where bilinear terms arise with an upper-level binary variable multiplied with a lower-level variable is common in many applications. For this case, the PAO solvers for Pyomo models include an option to linearize these bilinear terms.
The following models considers a variation of the PAO1 model where binary variables control the expression of lower-level constraints:
The PAO3 model can be expressed in Pyomo as follows:
>>> M = pe.ConcreteModel()
>>> M.w = pe.Var([1,2], within=pe.Binary)
>>> M.x = pe.Var(bounds=(2,6))
>>> M.y = pe.Var()
>>> M.z = pe.Var(bounds=(0,None))
>>> M.o = pe.Objective(expr=M.x + 3*M.z+5*M.w[1], sense=pe.minimize)
>>> M.c1 = pe.Constraint(expr= M.x + M.y == 10)
>>> M.c2 = pe.Constraint(expr= M.w[1] + M.w[2] >= 1)
>>> M.L = pao.pyomo.SubModel(fixed=[M.x,M.y,M.w])
>>> M.L.o = pe.Objective(expr=M.z, sense=pe.maximize)
>>> M.L.c1 = pe.Constraint(expr= M.x + M.w[1]*M.z <= 8)
>>> M.L.c2 = pe.Constraint(expr= M.x + 4*M.z >= 8)
>>> M.L.c3 = pe.Constraint(expr= M.x + 2*M.w[2]*M.z <= 13)
>>> opt = pao.Solver("pao.pyomo.FA", linearize_bigm=100)
>>> results = opt.solve(M)
>>> print(M.x.value, M.y.value, M.z.value, M.w[1].value, M.w[2].value)
6.0 4.0 3.5 0 1
In general, it may be difficult to determine a valid value of $M$. Thus, this transformation may result in a restriction or relaxation of the original problem (depending on where the big-M values are introduced).