Parameter#

Introduction#

One very important principle will motivate all our discussions on data:

Note

Data should be entered in its simplest form, and each data item should only be entered once.

This principle is adopted for two main reasons: Numbers are likely to change, and when they do, it’s important to make the process of updating them as easy and safe as possible. We also want to make our model easy for others to read and understand. Keeping the amount of data as small as possible will certainly help. The reader should always be kept aware of all the assumptions made during data manipulation in order to be able to reproduce the results of a study.

This chapter deals with the data type parameter.

Scalars#

A GAMSPy parameter with zero dimensionality or emtpy domain (domain=[]) is called a scalar. This means that there are no associated sets, so there is exactly one number associated with the parameter:

from gamspy import Container, Parameter

m = Container()
rho = Parameter(m, "rho", records=0.15)
irr = Parameter(m, "irr")
life = Parameter(m, "life", records=20)

The statement above initializes rho and life, but not irr. GAMSPy will automatically initalize the value to 0. Assignment statement or the setRecords function can be used to provide a value for a scalar:

irr[...] = 0.07
irr.setRecords(0.08)

Note

Assignment statements to scalar parameters need the ellipsis literal [...].

Parameters#

Higher dimensional GAMSPy parameters are indexed over one or more sets. The data records in the contructor, the addParameter function, or the setRecords function needs to be in a list-like format. The GAMS Transfer Python documentation gives many examples including lists, pandas dataframe, numpy matrix, etc.

The Syntax#

The following example illustrates the parameter statement

from gamspy import Container, Set, Parameter

m = Container()
j = Set(m, "j", description="markets")
dd = Parameter(
    m,
    name="dd",
    domain=j,
    description="distribution of demand",
    domain_forwarding=True,
    records=[["mexico-df", 55], ["guadalajara", 15]],
)

The class Parameter indicates that this is a parameter statement and name = "dd" is the internal name of the parameter in GAMSPy, it is an identifier. If the name is not provided, it will be autogenerated. A parameter may be defined over one or more sets that may be specified in the domain list. The domain specification for the parameter dd means that there will be a vector of data associated with it, one number corresponding to every member of the set j. The optional description is used to describe the parameter.

The Parameter initialization requires a list of data elements, each consisting of a label or label-tuple and a value. The label is an element of the defining set or - if there is more than one defining set - a combination of the elements of the defining sets. The referenced set elements must belong to the set that the parameter is indexed over. Finally, the value assigned to the record defined by the set element or element tuple.

Besides using the Parameter class directly, one can also facilitate the addParameter method of the Container class:

dd = m.addParameter(
    "dd",
    domain=j,
    description="distribution of demand",
    domain_forwarding=True,
    records=[["mexico-df", 55], ["guadalajara", 15]],
)

Parameter Data for Higher Dimensions#

A parameter may have several dimensions. GAMSPy parameters can have up to 20 dimensions. The list oriented data initialization through the parameter statement can be easily extended to data of higher dimensionality. The label that appears in the one-dimensional case is replaced by a tuple of labels for higher dimensions. Just like in the case of Multi-Dimensional Sets, the elements in the \(n\)-tuple can be created using various Python structures:

from gamspy import Container, Set, Parameter

m = Container()
i = Set(m, "i")
j = Set(m, "j")
a = Parameter(
    m,
    domain=[i, j],
    domain_forwarding=True,
    records=[
        ("seattle", "new-york", 2.5),
        ("seattle", "chicago", 1.7),
        ("seattle", "topeka", 1.8),
        ("san-diego", "new-york", 2.5),
        ("san-diego", "chicago", 1.8),
        ("san-diego", "topeka", 1.4),
    ],
)
In [1]: a.records
Out[1]:
            i              j        value
0     seattle       new-york          2.5
1     seattle        chicago          1.7
2     seattle         topeka          1.8
3   san-diego       new-york          2.5
4   san-diego        chicago          1.8
5   san-diego         topeka          1.4

It is also possible to define an empty parameter at declaration and fill it with data (e.g. from other sources like databases or spreadsheets) later on using the setRecords method of the Parameter class:

b = Parameter(m, domain=[i, j])
b.setRecords(a.records)

The Assignment Statement#

The assignment statement is the fundamental data manipulation statement in GAMSPy. It may be used to define or alter values associated with sets, variables, parameters or equations.

Scalar Assignments#

Consider the following artificial sequence:

x = Parameter(m, "x", records=1.5)
x[...] = 1.2
x[...] = x + 2

The scalar x is initialized to be 1.5. The second statement changes the value to 1.2, and the third changes it to 3.2. The second and third statements are assignments: each replaces the current value of x with a new one.

