You are on page 1of 23

GAMS Python API Tutorial

GAMS Development Corporation


Washington, DC, USA
8/20/2013
A GAMS Python API Tutorial I
2013 GAMS Development Corporation
Table of Contents
Chapter I A GAMS Python API Tutorial
1
................................................................................................................................... 1 1 Overview
................................................................................................................................... 2 2 Getting Started
................................................................................................................................... 2 3 Important Classes of the API
.......................................................................................................................................................... 3 GamsWorkspace
.......................................................................................................................................................... 3 GamsJob
.......................................................................................................................................................... 3 GamsDatabase
.......................................................................................................................................................... 4 GamsOptions
.......................................................................................................................................................... 5 GamsModelInstance
................................................................................................................................... 6 4 Examples
A GAMS Python API Tutorial 1
2013 GAMS Development Corporation
1 A GAMS Python API Tutorial
The goal of this tutorial is to gather information on the GAMS Python API. The tutorial should
provide a compact overview of the basic functionality of the GAMS Python API. It allows the
user to start immediately working with the API by providing a set of small examples based on
the well-known transportation problem. These examples introduce several API features step
by step.
Overview Overview of the main features provided by the GAMS Python
API
Getting Started A quick introduction about how to run a program
Important Classes Overview of some fundamental classes of the GAMS Python
API
Examples An extensive set of examples
Note that this tutorial does not claim to provide an all-embracing documentation of the API.
For more detailed information we refer to the documentation GAMS Python API.pdf.
1.1 Overview
The gams namespace provides objects to interact with the General Algebraic Modeling
System (GAMS). Objects in this namespace allow convenient exchange of input data and
model results (GamsDatabase), help to create and run GAMS models (GamsJob), that can
be customized by GAMS options (GamsOptions). Furthermore, it introduces a way to solve a
sequence of closely related model instances in the most efficient way (GamsModelInstance).
A GAMS program can include other source files (e.g. $include), load data from GDX files (e.g.
$GDXIN or execute-_load), and create PUT files. All these files can be specified with a
(relative) path and therefore an anchor into the file system is required. The base class
GamsWorkspace manages the anchor to the file system. If external file communication is not
an issue in a particular Python application, temporary directories and files will be managed by
objects in the namespace.
With the exception of GamsWorkspace the objects in the gams namespace cannot be
accessed across different threads unless the instance is locked. The classes themself are
thread safe and multiple objects of the class can be used from different threads (see below
for restrictions on solvers that are not thread safe within the GamsModel-Instance class).
This version of the gams namespace lacks support for the following GAMS components:
Acronyms, support for GAMS compilation/execution errors (GamsJob.run just throws an
exception), structured access to listing file, and proper support for solver options.
Currently only CplexD and Gurobi fully utilize the power of solving GamsModelInstances.
Some solvers will not even work in a multi-threaded application using GamsModelInstances.
For some solvers this is unavoidable because the solver library is not thread safe (e.g.
MINOS), other solvers are in principle thread safe but the GAMS link is not (e.g. Cplex, note
there is a thread safe version of Cplex called CplexD which lack some minor features e.g.
A GAMS Python API Tutorial 2
2013 GAMS Development Corporation
support for BCH framework). Moreover, GamsModelInstances are not available for quadratic
model types (QCP, MIQCP, RMIQCP).
1.2 Getting Started
The object oriented GAMS Python API is built on top of the different low level component APIs
and provides convenient access to GAMS from within Python. In further course of this tutorial
we refer to the GAMS System directory as [GAMSDIR] and to the diretcory of your Python
version as [PYTHONDIR]. Examples using the API are located in [GAMSDIR]\apifiles
\Python while the API itself is found in [GAMSDIR]\apifiles\Python\api for Python 2.7
and in [GAMSDIR]\apifiles\Python\api_26 for Python 2.6 (Windows and Linux only). The
bitness of the Python version has to be the same as the bitness of the GAMS system.
Assuming that the current directory is [GAMSDIR]\apifiles\Python, the API can be used
as follows via command line:
Installing the API and the required low level APIs to Python site-packages:
cd api && python setup.py install && cd ..
Using the API without installing:
export PYTHONPATH=api (on Windows: set PYTHONPATH=api)
Note that on non windows platforms it is required to set the load library path to the GAMS
system directory to run scripts using the GAMS Python API.
Linux:
export LD_LIBRARY_PATH=[GAMSDIR]:$LD_LIBRARY_PATH
OS X:
export DYLD_LIBRARY_PATH=[GAMSDIR]:$DYLD_LIBRARY_PATH
To run the transport1.py example type:
python transport1.py
1.3 Important Classes of the API
This section provides a quick overview of some fundamental classes of the GAMS
Namespace. Their usage is demonstrated by an extensive set of examples.
GamsWorkspace Class
GamsJob Class
GamsDatabase Class
GamsOptions Class
GamsModelInstance Class
Note that we use also other classes in our examples but those mentioned above can be
considered as especially important. For a detailed documentation of all classes provided by
the API we point again to GAMS Python API.pdf.
A GAMS Python API Tutorial 3
2013 GAMS Development Corporation
1.3.1 GamsWorkspace
The GamsWorkspace is the base class of the gams namespace.
Most objects of the gams namespace (e.g. GamsDatabase and GamsJob) should be
created by an "add" method of GamsWorkspace instead of using the constructors.
Unless a GAMS system directory is specified during construction of GamsWorkspace,
GamsWorkspace determines the location of the GAMS installation automatically. This is a
source of potential problems if more than one GAMS installation exist on the machine.
Furthermore, a working directory (the anchor into the file system) can be provided when
constructing the GamsWorkspace instance. All file based operation inside a GAMS model
should be relative to this location (e.g. $GDXIN and $include). There are options to add input
search paths (e.g. IDir) and output path (e.g. PutDir) to specify other file system locations. If
no working directory is supplied, GamsWorkspace creates a temporary folder and on
instance destruction removes this temporary folder.
In a typical Python application a single instance of GamsWorkspace will suffice, since the
class is thread-safe.
1.3.2 GamsJob
The GamsJob class manages the execution of a GAMS program given by GAMS model
source.
The GAMS source (or more precisely the root of a model source tree) of the job can be
provided as a string or by a filename (relative to the working directory of the
GamsWorkspace) of a text file containing the GAMS model source. The run method
organizes the export of the input GamsDatabases, calls the GAMS compiler and execution
system with the supplied options and on successful completion provides through the property
out_db (of type GamsDatabase) the results of the model run.
While the result data is captured in a GamsDatabase, the run method can also create a
GamsCheckpoint that not only captures data but represents the state of the entire GamsJob
and allows some other GamsJob to continue from this state. In case of a compilation or
execution error, the run method will throw an exception. If the log output of GAMS is of
interest, this can be captured by providing the output parameter of the run method (e.g.
sys.stdout).
1.3.3 GamsDatabase
An instance of GamsDatabase communicates data between the Python world and the GAMS
world.
A GamsDatabase consists of a collection of symbols (GamsDatabase implements __iter__()
and next() to allow iterating conveniently through the symbols in a GamsDatabase). The
symbol types available for a GamsDatabase correspond to the symbol types known from the
GAMS language: Set, Parameter, Variable, and Equation are represented in Python by a
derived class (e.g. GamsSet, GamsParameter, etc). Besides the type, a GamsSymbol has a
name (this has to match the name inside the GAMS model), a dimension (currently up to 20
or GMS_MAX_INDEX_DIM) and some explanatory text.
A GAMS Python API Tutorial 4
2013 GAMS Development Corporation
Variables and equations also have a subtype: e.g. VarType.Binary, VarType.Positive, etc. for
variables and e.g. EquType.E, EquType.G etc. for equations.
GamsDatabases can be created empty, or initialized from existing GDX files or from another
GamsDatabase (copy). Symbols can be added at any time (e.g.
GamsDatabase.add_parameter), but once a symbol is part of a GamsDatabase, it cannot be
removed. Only its associated data (GamsSymbolRecord) can be purged (see
GamsSymbol.clear()) or individually removed (GamsSymbol.delete_record). Individual data
elements are accessed record by record. A record is identified by the keys (a vector of
strings). The record data varies by symbol type. For example, a parameter record has a
value property, a variable has the properties level, lower, upper, marginal, and scale. Adding a
record with keys that already exist results in an exception. Similar, the unsuccessful search
for a record also results in an exception.
GamsSymbol implements __iter__() and next() to conveniently iterate through the records of
a symbol. There are also sliced access methods to symbol records that allow to iterate
through all records with a fixed index at some positions. GamsDatabases can be exported as
GDX files for permanent storage.
GamsJob.out_db and GamsModelInstance.sync_db provide instances of GamsDatabase to
communicate results from a GAMS run or a solve. These databases should only be used in
the context of the base object (GamsJob or GamsModelInstance). If a copy of such a
database is required GamsWorkspace.add_database can be used to initialize a
GamsDatabase from another database by specifying the optional parameter
source_database (e.g. newdb = workspace.add_database(GamsJob.out_db)).
GamsDatabases often provide the input data for a GamsJob. Such GamsDatabases are
listed in the GamsJob.run method. Inside the GAMS model source the GamsDatabase is
accessible through a GDX file. The GAMS model source requires a particular file name to
connect to the proper GDX file (e.g. $GDXIN filename). A GamsDatabase can be created with
a given name which can be then used inside the model (e.g. db = workspace.add_database
(database_name="SupplyData")) and then inside the GAMS model source: $GDXIN
SupplyData) or an automatically generated name can be used. This name can be passed
down to the GAMS model by using the defines dictionary of a GamsOptions instance:
db = workspace.add_database()
opt = workspace.add_options()
opt.defines["SupplyDataFileName"] = db.name
...
gamsjob.run(gams_options=opt, databases=db)
Inside the GAMS model source the name is accessed as follows:
$GDXIN %SupplyDataFileName%
1.3.4 GamsOptions
The GamsOptions class manages GAMS options (sometimes also called GAMS parameters
since they correspond to the command line parameters of the GAMS executable) for a
GamsJob and GamsModelInstance. There are integer (e.g. nodlim), double (e.g. reslim), and
string (e.g. putdir) valued options. There are also a few list options (defines to set string
macros inside GAMS and idir provide multiple search paths for include files) and a power
option to set a solver for all suitable model types (all_model_types).
A GAMS Python API Tutorial 5
2013 GAMS Development Corporation
Some options known from other interfaces to GAMS that are of limited use or could even
create problematic situations in the Python environment are not settable through the
GamsOptions class.
For some options (e.g. case) other GAMS interfaces use numeric values (e.g. 0,1) while the
GamsOptions class has enumerated types with proper names (e.g. MixedCase,
UpperCase).
1.3.5 GamsModelInstance
In general a GamsJob is the standard way of dealing with a GAMS model and the
corresponding solution provided by a solver. The GAMS language provides programming flow
that allows to solve models in a loop and do other sophisticated tasks, like building
decomposition algorithms.
In rare cases, the GAMS model generation time dominates the solver solution time and
GAMS itself becomes the bottleneck in an optimization application. For a model instance
which is a single mathematical model generated by a GAMS solve statement, the
GamsModelInstance class provides a controlled way of modifying a model instance and
solving the resulting problem in the most effcient way, by communicating only the changes of
the model to the solver and doing a hot start (in case of a continuous model like LP) without
the use of disk IO.
The GamsModelInstance requires a GamsCheckpoint that contains the model definition.
It can be created with the following method where cp is a GamsCheckpoint that can be
created by the GamsJob.Run method:
...
job = ws.add_job_from_file("trnsport.gms")
job.run(checkpoint=cp)
mi = cp.add_modelinstance()
...
To modify a GamsModelInstance we need to define GamsModifiers. The modification of the
model instance is done through data in sync_db (a property of GamsModelInstance of type
GamsDatabase). Consider to use a parameter bmult in our GAMS model.
bmult = mi.sync_db.add_parameter("bmult", 0, "demand multiplier")
The first argument is the string identifier, the second the dimension and the third is optional
explanatory text.
Afterwards we instantiate the model instance. That means we pass a model definition and
define a GamsModifier. A GamsModifier consists either of a GamsParameter
mi.instantiate("transport us lp min z", GamsModifier(b))
...
mi.solve();
or a triple of
1. A GamsVariable or GamsEquation to be modified
A GAMS Python API Tutorial 6
2013 GAMS Development Corporation
2. The modification action (e.g. Upper, Lower or Fixed for updating bounds of a variable, or
Primal/Dual for updating the level/marginal of a variable or equation mainly used for
starting non-linear models from different starting points)
3. A GamsParameter that holds the data for modification
mi = cp.add_modelinstance()
x = mi.sync_db.add_variable("x", 2, VarType.Positive)
xup = mi.sync_db.add_parameter("xup", 2, "upper bound on x")
mi.instantiate("transport use lp min z", GamsModifier(x,
UpdateAction.Upper, xup))
mi.solve()
1.4 Examples
In the GAMS system directory there are some examples provided that illustrate the usage of
the GAMS Python API. [GAMSDIR]\apifiles\Python contains multiple examples dealing
with the well-known transportation problem. In further course of this tutorial we discuss these
examples step by step and introduce new elements of the API in detail.
We recommend to open the aforementioned files to gain a complete overview of the
examples. Down below we explain the examples with the help of selected code snippets.
How to import packages/modules from the GAMS Python API (Transport1)
How to choose the GAMS system (Transport1)
How to run a GamsJob from file (Transport1)
How to retrieve a solution from an output database (Transport1)
How to specify the solver using GamsOptions (Transport1)
How to run a job with a solver option file (Transport1)
How to use include files (Transport2)
How to set a non-default working directory (Transport3)
How to read data from string and export to GDX (Transport3)
How to run a job using data from GDX (Transport3)
How to run a job using implicit database communication (Transport3)
How to define data using Python data structures (Transport4)
How to prepare a GamsDatabase from Python data structures (Transport4)
How to initialize a GamsCheckpoint by running a GamsJob (Transport5)
How to initialize a GamsJob from a GamsCheckpoint (Transport5)
How to run multiple GamsJobs in parallel using a GamsCheckpoint (Transport6)
How to create a GamsModelInstance from a GamsCheckpoint (Transport7)
How to modify a parameter of a GamsModelInstance using GamsModifier (Transport7)
How to modify a variable of a GamsModelInstance using GamsModifier (Transport7)
How to use a queue to solve multiple GamsModelInstances in parallel (Transport8)
How to fill a GamsDatabase by reading from MS Access (Transport9)
How to fill a GamsDatabase by reading from MS Excel (Transport10)
How to create and use a save/restart file (Transport11)
How to import packages/modules from the GAMS Python API
(Transport1)
If you followed the instructions from the Getting Started section and installed the GAMS
A GAMS Python API Tutorial 7
2013 GAMS Development Corporation
Python API there should be a folder [PYTHONDIR]\Lib\site-packages\gams that
contains the required packages and modules. We simply import all of them using
from gams import *
Conventional Python packages/modules can by imported like that:
import os
import sys
How to choose the GAMS system (Transport1)
By default the GAMS system is determined automatically. In case of having multiple
GAMS systems on your machine, the desired system can be specified via an additional
argument when the workspace is created. If we type python transport1.py C:\GAMS
\win64\24.0 we use the 64-bit version of GAMS 24.0 to run transport1.py even if our
default GAMS system might be a different one. This is managed by the following code:
...
if len(sys.argv) > 1:
ws = GamsWorkspace(system_directory = sys.argv[1])
else:
ws = GamsWorkspace()
...
Remember that the bitness of the GAMS system has to match the bitness of your
Python version.
How to run a GamsJob from file (Transport1)
At first we create our workspace using ws = GamsWorkspace(). Afterwards we load
the trnsport model from the GAMS model library which puts the corresponding gms
file in our working directory. Apparently you can create a GamsJob with any other gms
file you might have created on your own as long as it is located in the current working
directory. Then the GamsJob t1 can be defined using the add_job_from_file method
and afterwards we run the job.
...
ws.gamslib("trnsport")

