$title Dantzig Wolfe Decomposition and Grid Computing (DANWOLFE,SEQ=328)

$onText
This example implements the well known Dantzig Wolfe decomposition method. This
method exploits the structure in linear programs where the coefficient matrix
has the special form:

     B0  B1  B2 ... Bk
         A1
             A2
                .
                 .  Ak

The specific model is a multi-commodity network flow problem where Ak
corresponds to a commodity flow and Bk represents arc capacities.


Dantzig, G B, and Wolfe, P, Decomposition principle for linear
programs. Operations Research 8 (1960), 101-111.

Keywords: linear programming, Dantzig Wolfe decomposition, multi-commodity network flow problem,
          network optimization, grid computing
$offText

$eolCom //

$setDDList nodes comm maxtime
$if not set nodes   $set nodes    20
$if not set comm    $set comm      5
$if not set maxtime $set maxtime 100
$if not errorfree $abort wrong double dash parameters: --nodes=n --comm=n --maxtime=secs

Set
   i      'nodes'       / n1*n%nodes% /
   k      'commodities' / k1*k%comm%  /
   e(i,i) 'edges';

Alias (i,j);

Parameter
   cost(i,j) 'cost for edge use'
   bal(k,i)  'balance'
   kdem(k)   'demand'
   cap(i,j)  'bundle capacity';

Variable
   x(k,i,j) 'multi commodity flow'
   z        'objective';

Positive Variable x;

Equation
   defbal(k,i) 'balancing constraint'
   defcap(i,j) 'bundling capacity'
   defobj;

defobj..      z =e= sum((k,e), cost(e)*x(k,e));

defbal(k,i).. sum(e(i,j), x(k,e)) - sum(e(j,i),x(k,e)) =e= bal(k,i);

defcap(e)..   sum(k, x(k,e)) =l= cap(e);

Model mcf 'multi-commodity flow problem' / all /;

**** make a random instance
Scalar
   inum
   edgedensity / 0.3 /;

e(i,j)  = uniform(0,1) < edgedensity;
e(i,i)  = no;
cost(e) = uniform(1,10);
cap(e)  = uniform(50,100)*log(card(k));

loop(k,
   kdem(k) = uniform(50,150);
   inum    = uniformInt(1,card(i)); bal(k,i)$(ord(i) = inum) = kdem(k);
   inum    = uniformInt(1,card(i)); bal(k,i)$(ord(i) = inum) = bal(k,i) - kdem(k);
   kdem(k) = sum(i$(bal(k,i) > 0), bal(k,i));
);

**** see if the random model is feasible
option limRow = 0, limCol = 0, solPrint = off, solveLink = %solveLink.callModule%;

solve mcf min z using lp;

abort$(mcf.modelStat <> %modelStat.optimal%) 'problem not feasible. Increase edge density.'

Parameter xsingle(k,i,j) 'single solve';
xsingle(k,e) = x.l(k,e)$[x.l(k,e) > 1e-6];

display$(card(i) < 30) xsingle;

**** define master model
Set
   p           'paths idents' / p1*p100 /
   ap(k,p)     'active path'
   pe(k,p,i,j) 'edge path incidence vector';

Parameter pcost(k,p) 'path cost';

Positive Variable
   xp(k,p)
   slack(k);

Equation
   mdefcap(i,j) 'bundle constraint'
   mdefbal(k)   'balance constraint'
   mdefobj      'objective';

mdefobj..    z =e= sum(ap, pcost(ap)*xp(ap)) + sum(k, 999*slack(k));

mdefbal(k).. sum(ap(k,p), xp(ap)) + slack(k) =e= kdem(k);

mdefcap(e).. sum(pe(ap,e), xp(ap)) =l= cap(e);

Model master / mdefobj, mdefbal, mdefcap /;

**** define pricing model: shortest path
Parameter ebal(i);

Positive Variable xe(i,j);

Equation
   pdefbal(i) 'balance constraint'
   pdefobj    'objective';

pdefobj..    z =e= sum(e, (cost(e) - mdefcap.m(e))*xe(e));

