embmiex1.gms : Simple Embedded Code ModelInstance example

Description

The GAMS Embedded Code facility allows to execute foreign code (e.g. Python)
while GAMS runs and to exchange data with GAMS without any disk access (e.g. GDX).

In this example we combine the power of the embedded code facility with the
GAMS Python OO-API class GAMSModelInstance. An instance of this class provides
access to a model instance that can be modified and resolved without regenerating
the model over and over.

Here we generate the model instance once by using the libinclude pyEmbMI. The
arguments to this call provide all necessary information to instantiate an instance
of a GAMSModelInstance. In particular we provide the relevant part of the solve
statement as well as a list of modifiers. These are the parameters in the model
that are subject to change. In addition we can provide some options belonging to
a GAMS/Python OO-API class GAMSOptions via the -key=value pairs. See more about
the use of GAMSModelInstance and GAMSOptions in the GAMS/Python OO-API at
https://www.gams.com/latest/docs/apis/python/annotated.html

A traditional GAMS implemenation of such a scenario loop looks like this:

loop(ScenariosToRun,
   a(i) = newsupply(ScenariosToRun,i);
   b(j) = newdemand(ScenariosToRun,j);
   solve transport using lp minimizing z;
   resultantx(ScenariosToRun,i,j) = x.l(i,j)
);

With the embedded code/GAMSModelInstance solution this loop looks as follows:

loop(ScenariosToRun,
   a(i) = newsupply(ScenariosToRun,i);
   b(j) = newdemand(ScenariosToRun,j);
   continueEmbeddedCode:
   gams.db['a'].copy_symbol(tMI.sync_db['a'])
   gams.db['b'].copy_symbol(tMI.sync_db['b'])
   tMI.solve()
   tMI.sync_db['x'].copy_symbol(gams.db['x'])
   pauseEmbeddedCode x
   resultantx(ScenariosToRun,i,j) = x.l(i,j);
);

With a little helper function this code becomes even more similar:

loop(ScenariosToRun,
   a(i) = newsupply(ScenariosToRun,i);
   b(j) = newdemand(ScenariosToRun,j);
   continueEmbeddedCode:
   solveMI(tMI,['a','b'],['x'])
   pauseEmbeddedCode x
   resultantx(ScenariosToRun,i,j) = x.l(i,j);
);

In contrast to GUSS/Scenario Solver here we implement the loop logic in GAMS
and execute in the loop body the solve method of the GAMSModelInstance class
inside the embedded Python code. Rather than using the gams.get|set method of
the embedded code facility we use GAMSDatabase.copy_symbol to move data between
GAMS (gams.db) and the GAMSModelInstance.sync_db.

Even though we don't exercise the ability in this example, the combination of
GAMSModelInstance and embedded code provides a way of defining the scenario n+1
based on the result (primal and dual) of the nth scenario. This is not possible
in GUSS/Scenario Solver.

Keywords: linear programming, GAMS embedded code facility, Python,
          transportation problem, scheduling


Small Model of Type : LP


Category : GAMS Model library


Main file : embmiex1.gms

$title Simple Embedded Code ModelInstance Example (EMBMIEX1,SEQ=417)

$onText
The GAMS Embedded Code facility allows to execute foreign code (e.g. Python)
while GAMS runs and to exchange data with GAMS without any disk access (e.g. GDX).

In this example we combine the power of the embedded code facility with the
GAMS Python OO-API class GAMSModelInstance. An instance of this class provides
access to a model instance that can be modified and resolved without regenerating
the model over and over.

Here we generate the model instance once by using the libinclude pyEmbMI. The
arguments to this call provide all necessary information to instantiate an instance
of a GAMSModelInstance. In particular we provide the relevant part of the solve
statement as well as a list of modifiers. These are the parameters in the model
that are subject to change. In addition we can provide some options belonging to
a GAMS/Python OO-API class GAMSOptions via the -key=value pairs. See more about
the use of GAMSModelInstance and GAMSOptions in the GAMS/Python OO-API at
https://www.gams.com/latest/docs/apis/python/annotated.html

A traditional GAMS implemenation of such a scenario loop looks like this:

