The Dantzig-Wolfe Decomposition Algorithm

Example 18.2 Generalized Assignment Problem

The generalized assignment problem (GAP) is that of finding a maximum profit assignment from n tasks to m machines such that each task is assigned to precisely one machine subject to capacity restrictions on the machines. With each possible assignment, associate a binary variable x Subscript i j, which, if set to 1, indicates that machine i is assigned to task j. For ease of notation, define two index sets upper M equals StartSet 1 comma ellipsis comma m EndSet and upper N equals StartSet 1 comma ellipsis comma n EndSet. A GAP can be formulated as a MILP as follows:

StartLayout 1st Row 1st Column Blank 2nd Column maximize 3rd Column sigma-summation Underscript i element-of upper M Endscripts sigma-summation Underscript j element-of upper N Endscripts p Subscript i j Baseline x Subscript i j 2nd Row 1st Column Blank 2nd Column subject to 3rd Column sigma-summation Underscript i element-of upper M Endscripts x Subscript i j 4th Column equals 1 5th Column Blank 6th Column j element-of upper N 7th Column Blank 8th Column left-parenthesis Assignment right-parenthesis 3rd Row 1st Column Blank 2nd Column Blank 3rd Column sigma-summation Underscript j element-of upper N Endscripts w Subscript i j Baseline x Subscript i j 4th Column less-than-or-equal-to b Subscript i Baseline 5th Column Blank 6th Column i element-of upper M 7th Column Blank 8th Column left-parenthesis Knapsack right-parenthesis 4th Row 1st Column Blank 2nd Column Blank 3rd Column x Subscript i j 4th Column element-of StartSet 0 comma 1 EndSet 5th Column Blank 6th Column i element-of upper M comma j element-of upper N EndLayout

In this formulation, the Assignment constraints ensure that each task is assigned to exactly one machine. Knapsack constraints ensure that for each machine, the capacity restrictions are met.

Consider the following example taken from Koch et al. (2011) with n equals 24 tasks to be assigned to m equals 8 machines. The data set profit_data provides the profit for assigning a particular task to a particular machine:

%let NumTasks    = 24;
%let NumMachines = 8;

data profit_data;
   input p1-p&NumTasks;
   datalines;
25 23 20 16 19 22 20 16 15 22 15 21 20 23 20 22 19 25 25 24 21 17 23 17
16 19 22 22 19 23 17 24 15 24 18 19 20 24 25 25 19 24 18 21 16 25 15 20
20 18 23 23 23 17 19 16 24 24 17 23 19 22 23 25 23 18 19 24 20 17 23 23
16 16 15 23 15 15 25 22 17 20 19 16 17 17 20 17 17 18 16 18 15 25 22 17
17 23 21 20 24 22 25 17 22 20 16 22 21 23 24 15 22 25 18 19 19 17 22 23
24 21 23 17 21 19 19 17 18 24 15 15 17 18 15 24 19 21 23 24 17 20 16 21
18 21 22 23 22 15 18 15 21 22 15 23 21 25 25 23 20 16 25 17 15 15 18 16
19 24 18 17 21 18 24 25 18 23 21 15 24 23 18 18 23 23 16 20 20 19 25 21
;

The data set weight_data provides the amount of resources used by a particular task when assigned to a particular machine:

data weight_data;
   input w1-w&NumTasks;
   datalines;
 8 18 22  5 11 11 22 11 17 22 11 20 13 13  7 22 15 22 24  8  8 24 18  8
24 14 11 15 24  8 10 15 19 25  6 13 10 25 19 24 13 12  5 18 10 24  8  5
22 22 21 22 13 16 21  5 25 13 12  9 24  6 22 24 11 21 11 14 12 10 20  6
13  8 19 12 19 18 10 21  5  9 11  9 22  8 12 13  9 25 19 24 22  6 19 14
25 16 13  5 11  8  7  8 25 20 24 20 11  6 10 10  6 22 10 10 13 21  5 19
19 19  5 11 22 24 18 11  6 13 24 24 22  6 22  5 14  6 16 11  6  8 18 10
24 10  9 10  6 15  7 13 20  8  7  9 24  9 21  9 11 19 10  5 23 20  5 21
 6  9  9  5 12 10 16 15 19 18 20 18 16 21 11 12 22 16 21 25  7 14 16 10
;