pdefbal(i).. sum(e(i,j), xe(e)) - sum(e(j,i),xe(e)) =e= ebal(i);

Model pricing / pdefobj, pdefbal /;

**** serial decomposition method
Scalar
   done 'loop indicator'
   iter 'iteration counter';

Set nextp(k,p) 'next path to be added';

* clear path data
done          =   0;
iter          =   0;
ap(k,p)       =  no;
pe(k,p,e)     =  no;
pcost(k,p)    =   0;
nextp(k,p)    =  no;
nextp(k,'p1') = yes;

while(not done,
   iter = iter + 1;
   solve master using lp minimizing z;
   done = 1;
   loop(k$kdem(k),
      ebal(i) = bal(k,i)/kdem(k);
      solve pricing using lp minimizing z;
      pricing.solPrint = %solPrint.quiet%;  // turn off all outputs fpr pricing model
      if(mdefbal.m(k) - z.l > 1e-6,         // add new path
         ap(nextp(k,p))    = yes;
         pe(nextp(k,p),e)  = round(xe.l(e));
         pcost(nextp(k,p)) = sum(pe(nextp,e), cost(e));
         nextp(k,p)        = nextp(k,p-1);  // bump the path to the next free one
         abort$(sum(nextp(k,p),1) = 0) 'set p too small';
         done = 0;
      );
   );
);

abort$(abs(master.objVal - mcf.objVal) > 1e-3) 'different objective values', master.objVal, mcf.objVal;

Parameter xserial(k,i,j);
xserial(k,e) = sum(pe(ap(k,p),e), xp.l(ap));

display$(card(i) < 30) xserial;

**** Now the same with the Grid option
Parameter h(k) 'model handles';
pricing.solveLink = %solveLink.asyncGrid%;
pricing.solPrint  = %solPrint.summary%;

* clear path data
done          =   0;
iter          =   0;
ap(k,p)       =  no;
pe(k,p,e)     =  no;
pcost(k,p)    =   0;
nextp(k,p)    =  no;
nextp(k,'p1') = yes;

while(not done,
   iter = iter + 1;
   solve master using lp minimizing z;
   done = 1;
   pricing.number = 0;
   loop(k$kdem(k),
      ebal(i) = bal(k,i)/kdem(k);
      solve pricing using lp minimizing z;
      pricing.solPrint = %solPrint.quiet%; // turn off all outputs for pricing model
      h(k) = pricing.handle;
   );
   repeat
      loop(k$h(k),
         if(handlecollect(h(k)),
            if(mdefbal.m(k) - z.l > 1e-6,
               ap(nextp(k,p))    = yes;
               pe(nextp(k,p),e)  = round(xe.l(e));
               pcost(nextp(k,p)) = sum(pe(nextp,e), cost(e));
               nextp(k,p)        = nextp(k,p-1);
               abort$(sum(nextp(k,p),1) = 0) 'set p too small';
               done = 0;
            );
            display$handledelete(h(k)) 'trouble deleting handles' ;
            h(k) = 0;
         );
      );
      display$sleep(card(h)*0.1) 'sleep a bit';
   until card(h) = 0 or timeelapsed > %maxtime%;
   abort$(card(h) and timeelapsed <= %maxtime%) 'not all solves returned', h;
);

if(timeelapsed > %maxtime%,
   display 'Algorithm interrupted because maxtime=%maxtime% has been exceeded!';
);

abort$(abs(master.objVal - mcf.objVal) > 1e-3 and timeelapsed <= %maxtime%) 'different objective values', master.objVal, mcf.objVal;

Parameter xgrid(k,i,j);
xgrid(k,e) = sum(pe(ap(k,p),e), xp.l(ap));

display$(card(i) < 30) xgrid;

Parameter xall 'summary of flows';
xall(k,e,'single') = xsingle(k,e);
xall(k,e,'serial') = xserial(k,e);
xall(k,e,'grid')   = xgrid(k,e);

option  xall:3:3:1;
display xall;
