Debugging and Performance#

Specifying a Debugging Level#

By default, GAMSPy will delete all temporary files generated by GAMS if there are no errors in the execution. In order to keep the temporary files, the debugging_level parameter can be specified as keep. keep_on_error is the default behaviour. This debug level keeps the temporary files only if there are errors in the execution. delete can be used to delete all temporary files even if there are errors.

from gamspy import Container
m = Container(debugging_level="keep")
....
....
....
specify your model here
....
....
....
model.solve()
print(m.working_directory)

In this example, you keep your working directory in the temporary directory in your operating system. The temporary directories for Linux, Darwin, and Windows are usually /tmp, /var/tmp, and C:\\Users\\<username>\\AppData\\Local\\Temp respectively. You can see the path for your model’s temporary files by printing <container>.working_directory.

keep value for debugging_level also generates each .gms, .lst and .log files separately which might be useful for debugging your model.

Note

If one specifies a working directory, setting debugging_level to delete or keep_on_errors has no effect. This behaviour is intentional to avoid potential problems with deleting the working_directory. For example, if the user specifies working_directory=”/” on Linux or “C:" and debugging level as “delete”, we don’t want to nuke the whole system.

Specifying the Working Directory#

Alternatively, you can specify a working_directory to keep the temporary files generated by GAMS.

from gamspy import Container
m = Container(working_directory=".")
....
....
....
specify your model here
....
....
....
model.solve()

In this example, specifying the working directory as the current directory causes temporary GAMS files (.gms, .lst, .gdx files etc.) to be saved in the current directory. Be aware that unless the debugging_level is set to keep, each GAMS call will override the previous .gms file.

Generating a Listing File#

If one is only interested in the listing file that is generated after the solve statement, they can specify the path for the lst file through gamspy.Options().

from gamspy import Container, Options
m = Container()
....
....
....
specify your model here
....
....
....
model.solve(options=Options(listing_file="<path_to_the_listing_file>.lst"))

Generating a GDX File#

Sometimes, it might be useful to generate a GDX file which contains the records of the solved model. The savepoint can be enabled through gamspy.Options(). Check GAMS Options to learn about the meaning of all savepoint values.

from gamspy import Container, Options
m = Container()
....
....
....
specify your model here
....
....
....
model.solve(options=Options(savepoint=1))

Redirecting Output and Generating a Log File#

The output of GAMS can be redirected to the standard output or to a file by specifying the handle for the destination. For example:

import sys
from gamspy import Container
m = Container(working_directory=".")
....
....
....
specify your model here
....
....
....
model.solve(output=sys.stdout)

The code snippet above redirects the GAMS execution output to your console by specifying the output as standard output. You can also redirect the output to a file:

import sys
from gamspy import Container
m = Container(working_directory=".")
....
....
....
specify your model here
....
....
....
with open("my_output.txt", "w") as log:
    model.solve(output=log)

This code snippet redirects the output of the execution to a file named “mylog.txt”.

If you want to redirect GAMS logs to a file, log_file option can be provided.

import sys
from gamspy import Container
m = Container(working_directory=".")
....
....
....
specify your model here
....
....
....
model.solve(options=Options(log_file="my_log_file.txt"))

This code snippet would generate a log file in the specified working directory. The log can also be redirected to both a file and the console simultaneously.

import sys
from gamspy import Container
m = Container(working_directory=".")
....
....
....
specify your model here
....
....
....
model.solve(output=sys.stdout, options=Options(log_file="my_log_file.txt"))

This code snippet would redirect the output to your console as well as saving the log file in your working directory.

Inspecting Generated GAMS String#

Another alternative is to use the generateGamsString function. This function returns the GAMS code generated up to that point as a string.

from gamspy import Container
m = Container()
....
....
....
print(m.generateGamsString())
....
....
....

By default, generateGamsString returns exactly the same string that is executed but show_raw argument allows users to see only the raw model without any data or dollar calls or other necessary statements to make the model work.

For example, the following code snippet:

from gamspy import Container, Set
m = Container()
i = Set(m, "i")
j = Set(m, "j")
print(m.generateGamsString(show_raw=True))

generates:

Set i(*);
Set j(*);

Without show_raw argument, the following string would be generated:

$onMultiR
$onUNDF
$gdxIn <gdx_in_file_name>
Set i(*);
$loadDC i
$offUNDF
$gdxIn
$onMultiR
$onUNDF
$gdxIn <gdx_in_file_name>
Set j(*);
$loadDC j
$offUNDF
$gdxIn

To see the generated GAMS declaration for a certain symbol, getDeclaration function can be utilized.

from gamspy import Container, Set
m = Container()
i = Set(m, "i", records=['i1', 'i2'])
print(i.getDeclaration())

The code snippet above prints the GAMS statement for the symbol i:

'Set i(*);'

To see the generated GAMS definition for a certain symbol, getDefinition function can be utilized.

from gamspy import Sum, Container, Set, Parameter, Variable, Equation

m = Container()
i = Set(m, "i", records=['i1','i2'])
a = Parameter(m, 'a', domain=[i], records=[['i1','1'], ['i2','2']])
z = Variable(m, 'z')

