Set#
Simple Sets#
Introduction#
Sets are the basic building blocks of a GAMSPy model, corresponding exactly to the indices in the algebraic representations of models. A simple set consists of a set name and the elements of the set. Example:
from gamspy import Container, Set
m = Container()
i = Set(m, name = "i", records = ["seattle", "sandiego"], description = "plants")
j = Set(m, name = "j", records = ["newyork", "chicago", "topeka"],
description = "markets")
The effect of these statements is probably selfevident. We declared two sets using
the gamspy.Set()
class and gave them the names i
and j
. We also
assigned members to the sets as follows:
\(i = \{Seattle, San Diego\}\)
\(j = \{New York, Chicago, Topeka\}\)
They are labels, but are often referred to as elements or members. The optional description
may be used to describe the set for future reference and to ease readability.
Besides using the Set()
class directly, one can also facilitate the addSet()
method
of the gamspy.Container()
class:
from gamspy import Container
m = Container()
i = m.addSet(name="i", records = ["seattle", "sandiego"], description="plants")
Set declaration and data assignment can also be done separately:
from gamspy import Container, Set
m = Container()
i = Set(m, "i", description="plants")
i.setRecords(["seattle", "sandiego"])
Not only sets themselves, but also the individual elements can have a description, which is called element text:
from gamspy import Container, Set
m = Container()
i = Set(m, "i", records=[
("seattle", "home of sub pop records"),
("sandiego",),
("washington_dc", "former gams hq"),
],
)
In [1]: i.records
Out[1]:
uni element_text
0 seattle home of sub pop records
1 sandiego
2 washington_dc former gams hq
The order in which the set members are listed is usually not important. However, if the members represent, for example, time periods, then it may be useful to refer to the next or previous member. There are special operations to do this, and they are discussed in chapter Sets as Sequences: Ordered Sets. For now, it is enough to remember that the order in which set elements are specified is not relevant, unless and until some operation implying order is used. At that time, the rules change, and the set becomes what we will later call an ordered set.
Subsets#
It is often necessary to define sets whose members must all be members of some larger set. For instance, we may wish to define the sectors in an economic model:
from gamspy import Container, Set
m = Container()
i = Set(m,
name = "i",
description = "all sectors",
records = ["lightind","food+agr","heavyind","services"])
t = Set(m,
name = "t",
domain = i,
description = "traded sectors",
records = ["lightind","food+agr","heavyind"])
nt = Set(m,
name = "nt",
description = "nontraded sectors",
records = ["services"])
i 
t 
nt 


0 
lightind 
lightind 

1 
food+agr 
food+agr 

2 
heavyind 
heavyind 