Note that, as mentioned above, when assigning values to scalar parameters, one needs to provide the ellipsis literal [...]. This is not necessary for non-scalar parameters.

Note also that the same symbol can be used on the left and right of the = sign.

Indexed Assignments#

Performing indexed assignments offers what may be thought of as simultaneous or parallel assignments and provides a concise way of manipulating large amounts of data.

Consider the mathematical statement \(DJ_d = 2.75 * DA_d\) for all elements of \(d\). This means that for every member of the set \(d\), a value is assigned to \(DJ\). This can be written in GAMSPy as follows:

dj[d] = 2.75*da[d]

This assignment is known technically as an indexed assignment and set d as the controlling index or controlling set.

Note

The index set(s) on the left hand side of an indexed assignment are referred to synonymously as the controlling indices, controlling sets, or controlling domain of the assignment.

The extension to two or more controlling indices should be obvious. There will be an assignment made for each label combination that can be constructed using the indices inside the parentheses. Consider the following example of an assignment to all 100 data elements of the parameter a.

from gamspy import Container, Set, Parameter

m = Container()
row = Set(m, "row")
col = Set(m, "col")

r = Parameter(
    m,
    domain=row,
    domain_forwarding=True,
    records=[(f"r-{r}", 4) for r in range(7)]
    + [(f"r-{r}", 5) for r in range(7, 10)],
)
c = Parameter(
    m,
    domain=col,
    domain_forwarding=True,
    records=[(f"c-{c}", 3) for c in range(5)]
    + [(f"c-{c}", 5) for c in range(5, 10)],
)

a = Parameter(m, domain=[row, col])
a[row, col]  =  13.2 + r[row]*c[col]

The calculation in the last statement is carried out for each of the 100 unique two-label combinations that can be formed from the elements of row and col. An explicit formulation of the first of these assignments follows:

a["r-0", "c-0"] = 100 + r["r-0"]*c["c-0"]
In [1]: a.pivot()
Out[1]:
       c-0   c-1   c-2   c-3   c-4   c-5   c-6   c-7   c-8   c-9
r-0  112.0  25.2  25.2  25.2  25.2  33.2  33.2  33.2  33.2  33.2
r-1   25.2  25.2  25.2  25.2  25.2  33.2  33.2  33.2  33.2  33.2
r-2   25.2  25.2  25.2  25.2  25.2  33.2  33.2  33.2  33.2  33.2
r-3   25.2  25.2  25.2  25.2  25.2  33.2  33.2  33.2  33.2  33.2
r-4   25.2  25.2  25.2  25.2  25.2  33.2  33.2  33.2  33.2  33.2
r-5   25.2  25.2  25.2  25.2  25.2  33.2  33.2  33.2  33.2  33.2
r-6   25.2  25.2  25.2  25.2  25.2  33.2  33.2  33.2  33.2  33.2
r-7   28.2  28.2  28.2  28.2  28.2  38.2  38.2  38.2  38.2  38.2
r-8   28.2  28.2  28.2  28.2  28.2  38.2  38.2  38.2  38.2  38.2
r-9   28.2  28.2  28.2  28.2  28.2  38.2  38.2  38.2  38.2  38.2

Note that for indexed assignments a copy of the symbols on the right hand side is installed before the assignment is carried out. That means it does not work “in-place” or recursively. Consider the following example where we compute the first ten Fibonacci numbers and store them in parameter f using a loop. The example also illustrates how such a recursive calculation does not work with a parallel assignment statement for parameter g.

from gamspy import Container, Set, Parameter, Ord

m = Container()
i = Set(m, "i", records=[f"i{i}" for i in range(11)])

f = Parameter(m, "f", domain=i, records=[("i0", 0), ("i1", 1)])
g = Parameter(m, "g", domain=i, records=f.records)

for idx in range(2, 11):
    f[f"i{idx}"] = f[f"i{idx-1}"] + f[f"i{idx-2}"]

g[i].where[Ord(i)>=3] = g[i.lag(1)] + g[i.lag(2)]

Resulting in the following output

In [1]: f.records
Out[1]:
      i  value
0    i0   -0.0
1    i1    1.0
2    i2    1.0
3    i3    2.0
4    i4    3.0
5    i5    5.0
6    i6    8.0
7    i7   13.0
8    i8   21.0
9    i9   34.0
10  i10   55.0

In [2]: g.records
Out[2]:
      i  value
0    i0   -0.0
1    i1    1.0
2    i2    1.0
3    i3    1.0

