GAMSPy for GAMS Users#

This document is for GAMS users who are interested in translating their existing GAMS models to GAMSPy or vice versa.

Translating Symbols#

The way to create symbols such as Set, Alias, Parameter, Variable, and Equation is explained in their respective documentation pages and you can see an example for the creation of each symbol below:

import gamspy as gp

m = gp.Container()

i = gp.Set(m, 'i', records=['i1','i2'])
a = gp.Alias(m, 'a', alias_with=i)
p = gp.Parameter(m, 'p', domain=i, records=[['i1','1'], ['i2','2']])
v = gp.Variable(m, 'v', domain=i)
z = gp.Variable(m, 'z')
e = gp.Equation(m, 'e', domain=i)
e[i] = v[i] + p[i] <= z
my_model = gp.Model(m, equations=[e], problem="lp", sense="min", objective=z)
my_model.solve()
Set i / i1, i2 /;
Alias (i, a);
Parameter p(i) / i1 1, i2 2 /;
Variable v(i), z;
Equation e(i);
e(i) .. v(i) + p(i) =l= z
Model my_model / e /;
solve my_model using LP min z;

Translating Operations: Sum/Product/Smin/Smax#

Frequently used GAMS operations which accept an index list and an expression can be translated as follows:

import gamspy as gp

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

eq = gp.Equation(m, name="eq")
eq[...] = gp.Sum(i, a[i]) <= z
Set i / i1, i2 /;
Parameter a(i) / i1 1, i2 2 /;
Variable z;
Equation eq;
eq .. sum(i, a(i)) =l= z;

Card/Ord#

Card and Ord operations can be translated as follows:

import gamspy as gp
import math

m = gp.Container()
i = gp.Set(m, name='i', records=[f'i{i}' for i in range(181)])
step = gp.Parameter(m, name="step", records=math.pi / 180)
omega = gp.Parameter(m, name="omega", domain=i)
omega[i] = (gp.Ord(i) - 1) * step
Set i / i0*i180 /;
Parameter step;
step = pi / 180;
Parameter omega(i);
omega(i) = (ord(i) - 1) * step;

Domain#

This class is exclusively for conditioning on a domain with more than one set.

import gamspy as gp

m = gp.Container()
bus = gp.Set(m, name="bus", records=[f"i{b}" for b in range(1, 7)])
node = gp.Alias(m, name="node", alias_with=bus)
conex = gp.Set(m, name="conex", domain=[bus, bus])

branch = gp.Parameter(m,"branch", [bus, node, gp.UNIVERSE] ,records=...)

p = gp.Parameter(m, name="p")

conex[bus, node].where[branch[bus, node, "x"]] = True
conex[bus, node].where[conex[node, bus]] = True

p[...] = gp.Smax(
    gp.Domain(bus, node).where[conex[bus, node]],
    branch[bus, node, "bij"] * 3.14 * 2,
)
Set bus / i1*i6 /;
Alias (bus, node);
Set conex(bus, bus);

Parameter branch(bus, node, "*") / ...... /;
Parameter p;

conex(bus, node)$(branch(bus, node, "x")) = yes;
conex(bus, node)$(conex(node, bus)) = yes;

p = smax((bus, node) $ (conex(bus, node)), branch(bus, node, "bij") * 3.14 * 2)

Number#

This is for conditions on numbers or yes/no statements.

import gamspy as gp

m = gp.Container()
i = gp.Set(m, "i", records=range(1,5))
ie = gp.Set(m, "ie", domain=i)
x = gp.Variable(m, "x", domain=i)
ie[i] = gp.Number(1).where[x.lo[i] == x.up[i]]
Set i / 1*4 /;
Set ie(i);
Variable x(i);
ie(i) = yes$(x.lo(i) = x.up(i));

math package#

This package is for the mathematical operations of GAMS.

import gamspy as gp
import gamspy.math as gams_math
import random

m = gp.Container()
i = gp.Set(m, "i", records=['i1', 'i2'])
k = gp.Set(m, "k", records=['k1', 'k2'])
sigma = gp.Variable(m, name="sigma", domain=[i, k], type="Positive")
sigma.l[i, k] = gams_math.uniform(0.1, 1) # Generates a different value from uniform distribution for each element of the domain.
print(sigma.records)
sigma.l[i, k] = random.uniform(0.1, 1) # This is not equivalent to the statement above. This generates only one value for the whole domain.
print(sigma.records)
Set i / i1, i2 /;
Set k / k1, k2 /;
Positive Variable sigma(i,k);
sigma.l(i,k) = uniform(0.1, 1);
display sigma.l;

