Libinclude file pyembpy.gms
for the GAMS Python 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.
Usage:
$libinclude pyEmbMI myPyMI 'solve statement' [optlist] updateList
Where:
Argument Description myPyMI
Name of the GAMSModelInstance Python identifier. solve statement
Part of the solve statment that gives model name, solver type and optimization direction / variable, e.g. transport minimize z us lp
.optlist
Optional list of -optkey=optval
option pairs, e.g.-reslim=100
-all_model_types=cplex
that build update aGamsOption
object used in the instantiate call. See gams.control.options.GamsOptions Class Reference for details.updateList
List of update tuples (see details below):
Variable update tuples:gmsModelVar.Lower|Upper|Fixed|Primal|Dual.gmsPar.Zero|BaseCase|Accumulate
Parameter update pairs:gmsModelPar.Zero|BaseCase|Accumulate
The update tuples contain instructions how to deal with updating variable/equation attributes (mostly bounds) and parameters: If you want to change a parameter the entries holds the syntax: x.action
where x
is the GAMS name of the parameter and action
is the update type of the parameter. This identifies how a parameter is updated if there is no data corresponding to the set intersection defining the parameter.
parametername.Zero
will set all parameter values that are not explicitly set tozero
.parametername.BaseCase
will set all parameter values that are not explicitly set to the value from the first model instantiation.parametername.Accumulate
will keep all parameter values that are not explicitly set to their last known value.
To update an attribute of a variable/equation 4 things need to be considered:
- What variable or equation to work with
- What attribute to change (e.g. upper and lower corresponding bounds)
- What parameter contains the new values
- What to do with zeros which is the
Zero
,BaseCase
, andAccumulate
from above. This is expressed in thetuple variableName.attribute.parameterWithNewValues.zeroKeyword
. For examplex.Upper.xUp.BaseCase
would reset upper bounds forx
to the values in the parameterxup
and set the values that are not explicitly set to their last known value.
Hence a call to this include might look like this:
$libinclude pyEmbMI miTransport 'transport minimizing z using lp' -all_model_types=cplex x.Upper.xUp.BaseCase b.zero
Embedded Code Model Instance Example
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.
Background:
A traditional GAMS implemenation of 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 solveMI
(see below) 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
.
- Note
- 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.
Example:
* GMSPYTHONLIB gets automatically set to use the internal Python installation in sysdir/GMSPython.
$if not setEnv GMSPYTHONLIB $abort.noError Embedded code Python not ready to be used
$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:
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';
$libInclude pyEmbMI tMI 'transport us lp min z' -all_model_types=cplex 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);
);
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;
The complete example is also part of the GAMS Model Library, see model [embmiex1] for reference.