t1 = ws.add_job_from_file("trnsport.gms")
t1.run()
...
How to retrieve a solution from an output database (Transport1)
The following lines create the solution output and illustrate the usage of the
GamsJob.out_db property to get access to the GamsDatabase created by the run
method. To retrieve the content of variable x we use squared brackets that internally call
the get_symbol method.
...
for rec in t1.out_db["x"]:
A GAMS Python API Tutorial 8
2013 GAMS Development Corporation
print "x(" + rec.keys[0] + "," + rec.keys[1] + "): level=" +
str(rec.level) + " marginal=" + str(rec.marginal)
...
Note that instead of using the squared brackets we could also use
...
for rec in t1.out_db.get_symbol("x"):
...
How to specify the solver using GamsOptions (Transport1)
The solver can be specified via the GamsOptions class and the
GamsWorkspace.add_options method. The GamsOptions.all_model_types property
sets xpress as default solver for all model types that can be handled by the solver. Then
we run our GamsJob t1 with the new GamsOption.
...
opt = ws.add_options()
opt.all_model_types = "xpress"
t1.run(opt)
...
How to run a job with a solver option file (Transport1)
At first we create the file xpress.opt with content algorithm=barrier which will be
used as solver option file and is stored in the current working directory. Afterwards we
use a GamsOption just like in the preceding example and set GamsOption.opt_file
property to 1 to tell the solver to look for a solver option file.
...
file = open(os.path.join(ws.working_directory, "xpress.opt"), "w")
file.write("algorithm=barrier")
file.close()

