Benders cut generation

Coluna provides an interface and generic functions to implement a Benders cut generation algorithm.

In this section, we are first going to present the generic functions, the implementation with some theory backgrounds and then give the references of the interface. The default implementation is based on the paper of

You can find the generic functions and the interface in the Benders submodule and the default implementation in the Algorithm submodule at src/Algorithm/benders.

Context

The Benders submodule provides an interface and generic functions to implement a benders cut generation algorithm. The implementation depends on an object called context.

Coluna.Benders.AbstractBendersContextType

Supertype for the objects to which belongs the implementation of the Benders cut generation and that stores any kind of information during the execution of the Bender cut generation algorithm.

source

Benders provides two types of context:

Coluna.Algorithm.BendersPrinterContextType
BendersPrinterContext(reformulation, algo_params) -> BendersPrinterContext

Creates a context to run the default implementation of the Benders algorithm together with a printer that prints information about the algorithm execution.

source

Generic functions

Generic functions are the core of the Benders cut generation algorithm. There are three generic functions:

See ...

See ...

These functions are independent of any other submodule of Coluna. You can use them to implement your own Benders cut generation algorithm.

Reformulation

The default implementation works with a reformulated problem contained in MathProg.Reformulation where master and subproblems are MathProg.Formulation objects.

The master has the following form:

\[\begin{aligned} \min \quad& cx + \sum_{k \in K} \eta_k & &\\ \text{s.t.} \quad& Ax \geq a & & (1) \\ & \text{< benders cuts>} & & (2) \\ & l_1 \leq x \leq u_1 & & (3) \\ & \eta_k \in \mathbb{R} & \forall k \in K \quad& (4) \end{aligned}\]

where $x$ are first-stage variables, $\eta_k$ is the second-stage cost variable for the subproblem $k$, constraints $(1)$ are the first-stage constraints, constraints $(2)$ are the Benders cuts, constraints $(3)$ are the bounds on the first-stage variables, and expression $(4)$ shows that second-stage variables are free.

The subproblems have the following form:

\[\begin{aligned} \min \quad& fy + {\color{gray} \mathbf{1}z' + \mathbf{1}z''} &&& \\ \text{s.t.} \quad& Dy {\color{gray} + z'} \geq d - B\bar{x} && (5) \quad& {\color{blue}(\pi)} \\ & Ey {\color{gray} + z''} \geq e && (6) \quad& {\color{blue}(\rho)} \\ & l_2 \leq y \leq u_2 && (7) \quad& {\color{blue}(\sigma)} \end{aligned}\]

where $y$ are second-stage variables, $z'$ and $z''$ are artificial variables (in grey because they are deactivated by default), constraints (5) are the reformulation of linking constraints using the first-stage solution $\bar{x}$, constraints (6) are the second-stage constraints, and constraints (7) are the bounds on the second-stage variables. In blue, we define the dual variables associated to these constraints.

References:

Main loop

This is a description of how the Coluna.Benders.run_benders_loop! generic function behaves with the default implementation.

The loop stops if one of the following conditions is met:

  • the master is infeasible
  • a separation subproblem is infeasible
  • the time limit is reached
  • the maximum number of iterations is reached
  • no new cut is generated

The default implementation returns:

Coluna.Algorithm.BendersOutputType

Output of the default implementation of the Benders algorithm.

It contains:

  • infeasible: the original problem is infeasible
  • time_limit_reached: the time limit was reached
  • mlp: the final bound obtained with the Benders cut algorithm
  • ip_primal_sol: the best primal solution to the original problem found by the Benders cut algorithm
source

References:

Coluna.Benders.benders_output_typeFunction
benders_output_type(context) -> Type{<:AbstractBendersOutput}

Returns the type of the custom object that will store the output of the Benders cut generation algorithm.

source

Benders cut generation iteration

This is a description of how the Coluna.Benders.run_benders_iteration! generic function behaves with the default implementation.

These are the main steps of a Benders cut generation iteration without stabilization. Click on the step to go to the corresponding section.

