Tutorial

The goal of this tutorial is to provide a compact overview of the basic functionality of the GAMS C# 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.

Getting Started

This section takes you through the basic steps of creating and configuring a C# project in Visual Studio 2010 for use with the GAMS C# API. At the end of this section there is also a paragraph about Mono as an alternative to Microsoft Visual Studio.

Open a new Project

After opening Microsoft Visual Studio you can open the New Project wizard by choosing File > New > Project from the ribbon menu and type a name for your application, e.g. GAMSApplication.

Add a Reference to a Project

Once you opened the project add a reference by a right click on References inside the solution explorer. Then choose Add Reference.. and select the Browse tab. Next navigate to the location where you installed GAMS.net4.dll, typically the GAMS system directory, e.g. C:\GAMS\win64\48.3. Then choose GAMS.net4.dll and click OK.

Solving your first Model

This section illustrates how a model from the GAMS model library can be imported and solved.

Open a new project and reference the GAMS.net4.dll in your project as explained above. Open Program.cs which should be part of your project and replace the content by:

using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using GAMS;
namespace TransportSeq
{
class Transport0
{
static void Main(string[] args)
{
ws.GamsLib("trnsport");
// create a GAMSJob from file and run it with default settings
GAMSJob t0 = ws.AddJobFromFile("trnsport.gms");
t0.Run();
Console.WriteLine("Ran with Default:");
foreach (GAMSVariableRecord rec in t0.OutDB.GetVariable("x"))
Console.WriteLine("x(" + rec.Keys[0] + "," + rec.Keys[1] + "): level=" + rec.Level + " marginal=" + rec.Marginal);
Console.WriteLine("z=" + t0.OutDB.GetVariable("z").LastRecord().Level);
}
}
}
GAMSVariable GetVariable(string variableIdentifier)
GAMSDatabase OutDB
void Run(GAMSOptions gamsOptions=null, GAMSCheckpoint checkpoint=null, TextWriter output=null, Boolean createOutDB=true)
new GAMSVariableRecord LastRecord()
GamsWorkspace ws

Then click the highlighted button from figure (a) down below or press F5 to start debugging. You can also start the program without debugging by pressing Control + F5 or choosing Debug > Start Without Debugging from the ribbon menu as illustrated in figure (b).

Figure (a)

Figure(b)

By following the latter instruction the command line shell remains open and show the produced output.

Mono