Not only are many elements missing from g, the element g['i3'] is wrong. The reason for this is that the copy of g made at the beginning of the assignment consists of elements for i0 and i1 only. The calculation for i3 uses g['i1'] + g['i2']. g['i2'] is still 0 and hence we end up with g['i3'] = 1. The example, uses of Ord and Lag operations.

Note

There are many math functions that can be used in assignments provided by GAMSPy. The whole list can be found at gamspy.math. More information about each function can be found at functions <https://gams.com/latest/docs/UG_Parameters.html#UG_Parameters_Functions> documentation of GAMS.

Restricting the Domain in Assignments#

Sometimes it is necessary to make assignments over selected elements of a set instead of over the entire domain. There are several ways to accomplish this: using explicit labels, subsets, conditionals and tuples. Before we look at each method in more detail, below is an introductory example:

from gamspy import Container, Set, Parameter

m = Container()

# Set with element range from "i0" to "i100"
i = Set(m, "i", records=[(f"i{i}", i) for i in range(101)])
k = Parameter(m, domain=i)

# Assign 4 to all elements of k[i]
k[i] = 4

# Assign 15 to the specific elements of k['i77']
k["i77"] = 15

# Assign 10 to the first 10 elements of i to k[i]
j = Set(m, "j", domain=i, records=i.records[0:8])
k[j] = 10

The parameter k is declared over the set i but not assigned any values at first. In the first assignment statement k[i] = 4 all elements of the set i are assigned the value 4. k["i77"] refers to a specific set elements and is assigned the value 15. The third assignment assignes the value 10 to the first 8 elements of the set i by using a subset j. Read more about Set and Set Element Referencing here: Set and Set Element Referencing.

Explicit Labels#

The strongest restriction of the domain is assigning a value to just one element. Labels may be used explicitly in the context of assignments to accomplish this. The following example illustrates:

a["r-7", "c-4"] = -2.36

This statement assigns a constant value to just one element of the parameter a. All other elements of a remain unchanged. Labels must be quoted when used in this way.

Subsets#

In general, wherever a set name may occur in an indexed assignment, a subset may be used instead.

Consider the following example:

sro = Set(m, domain=row, records=row.records[-4:])
a[sro, "col-9"] = 2.44 -33*r[sro]

Since the set sro was declared as a subset of the set row, we can use sro as a controlling index in the assignment above to make the assignment only for the elements of sro.

Conditionals#

a[row, col].where[a[row, col] >= 100] = float("inf")

This assignment has the following effect: all elements of the parameter a whose value was at least 100 are assigned the value float("inf"), while all other elements of a remain unchanged.

Tuples#

Tuples or multi-dimensional sets are introduced in section Multi-Dimensional Sets. In this simple example we show how they may be used to restrict the domain. The example builds on a previous example in this section. We repeat the whole code here for clarity.

from gamspy import Container, Set, Parameter

m = Container()
row = Set(m, "row")
col = Set(m, "col")

r = Parameter(
    m,
    domain=row,
    domain_forwarding=True,
    records=[(f"r-{r}", 4) for r in range(7)] + [(f"r-{r}", 5) for r in range(7, 10)],
)
c = Parameter(
    m,
    domain=col,
    domain_forwarding=True,
    records=[(f"c-{c}", 3) for c in range(5)] + [(f"c-{c}", 5) for c in range(5, 10)],
)

tuples = Set(
    m,
    domain=[row, col],
    records=[("r-0", "c-0"), ("r-0", "c-9"), ("r-9", "c-0"), ("r-9", "c-9")],
)

a = Parameter(m, domain=[row, col])
a[row, col] = 13.2 + r[row] * c[col]
a[tuples[row, col]] = 7 + r[row] * c[col]
a[tuples] = 0.25 * a[tuples]

Note that we have introduced the new set tuples. It is two-dimensional and contains just four elements. As before, the parameter a is first assigned values for all its 100 elements. We then change some of these values using the set tuples as domain. The values of the elements of the parameter a that are not elements of the set tuples remain unchanged.

Issues with Controlling Indices#

Warning

The number of controlling indices on the left of the = sign should be at least as large as the number of indices on the right. There should be no index on the right-hand side of the assignment that is not present on the left unless it is operated on by an indexed operator. For more on indexed operators, see section Indexed Operations.

Consider the following statement:

a[row, "col-2"] = 22 - c[col]

GAMSPy will flag this statement as an error since col is an index on the right-hand side of the equation but not on the left.

Note that there would be no error here if col were a singleton set. Since there is only one element in a singleton set, the intent and behavior is well-defined even when col is not under control.

Warning

Each set is counted only once to determine the number of controlling indices. If the intent is for a set to appear independently more than once within the controlling domain, the second and subsequent occurrences of the set should be aliases of the original set, so that the number of controlling indices is equal to the number of indices. For details on aliases, see section Alias: Multiple Names for a Set.

