3 Solving Linear Programming Problems with Benders Decomposition
In this chapter, we use Benders decomposition to solve several linear programming (LP) problems in order to demonstrate the decomposition logic, especially how the restricted Benders master problem interacts with the subproblem in an iterative approach to reach final optimality. Most linear programs could be solved efficiently nowadays by either open source or commercial solvers without resorting to any decomposition approaches. However, by working through the example problems in the following sections, we aim to showcase the implementation details when applying Benders decomposition algorithm on real problems, which helps solidify our understanding of Benders decomposition. Hopefully, by the end of this chapter, we will build up enough intuition as well as hands-on experience such that we are ready to tackle most involved problems in the following chapters.
In the following sections, we employ several steps to illustrate the problem solving process of Benders decomposition.
- We will first create two linear programming solvers based on Gurobi and SCIP that can solve any linear programs defined in the standard form. They are used in later section to validate the correctness of the solutions produced by Benders decomposition.
- Next, we use a specific linear program and give the corresponding RBMP and DSP to prepare for the implementations.
- Then, we will solve the example linear program step by step by examining the outputs of the RBMP and DSP to decided the next set of actions.
- Futhermore, a holistic Benders decomposition implementation is then developed to solve the example linear program.
- Following the previous step, a more generic Benders decomposition implementation is created.
- Then, we will examine an alternative implementation using Gurobi callback functions.
- We will also provide an implementation based on SCIP.
- In the final section, we will do several benchmarking testing.
3.0.1 LP solvers based on Gurobi and SCIP
We aim to use Benders decomposition to solve several linear programming problems in the following sections. To do that, we intentionally decompose the LP problem under consideration into two sets, one set of complicating variables and the other set containing the remaining variables. In order to validate the correctness of the results obtained by Benders decomposition, we implement two additional ways of solving the target linear programming problems directly. The first option is based on the Gurobi API in python and the other is based on the open source solve SCIP. The two implementations defined here assume the LP problems under consideration follow the below standard form:
\[ \begin{aligned} \text{min.} &\quad \mathbf{c}^T \mathbf{x} \\ \text{s.t.} &\quad \mathbf{A} \mathbf{x} = \mathbf{b} \\ &\quad \mathbf{x} \geq 0 \end{aligned} \]
Listing 3.1 defines a solver for LP problems using Gurobi. It takes three constructor parameters:
obj_coeff
: this corresponds to the objective coefficients \(\mathbf{c}\).constr_mat
: this refers to the constraint matrix \(\mathbf{A}\).rhs
: this is the right-hand side \(\mathbf{b}\).
Inside the constructor __init__()
, a solver environment _env
is first created and then used to initialize a model object _model
. The input parameters are then used to create decision variables _vars
, constraints _constrs
and objective function respectively. The optimize()
function simply solves the problem and shows the solving status. Finally, the clean_up()
function frees up the computing resources.
Listing 3.2 presents an LP solver implementation in class LpSolverSCIP
using SCIP. The constructor requires the same of parameters as defined in LpSolverGurobi
. The model building process is similar with minor changes when required to create decision variables, constraints and the objective function.
Listing 3.3 generates a LP problem with 20 decision variables and 5 constraints.
Listing 3.4 solves the generated LP using LpSolverGurobi
and the solver output shows that an optimal solution was found with objective value of 36.90.
Optimal solution found!
Optimal objective = 36.90
Listing 3.5 solves the same LP problem using SCIP and not surprisingly, the same optimal objective value was found. This is not exciting, as it only indicates that the two solvers agree on the optimal solution on such a small LP problem as expected. However, they will become more useful in the following sections when we use them to validate our Benders decomposition results.
Optimal solution found!
Optimal objective = 36.90
3.0.2 A serious LP problem that cannot wait to be decomposed!
With the validation tools available for use, we are ready to solve some serious LP problems using Benders decomposition! What we have below is a LP problem with five decision variables, three of which are denoted by \(\mathbf{x} = (x_1, x_2, x_3)\) and the remaining two variables are denoted by \(\mathbf{y} = (y_1, y_2)\). We assume that \(\mathbf{y}\) are the complicating variables.
\[ \begin{aligned*} \text{min.} &\quad 8x_1 + 12x_2 +10x_3 + 15y_1 + 18y_2 \\ \text{s.t.} &\quad 2x_1 + 3x_2 + 2x_3 + 4y_1 + 5y_2 = 300 \\ &\quad 4x_1 + 2x_2 + 3x_3 + 2y_1 + 3y_2 = 220 \\ &\quad x_i \geq 0, \ \forall i = 1, \cdots, 3 \\ &\quad y_i \geq 0, \ \forall j = 1, 2 \end{aligned*} \]
According to the standard LP form presented in the previous section, \(\mathbf{c}^T = (8, 12, 10)\), \(\mathbf{f}^T = (15, 18)\) and \(\mathbf{b}^T = (300, 220)\). In addition,
\[\begin{equation*} \mathbf{A} = \begin{bmatrix} 2 & 3 & 2 \\ 4 & 2 & 3 \\ \end{bmatrix} \qquad \mathbf{B} = \begin{bmatrix} 4 & 5 \\ 2 & 3 \\ \end{bmatrix} \end{equation*}\]
Listing 3.6 solves the LP problem using the two solvers define above. Both solvers confirm the optimal objective value is 1091.43. With this information in mind, we will apply Benders decomposition to see if the same optimal solution could be identified or not.
Optimal solution found!
Optimal objective = 1091.43
Optimal solution found!
Optimal objective = 1091.43
3.0.3 Benders decomposition formulations
With \(\mathbf{y}\) being the complicating variable, we state the Benders subproblem (SP) below for the \(\mathbf{y}\) assuming fixed values \(\mathbf{\bar{y}} = (\bar{y_1}, \bar{y_2})\):
\[ \begin{aligned*} \text{min.} &\quad 8x_1 + 12x_2 +10x_3 \\ \text{s.t.} &\quad 2x_1 + 3x_2 + 2x_3 = 300 - 4\bar{y_1} - 5\bar{y_2} \\ &\quad 4x_1 + 2x_2 + 3x_3 = 220 - 2\bar{y_1} - 3\bar{y_2} \\ &\quad x_i \geq 0, \ \forall i = 1, \cdots, 3 \end{aligned*} \]
We define the dual variable \(\mathbf{u} = (u_1, u_2)\) to associate with the constraints in the (SP). The dual subproblem (DSP) could then be stated as follows:
\[ \begin{aligned*} \text{max.} &\quad (300 - 4\bar{y_1} - 5\bar{y_2}) u_1 + (220 - 2\bar{y_1} - 3\bar{y_2}) u_2 \\ \text{s.t.} &\quad 2u_1 + 4u_2 \leq 8\\ &\quad 3u_1 + 2u_2 \leq 12 \\ &\quad 2u_1 + 3u_2 \leq 10 \\ &\quad u_1, u_2\ \text{unrestricted} \end{aligned*} \]
The (RBMP) can be stated as below. Note that \(\mathbf{u} = (0, 0)\) is a feasible solution to (DSP) and the corresponding objective value is 0, which is the reason we restrict the variable \(g\) to be nonnegative.
\[ \begin{aligned*} \text{min.} &\quad 15 y_1 + 18 y_2 + g \\ \text{s.t.} &\quad y_1, y_2 \geq 0 \\ &\quad g \geq 0 \end{aligned*} \]
3.0.4 Benders decomposition step by step
Benders decomposition defines a problem solving process in which the restricted Benders master problem and the dual subproblem interact iteratively to identify the optimal solution or conclude infeasibility/unboundedness. To facilitate our understanding of the process, we demonstrate in this section the workings of Benders decomposition by solving the target LP problem step by step.
Listing 3.7 shows the codes that initialize the lower bound lb
, upper bound ub
and threshold value eps
. Furthermore, the restricted Benders master problem rbmp
is created with three variables and a minimizing objective function. Note that no constraints are yet included in the model at this moment.
Listing 3.8 initializes the Benders subproblem with two decision variables and three constraints.
In Listing 3.9, we solve the (RBMP) for the first time. It has an optimal solution with \((\bar{y_1}, \bar{y_2}, \bar{g}) = (0, 0, 0)\) and optimal objective value of 0. This is expected as all the variables assume their minimal possible values in order to minimize the objective function. This objective value also serves as the new lower bound.
Optimal solution found! Objective value = 0.00
(y1, y2, g) = (0.00, 0.00, 0.00)
lb=0.0, ub=1e+100
Given that \((\bar{y_1}, \bar{y_2}, \bar{g}) = (0, 0, 0)\), we now feed the values of \(\bar{y_1}\) and \(\bar{y_2}\) into the Benders dual subproblem (DSP) by updating its objective function, as shown in Listing 3.10:
Optimal objective = 1200.00
(u1, u2) = (4.00, 0.00)
lb=0.0, ub=1200.0
We see that the dual subproblem has an optimal solution. The upper bound ub
is also updated. Since the optimal objective value of the subproblem turns out to be 1200 and is greater than \(\bar{g} = 0\), which implies that an optimality cut is needed to make sure that the variable \(g\) in the restricted Benders master problem reflects this newly obtained information from the subproblem.
In Listing 3.11, the new optimality cut is added to the (RBMP), which is then solved to optimality.
Optimal solution found! Objective value = 1080.00
(y1, y2, g) = (0.00, 60.00, 0.00)
lb=1080.0, ub=1200.0
Armed with the optimal solution \((\bar{y_1}, \bar{y_2}, \bar{g}) = (0, 60, 0)\), Listing 3.12 updates the objective function of the (DSP) and obtains its optimal solution.
DSP is unbounded!
retrieve extreme ray (u1, u2) = (-2.0, 1.0)
Since the dual subproblem is unbounded, a feasibility cut is further needed. In Listing 3.13, we add the new cut and solve the restricted Benders master problem again.
Optimal solution found! Objective value = 1091.43
(y1, y2, g) = (0.00, 54.29, 114.29)
lb=1091.43, ub=1200.00
Note that a new lower bound is obtained after solving the master problem. Since there is still a large gap between the lower bound and upper bound, we continue solving the subproblem in Listing 3.14.
Optimal objective = 114.29
(u1, u2) = (4.00, 0.00)
lb=1091.43, ub=1091.43
Now that the difference between lb
and ub
is less than the preset threshold eps
, we conclude that an optimal solution is reached and the computation resources are freed up.
# release resources
rbmp.dispose()
dsp.dispose() env.dispose()
3.0.5 Benders decomposition automated
It typically takes Benders decomposition many iterations to reach optimality or conclude infeasibility/unboundedness. In this section, we put everything we have learned from the manual approach above into an automatic workflow.
Listing 3.15 defines an Enum
class that specifies four possible optimization statuses. The meanings of these statuses are self-explanatory from their corresponding names and further explanations are omitted here.
Listing 3.16 defines a ManualBendersMasterSolver
class that models the (RBMP). Its constructor contains the variable and objective function definitions. The ability to take in either feasibility or optimality cuts is implemented in separate functions add_feasibility_cut()
and add_optimality_cut()
, respectively. The solve()
function is responsible for optimizing the model and retrieve the optimal solution if any.
Listing 3.17 defines the Benders dual subproblem in a similar fashion. Notice that the update_objective()
function is used to set an updated objective function based on the optimal solution identified in the restricted Benders master problem.
Listing 3.18 shows the control flow of Benders decomposition. The main logic is stated as a while
loop, in which the master problem and dual subproblem are solved sequentially within each iteration. Depending on whether the subproblem is optimal or unbounded, an optimality or feasibility cut is added to the master problem. The process continues until the gap between the lower bound and the upper bound is within a certain threshold.
Listing 3.19 solves the LP problem using the wholesome Benders solver. The optimal solution agrees with the solution obtained in the manual approach.
Iteration: 1
--------------------------------------------------
Start solving master problem.
master problem is optimal.
opt_obj=0.00
opt_y1=0.00, opt_y2=0.00
opt_g=0.00
Finish solving master problem.
--------------------------------------------------
Bounds: lb=0.00, ub=inf
dual subproblem objective updated!
--------------------------------------------------
Start solving dual subproblem.
dual subproblem is optimal.
opt_obj=1200.00
opt_y1=4.00, opt_y2=0.00
Finish solving dual subproblem.
--------------------------------------------------
Bounds: lb=0.00, ub=1200.00
Benders optimality cut added!
Iteration: 2
--------------------------------------------------
Start solving master problem.
master problem is optimal.
opt_obj=1080.00
opt_y1=0.00, opt_y2=60.00
opt_g=0.00
Finish solving master problem.
--------------------------------------------------
Bounds: lb=1080.00, ub=1200.00
dual subproblem objective updated!
--------------------------------------------------
Start solving dual subproblem.
dual subproblem is unbounded!
extreme ray (u1, u2) = (-2.0, 1.0)
Finish solving dual subproblem.
--------------------------------------------------
Benders feasibility cut added!
Iteration: 3
--------------------------------------------------
Start solving master problem.
master problem is optimal.
opt_obj=1091.43
opt_y1=0.00, opt_y2=54.29
opt_g=114.29
Finish solving master problem.
--------------------------------------------------
Bounds: lb=1091.43, ub=1200.00
dual subproblem objective updated!
--------------------------------------------------
Start solving dual subproblem.
dual subproblem is optimal.
opt_obj=114.29
opt_y1=4.00, opt_y2=0.00
Finish solving dual subproblem.
--------------------------------------------------
Bounds: lb=1091.43, ub=1091.43
3.0.6 Benders decomposition - design and implementation
The Benders decomposition algorithm we put together in the last section is a big step towards automating the interaction between the master problem optimizer and the dual subproblem optimizer. However, it is still limited in that both optimizers are tied to a specific problem instance. Even a slight change in the instance data requires an updated optimizer implementation. Ideally, we would like to to have an algorithm that could solve various LP problems as long as they follow the same form.
In addition, we might want to have the flexibility to switch to different solvers within the master and dual subproblem optimizers. A good algorithm design should allow alternative implementations with minimal impact on the overall algorithm structure.
With these needs in mind, in this section, we aim to design a Benders decomposition algorithm that can solve any LP problems following a standard form and allow various implementations in both the master problem and dual subproblem optimizers.
We develop the algorithm in three steps. First, we present a Benders decomposition algorithm design that focuses on the overall algorithm workflow and only the abstract implementations of the master problem and dual subproblem optimizers. Next, an implementation of the Benders decomposition based on Gurobi is demonstrated. Lastly, an alternative implementation based on the open source solver SCIP is illustrated.
3.0.6.1 Benders decomposition algorithm design
We could see from the previous sections that Benders decomposition involves three key components:
- An optimizer that solves the master problem
- An optimizer that solves the dual subproblem
- An orchestrator that dictates the interaction between the two optimizers
To be able to solve LP problems of various sizes, both the master and dual subproblem optimizers must take a generic form and not be tied to a fixed number of various and/or constraints. Furthermore, to be able to switch to different solvers, both optimizers need to conform to a common interface.
To this end, Listing 3.21 defines a base class for master problem optimizers. The constructor __init__()
takes three inputs, namely, objective coefficients, constraint matrix and the right-hand side. Any concrete implementation of the base class is responsible for constructing a model from these three input values using solver-specific modeling languages. The base class also defines several other key functions:
solve()
: this is the function that invokes solver-specific optimization process and saves the corresponding optimization results.add_feasibility_cut()
: this function takes an extreme ray identified by the dual subproblem optimizer and adds a feasibility cut to the master problem.add_optimality_cut()
: this function takes an optimal solution identified by the dual subproblem optimizer and adds an optimality cut to the master problem.opt_obj_val()
: this function returns the optimal objective value obtained.opt_val_for_complicating_vars()
: this function returns the optimal solution values for all the complicating decision variables.opt_val_for_surrogate_var()
: this function returns the optimal value of the surrogate variable.
Similarly, Listing 3.21 presents a base class for dual subproblem optimizers. The constructor __init__()
takes the objective coefficients and constraint matrix that correspond to the variables in the subproblem. These information are required to create variables and constraints for the dual subproblem. However, the objective function will not be instantiated without the optimal solution of the restricted master problem. This base class also contains several other key functions:
solve()
: this is the function that calls solver-specific procedure to solve the underlying dual subproblem.update_objective()
: this function takes the optimal solution obtained by the master problem optimizer and updates the objective function for the dual subproblem.opt_obj_val()
: this function returns the optimal objective values identified by the optimizer.opt_sol()
: this function returns the optimal solution identified by the optimizer.extreme_ray()
: this function returns the extreme ray identified by the optimizer.
3.0.6.2 Master and subproblem solvers based on Gurobi
Listing 3.23 implements a master problem solver class named GenericMasterSolverGurobi
for (RBMP) characterized by the cost coefficient f
, the constraint matrix B
and right-hand side b
. The constructor initializes the complicating variables _y
and the dummy variable _g
. The solve()
function solves the problem and retrieves the optimal solution if exists. Moreover, the solver provides functions to add optimality and feasibility cuts to the existing model.
Listing 3.24 presents a solver for (DSP) that’s defined by the objective function coefficient c
and constraint matrix A
. The model objective function could be updated by update_objective()
with the latest value of y
. Notice that, the optimal solution is saved to _opt_u
if the underlying problem is optimal. Otherwise, an extreme ray is retrieved and stored in _extreme_ray
.
In Listing 3.26, we utilize the freshly baked solver to tackle the serious LP problem presented in the previous sections. The output shows that the same optimal objective value of 1091.43 was obtained in the end.
Iteration: 1
--------------------------------------------------
Start solving master problem.
master problem is optimal.
opt_obj=0.00
opt_g=0.00
opt_y0=0.0
opt_y1=0.0
Finish solving master problem.
--------------------------------------------------
Bounds: lb=0.00, ub=inf
dual subproblem objective updated!
--------------------------------------------------
Start solving dual subproblem.
dual subproblem is optimal.
opt_obj=1200.00
opt_u0=4.0
opt_u1=0.0
Finish solving dual subproblem.
--------------------------------------------------
DSP is optimal!
Bounds: lb=0.00, ub=1200.00
Benders optimality cut added!
Iteration: 2
--------------------------------------------------
Start solving master problem.
master problem is optimal.
opt_obj=1080.00
opt_g=0.00
opt_y0=0.0
opt_y1=60.0
Finish solving master problem.
--------------------------------------------------
Bounds: lb=1080.00, ub=1200.00
dual subproblem objective updated!
--------------------------------------------------
Start solving dual subproblem.
dual subproblem is unbounded
Finish solving dual subproblem.
--------------------------------------------------
DSP is unbounded!!!
Benders feasibility cut added!
Iteration: 3
--------------------------------------------------
Start solving master problem.
master problem is optimal.
opt_obj=1091.43
opt_g=114.29
opt_y0=0.0
opt_y1=54.285714285714285
Finish solving master problem.
--------------------------------------------------
Bounds: lb=1091.43, ub=1200.00
dual subproblem objective updated!
--------------------------------------------------
Start solving dual subproblem.
dual subproblem is optimal.
opt_obj=114.29
opt_u0=4.0
opt_u1=0.0
Finish solving dual subproblem.
--------------------------------------------------
DSP is optimal!
Bounds: lb=1091.43, ub=1091.43
3.0.6.3 Master and subproblem solvers based on SCIP
import pyscipopt
class GenericMasterSolverSCIP:
def __init__(self, f: np.array, B: np.array, b: np.array):
# save data
self._f = f
self._B = B
self._b = b
# env and model
self._model = pyscipopt.Model('MasterSolver')
# create variables
self._num_y_vars = len(f)
self._y = {
self._model.addVar(lb=0, vtype='C', name=f'y{i}')
i: for i in range(self._num_y_vars)
}self._g = self._model.addVar(lb=0,
='C',
vtype='g')
name
# create objective
self._model.setObjective(
self._f[i] * self._y.get(i)
pyscipopt.quicksum(for i in range(self._num_y_vars)) +
self._g,
'maximize')
self._opt_obj = None
self._opt_obj_y = None
self._opt_y = None
self._opt_g = None
def solve(self) -> OptStatus:
print('-' * 50)
print(f'Start solving master problem.')
self._model.setPresolve(pyscipopt.SCIP_PARAMSETTING.OFF)
self._model.setHeuristics(pyscipopt.SCIP_PARAMSETTING.OFF)
self._model.disablePropagation()
self._model.optimize()
= None
opt_status print(self._model.getStatus())
if self._model.getStatus() == 'optimal':
= OptStatus.OPTIMAL
opt_status self._opt_obj = self._model.getObjVal()
self._opt_y = {
self._model.getVal(self._y.get(i))
i: for i in range(self._num_y_vars)
}self._opt_g = self._model.getVal(self._g)
self._opt_obj_y = self._opt_obj - self._opt_g
print(f'\tmaster problem is optimal.')
print(f'\topt_obj={self._opt_obj:.2f}')
print(f'\topt_g={self._opt_g:.2f}')
for j in range(self._num_y_vars):
print(f'\topt_y{j}={self._opt_y.get(j)}')
elif self._model.getStatus() == 'infeasible':
print(f'\tmaster problem is infeasible.')
= OptStatus.INFEASIBLE
opt_status else:
print(f'\tmaster problem encountered error.')
= OptStatus.ERROR
opt_status
print(f'Finish solving master problem.')
print('-' * 50)
return opt_status
def add_feasibility_cut(self, ray_u: dict) -> None:
= [
constr_expr *
ray_u.get(u_idx)
(self._b[u_idx] -
pyscipopt.quicksum(self._B[u_idx][j] * self._y.get(j)
for j in range(self._num_y_vars)
)
)for u_idx in ray_u.keys()
]self._model.addCons(pyscipopt.quicksum(constr_expr) <= 0)
print(f'Benders feasibility cut added!')
def add_optimality_cut(self, opt_u: dict) -> None:
= [
constr_expr *
opt_u.get(u_idx)
(self._b[u_idx] -
pyscipopt.quicksum(self._B[u_idx][j] * self._y.get(j)
for j in range(self._num_y_vars)
)
)for u_idx in opt_u.keys()
]self._model.addCons(pyscipopt.quicksum(constr_expr) <= self._g)
self._model.update()
print(f'Benders optimality cut added!')
@property
def f(self):
return self._f
@property
def opt_obj(self):
return self._opt_obj
@property
def opt_obj_y(self):
return self._opt_obj_y
@property
def opt_y(self):
return self._opt_y
@property
def opt_g(self):
return self._g
import pyscipopt
class GenericSubprobSolverSCIP:
def __init__(self, A: np.array, c: np.array, B: np.array, b: np.array):
# save data
self._A = A
self._c = c
self._b = b
self._B = B
# env and model
self._model = pyscipopt.Model('SubprobSolver')
# create variables
self._num_vars = len(b)
self._u = {
self._model.addVar(vtype='C',
i: =-self._model.infinity(),
lb=self._model.infinity(),
ub=f'u{i}')
namefor i in range(self._num_vars)
}
# create constraints
for c_idx in range(len(c)):
self._model.addCons(
* self._u.get(i)
pyscipopt.quicksum(A[:,c_idx][i] for i in range(len(b))) <= c[c_idx]
)
self._opt_obj = None
self._opt_u = None
self._ray_u = None
def solve(self):
print('-' * 50)
print(f'Start solving dual subproblem.')
self._model.setPresolve(pyscipopt.SCIP_PARAMSETTING.OFF)
self._model.setHeuristics(pyscipopt.SCIP_PARAMSETTING.OFF)
self._model.disablePropagation()
self._model.optimize()
= None
status if self._model.getStatus() == 'optimal':
self._opt_obj = self._model.getObjVal()
self._opt_u = {
self._model.getVal(self._u.get(i))
i: for i in range(self._num_vars)
}= OptStatus.OPTIMAL
status print(f'\tdual subproblem is optimal.')
print(f'\topt_obj={self._opt_obj:.2f}')
for i in range(self._num_vars):
print(f'\topt_u{i}={self._opt_u.get(i)}')
elif self._model.getStatus() == 'unbounded':
= OptStatus.UNBOUNDED
status = self._model.hasPrimalRay()
hasRay print(f'primal ray exists!')
= self._model.getPrimalRay()
ray self._ray_u = {
i: ray[i]for i in range(self._num_vars)
}print(f'dual subproblem is unbounded')
else:
print(f'dual subproblem solve ERROR!')
= OptStatus.ERROR
status
print(f'Finish solving dual subproblem.')
print('-' * 50)
return status
def update_objective(self, opt_y: dict):
= [
obj_expr self._u.get(u_idx) *
(self._b[u_idx] -
sum(self._B[u_idx][j] * opt_y.get(j)
for j in range(len(opt_y))
)
)for u_idx in range(self._num_vars)
]self._model.setObjective(pyscipopt.quicksum(obj_expr), GRB.MAXIMIZE)
print(f'dual subproblem objective updated!')
@property
def opt_obj(self):
return self._opt_obj
@property
def opt_u(self):
return self._opt_u
@property
def ray_u(self):
return self._ray_u
Listing 3.26 solves same the LP problem that we have been tackling in the previous sections. The output confirms that the same optimal solution is identified as in the last section.
Iteration: 1
--------------------------------------------------
Start solving master problem.
unbounded
master problem encountered error.
Finish solving master problem.
--------------------------------------------------
presolving:
(0.0s) symmetry computation started: requiring (bin +, int +, cont +), (fixed: bin -, int -, cont -)
(0.0s) no symmetry present (symcode time: 0.00)
presolving (0 rounds: 0 fast, 0 medium, 0 exhaustive):
0 deleted vars, 0 deleted constraints, 0 added constraints, 0 tightened bounds, 0 added holes, 0 changed sides, 0 changed coefficients
0 implications, 0 cliques
presolved problem has 3 variables (0 bin, 0 int, 0 impl, 3 cont) and 0 constraints
Presolving Time: 0.00
time | node | left |LP iter|LP it/n|mem/heur|mdpt |vars |cons |rows |cuts |sepa|confs|strbr| dualbound | primalbound | gap | compl.
* 0.0s| 1 | 0 | 0 | - | LP | 0 | 3 | 0 | 0 | 0 | 0 | 0 | 0 | -- | -- | 0.00%| unknown
0.0s| 1 | 0 | 0 | - | 568k | 0 | 3 | 0 | 0 | 0 | 0 | 0 | 0 | -- | -- | 0.00%| unknown
SCIP Status : problem is solved [unbounded]
Solving Time (sec) : 0.00
Solving Nodes : 1
Primal Bound : +1.00000000000000e+20 (1 solutions)
Dual Bound : +1.00000000000000e+20
Gap : 0.00 %
TypeError: '>=' not supported between instances of 'float' and 'NoneType'
3.0.6.4 Detect infeasibility
import numpy as np
142)
np.random.seed(= np.random.randint(2, 6, size=20)
c = np.random.randint(1, 15, size=10)
f = np.random.randint(2, 6, size=(20, 20))
A = np.random.randint(2, 26, size=(20, 10))
B = np.random.randint(20, 50, size=20)
b
= np.concatenate([c, f])
obj_coeff = np.concatenate([A, B], axis=1)
constr_mat = b rhs
= LpSolverGurobi(obj_coeff, constr_mat, rhs)
gurobi_solver 'problem2.lp')
gurobi_solver.save_model( gurobi_solver.optimize()
Model is infeasible!
= LpSolverSCIP(obj_coeff, constr_mat, rhs)
scip_solver scip_solver.optimize()
Model is infeasible!
= GenericLpMasterSolver(f, B, b)
master_solver = GenericLpSubprobSolver(A, c, B, b)
dual_subprob_solver
= GenericBendersSolver(master_solver, dual_subprob_solver)
benders_solver benders_solver.optimize()
Iteration: 1
--------------------------------------------------
Start solving master problem.
master problem is optimal.
opt_obj=0.00
opt_g=0.00
opt_y0=0.0
opt_y1=0.0
Finish solving master problem.
--------------------------------------------------
Bounds: lb=0.00, ub=inf
dual subproblem objective updated!
--------------------------------------------------
Start solving dual subproblem.
dual subproblem is optimal.
opt_obj=1200.00
opt_u0=4.0
opt_u1=0.0
Finish solving dual subproblem.
--------------------------------------------------
DSP is optimal!
Bounds: lb=0.00, ub=1200.00
Benders optimality cut added!
Iteration: 2
--------------------------------------------------
Start solving master problem.
master problem is optimal.
opt_obj=1080.00
opt_g=0.00
opt_y0=0.0
opt_y1=60.0
Finish solving master problem.
--------------------------------------------------
Bounds: lb=1080.00, ub=1200.00
dual subproblem objective updated!
--------------------------------------------------
Start solving dual subproblem.
dual subproblem is unbounded
Finish solving dual subproblem.
--------------------------------------------------
DSP is unbounded!!!
Benders feasibility cut added!
Iteration: 3
--------------------------------------------------
Start solving master problem.
master problem is optimal.
opt_obj=1091.43
opt_g=114.29
opt_y0=0.0
opt_y1=54.285714285714285
Finish solving master problem.
--------------------------------------------------
Bounds: lb=1091.43, ub=1200.00
dual subproblem objective updated!
--------------------------------------------------
Start solving dual subproblem.
dual subproblem is optimal.
opt_obj=114.29
opt_u0=4.0
opt_u1=0.0
Finish solving dual subproblem.
--------------------------------------------------
DSP is optimal!
Bounds: lb=1091.43, ub=1091.43