Setup decomposition with BlockDecomposition

BlockDecomposition allows the user to perform two types of decomposition using BlockDecomposition.@dantzig_wolfe_decomposition and BlockDecomposition.@benders_decomposition.

For both decompositions, the index-set of the subproblems is declared through an BlockDecomposition.@axis. It returns an array. Each value of the array is a subproblem index wrapped into a BlockDecomposition.AxisId. Each time BlockDecomposition finds an AxisId in the indices of a variable and a constraint, it knows to which subproblem the variable or the constraint belongs.

The macro creates a decomposition tree where the root is the master and the depth is the number of nested decompositions. A classic Dantzig-Wolfe or Benders decomposition produces a decomposition tree of depth 1. At the moment, nested decomposition is not supported.

You can get the subproblem membership of all variables and constraints using the method BlockDecomposition.annotation.

BlockDecomposition does not change the JuMP model. It decorates the model with additional information. All this information is stored in the ext field of the JuMP model.

Errors and warnings

BlockDecomposition.MasterVarInDwSpType

Error thrown when a master variable is in a constraint that belongs to a Dantzig-Wolfe subproblem.

You can retrieve the JuMP variable and the JuMP constraint where the error occurs:

error.variable
error.constraint
source

References

BlockDecomposition.BlockModelFunction
BlockModel(optimizer [, direct_model = false])

Return a JuMP model which BlockDecomposition will decompose using instructions given by the user.

If you define direct_model = true, the method creates the model with JuMP.direct_model, otherwise it uses JuMP.Model.

source

These are the methods to decompose a JuMP model :

BlockDecomposition.@axisMacro
@axis(name, collection)

Declare collection as an index-set of subproblems. You can access the axis using the variable name.

Examples

Consider a formulation that has a decomposition which gives raise to 5 subproblems. Let {1,2,3,4,5} be the index-set of the subproblems.

To perform this decomposition with BlockDecomposition, we must declare an axis that contains the index-set of the subproblems :

julia> L = 1:5
1:5

julia> @axis(K, L)
BlockDecomposition.Axis{:K, Int64}(:K, BlockDecomposition.AxisId{:K, Int64}[1, 2, 3, 4, 5])

julia> K[1]
1

julia> typeof(K[1])
BlockDecomposition.AxisId{:K, Int64}

The elements of the axis are AxisId. You must use AxisId in the indices of the variables and the constraints that you declare otherwise BlockDecomposition assign them to the master problem.

@variable(model, x[l in L]) # x[l] belongs to the master for any l ∈ L
@variable(model, y[k in K]) # y[k], k ∈ K, belongs to subproblem k (because K is an axis)
source
BlockDecomposition.@benders_decompositionMacro
@benders_decomposition(model, name, axis)

Register a Benders decomposition on the JuMP model model where the index-set of the subproblems is defined by the axis axis.

Create a variable name from which the user can access decomposition tree.

source
BlockDecomposition.@dantzig_wolfe_decompositionMacro
@dantzig_wolfe_decomposition(model, name, axis)

Register a Dantzig-Wolfe decomposition on the JuMP model model where the index-set of the subproblems is defined by the axis axis.

Create a variable name from which the user can access the decomposition tree.

source

These are the methods to set additional information to the decomposition (multiplicity and optimizers) :

BlockDecomposition.getmasterFunction
getmaster(node) -> MasterForm

Return an object that wraps the annotation that describes the master formulation of a decomposition stored at the node of the decomposition tree.

This method is not defined if the node is a leaf of the decomposition tree.

source
BlockDecomposition.getsubproblemsFunction
getsubproblems(node) -> Vector{SubproblemForm}

Return a vector of objects that wrap the annotations that describe subproblem formulations of a decomposition stored at node of the decomposition tree.

This method is not defined if the node is a leaf of the decomposition tree.

source
BlockDecomposition.specify!Function
specify!(
    subproblem, 
    lower_multiplicity = 1,
    upper_multiplicity = 1,
    solver = nothing,
    branching_priority = 1
)

Method that allows the user to specify additional property of the subproblems.

The multiplicity of subproblem is the number of times that the same independent block shaped by the subproblem in the coefficient matrix appears in the model. It is equivalent to the number of solutions to the subproblem that can appear in the solution of the original problem.

Branching priority of a subproblem is equal to the branching priority of the associated integer variable (the number of columns from this subproblem in the global solution). It also determines the default branching priority of columns generated by this subproblem. Branching priority is also used in rounding and diving heuristics to prioritize which variables and columns to fix the first.

The solver of the subproblem is the way the subproblem will be optimized. It can be either a function (pricing callback), an optimizer of MathOptInterface (e.g. Gurobi.Optimizer, CPLEX.Optimizer, Glpk.Optimizer... with attributes), or nothing. In the latter case, the solver will use a default optimizer that should be defined in the parameters of the main solver.

Advanced usage : The user can use several solvers to optimize a subproblem :

specify!(subproblem, solver = [Gurobi.Optimizer, my_callback, my_second_callback])

Coluna always uses the first solver by default. Be cautious because changes are always buffered to all solvers. So you may degrade performances if you use a lot of solvers.

source

This method helps you to check your decomposition :

BlockDecomposition.annotationFunction
annotation(node)

Return the annotation that describes the master/subproblem of a given node of the decomposition tree.

annotation(model, variable)
annotation(model, constraint)

Return the subproblem to which a variable or a constraint belongs.

source