3 
services 
services 
Some types of economic activity, for example exporting and importing,
may be logically restricted to a subset of all sectors. In order to model
the trade balance we need to know which sectors are traded, and one obvious
way is to list them explicitly, as in the definition of the set t
above.
The domain specification for Set t
means that each member of the set t
must also be a member of the set i
. GAMS will enforce this relationship,
which is called domain checking. Obviously, the order of declaration and definition
is important: the membership of i
must be known before t
is defined,
otherwise checking cannot be done.
Note
All elements of the subset must also be elements of the superset.
It is legal but unwise to define a subset without reference to the larger set,
as is done above for the set nt
. In this case domain checking cannot be
performed: if services were misspelled no error would be marked, but the model
may give incorrect results. Hence, it is recommended to use domain checking
whenever possible. It catches errors and allows to write models that are
conceptually cleaner because logical relationships are made explicit.
An alternative way to define elements of a subset is with assignments:
from gamspy import Container, Set
m = Container()
i = Set(m,
name = "i",
description = "all sectors",
records = ["lightind","food+agr","heavyind","services"])
t = Set(m,
name = "t",
domain = i,
description = "traded sectors",
records = ["lightind","heavyind"])
t["food+agr"] = True
In the last line the element food+agr
of the set i
is assigned to the subset
t
. Assignments may also be used to remove an element from a subset:
t["lightind"] = False
Note
Note that if a subset is assigned to, it then becomes a dynamic set.
A subset can be used as a domain in the declaration of other sets, variables, parameters and in equations as long as it is no dynamic set.
MultiDimensional Sets#
It is often necessary to provide mappings between elements of different sets. For this purpose, GAMSPy allows the use of multidimensional sets. For the current maximum number of permitted dimensions, see Dimensions in the GAMS documentation. The next two subsections explain how to express onetoone and manytomany mappings between sets.
Onetoone Mapping#
Consider a set whose elements are pairs: \(A = \{(b,d),(a,c),(c,e)\}\). In this set there are three elements and each element consists of a pair of letters. This kind of set is useful in many types of modeling. In the following example a port has to be associated with a nearby mining region:
from gamspy import Container, Set
import pandas as pd
m = Container()
i = Set(m,
name = "i",
description = "mining regions",
records = ["china","ghana","russia","sleone"])
n = Set(m,
name = "n",
description = "ports",
records = ["accra","freetown","leningrad","shanghai"])
s = pd.Series(
index=pd.MultiIndex.from_tuples([("china", "shanghai"),
("ghana", "accra"),
("russia", "leningrad"),
("sleone", "freetown")])
)
# Alternative:
#
# s = pd.DataFrame([("china", "shanghai"),
# ("ghana", "accra"),
# ("russia", "leningrad"),
# ("sleone", "freetown")],
# columns=["i","n"])
#
# Note that uels_on_axes needs to be set to False in multi_in in this case.
multi_in = Set(m,
name = "in",
domain = [i, n],
description = "mines to ports map",
uels_on_axes=True,
records=s)
In [1]: multi_in.records
Out[1]:
i n element_text
0 china shanghai
1 ghana accra
2 russia leningrad
3 sleone freetown
Here i
is the set of mining regions, n
is the set of ports and multi_in
is a two
dimensional set that associates each port with a mining region. The pairs are created
using tuples in a
pandas MultiIndex object.
The set multi_in
has four elements, and each
element consists of a regionport pair. The domain = [i,n]
indicates that the
first member of each pair must be a member of the set i
of mining regions, and
that the second must be in the set n
of ports. GAMS will domain check the set
elements to ensure that all members belong to the appropriate sets.
ManytoMany Mapping#
A manytomany mapping is needed in certain cases. Consider the following sets:
from gamspy import Container, Set
import pandas as pd
m = Container()
i = Set(m, name = "i", records = ["a","b"])
j = Set(m, name = "j", records = ["c","d","e"])
ij1_data = pd.Series(
index=pd.MultiIndex.from_tuples([("a", "c"),
("a", "d")])
)
ij2_data = pd.Series(
index=pd.MultiIndex.from_tuples([("a", "c"),
("b", "c")])
)
ij3_data = pd.Series(
index=pd.MultiIndex.from_tuples([("a", "c"),
("b", "c"),
("a", "d"),
("b", "d")])
)
ij1 = Set(m, name = "ij1", domain = [i, j], uels_on_axes=True, records=ij1_data)
ij2 = Set(m, name = "ij2", domain = [i, j], uels_on_axes=True, records=ij2_data)
ij3 = Set(m, name = "ij3", domain = [i, j], uels_on_axes=True, records=ij3_data)
Here the set ij1
presents a onetomany mapping where one element of the set i
maps onto many elements of the set j
. The set ij2
represents a manytoone
mapping where many elements of the set i
map onto one element of the set j
.
The set ij3
is the most general case: a manytomany mapping where many elements
of the set i
map to many elements of the set j
:
In [1]: ij3.records
Out[1]:
i j element_text
0 a c
1 b c
2 a d
3 b d
Projection and Aggregation of Sets#
In GAMSPy aggregation operations on sets may be performed with an assignment and
the gamspy.Sum()
operator. Assignments and the sum operator are introduced
and discussed in detail in chapter Indexed Operations. Here we only show how
they may be used in the context of sets to perform projections and aggregations.
The following example serves as illustration.
from gamspy import Container, Set, Parameter, Sum
import pandas as pd
m = Container()
i = Set(m, "i", records = [("i" + str(i), i) for i in range(1,4)])
j = Set(m, "j", records = [("j" + str(j), j) for j in range(1,3)])
k = Set(m, "k", records = [("k" + str(k), k) for k in range(1,5)])
s = pd.Series(
index=pd.MultiIndex.from_tuples([("i1","j1","k1"),("i1","j1","k2"),("i1","j1","k3"),
("i1","j1","k4"),("i1","j2","k1"),("i1","j2","k2"),
("i1","j2","k3"),("i1","j2","k4"),("i2","j1","k1"),
("i2","j1","k2"),("i2","j1","k3"),("i2","j1","k4"),
("i2","j2","k1"),("i2","j2","k2"),("i2","j2","k3"),
("i2","j2","k4"),("i3","j1","k1"),("i3","j1","k2"),
("i3","j1","k3"),("i3","j1","k4"),("i3","j2","k1"),
("i3","j2","k2"),("i3","j2","k3"),("i3","j2","k4"),])
)
ijk = Set(m, name = "ijk", domain = [i,j,k], uels_on_axes=True, records=s)
ij1a = Set(m, name = "ij1a", domain = [i,j])
Count_1a = Parameter(m, "Count_1a")
Count_1b = Parameter(m, "Count_1b")
Count_2a = Parameter(m, "Count_2a")
Count_2b = Parameter(m, "Count_2b")
# Method 1: Using an assignment and the sum operator for a projection
ij1a[i,j] = Sum(k,ijk[i,j,k])
# Method 2: Using an assignment and the sum operator for aggregations
Count_2a[...] = Sum(ijk[i,j,k],1)
Count_1a[...] = Sum(ij1a[i,j],1)
Note that the set ijk
is a threedimensional set, its elements are 3tuples and all
permutations of the elements of the three sets i
, j
and k
are in its domain.
Thus the number of elements of the set ijk
is 3 x 2 x 4 = 24. The set ij1a
is a twodimensional
set that is declared in the set statement but not defined.
The first assignment statement defines the members of the set ij1a
. This is a projection
from the set ijk
to the set ij1a
where the threetuples of the first set are mapped
onto the pairs of the second set, such that the dimension k
is eliminated. This means
that the four elements "i1.j1.k1"
, "i1.j1.k2"
, "i1.j1.k3"
and "i1.j1.k4"
of
the set ijk
are all mapped to the element "i1.j1"
of the set ij1a
. Note that in
this context, the result of the gamspy.Sum()
operation is not a number but a set. The
second and third assignments are aggregations, where the number of elements of the two sets
are computed. As already mentioned, the result of the first aggregation is 24 and the result
of the second aggregation is 6 = 24 / 4.
Singleton Sets#
A singleton set in GAMSPy is a special set that has at most one element (zero elements
are allowed as well). Like other sets, singleton sets may have a domain with several
dimensions. Singleton sets are declared with the boolean is_singleton
in the
gamspy.Set()
class (or the gamspy.Container()
class).
from gamspy import Container, Set
m = Container()
i = Set(m, name = "i", records = ["a","b","c"])
j = Set(m, name = "j", is_singleton = True, records = ["d"])
k = Set(m, name = "k", is_singleton = True, domain = i, records = ["b"])
l = Set(m, name = "l", is_singleton = True, uels_on_axes=True, domain = [i,i],
records = pd.Series(
index=pd.MultiIndex.from_tuples([("b", "c")])
))
In [1]: i.records
Out[1]:
uni element_text
0 a
1 b
2 c
In [2]: j.records
Out[2]:
uni element_text
0 d
In [3]: k.records
Out[3]:
uni element_text
0 b
In [4]: l.records
Out[4]:
i_0 i_1 element_text
0 b c
The sets j
, k
and l
are declared as singleton sets, each of them has just
one element. The set k
is a subset of the set i
and the set l
is a
twodimensional set.
Note that a data statement for a singleton set with more than one element will create a compilation error:
from gamspy import Container, Set
m = Container()
j = Set(m, name = "j", is_singleton = True, records = range(1,5))
GamspyException: Singleton set records size cannot be more than one.
It also possible to assign an element to a singleton set. In this case the singleton set
is automatically cleared of the previous element first. For example, adding the following
line to the code above will result in set k
containing only element a
after
execution:
k["a"] = True
Singleton sets can be especially useful in assignment statements since they do not need to be controlled by a controlling index or an indexed operator like other sets. Consider the following example:
from gamspy import Container, Set, Parameter
m = Container()
i = Set(m, name = "i", records = ["a","b","c"])
k = Set(m, name = "k", is_singleton = True, domain = i, records = ["b"])
h = Set(m, name = "h", is_singleton = True, domain = i, records = ["a"])
n = Parameter(m, name = "n", domain = i, records = [["a", 2],["b", 3],["c", 5]])
z1 = Parameter(m, name = "z1")
z2 = Parameter(m, name = "z2")
z1[...] = n[k]
z2[...] = n[k] + 100*n[h]
The singleton sets k
and h
are both subsets of the set i
. The parameter n
is defined over the set i
. The scalar z1
is assigned a value of the parameter n
without naming the respective label explicitly in the assignment. It is already specified
in the definition of the singleton set k
. The assignment statement for the scalar z2
contains an expression where the singleton sets k
and h
are referenced without a
controlling index or an indexed operation.
Note
Singleton sets cannot be used as domains.
The Universal Set: *
as Set Identifier#
GAMSPy provides the universal set denoted by *
for cases where the user wishes not to
specify an index but have only a placeholder for it. The following examples show two ways
how the universal set is introduced in a model. We will discuss the advantages and
disadvantages of using the universal set later. First example:
from gamspy import Container, Set, Parameter
m = Container()
r = Set(m, name = "r", description = "raw materials", records = ["scrap","new"])
misc = Parameter(m, name = "misc", domain = ["*",r],
records = [["maxstock", "scrap", 400],
["maxstock", "new", 275],
["storagec", "scrap", 0.5],
["storagec", "new", 2],
["resvalue", "scrap", 15],
["resvalue", "new", 25]])
In our example, the first index of parameter misc
is the universal set "*"
and the
second index is the previously defined set r
. Since the first index is the universal set
any entry whatsoever is allowed in this position. In the second position elements of the set
r
must appear, they are domain checked, as usual.
The second example illustrates how the universal set is introduced in a model with an
gamspy.UniverseAlias()
statement:
from gamspy import Container, Set, UniverseAlias
m = Container()
r = UniverseAlias(m, name = "new_universe")
k = Set(m, name = "k", domain = r, records = "Chicago")
The gamspy.UniverseAlias()
statement links the universal set with the set name
new_universe
. Set k
is a subset of the universal set and Chicago
is declared to
be an element of k
. Any item may be added freely to k
.
Note
It is recommended to not use the universal set for data input, since there is no domain checking and thus typos will not be detected and data that the user intends to be in the model might actually not be part of it.
Observe that in GAMSPy a simple set is always regarded as a subset of the universal set. Thus the set definition
i = Set(m, "i", records = range(1,10))
is the same as
i = Set(m, "i", domain = "*", records = range(1,10))
GAMS follows the concept of a domain tree for domains in GAMS. It is assumed that a set and its subset are connected by an arc where the two sets are nodes. Now consider the following one dimensional subsets:
from gamspy import Container, Set
m = Container()
i = Set(m, "i")
ii = Set(m, "ii", domain = i)
j = Set(m, "j", domain = i)
jj = Set(m, "jj", domain = j)
jjj = Set(m, "jjj", domain = jj)
These subsets are connected with arcs to the set i
and thus form a domain tree that is rooted
in the universe node "*"
. This particular domain tree may be represented as follows:
*  i  ii

 j  jj  jjj