flowchart TB; id1(Optimize master) id2(Treat unbounded master) id3(Setup separation subproblems) id4(Separation subproblem iterator) id5(Optimize separation subproblem) id6(Push cut into set) id9(Master is unbounded?) id10(Error) id7(Insert cuts) id11(Build primal solution) id8(Iteration output) id1 --unbounded--> id2 id2 --certificate--> id3 id1 -- optimal --> id3 id3 --> id4 id4 -- subproblem --> id5 id5 --> id6 id6 --> id4 id4 -- end --> id9 id9 -- yes --> id10 id9 -- no --> id7 id7 --> id11 id11 --> id8 click id1 href "#Master-optimization" "Link to doc" click id2 href "#Unbounded-master-case" "Link to doc" click id3 href "#Setup-separation-subproblems" "Link to doc" click id4 href "#Subproblem-iterator" "Link to doc" click id5 href "#Separation-subproblem-optimization" "Link to doc" click id6 href "#Set-of-generated-cuts" "Link to doc" click id9 href "#Unboundedness-check" "Link to doc" click id11 href "#Current-primal-solution" "Link to doc" click id7 href "#Cuts-insertion" "Link to doc" click id8 href "#Iteration-output" "Link to doc"

In the default implementation, some sections may have different behaviors depending on the result of previous steps.

Master optimization

This operation consists in optimizing the master problem in order to find a first-level solution $\bar{x}$.

In the default implementation, master optimization can be performed using SolveLpForm (LP solver) or SolveIpForm (MILP solver). When getting the solution, we store the current value of second stage variables $\bar{\eta}_k$ as incumbent value (see Coluna.MathProg.getcurincval).

It returns an object of the following type:

Coluna.Algorithm.BendersMasterResultType

Output of the default implementation of the Benders.optimize_master_problem! method.

It contains:

  • ip_solver: true if the master problem is solved with a MIP solver and involves integral variables, false otherwise.
  • result: the result of the master problem optimization stored in an OptimizationState object.
  • infeasible: true if the master at the current iteration is infeasible; false otherwise.
  • unbounded: true if the master at the current iteration is unbounded; false otherwise.
  • certificate: true if the master at the current iteration is unbounded and if the current result is a dual infeasibility certificate, false otherwise.
source

References:

Coluna.Benders.optimize_master_problem!Function
optimize_master_problem!(master, context, env) -> MasterResult

Returns an instance of a custom object MasterResult that implements the following methods:

  • is_unbounded(res::MasterResult) -> Bool
  • is_infeasible(res::MasterResult) -> Bool
  • is_certificate(res::MasterResult) -> Bool
  • get_primal_sol(res::MasterResult) -> Union{Nothing, PrimalSolution}
source

Go back to the cut generation iteration diagram.

Unbounded master case

Second stage cost $\eta_k$ variables are free. As a consequence, the master problem is unbounded when there is no optimality Benders cuts.

In this case, Coluna.Benders.treat_unbounded_master_problem_case! is called. The main goal of the default implementation of this method is to get the dual infeasibility certificate of the master problem.

If the master has been solved with a MIP solver at the previous step, we need to relax the integrality constraints to get a dual infeasibility certificate.

If the solver does not provide a dual infeasibility certificate, the implementation has an "emergency" routine to provide a first-stage feasible solution by solving the master LP with cost of second stage variables set to zero. We recommend using a solver that provides a dual infeasibility certificate and avoiding the "emergency" routine.

References:

Coluna.Benders.treat_unbounded_master_problem_case!Function
treat_unbounded_master_problem_case!(master, context, env) -> MasterResult

When after a call to optimize_master_problem!, the master is unbounded, this method is called. Returns an instance of a custom object MasterResult.

source

Go back to the cut generation iteration diagram.

Setup separation subproblems

Info

The separation subproblems differs depending on whether the restricted master is unbounded or not:

  • if the restricted master is optimal, the generic function calls Coluna.Benders.update_sp_rhs!
  • if the restricted master is unbounded, the generic function calls Coluna.Benders.setup_separation_for_unbounded_master_case!

