Expression and Indexing#

Set-based Indexing and Literal Indexing#

Set-based indexing is at the core of the GAMSPy and GAMS execution system. They are concise, easy to read and have great performance. Therefore, we encourage the use of it in most contexts. Yet, in certain cases, one might be inclined to do literal indexing (with a str or an int). Because of that GAMSPy also allows literal indexing.

m = gp.Container()
i = gp.Set(m, records=['i1', 'i2'])
a = gp.Parameter(m, domain=i, records=[('i1', 1), ('i2', 2)])
a[i] = 5 # set-based indexing that sets all records of a to 5
import gamspy as gp

m = gp.Container()

i = gp.Set(m, records=['i1', 'i2'])
a = gp.Parameter(m, "a", domain=i, records=[('i1', 1), ('i2', 2)])
a['i1'] = 5 # literal indexing with a string that sets element 'i1' to 5
a['i2'] = 6 # literal indexing with a string that sets element 'i2' to 6

j = gp.Set(m, records=range(5))
j[1] = False # literal indexing with an integer that removes element 1 from the set.

Slices and Ellipsis#

GAMSPy supports NumPy-like indexing including the usage of Slices and Ellipsis. An ellipsis expands to the number of colon (:) objects needed for the selection tuple to index all dimensions. There may only be a single ellipsis present.

Usage of Slices#

Here is an example that shows how slices can be used:

import gamspy as gp

m = gp.Container()
i = gp.Set(m, name="i")
j = gp.Set(m, name="j")
d = gp.Parameter(m, domain=[i, j])
c = gp.Parameter(m, domain=[i, j])
c[:, :] = 90 * d[:, :] / 1000

Each : refers to the corresponding domain item in that index for the symbol. In this example, the first : is i and the second : is j. Hence, it is equivalent to:

import gamspy as gp
m = gp.Container()
i = gp.Set(m, name="i")
j = gp.Set(m, name="j")
d = gp.Parameter(m, domain=[i, j])
c = gp.Parameter(m, domain=[i, j])
c[i, j] = 90 * d[i, j] / 1000

Usage of Ellipsis#

Here is an example that shows how ellipsis can be used:

import gamspy as gp

m = gp.Container()
i = gp.Set(m, name="i")
j = gp.Set(m, name="j")
d = gp.Parameter(m, domain=[i, j])
c = gp.Parameter(m, domain=[i, j])
c[...] = 90 * d[...] / 1000

This is also equivalent to:

import gamspy as gp

m = gp.Container()
i = gp.Set(m, name="i")
j = gp.Set(m, name="j")
d = gp.Parameter(m, domain=[i, j])
c = gp.Parameter(m, domain=[i, j])
c[i, j] = 90 * d[i ,j] / 1000

For scalar symbols (symbols with no domain), slice and ellipsis means the same thing:

import gamspy as gp

m = gp.Container()
c = gp.Parameter(m)
c[...] = 90
# or
c[:] = 90

Expression#

GAMSPy lazily executes expressions to improve performance. Therefore, whenever you express an operation on GAMSPy symbols (addition, multiplication etc.), GAMSPy generates an expression instead of executing it right away. For example:

import gamspy as gp

m = gp.Container()
a = gp.Parameter(m, "a")
b = gp.Parameter(m, "b")
print(a + b)

would print an expression:

Expression(left=Parameter(name='a', domain=[]), data=+, right=Parameter(name='b', domain=[]))

As you can see in the output, each expression has a left operand, right operand and an operator. In this example, left and right operands are parameters and the operator is an addition operator. If one wants to evaluate the result of the expression, they can directly call .records on it.

import gamspy as gp

m = gp.Container()
a = gp.Parameter(m, "a", records=5)
b = gp.Parameter(m, "b", records=10)
print((a + b).records)

This would return:

   value
0   15.0

For scalar expressions such as the one above, one can also call .toValue to get the value directly. instead of getting a DataFrame as a result.

import gamspy as gp

m = gp.Container()
a = gp.Parameter(m, "a", records=5)
b = gp.Parameter(m, "b", records=10)
print((a + b).toValue())

This would return 15 as a float directly.

For indexed expressions, one can call .toList to get the result as a list of values.

import numpy as np
import gamspy as gp

m = gp.Container()
i = gp.Set(m, "i", records=range(2))
a = gp.Parameter(m, "a", domain=i, records=np.array([3, 5]))
b = gp.Parameter(m, "b", domain=i, records=np.array([2, 4]))
print((a + b).toList())

This would return the values of the expression as a list as follows:

[['0', 5.0], ['1', 9.0]]

Chained Expressions#

Expressions can be arbitrarily long depending on your definition/assignment statement. For example:

import gamspy as gp

m = gp.Container()
t = gp.Set(m, "t", records=range(3))
price = gp.Parameter(m, "price", domain=t, records=np.array([1, 2, 3]))
buy = gp.Parameter(m, "buy", domain=t, records=np.array([1, 2, 3]))
sell = gp.Parameter(m, "sell", domain=t, records=np.array([2, 3, 4]))
stock = gp.Parameter(m, "stock", domain=t, records=np.array([4, 1, 5]))
storecost = gp.Parameter(m, "storecost", records=5)
result = gp.Parameter(m, "result", domain=t)
expr = price[t] * (buy[t] - sell[t]) + storecost * stock[t]
print(expr)

This would result in four nested expressions. Instead of executing each executing eagerly, GAMSPy prepares the expression tree until it’s needed. For example, if we you need to see the result of the expression, then GAMSPy lets GAMS to run the expression and return the result.

print(expr.records)

The output would look like as follows:

   t  value
0  0   19.0
1  1    3.0
2  2   22.0