opt.opt_file = 1
t1.run(opt)
...
How to use include files (Transport2)
In this example, as in many succeeding, the data text and the model text are separated
into two different strings. Note that these strings accessed via functions get_data_text
and get_model_text are using GAMS syntax.
At first we write an include file tdata.gms that contains the data but not the model text
and save it in our current working directory.
...
file = open(os.path.join(ws.working_directory, "tdata.gms"), "w")
file.write(get_data_text())
file.close()
...
Afterwards we create a GamsJob using the GamsWorkspace.add_job_from_string
A GAMS Python API Tutorial 9
2013 GAMS Development Corporation
method. GamsOptions.defines is used like the 'double dash' GAMS parameters, i.e. it
corresponds to --incname=tdata on the command line where incname is used as
name for the include file in get_model_text as shown below.
...
t2 = ws.add_job_from_string(get_model_text())
opt = ws.add_options()
opt.defines["incname"] = "tdata"
t2.run(opt)
...
The string get_model_text contains the following lines to read in the data.
...
$if not set incname $abort 'no include file name for data file
provided'
$include %incname%
...
How to read data from string and export to GDX (Transport3)
We read the data from the string accessed via function get_data_text. Note that this
contains no model but only data definition in GAMS syntax. By running the corresponding
GamsJob a GamsDatabase is created that is available via the GamsJob.out_db
property. We can use the GamsDatabase.export method to write the content of this
database to a gdx file tdata.gdx in the current working directory.
...
t3 = ws.add_job_from_string(get_data_text())
t3.run()
t3.out_db.export(os.path.join(ws.working_directory, "tdata.gdx"))
...
How to run a job using data from GDX (Transport3)
This works quite similar to the usage of an include file explained in Transport2 - How to
use include files.
...
t3 = ws.add_job_from_string(get_model_text())