Default implementation of Coluna.Benders.update_sp_rhs! updates the right-hand side of the linking constraints (5).

Reference:

Coluna.Benders.update_sp_rhs!Function
update_sp_rhs!(context, sp, mast_primal_sol)

Updates the right-hand side of the separation problem sp by fixing the first-level solution obtained by solving the master problem mast_primal_sol.

source

Default implementation of Coluna.Benders.setup_separation_for_unbounded_master_case! gives rise to the formulation proposed in Lemma 2 of Bonami et al:

\[\begin{aligned} (SepB) \equiv \min \quad& fy + {\color{gray} \mathbf{1}z' + \mathbf{1}z''} &&& \\ \text{s.t.} \quad& Dy {\color{gray} + z'} \geq -B\bar{x} && (5a) \quad& {\color{blue}(\pi)} \\ & Ey {\color{gray} + z''} \geq 0 && (6a) \quad& {\color{blue}(\rho)} \\ & y \geq 0 && (7a) \quad& {\color{blue}(\sigma)} \end{aligned}\]

where $y$ are second-stage variables, $z'$ and $z''$ are artificial variables (in grey because they are deactivated by default), and $\bar{x}$ is an unbounded ray of the restricted master.

Reference:

Subproblem iterator

Not implemented yet.

Separation subproblem optimization

The default implementation first optimize the subproblem without the artificial variables $z'$ and $z''$. In the case where it finds $(\bar{\pi}, \bar{\rho}, \bar{\sigma})$ an optimal dual solution to the subproblem, the following cut is generated:

\[\eta_k + \bar{\pi}Bx \geq d\bar{\pi} + \bar{\rho}e + \bar{\sigma_{\leq}} l_2 + \bar{\sigma_{\geq}} u_2\]

with $\bar{\sigma_{\leq}} l_2$ (respectively $\bar{\sigma_{\geq}} u_2$) the dual of the left part (respectively the right part) of constraint $l_2 \leq y \leq u_2$ of the subproblem.

In the case where it finds the subproblem infeasible, it calls Coluna.Benders.treat_infeasible_separation_problem_case!. The default implementation of this method activates the artificial variables $z'$ and $z''$, sets the cost of second stage variables to 0, and optimizes the subproblem again.

If a solution with no artificial variables is found, the following cut is generated:

\[\bar{\pi}Bx \geq d\bar{\pi} + \bar{\rho}e + \bar{\sigma_{\leq}} l_2 + \bar{\sigma_{\geq}} u_2\]

Both methods return an object of the following type:

Coluna.Algorithm.BendersSeparationResultType

Output of the default implementation of the Benders.optimize_separation_problem! and Benders.treat_infeasible_separation_problem_case! methods.

It contains:

  • second_stage_estimation_in_master: the value of the second stage cost variable in the solution to the master problem.
  • second_stage_cost: the value of the second stage cost variable in the solution to the separation problem.
  • lp_primal_sol: the primal solution to the separation problem.
  • infeasible: true if the current separation problem is infeasible; false otherwise.
  • unbounded: true if the current separation problem is unbounded; false otherwise.
  • cut: the cut generated by the separation problem.
  • infeasible_treatment: true if this object is an output of the Benders.treat_infeasible_separation_problem_case! method; false otherwise.
  • unbounded_master: true if the separation subproblem has the form of Lemma 2 to separate a cut to truncate an unbounded ray of the restricted master problem; false otherwise.
source

References:

Coluna.Benders.optimize_separation_problem!Function
optimize_separation_problem!(context, sp_to_solve, env, unbounded_master) -> SeparationResult

Returns an instance of a custom object SeparationResult that implements the following methods:

  • is_unbounded(res::SeparationResult) -> Bool
  • is_infeasible(res::SeparationResult) -> Bool
  • get_obj_val(res::SeparationResult) -> Float64
  • get_primal_sol(res::SeparationResult) -> Union{Nothing, PrimalSolution}
  • get_dual_sp_sol(res::SeparationResult) -> Union{Nothing, DualSolution}