Finally, the data set capacity_data provides the resource capacity for each machine:

data capacity_data;
   input b @@;
   datalines;
36 35 38 34 32 34 31 34
;

The following PROC OPTMODEL statements read in the data and define the necessary sets and parameters:

proc optmodel;
   /* declare index sets */
   set TASKS    = 1..&NumTasks;
   set MACHINES = 1..&NumMachines;

   /* declare parameters */
   num profit   {MACHINES, TASKS};
   num weight   {MACHINES, TASKS};
   num capacity {MACHINES};

   /* read data sets to populate data */
   read data profit_data   into [i=_n_] {j in TASKS} <profit[i,j]=col('p'||j)>;
   read data weight_data   into [i=_n_] {j in TASKS} <weight[i,j]=col('w'||j)>;
   read data capacity_data into [_n_] capacity=b;

The following statements declare the optimization model:

   /* declare decision variables */
   var Assign {MACHINES, TASKS} binary;

   /* declare objective */
   max TotalProfit =
      sum {i in MACHINES, j in TASKS} profit[i,j] * Assign[i,j];

   /* declare constraints */
   con Assignment {j in TASKS}:
      sum {i in MACHINES} Assign[i,j] = 1;

   con Knapsack {i in MACHINES}:
      sum {j in TASKS} weight[i,j] * Assign[i,j] <= capacity[i];

The following statements use two different decompositions to solve the problem. The first decomposition defines each Assignment constraint as a block and uses the pure network simplex solver for the subproblem. The second decomposition defines each Knapsack constraint as a block and uses the MILP solver for the subproblem.

   /* each Assignment constraint defines a block */
   for{j in TASKS}
      Assignment[j].block = j;

   solve with milp / logfreq=1000
      decomp
      decompsubprob=(algorithm=nspure);

   /* each Knapsack constraint defines a block */
   for{j in TASKS}
      Assignment[j].block = .;
   for{i in MACHINES}
      Knapsack[i].block = i;

   solve with milp / decomp;
quit;

The solution summaries are displayed in Output 18.2.1.

Output 18.2.1: Solution Summaries

The OPTMODEL Procedure

Solution Summary
SolverMILP
AlgorithmDecomposition
Objective FunctionTotalProfit
Solution StatusOptimal
Objective Value563
  
Relative Gap0
Absolute Gap0
Primal Infeasibility0
Bound Infeasibility0
Integer Infeasibility0
  
Best Bound563
Nodes71
Solutions Found4
Iterations566
Presolve Time0.00
Solution Time0.35

Solution Summary
SolverMILP
AlgorithmDecomposition
Objective FunctionTotalProfit
Solution StatusOptimal
Objective Value563
  
Relative Gap0
Absolute Gap0
Primal Infeasibility2.220446E-15
Bound Infeasibility2.220446E-15
Integer Infeasibility2.220446E-15
  
Best Bound563
Nodes5
Solutions Found7
Iterations92
Presolve Time0.00
Solution Time0.17


The iteration log for both decompositions is shown in Output 18.2.2. This example is interesting because it shows the trade-off between the strength of the relaxation and the difficulty of its resolution. In the first decomposition, the subproblems are totally unimodular and can be solved trivially. Consequently, each iteration of the Dantzig-Wolfe decomposition algorithm is very fast. However, the bound obtained is as weak as the bound found in direct methods (the LP bound). The weaker bound leads to the need to enumerate more nodes overall. Alternatively, in the second decomposition, the subproblem is the knapsack problem, which is solved using MILP. In this case, the bound is much tighter and the problem solves in very few nodes. The trade-off, of course, is that each iteration takes longer because solving the knapsack problem is not trivial. Another interesting aspect of this problem is that the subproblem coverage in the second decomposition is much smaller than that of the first decomposition. However, when dealing with MILP, it is not always the size of the coverage that determines the overall effectiveness of a particular choice of decomposition.

Output 18.2.2: Log