Observe that the universal set is assumed to be ordered and operators for ordered sets such ord, lag and lead may be applied to any sets aliased with the universal set.
Set and Set Element Referencing#
Sets or set elements are referenced in many contexts, including assignments, calculations, equation definitions and loops. Usually GAMSPy statements refer to the whole set or a single set element. In addition, GAMSPy provides several ways to refer to more than one, but not all elements of a set. In the following subsections we will show by example how this is done.
Referencing the Whole Set#
Most commonly whole sets are referenced as in the following examples:
from gamspy import Container, Set, Parameter, Sum
m = Container()
i = Set(m, "i", records = [("i" + str(i), i) for i in range(1,101)])
k = Parameter(m, "k", domain = i)
k[i] = 4
z = Parameter(m, "z")
z[...] = Sum(i, k[i])
The parameter k
is declared over the set i
, in the assignment statement in the next line
all elements of the set i
are assigned the value 4. The scalar z
is defined to be the
gamspy.Sum()
of all values of the parameter k(i).
Referencing a Single Element#
Sometimes it is necessary to refer to specific set elements. This is done by using quotes around the label(s). We may add the following line to the example above:
k["i77"] = 15
Referencing a Part of a Set#
There are multiple ways to restrict the domain to more than one element, e.g. subsets,
conditionals and tuples. Suppose we want the parameter k
from the example above to be
assigned the value 10 for the first 8 elements of the set i
. The following two lines of
code illustrate how easily this may be accomplished with a subset:
j = Set(m, "j", domain = i, records = i.records[0:8])
k[j] = 10
First we define the set j
to be a subset of the set i
with exactly the elements we are
interested in. Then we assign the new value to the elements of this subset. The other values of
the parameter k
remain unchanged. For examples using conditionals and tuples, see sections
Conditionals and Tuples respectively.
Set Attributes#
A GAMSPy set has several attributes attached to it. For a complete list see gamspy.Set()
.
The attributes may be accessed like in the following example:
data[set_name] = set_name.attribute
Here data
is a parameter, set_name
is the name of the set and .attribute
is one of
the attributes listed in gamspy.Set()
. The following example serves as illustration:
from gamspy import Container, Set, Parameter
m = Container()
id = Set(m, "id", records = [("Madison","Wisconsin"),
("teatime","5"),
("inf",""),
("7",""),
("13.14","")])
attr = Parameter(m, "attr", domain = [id, "*"], description = "Set attribute values")
attr[id,"position"] = id.pos
attr[id,"reverse"] = id.rev
attr[id,"offset"] = id.off
attr[id,"length"] = id.len
attr[id,"textLength"] = id.tlen
attr[id,"first"] = id.first
attr[id,"last"] = id.last
The parameter attr
is declared to have two dimensions with the set id
in the first
position and the universal set in the second position. In the following seven statements the
values of attr
are defined for seven entries of the universal set.
position 
reverse 
offset 
length 
textLength 
first 
last 


