EngineSolveAsync.gms : Demonstrate how to asynchronously submit solve statements to Engine

Description

This model implements the well known transport problem and solves
asynchronouslys a couple of scenarios of the problem with GAMS
Engine. In a submission loop over the scenarios GAMS exports the
workfile and submits a GAMS job with the solve statement to Engine.
This is done in an embedded code Python code section that uses the
Python Engine API. The handle of the jobs are stored in the element
text of a dynamic set (h). In a collection loop the results of a job
are downloaded as soon as the job is finished on Engine. The results
come back in a GDX point file an a GDX file with the model attributes,
e.g transport.modelStat and transport.solveStat.

In order to see the independence of this asynchronous solve with GAMS
Engine from a particular model and data, the logic to submit and
collect models has been moved to a batInlude file engine_async.gms.
The overall logic for asynchronous solves follows the ideas of the
GAMS Grid and Multi-Threading Solve Facility ideas.

Contributor: Michael Bussieck, December 2023

  The model requires environment variables set to work properly:
  ENGINE_URL, ENGINE_USER, ENGINE_PASSWORD ENGINE_NAMESPACE


Category : GAMS Data Utilities library


Main file : EngineSolveAsync.gms   includes :  EngineSolveAsync.gms  engine_async.gms

$title Demonstrate how to asynchronously submit solve statements to Engine (ENGINESOLVEASYNC,SEQ=150)

$onText
This model implements the well known transport problem and solves
asynchronouslys a couple of scenarios of the problem with GAMS
Engine. In a submission loop over the scenarios GAMS exports the
workfile and submits a GAMS job with the solve statement to Engine.
This is done in an embedded code Python code section that uses the
Python Engine API. The handle of the jobs are stored in the element
text of a dynamic set (h). In a collection loop the results of a job
are downloaded as soon as the job is finished on Engine. The results
come back in a GDX point file an a GDX file with the model attributes,
e.g transport.modelStat and transport.solveStat.

In order to see the independence of this asynchronous solve with GAMS
Engine from a particular model and data, the logic to submit and
collect models has been moved to a batInlude file engine_async.gms.
The overall logic for asynchronous solves follows the ideas of the
GAMS Grid and Multi-Threading Solve Facility ideas.

Contributor: Michael Bussieck, December 2023
$offText

* The model requires environment variables set to work properly:
* ENGINE_URL, ENGINE_USER, ENGINE_PASSWORD ENGINE_NAMESPACE
$if not setEnv ENGINE_URL $abort.noError Environment variables for GAMS Engine are not set

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'
   z      'total transportation costs in thousands of dollars';

Positive Variable x;

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

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

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

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

Model transport / all /;

Set s 'scenarios' / 1*5 /;

Parameter
   dem(s,j) 'random demand';
Set   
   h(s)     'store the instance handle/token';

* Create some random demands
dem(s,j) = b(j)*uniform(.95,1.15); 

$batInclude engine_async init s h
loop(s,
    b(j) = dem(s,j);
$   batInclude engine_async solve transport min z using lp
);
loop(s, put_utility 'MsgLog' / s.tl:5 '  ' h.te(s));

Parameter
   repx(s,i,j) 'solution report'
   repy(s,*)   'summary report';

repy(s,'SolveStat') = na;
repy(s,'ModelStat') = na;

$onImplicitAssign
Scalar isCollected /0/;
repeat
   loop(s$h(s),
$     batInclude engine_async collect isCollected
      if (isCollected=1,
          repx(s,i,j)         = x.l(i,j);
          repy(s,'solvestat') = transport.solveStat;
          repy(s,'modelstat') = transport.modelStat;
          repy(s,'resusd'   ) = transport.resUsd;
          repy(s,'objval')    = transport.objVal;
      elseif isCollected>0,
          put_utility 'MsgLog' / 'Job ' s.tl:0 ' did unsuccessfully return with status ' isCollected:0:0
      );
   );
   display$sleep(card(h)*0.2) 'was sleeping for some time';
until card(h) = 0 or timeelapsed > 20;

display repx, repy;

abort$sum(s$(repy(s,'solvestat') = na),1) 'Some jobs did not return';