BuildAction and BuildCheck

This is a somewhat advanced topic. In some cases, it is desirable to trigger actions to be done as part of the model building process. The BuildAction function provides this capability in a Pyomo model. It takes as arguments optional index sets and a function to peform the action. For example,

model.BuildBpts = BuildAction(model.J, rule=bpts_build)

calls the function bpts_build for each member of model.J. The function bpts_build should have the model and a variable for the members of model.J as formal arguments. In this example, the following would be a valid declaration for the function:

def bpts_build(model, j):

A full example, which extends the Symbolic Index Sets and Piecewise Linear Expressions examples, is

# Similar to, but the breakpoints are created using a build action

from pyomo.environ import *

model = AbstractModel()

model.I = Set()
model.J = Set()

model.a = Param(model.I, model.J)
model.b = Param(model.I)
model.c = Param(model.J)

model.Topx = Param(default=6.1) # range of x variables
model.PieceCnt = Param(default=100)

# the next line declares a variable indexed by the set J
model.x = Var(model.J, domain=NonNegativeReals, bounds=(0,model.Topx))
model.y = Var(model.J, domain=NonNegativeReals)

# to avoid warnings, we set breakpoints beyond the bounds
# we are using a dictionary so that we can have different
# breakpoints for each index. But we won't.
model.bpts = {}
def bpts_build(model, j):
    model.bpts[j] = []
    for i in range(model.PieceCnt+2):
# The object model.BuildBpts is not refered to again;
# the only goal is to trigger the action at build time
model.BuildBpts = BuildAction(model.J, rule=bpts_build)

def f4(model, j, xp):
    # we not need j in this example, but it is passed as the index for the constraint
    return xp**4

model.ComputePieces = Piecewise(model.J, model.y, model.x, pw_pts=model.bpts, pw_constr_type='EQ', f_rule=f4)

def obj_expression(model):
    return summation(model.c, model.y)

model.OBJ = Objective(rule=obj_expression)

def ax_constraint_rule(model, i):
    # return the expression for the constraint for i
    return sum(model.a[i,j] * model.x[j] for j in model.J) >= model.b[i]

# the next line creates one constraint for each member of the set model.I
model.AxbConstraint = Constraint(model.I, rule=ax_constraint_rule)

This example uses the build action to create a model component with breakpoints for a Piecewise Linear Expressions function. The BuildAction is triggered by the assignment to model.BuildBpts. This object is not referenced again, the only goal is to cause the execution of bpts_build, which places data in the model.bpts dictionary. Note that if model.bpts had been a Set, then it could have been created with an initialize argument to the Set declaration. Since it is a special-purpose dictionary to support the Piecewise Linear Expressions functionality in Pyomo, we use a BuildAction.

Another application of BuildAction can be intialization of Pyomo model data from Python data structures, or efficient initialization of Pyomo model data from other Pyomo model data. Consider the Sparse Index Sets example. Rather than using an initialization for each list of sets NodesIn and NodesOut separately using initialize, it is a little more efficient and probably a little clearer, to use a build action.

The full model is:

# NodesIn and NodesOut are created by a build action using the Arcs
from pyomo.environ import *

model = AbstractModel()

model.Nodes = Set()
model.Arcs = Set(dimen=2)

model.NodesOut = Set(model.Nodes, within=model.Nodes, initialize=[])
model.NodesIn = Set(model.Nodes, within=model.Nodes, initialize=[])

def Populate_In_and_Out(model):
    # loop over the arcs and put the end points in the appropriate places
    for (i,j) in model.Arcs:

model.In_n_Out = BuildAction(rule = Populate_In_and_Out)

model.Flow = Var(model.Arcs, domain=NonNegativeReals)
model.FlowCost = Param(model.Arcs)

model.Demand = Param(model.Nodes)
model.Supply = Param(model.Nodes)

def Obj_rule(model):
    return summation(model.FlowCost, model.Flow)
model.Obj = Objective(rule=Obj_rule, sense=minimize)

def FlowBalance_rule(model, node):
    return model.Supply[node] \
     + sum(model.Flow[i, node] for i in model.NodesIn[node]) \
     - model.Demand[node] \
     - sum(model.Flow[node, j] for j in model.NodesOut[node]) \
     == 0
model.FlowBalance = Constraint(model.Nodes, rule=FlowBalance_rule)

for this model, the same data file can be used as for in Sparse Index Sets such as the toy data file:

set Nodes := CityA CityB CityC ;

set Arcs :=
CityA CityB
CityA CityC
CityC CityB

param : FlowCost :=
CityA CityB 1.4
CityA CityC 2.7
CityC CityB 1.6

param Demand :=
CityA 0
CityB 1
CityC 1

param Supply :=
CityA 2
CityB 0
CityC 0

Build actions can also be a way to implement data validation, particularly when multiple Sets or Parameters must be analyzed. However, the the BuildCheck component is prefered for this purpose. It executes its rule just like a BuildAction but will terminate the construction of the model instance if the rule returns False.