Madison 
1 
4 
7 
9 
1 

teatime 
2 
3 
1 
8 
1 

inf 
3 
2 
2 
4 

7 
4 
1 
3 
2 

13.14 
5 
4 
5 
1 
Implicit Set Definition#
As seen above, sets can be defined through data statements in the declaration. Alternatively, sets can be defined implicitly through data statements of other symbols which use these sets as domains. This is illustrated in the following example:
from gamspy import Container, Set, Parameter
import pandas as pd
m = Container()
distances = pd.DataFrame(
[
["seattle", "newyork", 2.5],
["seattle", "chicago", 1.7],
["seattle", "topeka", 1.8],
["sandiego", "newyork", 2.5],
["sandiego", "chicago", 1.8],
["sandiego", "topeka", 1.4],
],
columns=["from", "to", "distance"],
).set_index(["from", "to"])
i = Set(m, name="i", description="plants")
j = Set(m, name="j", description="markets")
d = Parameter(m, name="d",
domain=[i, j],
description="distance in thousands of miles",
records = distances.reset_index(),
domain_forwarding = True
)
The domain_forwarding = True
in the declaration of gamspy.Parameter()
d
forces set elements to be recursively included in all parent sets. Here set i
will therefore contain all elements which define the first dimension of symbol d
and set j
will contain all elements which define the second dimension of symbol
d
.
In [1]: i.records
Out[1]:
uni element_text
0 seattle
1 sandiego
In [2]: j.records
Out[2]:
uni element_text
0 newyork
1 chicago
2 topeka
Note, that domain_forwarding
can also pass as a list of bool to control which
domains to forward. Also domain_forwarding
is not limited to one symbol. One
domain set can be defined through multiple symbols using the same domain.
Dynamic Sets#
Introduction#
In this section we introduce a special type of sets: dynamic sets. The sets that we discuss in detail above have their elements stated and the membership is never changed. Therefore they are called static static sets. In contrast, the elements of dynamic sets are not fixed, but may be added and removed. Dynamic sets are most often used as controlling indices in assignments or equation definitions and as the conditional set in an indexed operation. We will first show how assignments are used to change set membership in dynamic sets. Then we will introduce set operations and the last part of this chapter covers dynamic sets in the context of conditions.
Assigning Membership to Dynamic Sets#
The Syntax#
Like any other set, a dynamic set has to be declared before it may be used in the model. Often, a dynamic set is declared as subset of a static set. Dynamic sets in GAMSPy may also be multidimensional like static sets. For the current maximum number of permitted dimensions, see Dimensions in the GAMS documentation. For multidimensional dynamic sets the index sets can also be specified explicitly at declaration. That way dynamic sets are domain checked. Of course it is also possible to use dynamic sets that are not domain checked. This provides additional power and flexibility but also a lack of intelligibility and danger. Any label is legal as long as such a set’s dimension, once established, is preserved.
In general, the syntax for assigning membership to dynamic sets in GAMSPy is:
set_name[index_list  label] = True  False
Set_name
is the internal name of the set in GAMSPy, index_list
refers to the
domain of the dynamic set and label
is one specific element of the domain. An
assignment statement may assign membership to the dynamic set either to the whole
domain or to a subset of the domain or to one specific element. Note that, as usual,
a label must appear in quotes.
Illustrative Example#
We start with assignments of membership to dynamic sets
from gamspy import Container, Set
m = Container()
item = Set(m, name="item", records = ["dish", "ink", "lipstick", "pen", "pencil", "perfume"])
subitem1 = Set(m, name="subitem1", records = ["pen", "pencil"], domain = item)
subitem2 = Set(m, name="subitem2", domain = item)
subitem1["ink"] = True
subitem1["lipstick"] = True
subitem2[item] = True
subitem2["perfume"] = False
Note that the sets subitem1
and subitem2
are declared like any other set. The
two sets become dynamic as soon as they are assigned to. They are also domain checked:
the only members they will ever be able to have must also be members of the set
item
.
The first assignment not only makes the set subitem1
dynamic, it also has the effect
that its superset item
becomes a static set and from then on its membership is
frozen. The first two assignments each add one new element to subitem1
. Note that both
are also elements of item
, as required. The third assignment is an example of the
familiar indexed assignment: subitem2
is assigned all the members of item
. The last
assignment removes the label "perfume"
from the dynamic set subitem2
.
In [1]: print(*subitem1.records["item"], sep=", ")
Out[1]: ink, lipstick, pen, pencil
In [2]: print(*subitem2.records["item"], sep=", ")
Out[2]: dish, ink, lipstick, pen, pencil
Note that even though the labels "pen"
and "pencil"
were declared to be members of
the set subitem1
before the assignment statements that added the labels "ink"
and
"lipstick"
to the set, they appear in the listing above at the end. The reason is that
elements are displayed in the internal order, which in this case is the order specified in
the declaration of the set item.
Dynamic Sets with Multiple Indices#
Dynamic sets may be multidimensional. The following lines continue the example above and illustrate assignments for multidimensional sets.
sold = Set(m, "sold", records = ["pencil", "pen"], domain = item)
sup = Set(m, "sup", records = ["bic", "parker", "waterman"])
supply = Set(m, "supply", domain = [sold, sup])
supply["pencil", "bic"] = True
supply["pen", sup] = True
In [1]: supply.records
Out[1]:
sold sup element_text
0 pen bic
1 pen parker
2 pen waterman
3 pencil bic
Equations Defined over the Domain of Dynamic Sets#
Generally, dynamic sets are not permitted as domains in declarations of sets, variables, parameters and equations. However, they may be referenced and sometimes it is necessary to define an equation over a dynamic set.
Note
The trick is to declare the equation over the entire domain but define it over the dynamic set.
For example, defining an equation over a dynamic set can be necessary in models that will be solved for arbitrary groupings of regions simultaneously. We assume there are no explicit links between regions, but that we have a number of independent models with a common data definition and common logic. We illustrate with an artificial example, leaving out lots of details.
from gamspy import Container, Set, Parameter, Variable, Equation m = Container() allr = Set(m, "allr", records = ["N", "S", "W", "E", "NE", "SW"], description = "all regions") r = Set(m, "r", domain = allr, description = "region subset for particular solution") type = Set(m, "type", description = "set for various types of data") price = Parameter(m, "price", records = 10) data = Parameter(m, "data", domain = [allr, type], description = "all other data ...") activity1 = Variable(m, "activity1", domain = allr, description = "first activity") activity2 = Variable(m, "activity2", domain = allr, description = "second activity") revenue = Variable(m, "revenue", domain = allr, description = "revenue") resource1 = Equation(m, "resource1", domain = allr, description = "first resource constraint ...") prodbal1 = Equation(m, "prodbal1", domain = allr, description = "first production balance ...") resource1[r] = activity1[r] <= data[r,"resource1"] prodbal1[r] = activity2[r]*price == revenue[r]
To repeat the important point: the equation is declared over the set allr
, but
defined over r
, a subset. Note that the variables and data are declared over
allr
but referenced over r
. Then the set r
may be assigned arbitrary
combinations of elements of the set allr
, and the model may be solved any number
of times for the chosen groupings of regions.
Assigning Membership to Singleton Sets#
Singleton sets have only one element. Hence any assignment to a singleton set first clears or empties the set, no explicit action to clear the set is necessary. This is illustrated with the following example:
from gamspy import Container, Set
m = Container()
i = Set(m, "i", records = ["a", "b", "c"], description = "Static Set")
ii = Set(m, "ii", domain = i, records = "b", description = "Dynamic Set")
si = Set(m, "si", domain = i, records = "b", is_singleton = True, description = "Dynamic Singleton Set")
ii["c"] = True
si["c"] = True
Note that both ii
and si
are subsets of the set i
, but only si
is declared as a
singleton set. The assignment statements assign to both sets the element "c"
. While "c"
is added to the set ii
, it replaces the original element in the singleton set si
:
In [1]: print(*ii.records["i"], sep=", ")
Out[1]: b, c
In [2]: print(*si.records["i"], sep=", ")
Out[2]: c
Set Operations#
GAMSPy provides symbols for arithmetic set operations that may be used with dynamic sets. An
overview of the set operations in GAMSPy is given below. Examples and alternative formulations
for each operation follow. Note that in the table below the set i
is the static superset
and the sets j
and k
are dynamic sets.
Set Operation 
Operator 
Description 