opt = ws.add_options()
opt.defines["gdxincname"] = "tdata"
opt.all_model_types = "xpress"
t3.run(opt)
...
Note that there are some minor changes in get_model_text compared to preceding
examples due to the usage of a gdx instead of an include file.
...
$if not set gdxincname $abort 'no include file name for data file
provided'
$gdxin %gdxincname%
A GAMS Python API Tutorial 10
2013 GAMS Development Corporation
$load i j a b d f
$gdxin
...
How to run a job using implicit database communication (Transport3)
This example does basically the same as the two preceding examples together. We
create two GamsJobs t3a and t3b where the first one contains only the data and the
second one contains only the model without data. After running t3a the corresponding
out_db can be read in directly just like a gdx file. Note that the database needs to be
passed to the GamsJob.run method as additional argument.
...
t3a = ws.add_job_from_string(get_data_text())
t3b = ws.add_job_from_string(get_model_text())
t3a.run()
opt.defines["gdxincname"] = t3a.out_db.name
t3b.run(opt, databases=t3a.out_db)
...
How to define data using Python data structures (Transport4)
We use squared brackets (Python Lists) to define the sets and curly brackets (Python
Dictionaries) for the parameter definition.
...
plants = [ "Seattle", "San-Diego" ]
markets = [ "New-York", "Chicago", "Topeka" ]
capacity = { "Seattle": 350.0, "San-Diego": 600.0 }
demand = { "New-York": 325.0, "Chicago": 300.0, "Topeka": 275.0 }
distance = { ("Seattle", "New-York") : 2.5,
("Seattle", "Chicago") : 1.7,
("Seattle", "Topeka") : 1.8,
("San-Diego", "New-York") : 2.5,
("San-Diego", "Chicago") : 1.8,
("San-Diego", "Topeka") : 1.4
}
...
How to prepare a GamsDatabase from Python data structures
(Transport4)
At first we create an empty GamsDatabase db using the
GamsWorkspace.add_database method. Afterwards we prepare the database. To add a
set to the database we use the GamsSet class and the GamsDatabase.add_set method
with arguments describing the identifier, dimension and explanatory text. To add the
records to the database we iterate over the elements of our Python data structure and
add them by using the GamsSet.add_record method.
For parameters the procedure is pretty much the same. Note that the table that specifies
the distances in GAMS can be treated as parameter with dimension 2.
The GamsJob can be run like explained in the preceding example of Transport3 about
implicit database communication.
A GAMS Python API Tutorial 11
2013 GAMS Development Corporation
...
db = ws.add_database()
i = db.add_set("i", 1, "canning plants")
for p in plants:
i.add_record(p)

