$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';