It’s been an exciting year for the GAMSPy community! Since the stable 1.0.0 release, we’ve been hard at work making our favorite optimization modeling package even more powerful, intuitive, and efficient. If you haven’t updated in a while, you’re in for a treat.
This post will walk you through the major enhancements and new features that will improve your workflow. Let’s dive in!
1. A Sleek New CLI with Typer
Your command-line experience just got a major upgrade. We’ve migrated GAMSPy CLI to Typer , a modern and robust framework to build CLI applications.
What does this mean for you?
-
Better Help & Autocompletion: Get where you’re going faster with intelligent tab-completion and clearer help messages for all commands.
-
A Modern Feel: The CLI is now more intuitive and user-friendly.
-
GDX Dump and Diff Utilities: GAMSPy CLI has been extended with a new
gdxapi. You can dump the contents of gdx files withgamspy gdx dump <filename>and compare two gdx files withgamspy gdx diff <file1> <file2>.
Simply run gamspy --help in your terminal to see the difference!
2. Intuitive Access to Records
Accessing the records of your expressions is now more direct. In order to get the results
of an expression, you normally assign it to a symbol with a matching domain and print
symbol.records. You can now use the .records
property of expressions to get a clean
DataFrame directly. GAMSPy will take care of the creation of the intermediate symbol.
import gamspy as gp
m = gp.Container()
i = gp.Set(m, records=['i1', 'i2'])
a = gp.Parameter(m, domain=i, records=[('i1', 1), ('i2', 2)])
b = gp.Parameter(m, domain=i, records=[('i1', 3), ('i2', 4)])
# Old way
c = gp.Parameter(m, domain=i)
c[i] = a[i] + b[i]
print(c.records)
# New, simple way:
print((a[i] + b[i]).records)
This syntax simplifies debugging and data analysis, making your code cleaner and easier to read.
3. Simplified Lag/Lead Syntax
Writing dynamic models with time-series data is now much more natural. We’ve replaced
.lead()
and
.lag()
methods with simple arithmetic operators. This makes your equations look much closer to their mathematical
representation.
Before:
inventory_balance[t] = inventory[t] == inventory[t.lag(1)] + production[t] - sales[t]
After:
inventory_balance[t] = inventory[t] == inventory[t-1] + production[t] - sales[t]
4. Save and Load Your Work with Serialization
Ever wanted to save the state of your model and come back to it later? Now you can! GAMSPy containers can be easily serialized (saved to a file) and deserialized (loaded from a file). This is perfect for checkpointing long-running processes, sharing your model setup, or simply pausing your work.
import gamspy as gp
# Assuming 'm' is your Container object
# Save the entire model state to a file
gp.serialize(m, "serialized.zip")
# Later, you can load it back
m2 = gp.deserialize("serialized.zip")
5. Speed Up Data Loading with Bulk setRecords
Loading data into your symbols just got a lot faster. Instead of calling
setRecords
individually for each symbol, you can now pass a dictionary of symbols and their
records to a single setRecords
call on the container. This significantly reduces overhead for data-heavy models.
import gamspy as gp
m = gp.Container()
i = gp.Set(m)
j = gp.Set(m)
# Pass all records in a single, efficient call
m.setRecords({i: range(5), j: range(5, 10)})
# instead of doing separate calls for each symbol
i.setRecords(range(5))
j.setRecords(range(5, 10))
6. Effortless Summation with .sum()
Here’s another great piece of syntactic sugar. Instead of wrapping a symbol in a Sum
statement, you can now call the .sum()
method directly on the symbol itself. It’s a small change that can make your code cleaner.
Before:
import gamspy as gp
m = gp.Container()
i = gp.Set(m)
j = gp.Set(m)
distances = gp.Parameter(m, domain=[i, j])
total = gp.Parameter(m)
# The classic way
total[...] = gp.Sum((i, j), distances[i, j])
# The new, concise way
total[...] = distances.sum()
The same syntactic sugar is also available for product
,
smin
,
smax
,
sand
and
sor
operations.
7. Configure GAMSPy with Package Options
You now have more control over GAMSPy’s behavior through new package options . This allows you to set global configurations for your sessions, such as skipping validations for performance improvement.
import gamspy as gp
gp.set_options({"SOLVER_OPTION_VALIDATION": 0})
...
...
your_model_definition
...
...
In the example above, we disable solver option validations that GAMSPy performs to make sure that the provided solver options are valid. After you make sure that your model behaves the way you planned, you can just disable the validations to gain extra performance.
8. Automatic Name Inference
With GAMSPy 1.17.0, the behavior of automatic name generation for symbols with no names has changed. For example, if you execute the following code snippet in GAMSPy 1.0.0:
import gamspy as gp
with gp.Container():
i = gp.Set()
print(f"Name of the set: {i.name}")
GAMSPy would generate an autoname that would look like the following:
Name of the set: s0f54b804_41a5_461a_a6b9_69bac2beb72c
With the changes in 1.17.0, GAMSPy now tries to get the Python variable name from the frames in the stack and assign it to the symbol. So, the same code snippet above would now print:
Name of the set: i
Beware that the frames might not always be available (e.g. in certain REPL sessions).
In that case, GAMSPy will still generate a name automatically. This behavior can be
controlled via USE_PY_VAR_NAME
.
9. Rename Symbols When Loading from GDX
Importing data from GDX files is now more flexible. The symbol_names argument of
container.loadRecordsFromGdx
method now accepts a dictionary, allowing you to map GDX symbol names to different
names within your GAMSPy model. This is incredibly useful for avoiding name collisions
or aligning imported data with your existing naming conventions.
import gamspy as gp
m = gp.Container()
model_demand = gp.Parameter(m, "model_demand")
m.loadRecordsFromGdx(
"data.gdx",
symbol_names={"gdx_demand": "model_demand"}
)
10. Performance Boosts
Who doesn’t love a speed-up? We’ve invested significant effort under the hood to make GAMSPy faster and more memory-efficient. Many models will be generated more quickly, letting you iterate on your work faster than ever before. For example, a simple benchmark on indus89.py model shows that there is 31% improvement in the model generation time between GAMSPy 1.0.0 and GAMSPy 1.17.0.
hyperfine -w 3 -r 10 'python indus89.py'
Benchmark 1 (GAMSPy 1.17.0): python indus89.py
Time (mean ± σ): 1.514 s ± 0.029 s [User: 2.900 s, System: 0.157 s]
Range (min … max): 1.484 s … 1.565 s 10 runs
Benchmark 2 (GAMSPy 1.0.0): python indus89.py
Time (mean ± σ): 2.197 s ± 0.067 s [User: 2.671 s, System: 0.097 s]
Range (min … max): 2.078 s … 2.335 s 10 runs
11. Enhancements for Machine Learning Workflows
We’re continuing to bridge the worlds of mathematical optimization and machine learning. Recent updates include:
- Expanded Model Support: We now support embedding a wider range of neural network blocks and constructs, including:
- Neural Networks:
Conv1d,Conv2d,Linear(Dense) ,flatten_dims,TorchSequential, and all pooling layers (MaxPool2d,MinPool2d,AvgPool2d). - Activation Functions: Support for
LeakyReLUand other variants of the ReLU family. - Decision Models: Integration of
DecisionTree,RandomForest, andGradientBoostingmodels.
- Neural Networks:
- Enhanced Performance:
- Improved stability for matrix multiplication, particularly in edge cases.
- Better bound propagation for a variety of blocks, including
Linear,flatten_dims, and all pooling and convolutional layers.
- New Capabilities:
- Support for piecewise linear functions.
- The ability to use singleton sets as domains.
- New examples demonstrating the embedding of feedforward and convolutional neural networks.
import gamspy as gp
import torch.nn as nn
from gamspy.math import dim
m = gp.Container()
model = nn.Sequential(
nn.Conv2d(1, 20, 5), nn.ReLU(), nn.Conv2d(20, 64, 5), nn.ReLU()
)
# load or train your Sequential model
# ...
m = gp.Container()
x = gp.Variable(m, domain=dim([1, 1, 32, 32]))
seq_formulation = gp.formulations.TorchSequential(m, model)
y, eqs = seq_formulation(x) # apply Sequential model to x and get y
print(y.domain) # 1, 64, 24, 24 -> batch, channels, height, width
Conclusion
The latest version of GAMSPy is all about making you more productive. With a slicker CLI, more Pythonic syntax, major performance gains, and powerful new features like serialization, there’s never been a better time to build your optimization models.
We encourage you to upgrade to the latest version
(pip install gamspy --upgrade)
and give these new features a try. Check out our documentation
for more details and
let us know what you think!
Happy modeling!