eq = Equation(m, name="eq")
eq[...] = Sum(i, a[i]) <= z
print(eq.getDefinition())

The code snippet above prints the GAMS statement for the symbol i:

'eq .. sum(i,a(i)) =l= z;'

Inspecting the Generated Equations and Variables#

The user may determine whether the model generated is the the model that the user has intended by studying the equation listing and variable listing. For more information about how this can be done, see Inspecting Generated Equations and Inspecting Generated Variables.

Inspecting Misbehaving (Infeasible) Models#

Infeasibility is always a possible outcome when solving models. Infeasibilities in a model can be calculated by using gamspy.Model.computeInfeasibilities(). This would list the infeasibilities in all equations of the model. Infeasibilities in a single equation as well as infeasibilities in a single variable can be computed with gamspy.Equation.computeInfeasibilities(), gamspy.Variable.computeInfeasibilities() respectively. The infeasibilities are computed by finding the distance of level to the nearest bound (i.e. lower bound or upper bound). While the computeInfeasibilities function of a model returns a dictionary where keys are the names of the equations and values are the infeasibilities as Pandas DataFrames, computeInfeasibilities function of a variable or an equation, returns a Pandas dataframe with infeasibilities.

from gamspy import Container
m = Container()
....
....
....
specify your model here
....
....
....
model.solve()
print(model.computeInfeasibilities())

Causes of infeasibility are not always easily identified. Solvers may report a particular equation as infeasible in cases where an entirely different equation is the cause. In these kind of complicated cases, one can dump all variables and equations in the listing file and inspect it. In the worst case, the solver returns no solution (model status 19: Infeasible - No Solution), leaving the variable levels untouched after a solve.

from gamspy import Container, Options
m = Container()
....
....
....
specify your model here
....
....
....
model.solve(options=Options(equation_listing_limit=100, variable_listing_limit=100, listing_file="<path_to_the_listing_file>.lst"))

The level attribute of the variables used in the model determine the equation level and the Equation Listing in the listing file show potential infeasibilities using the INFES marker.

The solver-dependent methods for dealing with infeasibilities can be used by providing solver_options. For example, you can turn on the conflict refiner of CPLEX solver also known as IIS finder if the problem is infeasible by providing a solver option.

from gamspy import Container, Options
m = Container()
....
....
....
specify your model here
....
....
....
model.solve(options=Options(solver="CPLEX", solver_options={"iis": 1}))

An automated approach offered in GAMS/Cplex is known as FeasOpt (for Feasible Optimization). It can be turned on by providing the FeasOpt argument in solver_options, which turns feasible relaxation on.

from gamspy import Container, Options
m = Container()
....
....
....
specify your model here
....
....
....
model.solve(options=Options(solver="CPLEX", solver_options={"FeasOpt": 1}))

There are also facilities of other solvers such as BARON, COPT, Gurobi, Lindo etc. which can be enabled in the same way. To see all facilities, refer to the solver manuals.

Selective Synchronization#

The state synchronization of symbols between GAMS and GAMSPy can be manipulated to improve performance in certain cases. GAMSPy by default synchronizes all declared symbols with GAMS state but symbols can be excluded from this synchronization on demand by setting .synchronize property to False. For example, while calculating Fibonacci numbers, it is not necessary to synchronize the records of symbol f with GAMS in every iteration.

m = gp.Container()
i = gp.Set(m,'i',records=[item for item in range(1000)])
f = gp.Parameter(m, 'f', domain=i)
f['0'] = 0
f['1'] = 1

f.synchronize = False
for n in range(2,1000):
    f[str(n)] = f[str(n-2)] + f[str(n-1)]
f.synchronize = True
print(f.records)

By disabling the synchronization of f, the state of f is synchronized with GAMS only at the end of the loop instead of 999 times.

Selective Loading on Solve#

One can pick and choose the symbols that will be updated with new records on a solve statement. For certain models where you are only interested in the objective value, loading the records of all symbols is unnecessary. In order to increase the performance by avoiding the load of symbols, one can specify load_symbols which are list of symbol objects. For example, in order to not load any of the symbol’s records, you can do the following:

from gamspy import Container, Options
m = Container()
....
....
....
specify your model here
....
....
....
model.solve(load_symbols=[])

This would prevent from loading the records of symbols. If load_symbols are not specified, records of all symbols will be loaded. Particular symbols can be loaded as follows:

from gamspy import Container, Options
m = Container()
....
....
....
x = Variable(m, "x")
specify your model here
....
....
....
model.solve(load_symbols=[x])

This example would only load the records of x.

Profiling#

GAMSPy has several options to allow profiling of the executed GAMS codes. profile option controls whether an execution profile will be generated in the listing file. Alternatively, profiling information can be directred to a file of your choosing by using profile_file. monitor_process_tree_memory option allows GAMS to record the high-memory mark for the GAMS process tree. memory_tick_interval can be used to set the wait interval in milliseconds between checks of the GAMS process tree memory usage.

from gamspy import Container, Options
m = Container()
....
....
....
specify your model here
....
....
....
model.solve(options=Options(profile_file="<file_path>", monitor_process_tree_memory=True))