The Decomposition Algorithm
Example 16.6 Resource Allocation Problem
(View the complete code for this example.)
This example describes a model for selecting tasks to be run on a shared resource (Gamrath 2010). Consider a set I of tasks and a resource capacity C. Each item has a profit
, a resource utilization level
, a starting period
, and an ending period
. The time horizon that is considered is from the earliest starting time to the latest ending time of all tasks. With each task, associate a binary variable
, which, if set to 1, indicates that the task is running from its start time until just before its end time. A task consumes capacity if it is running. The goal is to select which tasks to run in order to maximize profit while not exceeding the shared resource capacity. Let
define the set of start times for all tasks, and let
define the set of tasks that are running at each start time
. You can model the problem as a mixed integer linear programming problem as follows:
In this formulation, the CapacityCon constraints ensure that the running tasks do not exceed the resource capacity. To illustrate, consider the following five-task example with data: ,
,
,
, and
. The formulation leads to a constraint matrix that has a staircase structure that is determined by tasks coming online and offline:
Lagrangian Decomposition
This formulation clearly has no decomposable structure. However, you can use a common modeling technique known as Lagrangian decomposition to bring the model into block-angular form. Lagrangian decomposition works by first partitioning the constraints into blocks. Then, each original variable is split into multiple copies of itself, one copy for each block in which the variable has a nonzero coefficient in the constraint matrix. Constraints are added to enforce the equality of each copy of the original variable. Then, you can write the original constraints in block-angular form by using the duplicate variables.
To apply Lagrangian decomposition to the resource allocation problem, define a set B of blocks and let define the set of start times for a given block b, such that
. Given this partition of start times, let
define the set of blocks in which task
is scheduled to be running. Now, for each task
, define duplicate variables
for each
. Let
define the minimum block index for each class of variable that represents task i. You can now model the problem in block-angular form as follows:
In this formulation, the LinkDupVarsCon constraints ensure that the duplicate variables are equal to the original variables. Now, the five-task example has been transformed from a staircase structure to a block-angular structure:
To see how to apply Lagrangian decomposition
in PROC OPTMODEL, consider the data set TaskData from Caprara, Furini, and Malaguti (2010), which consists of 2,916 tasks:
data TaskData;
input profit weight start end;
datalines;
99 92 1 9
56 30 1 3
39 73 1 20
86 76 1 9
...
24 94 768 769
95 40 768 769
66 17 768 769
18 48 768 769
97 23 768 769
;
Using the MILP Solver Directly in PROC OPTMODEL
The following PROC OPTMODEL statements read in the data and solve the original staircase formulation by calling the MILP solver directly:
%macro SetupData(task_data=, capacity=);
set TASKS;
num capacity=&capacity;
num profit{TASKS}, weight{TASKS}, start{TASKS}, end{TASKS};
read data &task_data into TASKS=[_n_] profit weight start end;
/* the set of start times */
set STARTS = setof{i in TASKS} start[i];
/* the set of tasks i that are active at a given start time s */
set TASKS_START{s in STARTS}
= {i in TASKS: start[i] <= s < end[i]};
%mend SetupData;
%macro ResourceAllocation_Direct(task_data=, capacity=);
proc optmodel;
%SetupData(task_data=&task_data,capacity=&capacity);
/* select task i to come online from period [start to end) */
var x{TASKS} binary;
/* maximize the total profit of running tasks */
max TotalProfit = sum{i in TASKS} profit[i] * x[i];
/* enforce that the shared resource capacity is not exceeded */
con CapacityCon{s in STARTS}:
sum{i in TASKS_START[s]} weight[i] * x[i] <= capacity;
solve with milp / maxtime=200 logfreq=10000;
quit;
%mend ResourceAllocation_Direct;
%ResourceAllocation_Direct(task_data=TaskData, capacity=100);
The problem summary and solution summary are displayed in Output 16.6.1.
Output 16.6.1: Problem Summary and Solution Summary
| Problem Summary | |
|---|---|
| Objective Sense | Maximization |
| Objective Function | TotalProfit |
| Objective Type | Linear |
| Number of Variables | 2916 |
| Bounded Above | 0 |
| Bounded Below | 0 |
| Bounded Below and Above | 2916 |
| Free | 0 |
| Fixed | 0 |
| Binary | 2916 |
| Integer | 0 |
| Number of Constraints | 768 |
| Linear LE (<=) | 768 |
| Linear EQ (=) | 0 |
| Linear GE (>=) | 0 |
| Linear Range | 0 |
| Constraint Coefficients | 23236 |
| Solution Summary | |
|---|---|
| Solver | MILP |
| Algorithm | Branch and Cut |
| Objective Function | TotalProfit |
| Solution Status | Optimal within Relative Gap |
| Objective Value | 40982 |
| Relative Gap | 0.0000980648 |
| Absolute Gap | 4.0192857192 |
| Primal Infeasibility | 0 |
| Bound Infeasibility | 0 |
| Integer Infeasibility | 0 |
| Best Bound | 40986.019286 |
| Nodes | 5084 |
| Solutions Found | 19 |
| Iterations | 214002 |
| Presolve Time | 0.10 |
| Solution Time | 18.59 |
The iteration log, which contains the problem statistics, the progress of the solution, and the best integer feasible solution found, is shown in Output 16.6.2.
Output 16.6.2: Log
| NOTE: There were 2916 observations read from the data set WORK.TASKDATA. |
| NOTE: Problem generation will use 4 threads. |
| NOTE: The problem has 2916 variables (0 free, 0 fixed). |
| NOTE: The problem has 2916 binary and 0 integer variables. |
| NOTE: The problem has 768 linear constraints (768 LE, 0 EQ, 0 GE, 0 range). |
| NOTE: The problem has 23236 linear constraint coefficients. |
| NOTE: The problem has 0 nonlinear constraints (0 LE, 0 EQ, 0 GE, 0 range). |
| NOTE: The remaining solution time after problem generation and solver initialization is 199.52 |
| seconds. |
| NOTE: The initial MILP heuristics are applied. |
| NOTE: The MILP presolver value AUTOMATIC is applied. |
| NOTE: The MILP presolver removed 1021 variables and 126 constraints. |
| NOTE: The MILP presolver removed 12544 constraint coefficients. |
| NOTE: The MILP presolver modified 987 constraint coefficients. |
| NOTE: The presolved problem has 1895 variables, 642 constraints, and 10692 constraint |
| coefficients. |
| NOTE: The MILP solver is called. |
| NOTE: The parallel Branch and Cut algorithm is used. |
| NOTE: The Branch and Cut algorithm is using up to 4 threads. |
| Node Active Sols BestInteger BestBound Gap Time |
| 0 1 3 33939.0000000 109181 68.91% 0 |
| 0 1 3 33939.0000000 45862.9249030 26.00% 0 |
| 0 1 7 39381.0000000 45862.9249030 14.13% 0 |
| 0 1 7 39381.0000000 43471.6068311 9.41% 0 |
| 0 1 7 39381.0000000 42707.3723942 7.79% 0 |
| 0 1 7 39381.0000000 42238.8588892 6.77% 0 |
| 0 1 7 39381.0000000 41967.3454237 6.16% 1 |
| 0 1 7 39381.0000000 41743.7098575 5.66% 1 |
| 0 1 7 39381.0000000 41616.2315360 5.37% 1 |
| 0 1 7 39381.0000000 41502.8003604 5.11% 1 |
| 0 1 7 39381.0000000 41413.3091253 4.91% 2 |
| 0 1 7 39381.0000000 41356.7303013 4.78% 2 |
| 0 1 10 40606.0000000 41356.7303013 1.82% 2 |
| 0 1 10 40606.0000000 41319.7929965 1.73% 2 |
| 0 1 10 40606.0000000 41291.0535691 1.66% 2 |
| 0 1 10 40606.0000000 41257.4586910 1.58% 3 |
| 0 1 10 40606.0000000 41230.7941340 1.52% 3 |
| 0 1 10 40606.0000000 41211.5099745 1.47% 3 |
| 0 1 10 40606.0000000 41195.4072860 1.43% 3 |
| 0 1 10 40606.0000000 41178.0811024 1.39% 3 |
| 0 1 10 40606.0000000 41168.0422830 1.37% 4 |
| 0 1 10 40606.0000000 41157.1566155 1.34% 4 |
| 0 1 10 40606.0000000 41148.9202354 1.32% 4 |
| 0 1 10 40606.0000000 41144.7920422 1.31% 4 |
| NOTE: The MILP solver added 817 cuts with 10610 cut coefficients at the root. |
| 316 4 11 40879.0000000 41113.8580323 0.57% 6 |
| 1408 862 12 40930.0000000 41071.5367400 0.34% 8 |
| 2156 1276 13 40943.0000000 41053.2643070 0.27% 10 |
| 2726 1442 14 40972.0000000 41043.6598465 0.17% 12 |
| 2780 1456 15 40976.0000000 41043.5767327 0.16% 12 |
| 3783 1039 16 40978.0000000 41011.8999490 0.08% 15 |
| 3870 998 17 40979.0000000 41006.9850849 0.07% 16 |
| 5022 286 18 40981.0000000 40987.3881588 0.02% 18 |
| 5083 195 19 40982.0000000 40986.0192857 0.01% 18 |
| NOTE: Optimal within relative gap. |
| NOTE: Objective = 40982. |
Using the Decomposition Algorithm in PROC OPTMODEL
To transform this data into block-angular form, first sort the task data to help reduce the number of duplicate variables that are needed in the reformulation as follows:
proc sort data=TaskData;
by start end;
run;
Then, create the partition of constraints into blocks of size block_size as follows:
%macro ResourceAllocation_Decomp(task_data=, capacity=, block_size=);
proc optmodel;
%SetupData(task_data=&task_data,capacity=&capacity);
/* partition into blocks of size blocks_size */
num block_size = &block_size;
num num_blocks = ceil( card(TASKS) / block_size );
set BLOCKS = 1..num_blocks;
/* the set of starts s for which task i is active */
set STARTS_TASK{i in TASKS} = {s in STARTS: start[i] <= s < end[i]};
/* partition the start times into blocks of size block_size */
set STARTS_BLOCK{BLOCKS} init {};
num block_id init 1;
num block_count init 0;
for{s in STARTS} do;
STARTS_BLOCK[block_id] = STARTS_BLOCK[block_id] union {s};
block_count = block_count + 1;
if(mod(block_count, block_size) = 0) then
block_id = block_id + 1;
end;
Then, use the following PROC OPTMODEL statements to define the block-angular formulation and solve the problem by using the decomposition algorithm, the PRESOLVER=BASIC option, and block_size=20. Because this reformulation is equivalent to the original staircase formulation, disabling some of the advanced presolver techniques ensures that the model maintains block-angularity.
/* blocks in which task i is online */
set BLOCKS_TASK{i in TASKS} =
{b in BLOCKS: card(STARTS_BLOCK[b] inter STARTS_TASK[i]) > 0};
/* minimum block id in which task i is online */
num min_block{i in TASKS} = min{b in BLOCKS_TASK[i]} b;
/* select task i to come online from period [start to end)
in each block */
var x{i in TASKS, b in BLOCKS_TASK[i]} binary;
/* maximize the total profit of running tasks */
max TotalProfit = sum{i in TASKS} profit[i] * x[i,min_block[i]];
/* enforce that task selection is consistent across blocks */
con LinkDupVarsCon{i in TASKS, b in BLOCKS_TASK[i] diff {min_block[i]}}:
x[i,b] = x[i,min_block[i]];
/* enforce that the shared resource capacity is not exceeded */
con CapacityCon{b in BLOCKS, s in STARTS_BLOCK[b]}:
sum{i in TASKS_START[s]} weight[i] * x[i,b] <= capacity;
/* define blocks for decomposition algorithm */
for{b in BLOCKS, s in STARTS_BLOCK[b]} CapacityCon[b,s].block = b;
solve with milp / presolver=basic decomp;
quit;
%mend ResourceAllocation_Decomp;
%ResourceAllocation_Decomp(task_data=TaskData, capacity=100, block_size=20);
The problem summary and solution summary are displayed in Output 16.6.3. Compared to the original formulation, the numbers of variables and constraints are increased by the number of duplicate variables.
Output 16.6.3: Problem Summary and Solution Summary
| Problem Summary | |
|---|---|
| Objective Sense | Maximization |
| Objective Function | TotalProfit |
| Objective Type | Linear |
| Number of Variables | 3924 |
| Bounded Above | 0 |
| Bounded Below | 0 |
| Bounded Below and Above | 3924 |
| Free | 0 |
| Fixed | 0 |
| Binary | 3924 |
| Integer | 0 |
| Number of Constraints | 1776 |
| Linear LE (<=) | 768 |
| Linear EQ (=) | 1008 |
| Linear GE (>=) | 0 |
| Linear Range | 0 |
| Constraint Coefficients | 25252 |
| Solution Summary | |
|---|---|
| Solver | MILP |
| Algorithm | Decomposition |
| Objective Function | TotalProfit |
| Solution Status | Optimal within Relative Gap |
| Objective Value | 40982 |
| Relative Gap | 0.0000731975 |
| Absolute Gap | 3 |
| Primal Infeasibility | 2.88658E-15 |
| Bound Infeasibility | 2.88658E-15 |
| Integer Infeasibility | 2.88658E-15 |
| Best Bound | 40985 |
| Nodes | 15 |
| Solutions Found | 72 |
| Iterations | 258 |
| Presolve Time | 0.06 |
| Solution Time | 92.54 |
The iteration log, which contains the problem statistics, the progress of the solution, and the optimal objective value, is shown in Output 16.6.4.
Output 16.6.4: Log
| NOTE: There were 2916 observations read from the data set WORK.TASKDATA. |
| NOTE: Problem generation will use 4 threads. |
| NOTE: The problem has 3924 variables (0 free, 0 fixed). |
| NOTE: The problem has 3924 binary and 0 integer variables. |
| NOTE: The problem has 1776 linear constraints (768 LE, 1008 EQ, 0 GE, 0 range). |
| NOTE: The problem has 25252 linear constraint coefficients. |
| NOTE: The problem has 0 nonlinear constraints (0 LE, 0 EQ, 0 GE, 0 range). |
| NOTE: The initial MILP heuristics are applied. |
| NOTE: The MILP presolver value BASIC is applied. |
| NOTE: The MILP presolver removed 4 variables and 0 constraints. |
| NOTE: The MILP presolver removed 23 constraint coefficients. |
| NOTE: The MILP presolver modified 7312 constraint coefficients. |
| NOTE: The presolved problem has 3920 variables, 1776 constraints, and 25229 constraint |
| coefficients. |
| NOTE: The MILP solver is called. |
| NOTE: The Decomposition algorithm is used. |
| NOTE: The Decomposition algorithm is executing in single-machine mode. |
| NOTE: The DECOMP method value USER is applied. |
| NOTE: The problem has a decomposable structure with 39 blocks. The largest block covers 1.126% |
| of the constraints in the problem. |
| NOTE: The decomposition subproblems cover 3920 (100%) variables and 768 (43.24%) constraints. |
| NOTE: The deterministic parallel mode is enabled. |
| NOTE: The Decomposition algorithm is using up to 4 threads. |
| Iter Best Master Best LP IP CPU Real |
| Bound Objective Integer Gap Gap Time Time |
| . 44109.0000 37660.0000 37660.0000 14.62% 14.62% 1 0 |
| 7 44109.0000 37916.0000 37916.0000 14.04% 14.04% 3 1 |
| 9 44109.0000 38118.0000 38118.0000 13.58% 13.58% 5 2 |
| . 44109.0000 38118.0000 38118.0000 13.58% 13.58% 5 2 |
| 10 44109.0000 38118.0000 38118.0000 13.58% 13.58% 6 2 |
| 11 44109.0000 38236.0000 38236.0000 13.31% 13.31% 6 2 |
| 14 44109.0000 38679.0000 38679.0000 12.31% 12.31% 8 3 |
| 18 44109.0000 39086.0000 39077.0000 11.39% 11.41% 10 4 |
| . 44109.0000 39645.0000 39645.0000 10.12% 10.12% 10 4 |
| 20 44109.0000 39645.0000 39645.0000 10.12% 10.12% 11 4 |
| 22 44109.0000 39681.0000 39681.0000 10.04% 10.04% 12 5 |
| 23 44109.0000 39741.5000 39740.0000 9.90% 9.91% 14 5 |
| 25 44109.0000 39829.5000 39828.0000 9.70% 9.71% 15 6 |
| 30 44109.0000 40334.0000 40334.0000 8.56% 8.56% 19 8 |
| 35 44109.0000 40440.8333 40439.0000 8.32% 8.32% 26 10 |
| . 44109.0000 40455.9167 40448.0000 8.28% 8.30% 33 13 |
| 40 44109.0000 40455.9167 40448.0000 8.28% 8.30% 35 13 |
| 41 43745.1461 40455.9167 40448.0000 7.52% 7.54% 38 14 |
| 42 43569.2923 40467.0000 40448.0000 7.12% 7.16% 40 15 |
| 43 43157.1594 40467.0000 40448.0000 6.23% 6.28% 43 16 |
| 46 43157.1594 40477.0000 40462.0000 6.21% 6.24% 46 17 |
| 50 43157.1594 40519.9183 40580.0000 6.11% 5.97% 52 19 |
| 51 43157.1594 40609.0000 40584.0000 5.90% 5.96% 54 20 |
| 52 43157.1594 40609.0000 40594.0000 5.90% 5.94% 56 20 |
| 54 43157.1594 40706.3333 40689.0000 5.68% 5.72% 57 21 |
| 60 43157.1594 40791.3430 40689.0000 5.48% 5.72% 68 25 |
| 61 41745.3669 40806.5044 40689.0000 2.25% 2.53% 71 26 |
| 63 41622.2702 40846.9914 40689.0000 1.86% 2.24% 76 28 |
| 70 41622.2702 40911.9167 40689.0000 1.71% 2.24% 86 31 |
| . 41622.2702 40991.0833 40911.0000 1.52% 1.71% 95 34 |
| 80 41622.2702 40991.0833 40911.0000 1.52% 1.71% 96 34 |
| 81 41071.9167 40991.0833 40911.0000 0.20% 0.39% 98 35 |
| 83 41054.6944 40991.0833 40911.0000 0.15% 0.35% 99 35 |
| 90 41054.6944 40997.5833 40911.0000 0.14% 0.35% 101 36 |
| 98 41008.0833 40997.5833 40911.0000 0.03% 0.24% 103 38 |
| 99 41005.5833 40997.5833 40911.0000 0.02% 0.23% 103 38 |
| 100 41005.5833 40997.5833 40911.0000 0.02% 0.23% 103 38 |
| 101 41000.5833 40997.5833 40911.0000 0.01% 0.22% 104 38 |
| NOTE: The Decomposition algorithm stopped on the continuous RELOBJGAP= option. |
| . 41000.5833 40997.5833 40936.0000 0.01% 0.16% 104 38 |
| NOTE: Starting branch and bound. |
| Node Active Sols Best Best Gap CPU Real |
| Integer Bound Time Time |
| 0 1 70 40936.0000 41000.5833 0.16% 104 38 |
| 1 3 70 40936.0000 41000.5833 0.16% 134 48 |
| 3 5 70 40936.0000 40991.5833 0.14% 169 60 |
| 5 3 71 40981.0000 40991.5833 0.03% 174 62 |
| 8 4 72 40982.0000 40991.5833 0.02% 207 73 |
| 11 1 72 40982.0000 40987.3333 0.01% 237 84 |
| 14 0 72 40982.0000 40985.0000 0.01% 259 92 |
| NOTE: The Decomposition algorithm used 4 threads. |
| NOTE: The Decomposition algorithm time is 92.54 seconds. |
| NOTE: Optimal within relative gap. |
| NOTE: Objective = 40982. |
The Trade-Off between Coverage and Subproblem Difficulty
The reformulation of this resource allocation problem provides a nice example of the potential trade-offs in modeling a problem for use with the decomposition algorithm. As seen in Example 16.2, the strength of the bound is an important factor in the overall performance of the algorithm, but it is not always correlated to the magnitude of the subproblem coverage. In the current example, the block size determines the number of blocks. Moreover, it determines the number of linking variables that are needed in the reformulation. At one extreme, if the block size is set to be , then the number of blocks is 1, and the number of copies of original variables is 0. Using one block would be equivalent to the original staircase formulation and would not yield a model conducive to decomposition. As the number of blocks is increased, the number of linking variables increases (the size of the master problem), the strength of the decomposition bound decreases, and the difficulty of solving the subproblems decreases. In addition, as the number of blocks and their relative difficulty change, the efficient utilization of your machine’s parallel architecture can be affected.
The previous section used a block size of 20. The following statement calls the decomposition algorithm and uses a block size of 80:
%ResourceAllocation_Decomp(task_data=TaskData, capacity=100, block_size=80);
The solution summary is displayed in Output 16.6.5.
Output 16.6.5: Solution Summary
| Solution Summary | |
|---|---|
| Solver | MILP |
| Algorithm | Decomposition |
| Objective Function | TotalProfit |
| Solution Status | Optimal |
| Objective Value | 40982 |
| Relative Gap | 0 |
| Absolute Gap | 0 |
| Primal Infeasibility | 1.421085E-14 |
| Bound Infeasibility | 1.332268E-15 |
| Integer Infeasibility | 2.88658E-15 |
| Best Bound | 40982 |
| Nodes | 1 |
| Solutions Found | 52 |
| Iterations | 49 |
| Presolve Time | 0.05 |
| Solution Time | 47.90 |
The iteration log, which contains the problem statistics, the progress of the solution, and the optimal objective value, is shown in Output 16.6.6.
This version of the model provides a stronger initial bound and solves to optimality in the root node.
Output 16.6.6: Log
| NOTE: There were 2916 observations read from the data set WORK.TASKDATA. |
| NOTE: Problem generation will use 4 threads. |
| NOTE: The problem has 3151 variables (0 free, 0 fixed). |
| NOTE: The problem has 3151 binary and 0 integer variables. |
| NOTE: The problem has 1003 linear constraints (768 LE, 235 EQ, 0 GE, 0 range). |
| NOTE: The problem has 23706 linear constraint coefficients. |
| NOTE: The problem has 0 nonlinear constraints (0 LE, 0 EQ, 0 GE, 0 range). |
| NOTE: The initial MILP heuristics are applied. |
| NOTE: The MILP presolver value BASIC is applied. |
| NOTE: The MILP presolver removed 5 variables and 0 constraints. |
| NOTE: The MILP presolver removed 29 constraint coefficients. |
| NOTE: The MILP presolver modified 7312 constraint coefficients. |
| NOTE: The presolved problem has 3146 variables, 1003 constraints, and 23677 constraint |
| coefficients. |
| NOTE: The MILP solver is called. |
| NOTE: The Decomposition algorithm is used. |
| NOTE: The Decomposition algorithm is executing in single-machine mode. |
| NOTE: The DECOMP method value USER is applied. |
| NOTE: The problem has a decomposable structure with 10 blocks. The largest block covers 7.976% |
| of the constraints in the problem. |
| NOTE: The decomposition subproblems cover 3146 (100%) variables and 768 (76.57%) constraints. |
| NOTE: The deterministic parallel mode is enabled. |
| NOTE: The Decomposition algorithm is using up to 4 threads. |
| Iter Best Master Best LP IP CPU Real |
| Bound Objective Integer Gap Gap Time Time |
| . 41762.0080 37518.0000 37518.0000 10.16% 10.16% 3 1 |
| 8 41762.0080 38555.0000 38555.0000 7.68% 7.68% 12 4 |
| 10 41762.0080 38555.0000 38564.0000 7.68% 7.66% 15 5 |
| 12 41762.0080 39024.0000 39024.0000 6.56% 6.56% 18 6 |
| 15 41762.0080 40682.0000 40682.0000 2.59% 2.59% 25 8 |
| 19 41762.0080 40787.0000 40787.0000 2.33% 2.33% 34 11 |
| . 41762.0080 40787.0000 40787.0000 2.33% 2.33% 34 12 |
| 20 41762.0080 40787.0000 40787.0000 2.33% 2.33% 37 13 |
| 25 41762.0080 40793.0000 40793.0000 2.32% 2.32% 54 18 |
| 27 41762.0080 40824.0000 40824.0000 2.25% 2.25% 64 22 |
| 28 41556.4875 40824.0000 40824.0000 1.76% 1.76% 69 24 |
| 29 41322.1701 40824.0000 40824.0000 1.21% 1.21% 74 25 |
| 30 41322.1701 40824.0000 40824.0000 1.21% 1.21% 77 26 |
| 32 41322.1701 40883.0000 40883.0000 1.06% 1.06% 83 28 |
| 33 41322.1701 40937.0000 40937.0000 0.93% 0.93% 86 29 |
| 37 41322.1701 40978.0000 40978.0000 0.83% 0.83% 97 35 |
| . 41322.1701 40980.0000 40980.0000 0.83% 0.83% 102 36 |
| 40 41322.1701 40980.0000 40980.0000 0.83% 0.83% 103 37 |
| 41 41004.0000 40980.0000 40980.0000 0.06% 0.06% 108 39 |
| 42 40992.6000 40980.0000 40980.0000 0.03% 0.03% 110 40 |
| 46 40992.6000 40982.0000 40982.0000 0.03% 0.03% 118 44 |
| 49 40982.0000 40982.0000 40982.0000 0.00% 0.00% 121 47 |
| Node Active Sols Best Best Gap CPU Real |
| Integer Bound Time Time |
| 0 1 52 40982.0000 40982.0000 0.00% 121 47 |
| NOTE: The Decomposition algorithm used 4 threads. |
| NOTE: The Decomposition algorithm time is 47.90 seconds. |
| NOTE: Optimal. |
| NOTE: Objective = 40982. |