Logical Operations#

Since it is not possible in Python to overload keywords such as and, or, xor, and not, you need to use the bitwise operators &, |, ^, and ~. In GAMSPy these operators perform logical, not bitwise, operations.

Mapping:

  • and -> &

  • or -> |

  • xor -> ^

  • not -> ~

error01[s1, s2] = rt[s1, s2] & (~lfr[s1, s2]) | ((~rt[s1, s2]) & lfr[s1, s2])
error01(s1,s2) = rt(s1,s2) and not lfr(s1,s2) or not rt(s1,s2) and lfr(s1,s2);

Exponentiation#

The Python operator ** is not directly translated to the GAMS operator **. In GAMS, x ** y is equivalent to rPower(x,y), which is calculated internally as \(e^{y \log(x)}\) and therefore triggers an error if x is negative. GAMSPy instead translates ** to the GAMS function that fits the exponent, so that it stays defined wherever it is mathematically defined:

Mapping:

  • integral exponent, e.g. x ** 3 or x ** 3.0 -> power(x,3), which accepts a negative x

  • x ** 0.5 -> sqrt(x)

  • any other exponent, e.g. x ** 2.5`, but in particular any GAMSPy symbol, e.g. ``x ** p[i] results in rPower

import gamspy as gp

m = gp.Container()
x = gp.Parameter(m, "x", records=-2)
y = gp.Parameter(m, "y")
y[...] = x**2
print(y.toValue())  # 4.0
scalar x /-2/, y;
y = power(x,2);
display y;

Squaring a negative term with x ** 2 is a well-known pitfall in GAMS: the assignment above evaluates to UNDF if it is written as y = x**2;, and in an equation it triggers an evaluation error and most likely aborts the solve as soon as an iterate turns negative. Since the exponent is an integer, GAMSPy generates power(x,2) and the model solves.

Note

An exponent that is merely close to an integer is not rounded to one. x ** 2.9998 translates to rPower(x,2.9998), matching GAMS x ** 2.9998, and is an error for a negative x.

sqr, power and rpower are available in the math package if you prefer to be explicit.

If you would rather have no discrepancy at all, the package wide option STRICT_POWER_OPERATOR makes ** generate rPower for every exponent, which is exactly what GAMS does:

import gamspy as gp

gp.set_options({"STRICT_POWER_OPERATOR": 1})

m = gp.Container()
x = gp.Parameter(m, "x", records=-2)
y = gp.Parameter(m, "y")
y[...] = x**2   # rPower(x,2) -> the same GAMS error you would get in GAMS

Note

The option only selects the GAMS function that ** maps to. It does not affect sqr, power or rpower when you call them yourself, and GAMSPy’s own formulations such as vector_norm keep generating the same GAMS code either way.

Sparse Assignments#

The GAMS sparse assignment operator $= is expressed with sparse on the right-hand side of the assignment.

rho[i] = gp.sparse(sig[i])
rho(i) $= sig(i);

Translating GAMS Macros#

Macros in GAMS can be translated to GAMSPy as functions. The following example shows how GAMS Macro reciprocal can be defined as a function in Python to be used in GAMSPy:

import gamspy as gp

def reciprocal(y):
    return 1 / y

m = gp.Container()
z = gp.Parameter(m, "z")
x1 = gp.Parameter(m, "x1", records=2)
x2 = gp.Parameter(m, "x2", records=3)
z[:] = reciprocal(x1) + reciprocal(x2)
print(z.records)
$macro reciprocal(y) 1/y

scalar z, x1 /2/, x2 /3/;

z = reciprocal(x1) + reciprocal(x2);
display z;

Automatic Conversion of a GAMSPy Model to GAMS#

Existing GAMSPy models can be translated to a GAMS model automatically by using Model.toGams:

import gamspy as gp

m = gp.Container()
i = gp.Set(m, "i", records=["i1", "i2"])
a = gp.Parameter(m, "a", domain=i)
...
...
your_model = gp.Model(m, "your_model", equations=...)
...
...

your_model.toGams(path=<gams_model_path>)

The generated GAMS model can be found under <gams_model_path>/<model_name>.gms. If you also want to generate a pf file that contains the necessary option, you can provide it with options argument. If you want to dump the GAMS state, you can also set dump_gams_state to True.