NOTE: There were 8 observations read from the data set WORK.PROFIT_DATA.                        
NOTE: There were 8 observations read from the data set WORK.WEIGHT_DATA.                        
NOTE: There were 8 observations read from the data set WORK.CAPACITY_DATA.                      
NOTE: Problem generation will use 4 threads.                                                    
NOTE: The problem has 192 variables (0 free, 0 fixed).                                          
NOTE: The problem has 192 binary and 0 integer variables.                                       
NOTE: The problem has 32 linear constraints (8 LE, 24 EQ, 0 GE, 0 range).                       
NOTE: The problem has 384 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 AUTOMATIC is applied.                                            
NOTE: The MILP presolver removed 0 variables and 0 constraints.                                 
NOTE: The MILP presolver removed 0 constraint coefficients.                                     
NOTE: The MILP presolver modified 5 constraint coefficients.                                    
NOTE: The presolved problem has 192 variables, 32 constraints, and 384 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.                                                  
WARNING: The subproblem solver chosen is an LP solver but at least one block has integer        
         variables.                                                                             
NOTE: The problem has a decomposable structure with 24 blocks. The largest block covers 3.125%  
      of the constraints in the problem.                                                        
NOTE: The decomposition subproblems cover 192 (100%) variables and 24 (75%) 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                   
         .     574.0000     558.7419     554.0000    2.66%    3.48%    0    0                   
         5     570.2597     568.0252     563.0000    0.39%    1.27%    0    0                   
         6     568.6281     568.6281     563.0000    0.00%    0.99%    0    0                   
NOTE: Starting branch and bound.                                                                
         Node  Active   Sols         Best         Best      Gap    CPU   Real                   
                                  Integer        Bound            Time   Time                   
            0       1      4     563.0000     568.6281    0.99%      0      0                   
           70       0      4     563.0000     563.0000    0.00%      0      0                   
NOTE: The Decomposition algorithm used 4 threads.                                               
NOTE: The Decomposition algorithm time is 0.35 seconds.                                         
NOTE: Optimal.                                                                                  
NOTE: Objective = 563.                                                                          
NOTE: The initial MILP heuristics are applied.                                                  
NOTE: The MILP presolver value AUTOMATIC is applied.                                            
NOTE: The MILP presolver removed 0 variables and 0 constraints.                                 
NOTE: The MILP presolver removed 0 constraint coefficients.                                     
NOTE: The MILP presolver modified 5 constraint coefficients.                                    
NOTE: The presolved problem has 192 variables, 32 constraints, and 384 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 8 blocks. The largest block covers 3.125%   
      of the constraints in the problem.                                                        
NOTE: The decomposition subproblems cover 192 (100%) variables and 8 (25%) 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                   
         .     820.0000     548.0000     548.0000   33.17%   33.17%    0    0                   
         1     820.0000     548.0000     548.0000   33.17%   33.17%    0    0                   
         4     779.6000     548.0000     548.0000   29.71%   29.71%    0    0                   
         5     763.7963     548.0000     548.0000   28.25%   28.25%    0    0                   
         6     661.5778     548.0000     548.0000   17.17%   17.17%    0    0                   
         7     656.1538     548.0000     548.0000   16.48%   16.48%    0    0                   
         9     634.0000     548.0000     548.0000   13.56%   13.56%    0    0                   
        10     634.0000     548.0000     561.0000   13.56%   11.51%    0    0                   
        11     597.1034     561.0000     561.0000    6.05%    6.05%    0    0                   
        12     579.8182     561.0000     561.0000    3.25%    3.25%    0    0                   
        15     570.3333     561.0000     561.0000    1.64%    1.64%    0    0                   
        16     570.3333     562.0000     562.0000    1.46%    1.46%    0    0                   
        18     570.3333     563.2000     563.0000    1.25%    1.29%    0    0                   
        19     565.0000     564.0000     563.0000    0.18%    0.35%    0    0                   
         .     565.0000     564.0000     563.0000    0.18%    0.35%    0    0                   
        20     564.0000     564.0000     563.0000    0.00%    0.18%    0    0                   
         .     564.0000     564.0000     563.0000    0.00%    0.18%    0    0                   
NOTE: Starting branch and bound.                                                                
         Node  Active   Sols         Best         Best      Gap    CPU   Real                   
                                  Integer        Bound            Time   Time                   
            0       1      5     563.0000     564.0000    0.18%      0      0                   
            4       0      7     563.0000     563.0000    0.00%      0      0                   
NOTE: The Decomposition algorithm used 4 threads.                                               
NOTE: The Decomposition algorithm time is 0.17 seconds.                                         
NOTE: Optimal.                                                                                  
NOTE: Objective = 563.                                                                          


Last updated: June 22, 2026