loop(ScenariosToRun,
   a(i) = newsupply(ScenariosToRun,i);
   b(j) = newdemand(ScenariosToRun,j);
   solve transport using lp minimizing z;
   resultantx(ScenariosToRun,i,j) = x.l(i,j)
);

With the embedded code/GAMSModelInstance solution this loop looks as follows:

$libInclude pyEmbMI tMI 'transport us lp min z' -all_model_types=cplex a.Zero b.Zero
loop(ScenariosToRun,
   a(i) = newsupply(ScenariosToRun,i);
   b(j) = newdemand(ScenariosToRun,j);
   continueEmbeddedCode:
   gams.db['a'].copy_symbol(tMI.sync_db['a'])
   gams.db['b'].copy_symbol(tMI.sync_db['b'])
   tMI.solve()
   tMI.sync_db['x'].copy_symbol(gams.db['x'])
   pauseEmbeddedCode x
   resultantx(ScenariosToRun,i,j) = x.l(i,j);
);

With a little helper function this code becomes even more similar:

$libInclude pyEmbMI tMI 'transport us lp min z' -all_model_types=cplex a.Zero b.Zero
loop(ScenariosToRun,
   a(i) = newsupply(ScenariosToRun,i);
   b(j) = newdemand(ScenariosToRun,j);
   continueEmbeddedCode:
   solveMI(tMI,['a','b'],['x'])
   pauseEmbeddedCode x
   resultantx(ScenariosToRun,i,j) = x.l(i,j);
);

In contrast to GUSS/Scenario Solver here we implement the loop logic in GAMS
and execute in the loop body the solve method of the GAMSModelInstance class
inside the embedded Python code. Rather than using the gams.get|set method of
the embedded code facility we use GAMSDatabase.copy_symbol to move data between
GAMS (gams.db) and the GAMSModelInstance.sync_db.

Even though we don't exercise the ability in this example, the combination of
GAMSModelInstance and embedded code provides a way of defining the scenario n+1
based on the result (primal and dual) of the nth scenario. This is not possible
in GUSS/Scenario Solver.

Keywords: linear programming, GAMS embedded code facility, Python,
          transportation problem, scheduling
$offText


$log --- Using Python library %sysEnv.GMSPYTHONLIB%

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'
   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 to run' / base, run1, run2 /;

Table newsupply(s,i) 'updater for a (capacity)'
         seattle  san-diego
   base      350        600
   run1      300        650
   run2      400        550;

Table newdemand(s,j) 'updater for b (demand)'
         new-york  chicago  topeka
   base       325      300     275
   run1       325      300     275
   run2       350      300     250;

$set solverlog
$if set useSolverLog $set solverlog output=sys.stdout
embeddedCode Python:
gams.wsWorkingDir = '.'
def solveMI(mi, symIn=[], symOut=[]):
  for sym in symIn:
    gams.db[sym].copy_symbol(mi.sync_db[sym])
  mi.solve(%solverlog%)
  for sym in symOut:
    try:
      gams.db[sym].clear() # Explicitly clear the symbol to ensure setting "writtenTo" flag for sym
      mi.sync_db[sym].copy_symbol(gams.db[sym])
    except:
      pass
pauseEmbeddedCode
abort$execerror 'Python error. Check the log';

$set license
$if not "%gams.license%" $set license '"-license=%gams.license%"'
$libInclude pyEmbMI tMI 'transport us lp min z' -all_model_types=cplex %license% a.Zero b.Zero

Parameter repX(s,i,j) 'collector for level of x';

loop(s,
   a(i) = newsupply(s,i);
   b(j) = newdemand(s,j);
   continueEmbeddedCode:
   solveMI(tMI,['a','b'],['x'])
   pauseEmbeddedCode x
   repX(s,i,j) = x.l(i,j);
);

continueEmbeddedCode:
tMI.cleanup()
endEmbeddedCode

option  repX:0:1:2;
display repX;

Set error(s) 'empty solution';
error(s) = sum((i,j), repX(s,i,j)) = 0;
abort$card(error) 'Missing solution for some scenarios', error;