j = GamsSet(db, "j", 1, "markets")
for m in markets:
j.add_record(m)

a = GamsParameter(db, "a", 1, "capacity of plant i in cases")
for p in plants:
a.add_record(p).value = capacity[p]

b = GamsParameter(db, "b", 1, "demand at market j in cases")
for m in markets:
b.add_record(m).value = demand[m]

d = GamsParameter(db, "d", 2, "distance in thousands of miles")
for k, v in distance.iteritems():
d.add_record(k).value = v

f = GamsParameter(db, "f", 0, "freight in dollars per case per
thousand miles")
f.add_record().value = 90

t4 = GamsJob(ws, source=get_model_text())
opt = GamsOptions(ws)

opt.defines["gdxincname"] = db.name
opt.all_model_types = "xpress"
t4.run(opt, databases = db)
...
How to initialize a GamsCheckpoint by running a GamsJob
(Transport5)
The following lines of code conduct several operations. While the first line simply creates
a GamsCheckpoint, the second one uses the GamsWorkspace.add_job_from_string
method to create a GamsJob containing the model text and data but no solve statement.
Afterwards the run method gets the GamsCheckpoint as argument. That means the
GamsCheckpoint cp captures the state of the GamsJob.
...
cp = ws.add_checkpoint()
t5 = ws.add_job_from_string(get_model_text())
t5.run(checkpoint=cp)
...
How to initialize a GamsJob from a GamsCheckpoint (Transport5)
A GAMS Python API Tutorial 12
2013 GAMS Development Corporation
Note that the string returned from function get_model_text contains the entire model
and data definition plus an additional demand multiplier and scalars for model and solve
status but no solve statement:
...
Scalar bmult demand multiplier /1/;
...
demand(j) .. sum(i, x(i,j)) =g= bmult*b(j) ;
...
Scalar ms 'model status', ss 'solve status';
...
In Transport5 we create a list with eight different values for this demand multiplier.
...
bmultlist = [ 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3 ]
...
For each entry of that list we create a GamsJob t5 using the
GamsWorkspace.add_job_from_string method. Besides another string which resets the
demand multiplier bmult, specifies the solve statement and assigns values to the
scalars ms and ss we pass the checkpoint cp as additional argument. This results in a
GamsJob combined from the checkpoint plus the content provided by the string.
We run the GamsJob and print some interesting data from the out_db.
...
for b in bmultlist:
t5 = ws.add_job_from_string("bmult=" + str(b) + "; solve
transport min z use lp; ms=transport.modelstat;
ss=transport.solvestat;", cp)
t5.run()
print "Scenario bmult=" + str(b) + ":"
print " Modelstatus: " + str(t5.out_db["ms"].find_record
().value)
print " Solvestatus: " + str(t5.out_db["ss"].find_record
().value)
print " Obj: " + str(t5.out_db["z"].find_record().level)
...
NOTE: Some of demand multipliers cause infeasibility. Nevertheless, GAMS keeps the
incumbent objective function value. Therefore the model status and the solve status provide
important information for a correct solution interpretation.
How to run multiple GamsJobs in parallel using a GamsCheckpoint
(Transport6)
This example illustrates how to run the jobs known from Transport5 in parallel. We
initialize the GamsCheckpoint cp and introduce a demand multiplier as we did before :
...
cp = ws.add_checkpoint()
t6 = ws.add_job_from_string(get_model_text())
t6.run(checkpoint=cp)
A GAMS Python API Tutorial 13
2013 GAMS Development Corporation
bmultlist = [ 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3 ]
...
Furthermore, we introduce a lock object io_lock that will be used to avoid mixed up
output from the parallel jobs. We create one scenario for each entry of bmultlist and
cause a thread to begin execution.
...
io_lock = threading.Lock()
threads = {}
for b in bmultlist:
threads[b] = threading.Thread(target=run_scenario, args=(ws,
cp, io_lock, b))
threads[b].start()
for b in bmultlist:
threads[b].join()
...
In function run_scenario a GamsJob t6 is created and run just like in the preceding
example of Transport5. The output section is also the same except for the fact that it is
'locked' by the object io_lock which means that the output section cannot be executed
simultaneously for multiple demand multipliers.
...
def run_scenario(workspace, checkpoint, io_lock, b):
t6 = workspace.add_job_from_string("bmult=" + str(b) + "; solve
transport min z use lp; ms=transport.modelstat;
ss=transport.solvestat;", checkpoint)
t6.run()
# we need to make the ouput a critical section to avoid messed up
report informations
io_lock.acquire()
print "Scenario bmult=" + str(b) + ":"
print " Modelstatus: " + str(t6.out_db["ms"][()].value)
print " Solvestatus: " + str(t6.out_db["ss"][()].value)
print " Obj: " + str(t6.out_db["z"][()].level)
io_lock.release()
...
While the output in Transport5 is strictly ordered subject to the order of the elements of
bmultlist in Transport6 the output blocks might change their order but the blocks
describing one scenario are still appearing together due to the io_lock object.
How to create a GamsModelInstance from a GamsCheckpoint
(Transport7)
In Transport7 the usage of GamsModelInstance is demonstrated.
At first checkpoint cp is created as in the preceding examples. Note that the GamsJob
t7 again contains no solve statement and the demand multiplier is already included with
default value 1. We create the GamsModelInstance mi using the
A GAMS Python API Tutorial 14
2013 GAMS Development Corporation
GamsCheckpoint.add_modelinstance method.
...
cp = ws.add_checkpoint()
t7 = ws.add_job_from_string(get_model_text())
t7.run(checkpoint=cp)
mi = cp.add_modelinstance()
...
How to modify a parameter of a GamsModelInstance using
GamsModifier (Transport7)
A GamsModelInstance uses a sync_db to maintain the data. We define bmult as
GamsParameter using the GamsDatabase.add_parameter method and specify gurobi
as solver. Afterwards the GamsModelInstance is instantiated with 3 arguments, the solve
statement, GamsModifier bmult and GamsOptions opt. The GamsModifier means that
bmult is modifiable while all other parameters, variables and equations of
GamsModelInstance mi stay unchanged. We use the GamsParameter.add_record
method to assign a value to bmult. That value can be varied afterwards using the
GamsParameter.first_record method to reproduce our well-known example with different
demand multipliers.
...
bmult = mi.sync_db.add_parameter("bmult", 0, "demand multiplier")
opt = ws.add_options()
opt.all_model_types = "gurobi"
mi.instantiate("transport use lp min z", GamsModifier(bmult), opt)
bmult.add_record().value = 1.0
bmultlist = [ 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3 ]
for b in bmultlist:
bmult.first_record().value = b
mi.solve()
print "Scenario bmult=" + str(b) + ":"
print " Modelstatus: " + str(mi.model_status)
print " Solvestatus: " + str(mi.solver_status)
print " Obj: " + str(mi.sync_db.get_variable("z").find_record
().level)
...
How to modify a variable of a GamsModelInstance using
GamsModifier (Transport7)
We create a GamsModelInstance using the GamsCheckpoint.add_modelinstance
method. Afterwards we define x as GamsVariable and a GamsParameter xup that will
be used as upper bound for x. At the following instantiate method GamsModifier has 3
arguments. The first one says that x is modifieable, the second determines which part of
A GAMS Python API Tutorial 15
2013 GAMS Development Corporation
the variable (lower bound, upper bound or level) can be modified and the third specifies
the GamsParameter that holds the new value, in this case xup.
In the following loops we set the upper bound of one link of the network to zero, which
means that no transportation between the corresponding plant and market is possible,
and solve the modified transportation problem.
...
mi = cp.add_modelinstance()
x = mi.sync_db.add_variable("x", 2, VarType.Positive)
xup = mi.sync_db.add_parameter("xup", 2, "upper bound on x")
mi.instantiate("transport use lp min z", GamsModifier(x,
UpdateAction.Upper, xup))
mi.solve()
for i in t7.out_db["i"]:
for j in t7.out_db["j"]:
xup.clear()
xup.add_record((i.keys[0],j.keys[0])).value = 0
mi.solve()
print "Scenario link blocked: " + i.keys[0] + " - " +
j.keys[0]
print " Modelstatus: " + str(mi.model_status)
print " Solvestatus: " + str(mi.solver_status)
print " Obj: " + str(mi.sync_db["z"].find_record().level)
...
How to use a queue to solve multiple GamsModelInstances in parallel
(Transport8)
We initialize a GamsCheckpoint cp from a GamsJob. Then we define a list that
represents the different values of the demand multiplier. That list will be used like a
queue where we extract the last element first. The objects list_lock and io_lock are
used later to avoid multiple reading of one demand multiplier and messed up output.
Then we call function scen_solve multiple times in parallel. The number of parallel calls
is specified by nr_workers.
...
cp = ws.add_checkpoint()
t8 = ws.add_job_from_string(get_model_text())
t8.run(checkpoint=cp)
bmult_list = [ 1.3, 1.2, 1.1, 1.0, 0.9, 0.8, 0.7, 0.6 ]
list_lock = threading.Lock()
io_lock = threading.Lock()
# start 2 threads
nr_workers = 2
threads = {}
for i in range(nr_workers):
A GAMS Python API Tutorial 16
2013 GAMS Development Corporation
threads[i] = threading.Thread(target=scen_solve, args=(ws, cp,
bmult_list, list_lock, io_lock))
threads[i].start()
for i in range(nr_workers):
threads[i].join()
...
In function scen_solve we create and instantiate a GamsModelInstance as in the
preceding examples and make parameter bmult modifiable. Note that we chose cplexd
as solver because it is thread safe (gurobi would also be possible).
We have two critical sections that are locked by the objects list_lock and io_lock.
Note that the pop method removes and returns the last element from the list and deletes
it. Once the list is empty the loop terminates.
...
def scen_solve(workspace, checkpoint, bmult_list, list_lock, io_lock):
mi = checkpoint.add_modelinstance()
bmult = mi.sync_db.add_parameter("bmult", 0, "demand multiplier")
opt = ws.add_options()
opt.all_model_types = "cplexd"
# instantiate the GAMSModelInstance and pass a model definition and
GAMSModifier to declare bmult mutable
mi.instantiate("transport use lp min z", GamsModifier(bmult), opt)
bmult.add_record().value = 1.0
while True:
# dynamically get a bmult value from the queue instead of
passing it to the different threads at creation time
list_lock.acquire()
if 0 == len(bmult_list):
list_lock.release()
return
b = bmult_list.pop()
list_lock.release()
bmult.first_record().value = b
mi.solve()
# we need to make the ouput a critical section to avoid messed
up report informations
io_lock.acquire()
print "Scenario bmult=" + str(b) + ":"
print " Modelstatus: " + str(mi.model_status)
print " Solvestatus: " + str(mi.solver_status)
print " Obj: " + str(mi.sync_db.get_variable("z").find_record
().level)
io_lock.release()
...
How to fill a GamsDatabase by reading from MS Access (Transport9)
This example illustrates how to import data from Microsoft Access to a GamsDatabase.
A GAMS Python API Tutorial 17
2013 GAMS Development Corporation
There are a few prerequisites required to run Transport9 successfully.
We import pyodbc which can be downloaded from http://code.google.com/p/pyodbc/.
Note that an architecture mismatch might cause problems. The bitness of your MS
Access, Python, pyodbc and GAMS should be identical.
We call a function read_data_from_access that finally returns a GamsDatabase as
shown below.
...
db = read_from_access(ws)
...
The function read_from_access begins with the creation of an empty database.
Afterwards we set up a connection to the MS Access database transport.accdb which
can be found in [GAMSDIR]\apifiles\Data. To finally read in GAMS sets and
parameters we call the functions read_set and read_parameter that are explained
down below.
...
def read_from_access(ws):
db = ws.add_database()
# connect to database
str_access_conn = 'DRIVER={Microsoft Access Driver (*.mdb,
*.accdb)};DBQ=..\\Data\\transport.accdb'

try:
connection = pyodbc.connect(str_access_conn)
except Exception as ex:
print "Error: Failed to create a database connection. \n
{0}".format(ex)
sys.exit(1)
# read GAMS sets
read_set(connection, db, "SELECT Plant FROM plant", "i", 1,
"canning plants")
read_set(connection, db, "SELECT Market FROM Market", "j", 1,
"markets")
# read GAMS parameters
read_parameter(connection, db, "SELECT Plant,Capacity FROM Plant",
"a", 1, "capacity of plant i in cases")
read_parameter(connection, db, "SELECT Market,Demand FROM Market",
"b", 1, "demand at market j in cases")
read_parameter(connection, db, "SELECT Plant,Market,Distance FROM
Distance", "d", 2, "distance in thousands of miles")

connection.close()
return db
...
A GAMS Python API Tutorial 18
2013 GAMS Development Corporation
The function read_set adds a set to the GamsDatabase that is filled with the data from
the MS Access file afterwards. The function read_parameter works quite similar.
...
def read_set(connection, db, query_string, set_name, set_dim,
set_exp=""):
try:
cursor = connection.cursor()
cursor.execute(query_string)
data = cursor.fetchall()
if len(data[0]) != set_dim:
print "Number of fields in select statement does not match
setDim"
sys.exit(1)

i = db.add_set(set_name, set_dim, set_exp)

for row in data:
keys = []
for key in row:
keys.append(str(key))
i.add_record(keys)
except Exception as ex:
print "Error: Failed to retrieve the required data from the
DataBase.\n{0}".format(ex)
sys.exit(1)
finally:
cursor.close()
...
Once we read in all the data we can create a GamsJob from the GamsDatabase and
run it as usual.
How to fill a GamsDatabase by reading from MS Excel (Transport10)
This example illustrates how to read data from Excel, or to be more specific, from
[GAMSDIR]\apifiles\Data\transport.xls.
At first you have to download an additional package from https://pypi.python.org/pypi/xlrd,
e.g. to C:\Users\[USERNAME]\Downloads. Unzip the package and run the following
code from command line:
cd C:\Users\[Username]\Downloads\xlrd-0.9.2 && python setup.py
install && cd [GAMSDIR]\apifiles\Python
Now you should be able to find the folder xlrd in [PYTHONDIR]\Lib\site-packages
and to run transport10.
In transport10 the model is given as string without data like in many examples before and
the Excel file transport.xls is located at [GAMSDIR]\apifiles\Data.
A GAMS Python API Tutorial 19
2013 GAMS Development Corporation
At first we define the workbook to read from and the different sheet names. To ensure to
have the same number of markets and plants in all spreadsheets, we conduct a little test
that checks for the number of rows and columns. Our workspace is only created if this
test yields no errors.
...
wb = open_workbook("..\\Data\\transport.xls")
capacity = wb.sheet_by_name("capacity")
demand = wb.sheet_by_name("demand")
distance = wb.sheet_by_name("distance")
# number of markets/plants have to be the same in all spreadsheets
assert (distance.ncols-1 == demand.ncols) and (distance.nrows-1 ==
capacity.ncols), \
"Size of the spreadsheets doesn't match"
...
Now we can create a GamsDatabase and read in the data contained in the different
worksheets. We iterate over the columns and read in the set names and the
corresponding parameter values.
...
db = ws.add_database()
i = db.add_set("i", 1, "Plants")
j = db.add_set("j", 1, "Markets")
capacity_param = db.add_parameter("a", 1, "Capacity")
demand_param = db.add_parameter("b", 1, "Demand")
distance_param = db.add_parameter("d", 2, "Distance")
for cx in range(capacity.ncols):
i.add_record(str(capacity.cell_value(rowx=0, colx=cx)))
capacity_param.add_record(str(capacity.cell_value(rowx=0,
colx=cx))).value = capacity.cell_value(rowx=1, colx=cx)

for cx in range(demand.ncols):
j.add_record(str(demand.cell_value(rowx=0, colx=cx)))
demand_param.add_record(str(demand.cell_value(rowx=0,
colx=cx))).value = demand.cell_value(rowx=1, colx=cx)

for cx in range(1, distance.ncols):
for rx in range(1, distance.nrows):
keys = ( str(distance.cell_value(rowx=rx, colx=0)), str
(distance.cell_value(rowx=0, colx=cx)) )
distance_param.add_record(keys).value = distance.cell_value
(rowx=rx, colx=cx)
...
Note that we can name sets and parameters just like in the database but we don't have
to. Now we can run our GamsJob as usual.
...
t10 = ws.add_job_from_string(get_model_text())
A GAMS Python API Tutorial 20
2013 GAMS Development Corporation
opt = ws.add_options()
opt.defines["gdxincname"] = db.name
opt.all_model_types = "xpress"
t10.run(opt, databases=db)
for rec in t10.out_db["x"]:
print "x(" + rec.keys[0] + "," + rec.keys[1] + "): level=" +
str(rec.level) + " marginal=" + str(rec.marginal)
...
How to create and use a save/restart file (Transport11)
In Transport11 we demonstrate how to create and use a save/restart file. Usually such a
file should be supplied by an application provider but in this example we create one for
demonstration purpose. Note that the restart is launched from a GamsCheckpoint.
We create a folder tmp with internal identifier w_dir in the directory we are currently in
(usually [GAMSDIR]\apifiles\Python). This file will be used as working directory later.
From the main function we call the function create_save_restart giving it folder tmp
and the desired name for the save/restart file (tbase) as arguments.
...
w_dir = os.path.join(".", "tmp")
create_save_restart(os.path.join(w_dir, "tbase"));
...
In function create_save_restart we create a workspace with the given working
directory (w_dir refers to tmp). Then we create a GamsJob from a string. Note that the
string given via get_base_model_text contains the basic definitions of sets without
giving them a content (that is what $onempty is used for). Afterwards we specify a
GamsOption to only compile the job but do not execute it. Then we create a checkpoint
cp that is initialized by the following run of the GamsJob and stored in the file given as
argument to the function, in our case tbase. This becomes possible because the
add_checkpoint method accepts identifiers as well as file names as argument.
...
def create_save_restart(cp_file_name):
if len(sys.argv) > 1:
ws = GamsWorkspace(os.path.dirname(cp_file_name), sys.argv[1])
else:
ws = GamsWorkspace(os.path.dirname(cp_file_name))
j1 = ws.add_job_from_string(get_base_model_text())
opt = ws.add_options()

opt.action = Action.CompileOnly
cp = ws.add_checkpoint(os.path.basename(cp_file_name))
j1.run(opt, cp)
del opt
...
So what you should keep in mind before we return to further explanations of the main
function is, that the file tbase is now in the current working directory and contains a
A GAMS Python API Tutorial 21
2013 GAMS Development Corporation
checkpoint that will work exactly like a restart file.
In the main function we define some data using Python data structures as we already did
in Transport4 before we create the GamsWorkspace and a GamsDatabase.
...
if len(sys.argv) > 1:
ws = GamsWorkspace(w_dir, sys.argv[1])
else:
ws = GamsWorkspace(w_dir)
db = ws.add_database()
...
Afterwards we set up the GamsDatabase like we already did in Transport4. Once this is
done we run a GamsJob using this data plus the checkpoint stored in file tbase.
...
cp_base = ws.add_checkpoint("tbase")
t4 = ws.add_job_from_string(get_model_text(), cp_base)
opt = ws.add_options()
opt.defines["gdxincname"] = db.name
opt.all_model_types = "xpress"
t4.run(opt, databases=db)
...
Note that the string from which we create job t11 is different to the one used to prepare
the checkpoint stored in tbase and is only responsible for reading in the data from the
GamsDatabase correctly. The entire model definition is delivered by the checkpoint
cp_base which is equal to the one we saved in tbase.

You might also like