The 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 sessref="mysess";
   /* 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
Nodes69
Solutions Found4
Iterations560
Presolve Time0.00
Solution Time0.59

Solution Summary
SolverMILP
AlgorithmDecomposition
Objective FunctionTotalProfit
Solution StatusOptimal
Objective Value563
  
Relative Gap0
Absolute Gap0
Primal Infeasibility3.996803E-15
Bound Infeasibility3.996803E-15
Integer Infeasibility3.996803E-15
  
Best Bound563
Nodes3
Solutions Found6
Iterations65
Presolve Time0.00
Solution Time0.16


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 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 8 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 SOLVE statement is executing in the distributed computing environment in              
      single-machine mode.                                                                      
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 number of block threads has been reduced to 24 threads.                               
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 80 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                   
           68       0      4     563.0000     563.0000    0.00%      0      0                   
NOTE: The Decomposition algorithm used 80 threads.                                              
NOTE: The Decomposition algorithm time is 0.59 seconds.                                         
NOTE: Optimal.                                                                                  
NOTE: Objective = 563.                                                                          
NOTE: The SOLVE statement is executing in the distributed computing environment in              
      single-machine mode.                                                                      
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 number of block threads has been reduced to 8 threads.                                
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 80 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     669.0000     548.0000     548.0000   18.09%   18.09%    0    0                   
         5     645.0000     548.0000     548.0000   15.04%   15.04%    0    0                   
         6     629.2500     548.0000     548.0000   12.91%   12.91%    0    0                   
         7     613.4643     548.0000     548.0000   10.67%   10.67%    0    0                   
         9     613.4643     563.0000     563.0000    8.23%    8.23%    0    0                   
         .     613.4643     563.0000     563.0000    8.23%    8.23%    0    0                   
        10     601.0000     563.0000     563.0000    6.32%    6.32%    0    0                   
        11     586.5000     563.0000     563.0000    4.01%    4.01%    0    0                   
        12     585.8652     563.0000     563.0000    3.90%    3.90%    0    0                   
        13     576.6667     563.0000     563.0000    2.37%    2.37%    0    0                   
        14     575.3333     563.0000     563.0000    2.14%    2.14%    0    0                   
        15     568.5000     563.0000     563.0000    0.97%    0.97%    0    0                   
        16     567.5000     563.0000     563.0000    0.79%    0.79%    0    0                   
         .     567.5000     564.0000     563.0000    0.62%    0.79%    0    0                   
        20     567.5000     564.0000     563.0000    0.62%    0.79%    0    0                   
        21     564.2500     564.0000     563.0000    0.04%    0.22%    0    0                   
        23     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                   
            2       0      6     563.0000     563.0000    0.00%      0      0                   
NOTE: The Decomposition algorithm used 80 threads.                                              
NOTE: The Decomposition algorithm time is 0.16 seconds.                                         
NOTE: Optimal.                                                                                  
NOTE: Objective = 563.                                                                          


Last updated: November 29, 2023