asyncloop.gms : Transportation Problem with async loop body execution

Description

This problem finds a least cost shipping schedule that meets
requirements at markets and supplies at factories.

The model demonstrates how to run multiple scenarios with a rich loop body
in a parallel fashion using the GAMS asynchronous execution facility.

This model can be parameterized. With the double-dash option --driver=no
GAMS just executed the scenarios in sequence. Without the double-dash
option --driver or --driver=yes the model calls itself recursively first
executing the code before the loop, then executing the loop body in parallel
and then executing the code after the loop. The merging of the results from
the jobs that executed the loop body is done via restart and GDX.


Small Model of Type : LP


Category : GAMS Model library


Main file : asyncloop.gms

$title Transportation Problem with async loop body Execution (ASYNCLOOP,SEQ=411)

$onText
This problem finds a least cost shipping schedule that meets
requirements at markets and supplies at factories.

The model demonstrates how to run multiple scenarios with a rich loop body
in a parallel fashion using the GAMS asynchronous execution facility.

This model can be parameterized. With the double-dash option --driver=no
GAMS just executed the scenarios in sequence. Without the double-dash
option --driver or --driver=yes the model calls itself recursively first
executing the code before the loop, then executing the loop body in parallel
and then executing the code after the loop. The merging of the results from
the jobs that executed the loop body is done via restart and GDX.


Dantzig, G B, Chapter 3.3. In Linear Programming and Extensions.
Princeton University Press, Princeton, New Jersey, 1963.

Keywords: linear programming, transportation problem, scheduling, GAMS
          language features, asynchronous execution facility
$offText

$if not set driver  $set driver yes
$if set before_loop $goto before_loop
$if set run_loop    $goto run_loop
$if set after_loop  $goto after_loop
$ifi %driver%==yes  $goto driver

* Normal sequential run
$set cond

$label before_loop
Set
   i 'canning plants' / seattle,  san-diego /
   j 'markets'        / new-york, chicago, topeka /;

Parameter
   a(i) 'capacity of plant i in cases'
        / seattle    350
          san-diego  600 /

   b(j) 'demand at market j in cases'
        / new-york   325
          chicago    300
          topeka     275 /;

Table d(i,j) 'distance in thousands of miles'
              new-york  chicago  topeka
   seattle         2.5      1.7     1.8
   san-diego       2.5      1.8     1.4;

Scalar f 'freight in dollars per case per thousand miles' / 90 /;

Parameter c(i,j) 'transport cost in thousands of dollars per case';
c(i,j) = f*d(i,j)/1000;

Variable
   x(i,j)   'shipment quantities in cases'
   slack(j) 'slack for unfilled demand'
   z        'total transportation costs in thousands of dollars';

Positive Variable x, slack;

Equation
   cost      'define objective function'
   supply(i) 'observe supply limit at plant i'
   demand(j) 'satisfy demand at market j';

Scalar pen / 1000 /;

cost..      z =e= sum((i,j), c(i,j)*x(i,j)) + pen*sum(j, slack(j));

supply(i).. sum(j, x(i,j)) =l= a(i);

demand(j).. sum(i, x(i,j)) =g= b(j) + slack(j);

Model transport / all /;

Set s 'scenarios' / scen1*scen10 /;

Parameter bsave(j), bmult(s), obj(s);
bsave(j) = b(j);
bmult(s) = uniform(0.9,1.1);

$if set before_loop $exit

$label run_loop
$if set run_loop $set cond $sameas(s,'%myscen%')
loop(s%cond%,
   solve transport using lp minimizing z;
   obj(s) = z.l;
);
$if set run_loop $exit

$label after_loop
$ifThen set after_loop
   loop(s$(ord(s) <> 1),
     put_utility 'gdxin' / s.tl:0 '.gdx';
     execute_loadpoint obj;
   );
$endIf
display obj;
$exit

$label driver
* Implement the logic to run the loop body asynchronously
* First run code before the loop
$call gams "%gams.input%" --before_loop=1 lo=%gams.lo% o=before_loop.lst lf=before_loop.log s=before_loop gdx=before_loop
$ifE errorLevel<>0 $abort problems with before_loop

* Next run loop body code asynchronously
Set s 'loop scenarios';

$gdxIn before_loop
$load s
$gdxIn

$eolCom //

Set
   submit(s) 'list of jobs to submit'
   done(s)   'list of completed jobs';

Parameter
   h(s)   'store the instance handle'
   maxS   'maximum number of active jobs' / 4 /
   tStart 'time stamp';

tStart = jnow;
done(s) = no;
h(s)    =  0;

File fx;
repeat
   submit(s) = no;
   loop(s$(not (done(s) or h(s))), submit(s) = yes$(card(submit) + card(h) < maxS););
   loop(submit(s),
      put_utility fx 'log' / 'submit myscen=' s.tl:0;
      put_utility$(ord(s) = 1) 'exec.async' / 'gams "%gams.input%" --run_loop=1 lo=2 r=before_loop --myscen=' s.tl:0 ' lf=' s.tl:0 '.log o=' s.tl:0 '.lst gdx=' s.tl:0 '.gdx s=run_loop';
      put_utility$(ord(s) > 1) 'exec.async' / 'gams "%gams.input%" --run_loop=1 lo=2 r=before_loop --myscen=' s.tl:0 ' lf=' s.tl:0 '.log o=' s.tl:0 '.lst gdx=' s.tl:0 '.gdx';
      h(s) = JobHandle;
   );
   loop(s$(JobStatus(h(s)) = 2),
      put_utility 'log' / 'collect myscen=' s.tl:0;
      abort$errorLevel 'problem with one of the loop jobs';
      done(s) = yes;
      h(s)    =   0;
   );
   display$sleep(card(h)*0.05) 'sleep some time';
until card(done) = card(s) or timeelapsed > 100;  // wait until all jobs are done
tStart = (jnow - tStart)*3600*24;
display 'Time to run all loops:', tStart;

* Run post loop code that merges the results from the loop jobs
execute 'gams "%gams.input%" --after_loop=1 r=run_loop o=after_loop.lst lf=after_loop.log lo=%gams.lo%';
abort$errorLevel 'problems with after_loop';