Consider the following statement as an illustration:

b[row, row] = 7.7 - r[row]

This statement has only one controlling index, namely row. One element (on the diagonal of b) is assigned for each element of row, for a total of 10 assigned values. None of the off-diagonal elements of b will be changed!

If the intent is to assign values to each element of b, this can be done by introducing an alias rowp for row and using this alias in the second index position. There will then be two controlling indices and GAMSPy will make assignments over all 100 values of the full Cartesian product. The following example illustrates this method:

rowp = Alias(m, name="rowp", alias_with=row)
b[row, rowp] = 7.7 - [r[row] + r[rowp]]/2

Indexed Operations#

GAMSPy provides the following four indexed operations: Sum, Product, Smax, Smin, Sand, and Sor. These operations are performed over one or more controlling indices. Consider the following simple example:

from gamspy import Container, Set, Parameter, Sum, Smax

m = Container()
i = Set(m, "i", description="plants")
p = Set(m, "p", description="product")

capacity = Parameter(
    m,
    domain=[i, p],
    description="capacity in tons per day",
    domain_forwarding=True,
    records=[
        ["cartagena", "nitr-acid", 10],
        ["cartagena", "sulf-acid", 20],
        ["cartagena", "amm-sulf", 30],
        ["callao", "nitr-acid", 20],
        ["callao", "sulf-acid", 30],
        ["callao", "amm-sulf", 40],
        ["moron", "nitr-acid", 30],
        ["moron", "sulf-acid", 40],
        ["moron", "amm-sulf", 50],
    ],
)

totcap = Parameter(m, domain=p, description="total capacity by process")
totcap[p] = Sum(i, capacity[i, p])

The index over which the summation is done, i, is separated from the word Sum by a left bracket and from the data term capacity[i,m] by a comma. The set i is called the controlling index for this operation. The scope of the control is the pair of brackets [] that start immediately after the Sum. Note that using normal mathematical representation the last line could be written as: \(totC_p = \sum_{i}C_{ip}\).

Note

Notice that Sum is different than builtin sum operation of Python.

It is also possible to sum simultaneously over the domain of two or more sets as in the first assignment that follows. The second assignment demonstrates the use of a less trivial expression than an identifier within the indexed operation.

total_capacity = Parameter(m)
total_capacity[...] = Sum((i, p), capacity[i, p]) # or Sum(p, totcap[p])

The equivalent mathematical forms are:

\(total\_capacity = \sum_{i}\sum_{p}capacity_{ip}\) or \(total\_capacity = \sum_{p}totcap_{p}\)

Note that the following alternative notation may be used for the first assignment above:

total_capacity[...] = Sum(i, Sum(p, capacity[i, p]))

Note

In the context of sets the Sum operator may be used to compute the number of elements in a set.

The Smin and Smax operations are used to find the largest and smallest values over the domain of the index set or sets. The index for the Smin and Smax operators is specified in the same manner as in the index for the Sum operator. In the following example we want to find the largest capacity:

max_cap = Parameter(m)
max_cap[...] = Smax((i, p), capacity[i,p])

The Sand and Sor operations are used for boolean and and or calculation over the domain of the index set or sets. The index for the Sand and Sor operators is specified in the same manner as in the index for the Sum operator. In the following example we represent a (random) boolean expression in conjunctive normal form (CNF) using numerical data and check if some (random) configuration of the boolean varibales satisfies the boolean expression:

import gamspy as gp

m = gp.Container()
c = gp.Set(m, "c", description="conjunctions", records=range(6))
j = gp.Set(m, "j", records=range(5))
cnf = gp.Parameter(m, domain=[c, j], description="conjunctive normal form 1: A, -1: ~A")
cnf[c,j].where[gp.math.uniform(0,1)<0.5] = 2*gp.math.uniformInt(0,1) - 1
conf = gp.Parameter(m, domain=j)
conf[j] = gp.math.uniformInt(0,1)
result = gp.Parameter(m)

result[:] = gp.Sand(
    c,
    gp.Sor(j.where[cnf[c, j] > 0], conf[j]) | gp.Sor(j.where[cnf[c, j] < 0], ~conf[j]),
)
result.records

Note

  • In the context of assignment statements, the attributes of variables and equations (e.g. gamspy.Variable.up()) may be used in indexed operations just as scalars and parameters are used. For more on variable and equations attributes, see sections Variable Attributes and Equation Attributes respectively.

  • In the context of equation definitions, scalars, parameters and variables may appear freely in indexed operations. For more on equation definitions, see section Defining Equations.