The GAMS .NET API works also on non-Windows platforms where no Visual Studio is available. It can be used for example with the open source .Net framework Mono (http://www.mono-project.com). Mono offers a IDE that could be used similarly to Visual Studio. If one prefers to compile and execute from a shell, the following code shows how this can be done using the example Transport1 which comes with the GAMS system (this assumes that xbuild and mono are in the PATH):

cd <GAMS Dir>/apifiles/CSharp/Transport1
xbuild /t:rebuild /p:Configuration=Release Transport1.csproj
mono bin/Release/Transport1.exe ../../..
GamsInteractive p
Transport t

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.

How to use API

In the GAMS system directory there are some examples provided that illustrate the usage of the C# API. <GAMS system directory>\apifiles\CSharp contains a file TransportSeq.sln that can be opened in Microsoft Visual Studio. The contained projects deal 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 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. When running the examples, we can provide an additional command line argument in order to define the GAMS system directory that should be used. By executing Transport1.exe with C:/GAMS/win64/48.3 we use the 64-bit version of GAMS 48.3 to run Transport1 even if our default GAMS system might be a different one. This is managed by the following code:

...
GAMSWorkspace ws;
if (Environment.GetCommandLineArgs().Length > 1)
ws = new GAMSWorkspace(systemDirectory: Environment.GetCommandLineArgs()[1]);
else
ws = new GAMSWorkspace();
...

Remember that the bitness of the GAMS system has to match the bitness of your .NET program.

How to export data to GDX (TransportGDX)

Although the Object-oriented .NET API offers much more than exchanging data between .NET and GDX, a common use case is the export and import of GDX files. The central class for this purpose is GAMSDatabase. We assume that the data to be exported is available in .NET data structures.

...
List<string> plants = new List<string>()
{
"Seattle", "San-Diego"
};
List<string> markets = new List<string>()
{
"New-York", "Chicago", "Topeka"
};
Dictionary<string, double> capacity = new Dictionary<string, double>()
{
{ "Seattle", 350.0 }, { "San-Diego", 600.0 }
};
Dictionary<string, double> demand = new Dictionary<string, double>()
{
{ "New-York", 325.0 }, { "Chicago", 300.0 }, { "Topeka", 275.0 }
};
Dictionary<Tuple<string, string>, double> distance = new Dictionary<Tuple<string, string>, double>()
{
{ new Tuple<string,string> ("Seattle", "New-York"), 2.5 },
{ new Tuple<string,string> ("Seattle", "Chicago"), 1.7 },
{ new Tuple<string,string> ("Seattle", "Topeka"), 1.8 },
{ new Tuple<string,string> ("San-Diego", "New-York"), 2.5 },
{ new Tuple<string,string> ("San-Diego", "Chicago"), 1.8 },
{ new Tuple<string,string> ("San-Diego", "Topeka"), 1.4 }
};
...
GamsWorkspace demand
dict capacity
list markets
dict distance
list plants

Different GAMS symbols are represented using different .NET data structures. The data for the GAMS sets is represented using lists of strings (e.g. plants and markets). On the other hand, GAMS parameters are represented by dictionaries (e.g. capacity and demand). Note that the representation of the two dimensional parameter distance uses tuples for storing the keys. The choice of data structures can also be different, but the used structures in this example fit well for representing GAMS data with .NET data structures.

A new GAMSDatabase instance can be created using GAMSWorkspace.AddDatabase.

...
// prepare a GAMSDatabase with data from the C# data structures
GAMSDatabase db = ws.AddDatabase();
...
GamsWorkspace db

We start adding GAMS sets using the method GAMSDatabase.AddSet which takes the name and the dimension as arguments. The third argument is an optional explanatory text. A foreach-loop iterates through plants and adds new records to the recently created GAMSSet instance i using GAMSSet.AddRecord.

...
// add 1-dimensional set 'i' with explanatory text 'canning plants' to the GAMSDatabase
GAMSSet i = db.AddSet("i", 1, "canning plants");
foreach (string p in plants)
i.AddRecord(p);
...
GamsWorkspace i

GAMSParameter instances can be added by using the method GAMSDatabase.AddParameter. It has the same signature as GAMSDatabase.AddSet. Anyhow, in this example we use an overload of the method which takes a list of GAMSSet instances instead of the dimension for creating a parameter with domain information.

...
// add parameter 'a' with domain 'i'
GAMSParameter a = db.AddParameter("a", "capacity of plant i in cases", i);
foreach (string p in plants)
a.AddRecord(p).Value = capacity[p];
...
GamsWorkspace a

As soon as all data is prepared in the GAMSDatabase, the method GAMSDatabase.Export can be used to create a GDX file.

...
// export the GAMSDatabase to a GDX file with name 'data.gdx' located in the 'workingDirectory' of the GAMSWorkspace
db.Export("data.gdx");
...

How to import data from GDX (TransportGDX)

Data can be imported from a GDX file using GAMSWorkspace.AddDatabaseFromGDX. The method takes a path to a GDX file and creates a GAMSDatabase instance.

...
// add a new GAMSDatabase and initialize it from the GDX file just created
GAMSDatabase db2 = ws.AddDatabaseFromGDX("data.gdx");
...
GamsWorkspace db2

Reading the data from the GAMSSet i into a list can be done as follows:

...
// read data from symbols into .NET data structures
List<string> plants2 = new List<string>();
foreach (GAMSSetRecord item in db2.GetSet("i"))
plants2.Add(item.Key(0));
...
string Key(int index)

A new list plants2 is created. i is retrieved by calling GAMSDatabase.GetSet on db2. The returned GAMSSet object can be iterated using a foreach-loop to access the records of the set. Each record is of type GAMSSetRecord and can be asked for its keys.

You can do the same for GAMSParameters. Instead of creating a list, we want to have the data in the form of a dictionary. GAMSParameterRecords can not only be asked for their keys, but also for their value. The following code snippet shows how to read the one dimensional parameter a into a Dictionary<string, double>.

...
Dictionary<string, double> capacity2 = new Dictionary<string, double>();
foreach (GAMSParameterRecord item in db2.GetParameter("a"))
capacity2.Add(item.Key(0), item.Value);
...

For multi dimensional symbols, we choose the dictionary keys to be tuples instead of string.

...
Dictionary<Tuple<string, string>, double> distance2 = new Dictionary<Tuple<string, string>, double>();
foreach (GAMSParameterRecord item in db2.GetParameter("d"))
distance2.Add(new Tuple<string, string>(item.Key(0), item.Key(1)), item.Value);
...

How to run a GAMSJob from file (Transport1)

Here we load the model trnsport from the GAMS Model Library. In doing so it is made available in the current working directory and can be loaded by the GAMSWorkspace.AddJobFromFile Method afterwards. Apparently this method also works 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 is defined from that file and run by the GAMSJob.Run method.

The following lines create the solution output and illustrate the usage of the GAMSJob.OutDB property to get access to the GAMSDatabase created by the Run method. To retrieve the content of variable x we use the GAMSVariableRecord class and the GAMSDatabase.GetVariable method.

...
ws.GamsLib("trnsport");
// create a GAMSJob from file and run it with default settings
GAMSJob t1 = ws.AddJobFromFile("trnsport.gms");
t1.Run();
Console.WriteLine("Ran with Default:");
foreach (GAMSVariableRecord rec in t1.OutDB.GetVariable("x"))
Console.WriteLine("x(" + rec.Keys[0] + "," + rec.Keys[1] + "): level=" + rec.Level + " marginal=" + rec.Marginal);
...

How to specify the solver (Transport1)

The solver can be specified via the GAMSOptions class and the GAMSWorkspace.AddOptions method. The GAMSOptions.AllModelTypes property sets xpress as default solver for all model types which the solver can handle.

...
// run the job again with another solver
using (GAMSOptions opt = ws.AddOptions())
{
opt.AllModelTypes = "xpress";
t1.Run(opt);
}
...

How to run a job with a solver option file and capture its log output (Transport1)

At first we use the StreamWriter to create the file xpress.opt which will be used as solver option file and is stored in the current working directory. Then we write algorithm=barrier to this file and specify the barrier algorithm as algorithm before we close the solver option file. We choose xpress as solver just like in the preceding example and set the GAMSOptions.OptFile Property to 1 to tell the solver to look for a solver option file. In addition, we specify the argument output in order to write the log of the GAMSJob into the file transport1_xpress.log.

...
using (StreamWriter optFile = new StreamWriter(Path.Combine(ws.WorkingDirectory, "xpress.opt")))
using (TextWriter logFile = new StreamWriter(Path.Combine(ws.WorkingDirectory, "transport1_xpress.log")))
using (GAMSOptions opt = ws.AddOptions())
{
optFile.WriteLine("algorithm=barrier");
optFile.Close();
opt.AllModelTypes = "xpress";
opt.OptFile = 1;
t1.Run(opt, output: logFile);
}
...

Instead of writing the log output to a file, it can be written into any TextWriter. In order to write the log directly to Console.Out, we can use the following code:

...
t1.Run(opt, output: Console.Out);
...

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 GetDataText and GetModelText are using GAMS syntax.

At first we write an include file tdata.gms that contains the data but not the model text:

...
using (StreamWriter writer = new StreamWriter(ws.WorkingDirectory + Path.DirectorySeparatorChar + "tdata.gms"))
{
writer.Write(GetDataText());
}
...

Afterwards we create a GAMSJob using the GAMSWorkspace.AddJobFromString method. The GAMSOptions.Defines field is used like the 'double dash' GAMS parameters, i.e. it corresponds to --incname=tdata on the command line.

...
using (GAMSOptions opt = ws.AddOptions())
{
GAMSJob t2 = ws.AddJobFromString(GetModelText());
opt.Defines.Add("incname", "tdata");
t2.Run(opt);
...
}
...
GamsWorkspace opt

Note that the string GetModelText 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 GetDataText as we did in the preceding example. Note that this contains no solve statement but only data definition in GAMS syntax. By running the corresponding GAMSJob a GAMSDatabase is created that is available via the GAMSJob.OutDb property. We can use the GAMSDatabase.Export method to write the content of this database to a gdx file tdata.gdx.

...
GAMSJob t3 = ws.AddJobFromString(GetDataText());
t3.Run();
t3.OutDB.Export(ws.WorkingDirectory + Path.DirectorySeparatorChar + "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 (Transport2).

...
t3 = ws.AddJobFromString(GetModelText());
using (GAMSOptions opt = ws.AddOptions())
{
opt.Defines.Add("gdxincname", "tdata");
opt.AllModelTypes = "xpress";
t3.Run(opt);
...
}
...

Note that there are some changes in GetModelText due to the usage of a GDX file instead of an include file.

...
$if not set gdxincname $abort 'no include file name for data file provided'
$gdxin %gdxincname%
$load i j a b d f
$gdxin
...
dict d
GamsSet j
GamsWorkspace b
GamsWorkspace f

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 OutDB 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.

...
using (GAMSOptions opt = ws.AddOptions())
{
GAMSJob t3a = ws.AddJobFromString(GetDataText());
GAMSJob t3b = ws.AddJobFromString(GetModelText());
t3a.Run();
opt.Defines.Add("gdxincname", t3a.OutDB.Name);
opt.AllModelTypes = "xpress";
t3b.Run(opt, t3a.OutDB);
...
}
...

How to define data using C# data structures (Transport4)

We use the List<T> class, the Dictionary<TKey, TValue> class and the Tuple<T1, T2> class to define C# data structures that correspond to the sets, parameters and tables used for the data definition in GAMS.

...
List<string> plants = new List<string>()
{
"Seattle", "San-Diego"
};
List<string> markets = new List<string>()
{
"New-York", "Chicago", "Topeka"
};
Dictionary<string, double> capacity = new Dictionary<string, double>()
{
{ "Seattle", 350.0 }, { "San-Diego", 600.0 }
};
Dictionary<string, double> demand = new Dictionary<string, double>()
{
{ "New-York", 325.0 }, { "Chicago", 300.0 }, { "Topeka", 275.0 }
};
Dictionary<Tuple<string,string>, double> distance = new Dictionary<Tuple<string,string>, double>()
{
{ new Tuple<string,string> ("Seattle", "New-York"), 2.5 },
{ new Tuple<string,string> ("Seattle", "Chicago"), 1.7 },
{ new Tuple<string,string> ("Seattle", "Topeka"), 1.8 },
{ new Tuple<string,string> ("San-Diego", "New-York"), 2.5 },
{ new Tuple<string,string> ("San-Diego", "Chicago"), 1.8 },
{ new Tuple<string,string> ("San-Diego", "Topeka"), 1.4 }
};
...

How to prepare a GAMSDatabase from C# data structures (Transport4)

At first we create an empty GAMSDatabase db using the GAMSWorkspace.AddDatabase method. Afterwards we prepare the database. To add a set to the database we use the GAMSSet class and the GAMSDatabase.AddSet method with arguments describing the identifier, dimension and explanatory text. To add the records to the database we iterate over the elements of our C# data structure and add them by using the GAMSSet.AddRecord 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 about implicit database communication.

...
GAMSDatabase db = ws.AddDatabase();
GAMSSet i = db.AddSet("i", 1, "canning plants");
foreach (string p in plants)
i.AddRecord(p);
...
GAMSParameter a = db.AddParameter("a", 1, "capacity of plant i in cases");
foreach (string p in plants)
a.AddRecord(p).Value = capacity[p];
...
GAMSParameter d = db.AddParameter("d", 2, "distance in thousands of miles");
foreach(Tuple<string,string> t in distance.Keys)
d.AddRecord(t.Item1,t.Item2).Value = distance[t];
GAMSParameter f = db.AddParameter("f", 0, "freight in dollars per case per thousand miles");
f.AddRecord().Value = 90;
// run a job using data from the created GAMSDatabase
GAMSJob t4 = ws.AddJobFromString(GetModelText());
using (GAMSOptions opt = ws.AddOptions())
{
opt.Defines.Add("gdxincname", db.Name);
opt.AllModelTypes = "xpress";
t4.Run(opt, db);
...
}
...

How to initialize a GAMSCheckpoint by running a GAMSJob (Transport5)

The following two lines of code conduct several operations. While the first line simply creates a GAMS Checkpoint, the second one uses the GAMSWorkspace.AddJobFromString method to create a GAMSJob containing the model text and data but no solve statement. In contrast to the preceding examples it runs the job immediately using the GAMSJob.Run method. Furthermore, it passes an additional checkpoint argument to the Run method. That means the GAMSCheckpoint cp captures the state of the GAMSJob.

...
GAMSCheckpoint cp = ws.AddCheckpoint();
...
ws.AddJobFromString(GetModelText()).Run(cp);
...
GamsWorkspace cp

This creates the same checkpoint as for example the following code snippet:

GAMSCheckpoint cp = ws.AddCheckpoint();
GAMSJob t5a = ws.AddJobFromString(GetModelText());
t5a.Run(cp);

How to initialize a GAMSJob from a GAMSCheckpoint (Transport5)

Note that the string returned from function GetModelText() 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';
...
GamsWorkspace bmult
GamsWorkspace x

We create a list with eight different values for this demand multiplier.

double[] bmultlist = new double[] { 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3 };
list bmultlist

For each entry of that list we create a GAMSJob t5 using the GAMSWorkspace.AddJobFromString method. Besides the 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 echo some interesting data from the OutDB using the GAMSDatabase.GetParameter and GAMSDatabase.GetVariable methods, the GAMSParameter.FindRecord and GAMSVariable.FindRecord methods plus the GAMSParameterRecord.Value property and the GAMSVariableRecord.Level property.

...
foreach (double b in bmultlist)
{
GAMSJob t5 = ws.AddJobFromString("bmult=" + b + "; solve transport min z use lp; ms=transport.modelstat; ss=transport.solvestat;", cp);
t5.Run();
Console.WriteLine("Scenario bmult=" + b + ":");
Console.WriteLine(" Modelstatus: " + t5.OutDB.GetParameter("ms").FindRecord().Value);
Console.WriteLine(" Solvestatus: " + t5.OutDB.GetParameter("ss").FindRecord().Value);
Console.WriteLine(" Obj: " + t5.OutDB.GetVariable("z").FindRecord().Level);
}
...
GAMSParameter GetParameter(string parameterIdentifier)
new GAMSParameterRecord FindRecord(params string[] keys)
new GAMSVariableRecord FindRecord(params string[] keys)
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 :

...
GAMSCheckpoint cp = ws.AddCheckpoint();
ws.AddJobFromString(GetModelText()).Run(cp);
double[] bmultlist = new double[] { 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3 };
...

Furthermore, we introduce a new object ioMutex that will be used to avoid mixed up output from the parallel jobs. For each element b from the list of demand multipliers we call a delegate of the function RunScenario. Note that these calls are parallel!

...
// run multiple parallel jobs using the created GAMSCheckpoint
Object ioMutex = new Object();
System.Threading.Tasks.Parallel.ForEach(bmultlist, delegate(double b) { RunScenario(ws, cp, ioMutex, b); });
...

In function RunScenario a GAMSJob 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 marked as critical section by the lock keyword. That means the delegates of RunScenario are running in parallel but the output block of different delegates cannot be executed in parallel since it is 'locked' by the same object ioMutex for all delegates.

...
static void RunScenario(GAMSWorkspace ws, GAMSCheckpoint cp, object ioMutex, double b)
{
GAMSJob t6 = ws.AddJobFromString("bmult=" + b + "; solve transport min z use lp; ms=transport.modelstat; ss=transport.solvestat;", cp);
t6.Run();
// we need to make the ouput a critical section to avoid messed up report information
lock (ioMutex)
{
Console.WriteLine("Scenario bmult=" + b + ":");
Console.WriteLine(" Modelstatus: " + t6.OutDB.GetParameter("ms").FindRecord().Value);
Console.WriteLine(" Solvestatus: " + t6.OutDB.GetParameter("ss").FindRecord().Value);
Console.WriteLine(" Obj: " + t6.OutDB.GetVariable("z").FindRecord().Level);
}
}
...
Lock lock

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 lock keyword.

If you want a further impression of the impact of the lock keyword, just rerun Transport6 but comment out the lock as follows and compare the output.

...
//lock (ioMutex)
//{
Console.WriteLine("Scenario bmult=" + b + ":");
Console.WriteLine(" Modelstatus: " + t6.OutDB.GetParameter("ms").FindRecord().Value);
Console.WriteLine(" Solvestatus: " + t6.OutDB.GetParameter("ss").FindRecord().Value);
Console.WriteLine(" Obj: " + t6.OutDB.GetVariable("z").FindRecord().Level);
//}
...

How to create a GAMSModelInstance from a GAMSCheckpoint (Transport7)

In Transport7 the usage of GAMS::GAMSModelInstance is demonstrated.

At first checkpoint cp is created as in the preceding examples. Then we create the GAMSModelInstance mi using the GAMSCheckpoint.AddModelInstance method. Note that the GAMSJob again contains no solve statement and the demand multiplier is already included with default value 1.

...
GAMSCheckpoint cp = ws.AddCheckpoint();
GAMSJob t7 = ws.AddJobFromString(GetModelText());
t7.Run(cp);
GAMSModelInstance mi = cp.AddModelInstance();
...
GamsWorkspace mi

How to modify a parameter of a GAMSModelInstance using GAMSModifier (Transport7)

A GAMSModelInstance uses a SyncDB to maintain the data. We define bmult as GAMSParameter using the GAMSDatabase.AddParameter method and specify gurobi as solver. Afterwards the GAMSModelInstance is instantiated with arguments opt and GAMSModifier bmult. The GAMSModifier means that bmult is modifiable while all other parameters, variables and equations of ModelInstance mi stay unchanged. We use the GAMSParameter.AddRecord method to assign a value to bmult that can be varied afterwards using the GAMSParameter.FirstRecord method to reproduce our well-known example with different demand multipliers.

...
GAMSParameter bmult = mi.SyncDB.AddParameter("bmult", 0, "demand multiplier");
GAMSOptions opt = ws.AddOptions();
opt.AllModelTypes = "gurobi";
mi.Instantiate("transport use lp min z", opt, new GAMSModifier(bmult));
bmult.AddRecord().Value = 1.0;
double[] bmultlist = new double[] { 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3 };
foreach (double b in bmultlist)
{
bmult.FirstRecord().Value = b;
mi.Solve();
Console.WriteLine("Scenario bmult=" + b + ":");
Console.WriteLine(" Modelstatus: " + mi.ModelStatus);
Console.WriteLine(" Solvestatus: " + mi.SolveStatus);
Console.WriteLine(" Obj: " + mi.SyncDB.GetVariable("z").FindRecord().Level);
}
...

How to modify a variable of a GAMSModelInstance using GAMSModifier (Transport7)

We create a GAMSModelInstance just like in the next to last example. We define x as GAMSVariable and its upper bound as GAMSParameter xup. At the following instantiate method GAMSModifier has three arguments. The first one says that x is modifiable, the second determines which part of the variable (lower bound, upper bound or level) can be modified and the third specifies the GAMSParameter that holds the new value.

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.AddModelInstance();
GAMSVariable x = mi.SyncDB.AddVariable("x", 2, VarType.Positive, "");
GAMSParameter xup = mi.SyncDB.AddParameter("xup", 2, "upper bound on x");
mi.Instantiate("transport use lp min z", modifiers: new GAMSModifier(x,UpdateAction.Upper,xup));
foreach (GAMSSetRecord i in t7.OutDB.GetSet("i"))
foreach (GAMSSetRecord j in t7.OutDB.GetSet("j"))
{
xup.Clear();
xup.AddRecord(i.Keys[0],j.Keys[0]).Value = 0;
mi.Solve();
Console.WriteLine("Scenario link blocked: " + i.Keys[0] + " - " + j.Keys[0]);
Console.WriteLine(" Modelstatus: " + mi.ModelStatus);
Console.WriteLine(" Solvestatus: " + mi.SolveStatus);
Console.WriteLine(" Obj: " + mi.SyncDB.GetVariable("z").FindRecord().Level);
}
...
GAMSSet GetSet(string setIdentifier)
UpdateAction
GamsWorkspace xup

How to use a queue to solve multiple GAMSModelInstances in parallel (Transport8)

We initialize a GAMSCheckpoint cp from a GAMSJob. Then we define a queue that represents the different values of the demand multiplier. A queue follows the first-in-first-out-principle. The objects queueMutex and ioMutex are used later to avoid messed up output. Then we call two delegates of the function ScenSolve in parallel that get the same queue as argument.

...
GAMSCheckpoint cp = ws.AddCheckpoint();
ws.AddJobFromString(GetModelText()).Run(cp);
Queue<double> bmultQueue = new Queue<double>(new double[] { 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3 });
Object queueMutex = new Object();
Object ioMutex = new Object();
Parallel.For(0, 2, delegate(int i) { ScenSolve(ws, cp, bmultQueue, queueMutex, ioMutex); });
...

In function ScenSolve we create and instantiate a GAMSModelInstance as in the preceding examples and make bmult modifiable. The two delegates of the function extract the elements of the queue using bmultQueue.Dequeue. Note that we chose cplex as solver because it is thread safe (gurobi would also be possible). Once the queue is empty the loop terminates.

private static void ScenSolve(GAMSWorkspace ws, GAMSCheckpoint cp, Queue<double> bmultQueue, Object queueMutex, Object ioMutex, int i)
{
GAMSModelInstance mi = cp.AddModelInstance();
GAMSParameter bmult = mi.SyncDB.AddParameter("bmult", 0, "demand multiplier");
GAMSOptions opt = ws.AddOptions();
opt.AllModelTypes = "cplex";
mi.Instantiate("transport use lp min z", opt, new GAMSModifier(bmult));
bmult.AddRecord().Value = 1.0;
while (true)
{
double b;
// dynamically get a bmult value from the queue instead of passing it to the different threads at creation time
lock (queueMutex)
{
if(0 == bmultQueue.Count)
return;
b = bmultQueue.Dequeue();
}
bmult.FirstRecord().Value = b;
mi.Solve();
// we need to make the output a critical section to avoid messed up report informations
lock (ioMutex)
{
Console.WriteLine("Scenario bmult=" + b + ":");
Console.WriteLine(" Modelstatus: " + mi.ModelStatus);
Console.WriteLine(" Solvestatus: " + mi.SolveStatus);
Console.WriteLine(" Obj: " + mi.SyncDB.GetVariable("z").FindRecord().Level);
}
}
}

How to fill a GAMSDatabase by reading from MS Access (Transport9)

This example illustrates how to import data from Microsoft Access to a GAMSDatabase. We call a function ReadFromAccess that finally returns a GAMSDatabase as shown below.

...
GAMSDatabase db = ReadFromAccess(ws);
...

The data we are going to read can be found in <GAMS system directory>\apifiles\Data\transport.accdb. It might be helpful to open this file for a better understanding. The function begins with the creation of an empty GAMSDatabase. Afterwards we connect to the MS Access database by first specifying the provider and then defining the aforementioned file as data source. The OleDbConnection represents an open connection to a data source. The following lines initialize the connection and use a try and a catch block for potential exceptions, i.e. errors that occur during application execution. To finally read in GAMS sets and parameters we call the functions ReadSet and ReadParameter. We will use several classes of the System.Data.OleDb namespace that are documented here.

static GAMSDatabase ReadFromAccess(GAMSWorkspace ws)
{
GAMSDatabase db = ws.AddDatabase();
// connect to database
string strAccessConn = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=..\..\..\..\Data\transport.accdb";
OleDbConnection connection = null;
try
{
connection = new OleDbConnection(strAccessConn);
}
catch (Exception ex)
{
Console.WriteLine("Error: Failed to create a database connection. \n{0}", ex.Message);
Environment.Exit(1);
}
// read GAMS sets
ReadSet(connection, db, "SELECT Plant FROM Plant", "i", 1, "canning plants");
ReadSet(connection, db, "SELECT Market FROM Market", "j", 1, "markets");
// read GAMS parameters
ReadParameter(connection, db, "SELECT Plant,Capacity FROM Plant", "a", 1, "capacity of plant i in cases");
ReadParameter(connection, db, "SELECT Market,Demand FROM Market", "b", 1, "demand at market j in cases");
ReadParameter(connection, db, "SELECT Plant,Market,Distance FROM Distance", "d", 2, "distance in thousands of miles");
return db;
}

The function ReadSet incorporates a try and a catch block. In the try block we prepare the reading from the MS Access file. Then we add a set symbol to the GAMSDatabase that is filled with the data from the MS Access file afterwards. The function ReadParameter works quite similar.

static void ReadSet(OleDbConnection connect, GAMSDatabase db, string strAccessSelect, string setName, int setDim, string setExp = "")
{
try
{
OleDbCommand cmd = new OleDbCommand(strAccessSelect, connect);
connect.Open();
OleDbDataReader reader = cmd.ExecuteReader();
if (reader.FieldCount != setDim)
{
Console.WriteLine("Number of fields in select statement does not match setDim");
Environment.Exit(1);
}
GAMSSet i = db.AddSet(setName, setDim, setExp);
string[] keys = new string[setDim];
while (reader.Read())
{
for (int idx = 0; idx < setDim; idx++)
keys[idx] = reader.GetString(idx);
i.AddRecord(keys);
}
}
catch (Exception ex)
{
Console.WriteLine("Error: Failed to retrieve the required data from the DataBase.\n{0}", ex.Message);
Environment.Exit(1);
}
finally
{
connect.Close();
}
}
tuple keys

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 <GAMS system directory>\apifiles\Data\transport.xlsx. The model is given as string without data like in in many examples before. At first we have to add

using Excel = Microsoft.Office.Interop.Excel;

to the preamble to be able to use the Microsoft.Office.Interop.Excel namespace. Then we define excelApp as Excel application using the implicit variable type var, use the aforementioned file as workbook wb and define the Excel range that can represent a cell, a row, a column, a selection of cells containing one or more contiguous blocks of cells, or a 3-D range.

...
var excelApp = new Excel.Application();
Excel.Workbook wb = excelApp.Workbooks.Open(Directory.GetCurrentDirectory() + @"\..\..\..\..\Data\transport.xlsx");
Excel.Range range;
...
load_workbook wb

The following lines address the different worksheets and read in the contained data. Afterwards we make an errorcheck to ensure that the number of plants and markets is the same in all worksheets.

...
Excel.Worksheet capacity = (Excel.Worksheet)wb.Worksheets.get_Item("capacity");
range = capacity.UsedRange;
Array capacityData = (Array)range.Cells.Value;
int iCount = capacity.UsedRange.Columns.Count;
Excel.Worksheet demand = (Excel.Worksheet)wb.Worksheets.get_Item("demand");
range = demand.UsedRange;
Array demandData = (Array)range.Cells.Value;
int jCount = range.Columns.Count;
Excel.Worksheet distance = (Excel.Worksheet)wb.Worksheets.get_Item("distance");
range = distance.UsedRange;
Array distanceData = (Array)range.Cells.Value;
// number of markets/plants have to be the same in all spreadsheets
Debug.Assert((range.Columns.Count - 1) == jCount && (range.Rows.Count - 1) == iCount,
"Size of the spreadsheets doesn't match");
wb.Close();
...

If you have problems to see through the steps above, adding the following output section right after wb.Close() might be helpful to understand where which data is stored.

...
for (int ii = 1; ii <= iCount; ii++)
{
Console.WriteLine("capacityData(1, " + ii + ") = " + capacityData.GetValue(1, ii));
Console.WriteLine("capacityData(2, " + ii + ") = " + capacityData.GetValue(2, ii));
}
for (int jj = 1; jj <= jCount; jj++)
{
Console.WriteLine("demandData(1, " + jj + ") = " + demandData.GetValue(1, jj));
Console.WriteLine("demandData(2, " + jj + ") = " + demandData.GetValue(2, jj));
}
for (int ii = 1; ii <= iCount; ii++)
{
for (int jj = 1; jj <= jCount; jj++)
Console.WriteLine("distanceData("+ (ii+1) + "," + (jj+1) +") = " + distanceData.GetValue(ii+1, jj+1));
}
...
GamsWorkspace ii

Now we can create the GAMSWorkspace as usual and afterwards create a GAMSDatabase and fill it with the workbook data as follows:

...
GAMSDatabase db = ws.AddDatabase();
GAMSSet Set1 = db.AddSet("i", 1, "Plants");
GAMSSet j = db.AddSet("j", 1, "Markets");
GAMSParameter capacityParam = db.AddParameter("a", 1, "Capacity");
GAMSParameter demandParam = db.AddParameter("b", 1, "Demand");
GAMSParameter distanceParam = db.AddParameter("d", 2, "Distance");
for (int ic = 1; ic <= iCount; ic++)
{
Set1.AddRecord((string)capacityData.GetValue(1, ic));
capacityParam.AddRecord((string)capacityData.GetValue(1, ic)).Value = (double)capacityData.GetValue(2, ic);
}
for (int jc = 1; jc <= jCount; jc++)
{
j.AddRecord((string)demandData.GetValue(1, jc));
demandParam.AddRecord((string)demandData.GetValue(1, jc)).Value = (double)demandData.GetValue(2, jc);
for (int ic = 1; ic <= iCount; ic++)
{
distanceParam.AddRecord((string)distanceData.GetValue(ic + 1, 1), (string)distanceData.GetValue(1, jc + 1)).Value = (double)distanceData.GetValue(ic + 1, jc + 1);
}
}
...
new GAMSParameterRecord AddRecord(params string[] keys)
new GAMSSetRecord AddRecord(params string[] keys)

Note that we can name sets and parameters just like in the database but we don't have to. Now we finally set up the database and can run our GAMSJob as usual.

...
using (GAMSOptions opt = ws.AddOptions())
{
GAMSJob t10 = ws.AddJobFromString(GetModelText());
opt.Defines.Add("gdxincname", db.Name);
opt.AllModelTypes = "xpress";
t10.Run(opt, db);
foreach (GAMSVariableRecord rec in t10.OutDB.GetVariable("x"))
Console.WriteLine("x(" + rec.Keys[0] + "," + rec.Keys[1] + "): level=" + rec.Level + " marginal=" + 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.

In the main function we start with the creation of a folder called tmp that internally is denoted as wDir. Then we call the function CreateSaveRestart.

...
string wDir = Path.Combine(".", "tmp");
CreateSaveRestart(Path.Combine(wDir, "tbase"));
...

In function CreateSaveRestart we choose the path of the file given as argument as working directory, so it should be wDir. Then we create a GAMSJob from a string. Note that the string given via GetBaseModelText() 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. This becomes possible because the AddCheckpoint method accepts identifiers as well as file names as argument.

static void CreateSaveRestart(string cpFileName)
{
if (Environment.GetCommandLineArgs().Length > 1)
ws = new GAMSWorkspace(workingDirectory: Path.GetDirectoryName(cpFileName), systemDirectory: Environment.GetCommandLineArgs()[1]);
else
ws = new GAMSWorkspace(workingDirectory: Path.GetDirectoryName(cpFileName));
GAMSJob j1 = ws.AddJobFromString(GetBaseModelText());
GAMSOptions opt = ws.AddOptions();
opt.Action = GAMSOptions.EAction.CompileOnly;
GAMSCheckpoint cp = ws.AddCheckpoint(Path.GetFileName(cpFileName));
j1.Run(opt, cp);
opt.Dispose();
}

So what you should keep in mind before we return to further explanations of the main function is, that now the file tbase in folder tmp which is denoted as wDir contains a checkpoint. Now in the main function we define some data using C# data structures as we already did in Transport4 before we create the GAMSWorkspace with working directory wDir.

...
GAMSWorkspace ws;
if (Environment.GetCommandLineArgs().Length > 1)
ws = new GAMSWorkspace(workingDirectory: wDir, systemDirectory: Environment.GetCommandLineArgs()[1]);
else
ws = new GAMSWorkspace(workingDirectory: wDir);
...

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.

...
GAMSCheckpoint cpBase = ws.AddCheckpoint("tbase");
using (GAMSOptions opt = ws.AddOptions())
{
GAMSJob t4 = ws.AddJobFromString(GetModelText(), cpBase);
opt.Defines.Add("gdxincname", db.Name);
opt.AllModelTypes = "xpress";
t4.Run(opt, db);
foreach (GAMSVariableRecord rec in t4.OutDB.GetVariable("x"))
Console.WriteLine("x(" + rec.Keys[0] + "," + rec.Keys[1] + "): level=" + rec.Level + " marginal=" + rec.Marginal);
}
...

Note that the string from which we create job t4 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 cpBase which is equal to the one we saved in tbase.