Performance#
This section describes practical workflows in GAMSPy for improving the performance of your model.
Set-Based Assignments Instead of Python Loops#
One of the most important performance considerations when using GAMSPy is to prefer set-based (vectorized) assignments over explicit Python for loops. Set-based assignments are translated into native GAMS statements and executed inside the GAMS execution engine, which is optimized for bulk operations. In contrast, Python loops generate many small assignments that incur Python overhead and repeated communication with the execution engine.
Simple Example#
Consider computing a parameter over a set i.
Using a Python loop:
import gamspy as gp
m = gp.Container()
i = gp.Set(m, "i", records=range(1000))
a = gp.Parameter(m, domain=i)
b = gp.Parameter(m, domain=i)
b.generateRecords()
for ii in i.toList():
a[ii] = 3 * b[ii]
This loop executes 1,000 individual assignments.
The same operation using a set-based assignment:
a[i] = 3 * b[i]
This generates a single GAMSPy assignment and is significantly faster and more memory-efficient.
Assignments with Conditions#
Set-based assignments can include conditional logic using where.
Using a Python loop:
for ii in i.toList():
if int(ii) % 2 == 0:
a[ii] = b[ii]
Set-based equivalent:
a[i].where[i.val % 2 == 0] = b[i]
The conditional expression is evaluated inside the GAMS engine and avoids branching in Python.
Lagged Assignments#
Some patterns that are often written as loops can still be expressed in a set-based way.
For example, assigning values based on a predecessor element:
Python loop version:
import gamspy as gp
m = gp.Container()
i = gp.Set(m, records=range(1, 10))
a = gp.Parameter(m, domain=i)
a.generateRecords()
f = gp.Parameter(m, domain=i)
for n in i.toList():
if int(n) > 1:
f[n] = a[int(n) - 1] + 5
Set-based version:
f[i].where[gp.Ord(i) > 1] = a[i - 1] + 5
This avoids iteration in Python and lets GAMS handle the indexed operation.
When to Use Python Loops vs. GAMSPy Loops#
Not all logic can or should be converted into set-based assignments. However,
you must carefully choose between native Python loops and the
GAMSPy Loop context manager.
1. When to use the GAMSPy Loop:
If you need to iterate over a domain but cannot express the logic as a single
algebraic assignment, you should execute the loop inside the GAMS engine using
gp.Loop. This is critical for:
Iterative algorithmic calculations.
Modifying parameters and executing model solves repeatedly inside a loop.
2. When to use standard Python loops:
Standard Python for or while loops should not be used for numerical assignments
over large GAMSPy index sets. They are only appropriate for structural generation
before execution, such as:
Building sets or generating records dynamically before loading them into GAMSPy.
Creating GAMSPy symbols (Sets, Parameters, Variables) conditionally based on external configuration.
Performing pure Python control flow that does not interact continuously with the GAMSPy execution engine.
External Numerical Computations with setRecords#
In some cases, neither Python loops nor set-based GAMSPy assignments are ideal. This is especially true for pure numerical computations such as simulations, filters, or cumulative calculations that:
are inherently sequential,
do not require optimization or symbolic manipulation, and
can be efficiently computed using vectorized numerical libraries.
In such cases, it can be advantageous to compute the data using other vectorized libraries such as NumPy and then transfer the results into GAMSPy using setRecords.
Example: Autoregressive Time Series
Consider the recursive process:
for t = 1..T with a given initial value.
Step 1: Compute the Time Series with NumPy
import numpy as np
T = 1000
alpha = 0.8
# Time index
t_values = np.arange(1, T + 1)
# Exogenous input
x = np.ones(T)
# Allocate result array
y = np.zeros(T)
# Initial condition
y[0] = 1.0
# Fast numerical recursion (highly optimized C code)
for i in range(1, T):
y[i] = alpha * y[i - 1] + x[i]
Even though a Python loop is shown here, the heavy lifting is done on contiguous NumPy arrays, which is substantially faster than assigning GAMSPy symbols element by element.
(If desired, this can also be implemented using scipy.signal.lfilter or numba for even higher performance.)
Step 2: Load the Results into GAMSPy
import gamspy as gp
m = gp.Container()
t = gp.Set(m, "t", records=t_values)
y_param = gp.Parameter(m, "y", domain=t)
# Convert to GAMSPy records format
records = list(zip(t_values, y))
y_param.setRecords(records)
At this point, the entire time series is transferred into the GAMS execution engine in one operation.
When to Use This Approach
This pattern is especially useful when:
The computation is purely numerical
The logic is sequential or recursive
Results are used as parameters in a model
No symbolic relationships are needed inside GAMS
Typical examples include:
Time-series simulations
Demand or price forecasts
Pre-computed trajectories
Scenario generation
Monte Carlo paths
Selective Synchronization#
The state synchronization of symbols GAMSPy and the GAMS execution engine can be manipulated to improve performance in certain cases.
GAMSPy by default synchronizes the data of all symbols with the GAMS execution engine. This synchronization while in most cases done
efficiently can cause some performance degradation in some extreme cases and can be temporarily paused by setting .synchronize
property to False. In this state GAMSPy assignment statements will only update the symbols in the GAMS execution enine but not update
the records attribute of the GAMSPy symbol. Similarly, the setRecords method will update the records attribute of the GAMSPy symbols
but will not update the data of the symbol in the GAMS execution engine. For example, while calculating Fibonacci numbers with the GAMS execution engine, it is not
necessary to synchronize the records of symbol f with the GAMSPy records attribute in every iteration.
import gamspy as gp
m = gp.Container()
i = gp.Set(m, 'i', records=range(1000))
f = gp.Parameter(m, 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.
Disabling symbol synchronization should be done with caution because it can cause unexpected results. Here is an example:
import gamspy as gp
m = gp.Container()
f = gp.Parameter(m, records=1)
g = gp.Parameter(m, records=10)
f.synchronize = False
f.setRecords(2)
g[...] = f * g
print(g.records) # this will print g=10 not 20
f[...] = 5
print(f.records) # this will print f=2 not 5
f.synchronize = True
print(f.records) # this will print f=5 since the assignment was the last statement performed on f
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 or some key variables, 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:
model.solve(load_symbols=[])
This would prevent GAMSPy loading the records of symbols. If load_symbols is not specified, records of all symbols will be loaded.
Particular symbols can be loaded as follows:
x = Variable(m)
... # specify your model here
model.solve(load_symbols=[x])
This example would only load the records of x.
Profiling and Execution Engine Memory Consumption#
GAMSPy has several options to allow profiling the instructions performed by the GAMS exeuction engine. The 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 choice
by using profile_file.
monitor_process_tree_memory option allows GAMSPy to record the high-memory mark for the GAMS execution engine
process tree excluding the Python process itself. memory_tick_interval can be used to set the wait interval in milliseconds between checks of the GAMSPy process
tree memory usage.
from gamspy import Container, Options
m = Container(options=Options(profile=1, profile_file="<file_path>", monitor_process_tree_memory=True))
Setting GAMSPy Configurations#
GAMSPy allows setting package wide options via gp.set_options. For example,
one can skip the domain validation by setting DOMAIN_VALIDATION to 0. By default, GAMSPy performs
domain validation which is helpful while writing mathematical models but might add a small overhead
to the execution time.
import gamspy as gp
gp.set_options({"DOMAIN_VALIDATION": 0})
Note
One can also set the system directory via GAMSPY_GAMS_SYSDIR option. Beware that if a system directory
is given in the constructor of the Container, it overrides this option. Package wide options can also
be set via environment variables. Environment variable names are always in the format of GAMSPY_<option_name>.
GAMSPY_DOMAIN_VALIDATION=0 python test.py
Note
Note that package wide options are different than model options. While package wide options affect the behavior of the whole package, model options affect the behavior of the solve process.
Here is a list of package wide options:
Option Name |
Type |
Description |
|---|---|---|
VALIDATION |
int |
Whether to enable all validations of GAMSPy. Set to 1 by default. |
DROP_DOMAIN_VIOLATIONS |
int |
Whether to drop domain violations in the records of a symbol. Set to 0 by default. |
DOMAIN_VALIDATION |
int |
Whether to check for domain validation. Set to 1 by default. |
SOLVER_VALIDATION |
int |
Whether to validate the given solver name. Set to 1 by default. |
SOLVER_OPTION_VALIDATION |
int |
Whether to validate solver options. Set to 1 by default. |
GAMS_SYSDIR |
str |
Path to the GAMS system directory. Set to gamspy_base directory by default. |
MAP_SPECIAL_VALUES |
int |
Map special values. Can be disabled for performance if there are no special values in the records. Set to 1 by default. |
ASSUME_VARIABLE_SUFFIX |
int |
Activates or deactivates the automatic addition of .l or .scale attribute to variables on the right-hand side of assignments. Set to 1 by default. 0: deactivate, 1: use .l attribute, 2: use .scale attribute. |
USE_PY_VAR_NAME |
str |
“no”: Do not try to use the Python variable name as the GAMSPy symbol name. “yes”: Try using the variable name as the symbol name. If the variable name is not a valid GAMSPy symbol name, raise ValidationError. “yes-or-autogenerate”: Try using the variable name as the symbol name. If the name is not a valid symbol name or if it already exists in the container autogenerate a new name. (default value for this option). |
ALLOW_AMBIGUOUS_EQUATIONS |
str |
“auto”: Do not allow ambiguous equations in MCP, EMP, MPEC, and RMPEC models but allow them in other model types. “no”: Do not allow ambiguous equations in any model types. “yes”: Allow ambiguous equations in all model types. |
Warning
GAMSPy validations are essential during development. Setting VALIDATION to 0 should only be done to improve the performance by skipping the validation steps after you are convinced that your model works as intended.