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}\).
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.