Set Union 
j[i] + k[i] 
Returns a subset of i that contains all the elements of the sets j and k. 
Set Intersection 
j[i] & k[i] 
Returns a subset of i that contains the elements of the set j that are also elements of the set k. 
Set Complement 
~ j[i] 
Returns a subset of i that contains all the elements of the set i that are not elements of the set j. 
Set Difference 
j[i]  k[i] 
Returns a subset of i that contains all the elements of the set j that are not elements of the set k. 
Example: The set item
is the superset of the dynamic sets subitem1
and subitem2
.
We add new dynamic sets for the results of the respective set operations.
from gamspy import Container, Set, Number
m = Container()
item = Set(m, name="item", records = ["dish", "ink", "lipstick", "pen", "pencil", "perfume"])
subitem1 = Set(m, name="subitem1", records = ["pen", "pencil"], domain = item)
subitem2 = Set(m, name="subitem2", domain = item)
subitem1["ink"] = True
subitem1["lipstick"] = True
subitem2[item] = True
subitem2["perfume"] = False
union1 = Set(m, "union1", domain = item)
union2 = Set(m, "union2", domain = item)
intersection1 = Set(m, "intersection1", domain = item)
intersection2 = Set(m, "intersection2", domain = item)
complement1 = Set(m, "complement1", domain = item)
complement2 = Set(m, "complement2", domain = item)
difference1 = Set(m, "difference1", domain = item)
difference2 = Set(m, "difference2", domain = item)
union1[item] = subitem2[item] + subitem1[item]
union2[subitem1] = True
union2[subitem2] = True
intersection1[item] = subitem2[item] * subitem1[item]
intersection2[item] = Number(1).where[subitem1[item] & subitem2[item]]
complement1[item] = ~subitem1[item]
complement2[item] = True
complement2[subitem1] = False
difference1[item] = subitem2[item]  subitem1[item]
difference2[item] = Number(1).where[subitem2[item]]
difference2[subitem1] = False
In [1]: print(*intersection1.records["item"], sep=", ")
Out[1]: ink, lipstick, pen, pencil
Looking at the results of each operation will show that the above assignment statements for each operation result in the same dynamic set like using the set operator. Observe that the alternative formulations for the set intersection and set difference involve conditional assignments. Conditional assignments in the context of dynamic sets are discussed in depth in the next section.
Note
The indexed operation gamspy.Sum()
may be used for set unions. Similarly,
the indexed operation gamspy.Product()
may be used for set intersections.
For examples see section Conditional Indexed Operations with Dynamic Sets below.
Controlling Dynamic Sets#
Recall that set membership of subsets and dynamic sets may be used as a logical
condition. Set membership may also be a building block in complex logical conditions
that are constructed using the logical python operators ~
(not), &
(and),