source
Coluna.Benders.treat_infeasible_separation_problem_case!Function
treat_infeasible_separation_problem_case!(context, sp_to_solve, env, unbounded_master) -> SeparationResult

When after a call to optimize_separation_problem!, the separation problem is infeasible, this method is called. Returns an instance of a custom object SeparationResult.

source

Go back to the cut generation iteration diagram.

Set of generated cuts

You can define your data structure to manage the cuts generated at a given iteration. Columns are inserted after the optimization of all the separation subproblems to allow the parallelization of the latter.

In the default implementation, cuts are represented by the following data structure:

Coluna.Algorithm.GeneratedCutType

Solution to the separation problem together with its corresponding benders cut.

It contains:

  • min_sense: true if it's a minimization problem; false otherwise.
  • lhs: the left-hand side of the cut.
  • rhs: the right-hand side of the cut.
  • dual_sol: an optimal dual solution to the separation problem.
source

We use the following data structures to store the cuts and the primal solutions to the subproblems:

Coluna.Algorithm.SepSolSetType

Primal solutions to the separation problems optimized at the current iteration. This is used to build a primal solution.

It contains sols a vector of primal solutions.

source

The default implementation of push_in_set! has the responsibility to check if the cut is violated. Given $\bar{\eta}_k$ solution to the restricted master and $\bar{y}$ solution to the separation problem, the cut is considered as violated when:

  • the separation subproblem was infeasible
  • or $\bar{\eta}_k \geq f\bar{y}$

References:

Coluna.Benders.set_of_cutsFunction

Returns an empty container that will store all the cuts generated by the separation problems during an iteration of the Benders cut generation algorithm. One must be able to iterate on this container to insert the cuts in the master problem.

source
Coluna.Benders.set_of_sep_solsFunction

Returns an empty container that will store the primal solutions to the separation problems at a given iteration of the Benders cut generation algorithm.

source
Coluna.Benders.push_in_set!Function
push_in_set!(context, cut_pool, sep_result) -> Bool

Inserts a cut in the set of cuts generated at a given iteration of the Benders cut generation algorithm. The cut_pool structure is generated by set_of_cuts(context).

push_in_set!(context, sep_sp_sols, sep_result) -> Bool

Inserts a primal solution to a separation problem in the set of primal solutions generated at a given iteration of the Benders cut generation algorithm. The sep_sp_sols structure is generated by set_of_sep_sols(context).

Returns true if the cut or the primal solution was inserted in the set, false otherwise.

source

Go back to the cut generation iteration diagram.

Unboundedness check

Info

This check is performed only when the restricted master is unbounded.

To perform this check, we need a solution to each separation problem.

Let $(\bar{\eta}_k)_{k \in K}$ be the value of second stage variables in the dual infeasibility certificate of the restricted master. Let $\bar{y}$ be an optimal solution to the separation problem (SepB).

As indicated by Bonami et al., if $f\bar{y} \leq \sum\limits_{k \in K} \bar{\eta}_k$, then the original problem is unbounded (by definition of an unbounded ray of the original problem).

References:

Cuts insertion

The default implementation inserts into the master all the cuts stored in the CutsSet object.

Reference:

Go back to the cut generation iteration diagram.

Current primal solution

Lorem ipsum.

References:

Iteration output

Coluna.Algorithm.BendersIterationOutputType

Output of the default implementation of an iteration of the Benders algorithm.

It contains:

  • min_sense: the original problem is a minimization problem
  • nb_new_cuts: the number of new cuts added to the master problem
  • ip_primal_sol: the primal solution to the original problem found during this iteration
  • infeasible: the original problem is infeasible
  • time_limit_reached: the time limit was reached
  • master: the solution value to the master problem
source

References:

Go back to the cut generation iteration diagram.

Getters for Result data structures

Method nameMasterSeparation
is_unboundedXX
is_infeasibleXX
is_certificateX
get_primal_solXX
get_dual_solX
get_obj_valXX

Go back to the cut generation iteration diagram.

Stabilization

Not implemented yet.