(or), ^
(xor), not(x) or y
(logical implication) and
==
(logical equivalence). Moreover, the set operations introduced in the previous
section may also be used in logical conditions. Dynamic sets can be controlled in the
context of assignments, indexed operations and equations. We will discuss in detail
each of these in the following subsections.
Apart from being part of logical conditions, dynamic sets may be assigned members with conditional assignments. Examples are given in the next subsection.
Dynamic Sets in Conditional Assignments#
Dynamic sets may be used in two ways in conditional assignments: they may be the item on the lefthand side that is assigned to and they may be part of the logical condition. Below we present examples for both.
from gamspy import Container, Set
m = Container()
item = Set(m, name="item",
records = ["dish", "ink", "lipstick", "pen", "pencil", "perfume"])
subitem1 = Set(m, name="subitem1",
records = ["ink", "lipstick", "pen", "pencil"],
domain = item)
subitem2 = Set(m, name="subitem2", domain = item)
subitem2[item].where[subitem1[item]] = True
The conditional assignment adds the members of dynamic set subitem1
to the dynamic set
subitem2
. Thus subitem2
will have the following elements:
In [1]: print(*subitem2.records["item"], sep=", ")
Out[1]: ink, lipstick, pen, pencil
Note that instead of using subitem1
in where[]
we could also write:
subitem2[subitem1] = True
In the next example of a conditional assignment, a dynamic set features in the
logical condition on the righthand side. The first statement clears the set
subitem2
of any previously assigned members and the second statement assigns
all members of subitem1
to subitem2
using gamspy.Number()
. The
following conditional assignment will have the same result:
subitem2[item] = False
subitem2[item] = Number(1).where[subitem1[item]]
The logical condition in this assignment is subitem1[item]
. It is satisfied
for all members of the set subitem1
. Hence the statement assigns all elements
of the domain item
that are members of the set subitem1
to the dynamic set
subitem2
. Note that in this assignment the where[]
is on the right.
Conditional assignments with where[]
on the righthand side imply an
ifthenelse
structure where the else
case is automatically zero. Unlike
parameters, dynamic sets cannot be assigned the value of zero, they are assigned
False
instead. Therefore a more explicit formulation of the conditional
assignment above would be:
subitem2[item] = False
subitem2[item] = Number(1).where[subitem1[item]] + Number(0).where[~ subitem1[item]]
Conditional Indexed Operations with Dynamic Sets#
Indexed operations in GAMSPy may be controlled by where[]
conditions. The domain
of conditional indexed operations is often restricted by a set, called the
conditional set. Dynamic sets may be used as conditional sets or they may be assigned
to with a statement that features a conditional indexed operation on the righthand
side. We will illustrate both cases with examples.
Suppose we have a set of origins, a set of destinations and a parameter specifying the flight distance between them:
from gamspy import Container, Set, Parameter, Smax, Domain
import pandas as pd
m = Container()
distances = pd.DataFrame(
[
["Chicago", "Vancouver", 1777],
["Chicago", "Bogota", 2691],
["Chicago", "Dublin", 3709],
["Chicago", "Rio", 5202],
["Chicago", "Marrakech", 4352],
["Philadelphia", "Vancouver", 2438],
["Philadelphia", "Bogota", 2419],
["Philadelphia", "Dublin", 3306],
["Philadelphia", "Rio", 4695],
["Philadelphia", "Marrakech", 3757],
],
columns=["from", "to", "distance"],
).set_index(["from", "to"])
i = Set(m, name="i", records = ["Chicago", "Philadelphia"], description = "origins")
j = Set(m, name="j", records = ["Vancouver", "Bogota", "Dublin", "Rio", "Marrakech"],
description = "destinations")
d = Parameter(m, "d", domain = [i, j], records = distances.reset_index(),
description = "distance (miles)")
We wish to find the longest distance that we can travel given that we have a limit of 3500 miles.
can_do = Set(m, name="can_do", domain = [i, j],
description = "connections with less than 3500 miles")
can_do[i,j].where[d[i,j] < 3500] = True
maxd = Parameter(m, "maxd", description = "longest distance possible")
maxd[...] = Smax(Domain(i,j).where[can_do[i,j]], d[i,j])
The dynamic set can_do
contains all connections that are less than 3500 miles.
The scalar maxd
is defined by a conditional assignment where the indexed operation
gamspy.Smax()
scans all entries of the parameter d
whose label combinations
are members of the set can_do
and chooses the largest value.
In [1]: can_do.pivot(index = "i", columns = "j")
Out[1]:
Vancouver Bogota Dublin
Chicago True True False
Philadelphia True True True
In [2]: maxd.records
Out[2]:
value
0 3306.0
There is a shorter alternative formulation for this assignment; see subsection Filtering through Dynamic Sets below for details.
Finally, we also wish to know which flight connection is linked to the longest possible distance. Consider the following two lines:
maxc = Set(m, name="maxc", domain = [i, j], is_singleton = True, description = "maximum distance connection")
maxc[i,j] = Number(1).where[can_do[i,j] & (d[i,j] == maxd)]
Which gives
In [1]: maxc.records
Out[1]:
i j element_text
0 Philadelphia Dublin
The dynamic singleton set is assigned the member of the dynamic set can_do
whose
distance equals the maximum distance.
The full power of indexed operators becomes apparent with multidimensional dynamic sets
from gamspy import Container, Set, Sum, Product
m = Container()
dep = Set(m, "dep", description="departments",
records=["cosmetics", "hardware", "household", "stationary", "toy"])
sup = Set(m, "sup", description="suppliers",
records=["bic", "dupont", "parker", "revlon"])
item = Set(m, "item", description="items_sold",
records=["dish", "ink", "lipstick", "pen", "pencil", "perfume"])
sales_data = {
("cosmetics", "lipstick"),
("cosmetics", "perfume"),
("hardware", "ink"),
("household", "dish"),
("household", "pen"),
("stationary", "dish"),
("stationary", "ink"),
("stationary", "pen"),
("stationary", "pencil"),
("toy", "ink"),
("toy", "pen"),
("toy", "pencil")
}
sales = Set(m, name="sales", domain=[dep, item],
description="departments and items sold", uels_on_axes=True,
records=sales_data)
# Note the alternative, more compact notation of the supply data.
# GAMSPy still needs flat data in the end
supply_data = {
"dish": ("bic", "dupont"),
"ink": ("bic", "parker"),
"lipstick": "revlon",
"pen": ("parker", "revlon"),
"pencil": ("bic", "parker"),
"perfume": "revlon"
}
supply = Set(m, name="supply", domain=[item, sup],
description="items and suppliers", uels_on_axes=True,
records=[(item, sup) for item, sups in supply_data.items()
for sup in (sups if isinstance(sups, (list, tuple))
else [sups])])
g03 = Set(m, name = "g03", domain = dep,
description = "departments selling items supplied by Parker")
g03[dep] = Sum(item.where[supply[item,"parker"]], sales[dep,item])
The assignment above is used to create the set of departments that sell items supplied
by "parker"
. Note that the set g03
is a subset of the set dep
. Its members
are specified by assignment, hence it is a dynamic set. Note that the assignment is made
to a set, therefore the indexed operator gamspy.Sum()
refers to a set union (and
not to an addition as would be the case if the assignment were made to a parameter).
The indexed operation is controlled by the twodimensional set supply
with the label
"parker"
in the second index position. This logical condition is True for all members
of the set supply
where the second index is "parker"
. Hence the summation is over
all items sold, provided that the supplier is "parker"
. Given the declaration of the
set supply
, this means "ink"
, "pen"
and "pencil"
. The associated departments are
thus all departments except for "cosmetics"
:
In [1]: print(*g03.records["dep"], sep=", ")
Out[1]: hardware, household, stationary, toy
Now suppose we are interested in the departments that are selling only items supplied by
"parker"
. We introduce a new dynamic set g11
and the following assignment adds the
desired departments:
g11 = Set(m, name = "g11", domain = dep,
description = "departments only selling items supplied by parker")
g11[dep] = Product(sales[dep,item], supply[item,"parker"]);
Note that the indexed operation gamspy.Product()
refers to set intersections in the
context of assignments to dynamic sets. From all departments linked with items only those
are included where all items sold are supplied by "parker"
. This means that
departments that additionally sell items that are not supplied by "parker"
are
excluded. Hence, only "hardware"
and "toy"
are added to g11
.
In [1]: print(*g11.records["dep"], sep=", ")
Out[1]: hardware, toy
Conditional Equations with Dynamic Sets#
where[]
conditions in the context of equations may restrict the domain of the equation
and they may also feature in the algebraic formulation of the equation. In both instances
dynamic sets may be used as part of the logical condition. where[]
conditions with
dynamic sets in the algebra of equations are similar to conditional assignments with dynamic
sets; see section Dynamic Sets in Conditional Assignments above. The example that follows
illustrates the use of a dynamic set to restrict the domain of definition of an equation. In
section Equations Defined over the Domain of Dynamic Sets above we had the following
equation definition:
prodbal1[r] = activity2[r]*price == revenue[r]
Recall that r
is a dynamic set and a subset of the set allr
. Hence this equation may
be rewritten in the following way:
prodbal1[allr].where[r[allr]] = activity2[allr]*price == revenue[allr]
Note that both formulations achieve the same result: restricting the domain of definition to
those elements that belong to the dynamic set r
. While in the second formulation the
condition is specified explicitly, in the first formulation the domain is filtered through
the dynamic set r
. This is the topic of the next subsection.
Filtering through Dynamic Sets#
In certain circumstances the filtering process is an alternative to the where[]
condition
to restrict the domain of equations, sets, variables, parameters and indexed operations. We
already saw an example for restricting the domain of definition of an equation in the previous
subsection. The next example refers to restricting the domain in an indexed operation. In
section Conditional Indexed Operations with Dynamic Sets we had the following assignment:
maxd[...] = Smax(Domain(i,j).where[can_do[i,j]], d[i,j])
Recall that maxd
is a scalar, i
and j
are sets, can_do
is a dynamic set and
d
is a twodimensional parameter. Note that the conditional set is the dynamic set
can_do
. The assignment may be rewritten in the following way:
maxd[...] = Smax(can_do[i,j], d[i,j])
Here the indexed operation is filtered through the dynamic set can_do
, a where[]
condition is not necessary.
Sets as Sequences: Ordered Sets#
Introduction#
We initially stated that in general, sets in GAMSPy are regarded as an unordered collection of labels. However, in some contexts, say, multiperiod planning models, some sets need to be treated as if they were sequences. In this chapter we will establish the notion of ordered sets and we will cover their special features and the associated operations.
Examples where ordered sets are needed include economic models that explicitly represent conditions in different time periods that are linked, location problems where the formulation may require a representation of contiguous areas, as in a grid representation of a city, scheduling problems and programs that model stocks of capital with equations of the form ‘stocks at the end of period \(n\) are equal to stocks at the end of period \(n1\) plus net gains during period \(n\)’.
Note
Models involving sequences of time periods are often called dynamic models, because they describe how conditions change over time. This use of the word dynamic unfortunately has a different meaning from that used in connection with Dynamic Sets, but this is unavoidable.
Ordered and Unordered Sets#
Certain onedimensional sets may be treated as if they were a sequence. Those sets need to be ordered and static. A onedimensional set is ordered if the definition or initialization of the elements in the set corresponds to the order of the labels in the GAMSPy Entry order.
Note
The GAMSPy entry order is the order in which the individual labels first appear in the GAMSPy program.
For the sake of simplicity, sets that are static and ordered are often just referred to as ordered sets.
GAMS maintains a unique element list where all labels that are used as elements in one or
more sets are listed. The order of the elements in any one set is the same as the order of
those elements in the unique element list. This means that the order of a set may not be
what it appears to be if some of the labels were used in an earlier definition. The internal
GAMS order of the labels can be made visible with the getUELs()
method of the
gamspy.Container()
class. A good rule of thumb is that if the user wants a set to be
ordered and the labels in the set have not been used already, then they will be ordered.
In the example below we show ordered and unordered sets and the map showing the order. The input is:
from gamspy import Container, Set
m = Container()
t1 = Set(m, name = "t1", records = ["1987","1988","1989","1990","1991"])
t2 = Set(m, name = "t2", records = ["1983","1984","1985","1986","1987"])
t3 = Set(m, name = "t3", records = ["1987","1989","1991","1983","1985"])
Note that the label "1987"
is the first label seen by GAMS. It appears again as the
last label in the initialization list for the set t2
. This means that the set t2
is not ordered and any attempt to use t2
in a context implying order will cause error
messages. Observe that the set t3
is ordered, as all the members of t3
have appeared
in the GAMSPy program before, and in the same order that they are listed in the definition of
t3
.
In [1]: m.getUELs()
Out[1]: ['1987', '1988', '1989', '1990', '1991', '1983', '1984', '1985', '1986']
Note
A set can always be made ordered by moving its declaration closer to the beginning of the program.
Sorting a Set#
reorderUELs
is a method of all GAMSPy symbol classes. This method allows the user to
reorder UELs of a specific symbol dimension. Example:
from gamspy import Container, Set, Parameter
m = Container()
i = Set(m, "i", records=["i1", "i2", "i3"])
j = Set(m, "j", i, records=["j1", "j2", "j3"])
a = Parameter(m, "a", [i, j], records=[(f"i{i}", f"j{i}", i) for i in range(1,4)])
In [1]: i.getUELs()
Out[1]: ['i1', 'i2', 'i3']
In [2]: m.getUELs()
Out[2]: ['i1', 'i2', 'i3', 'j1', 'j2', 'j3']
But perhaps we want to reorder the UELs i1
, i2
, i3
to i3
, i2
, i1
.
In [1]: i.reorderUELs(['i3', 'i2', 'i1'])
In [2]: i.getUELs()
Out[2]: ['i3', 'i2', 'i1']
In [3]: i.records
Out[3]:
uni element_text
0 i1
1 i2
2 i3
Note that this example does not change the indexing scheme of the Pandas DataFrame at all, it only changes the underlying integer numbering scheme for the categories. We can see this by looking at the Pandas codes:
In [1]: i.records["uni"].cat.codes
Out[1]:
0 2
1 1
2 0
dtype: int8