Set Handling Functions
LINGO offers several functions that assist with handling sets. The @IN function determines if a set element is contained in a set. The @INDEX function returns the index of a set element within its set, while @INSERT allows you to dynamically add set members to a derived set. The @SIZE function returns the number of elements in a set. Finally, the @WRAP function is useful for "wrapping" set indices from one end of a time horizon to another in multiperiod planning models. These are described in more detail below.
@IN( set_name, primitive_1_index [, primitive_2_index ...])
This returns TRUE if the set member referenced by the primitive set member index tuple (primitive_1_index, primitive_2_index, ...) is contained in the set_name set. As the following example shows, the @IN operator is useful for generating complements of subsets in set membership conditions.
Example 1:
For example, to derive a set of open plants based on a subset of closed plants, your sets section might resemble the following:
SETS:
PLANTS / SEATTLE, DENVER,
CHICAGO, ATLANTA/:;
CLOSED( PLANTS) /DENVER/:;
OPEN( PLANTS) |
#NOT# @IN( CLOSED, &1):;
ENDSETS
The OPEN set is derived from the PLANTS set. We use a membership condition containing the @IN function to allow only those plants not contained in the CLOSED set to be in the OPEN set.
Example 2:
In this example, we illustrate how to determine if the set element (B, Y) belongs to the derived S3 set. In this case, (B, Y) is indeed a member of S3, so X will be set to 1. Note that, in order to get the index of the primitive set elements B and Y, we made use of the @INDEX function, which is discussed next.
SETS:
S1 / A B C/:;
S2 / X Y Z/:;
S3( S1, S2) / A,X A,Z B,Y C,X/:;
ENDSETS
X = @IN( S3, @INDEX( S1, B), @INDEX( S2, Y));
@INDEX( set_name, set_member)
This returns the index of a set member set_member in the set set_name. If the specified set member does not belong to the set @INDEX will return 0. Unlike @IN, which requires you to specify the indices for the set elements, @INDEX allows you to refer to set member names directly.
Example 1:
In this example, we illustrate how to get the index of set member (R1, C3) in the derived S3 set. In this case, (R1, C3) is the third member SRXC, so NDX will be set to 3.
SETS:
ROWS /R1..R27/;
COLS /C1..C3/;
RXC( ROWS, COLS): XRNG;
ENDSETS
! return the index of (r1,c3) in the rxc set;
NDX = @INDEX( RXC, R1, C3);
LINGO allows you to omit the set name argument if the set member belongs to a primitive set. This is to maintain compatibility with earlier releases of LINGO. As the following example illustrates, it's good practice to always specify a set name in the @INDEX function:
Example 2:
A model's set elements can come from external sources that the modeler may have little control over. This can potentially lead to confusion when using the @INDEX function. Consider the sets section:
SETS:
GIRLS /DEBBIE, SUE, ALICE/;
BOYS /BOB, JOE, SUE, FRED/;
ENDSETS
Now, suppose you want to get the index of the boy named Sue within the BOYS set. The value of this index should be 3. Simply using @INDEX( SUE) would return 2 instead of 3, because LINGO finds SUE in the GIRLS set first. In this case, to get the desired result, you must specify the BOYS set as an argument and enter @INDEX( BOYS, SUE).
Note: | The set member argument to @INDEX is considered to be a text literal. The following example illustrates how this can potentially lead to unexpected results. |
Example 3:
In the model below, we loop over the GIRLS set using a set index variable named G. Given that @INDEX considers the set member argument (G in this case) to be a text literal, each element of XINDEX will be set to 0; this is because the GIRLS set does not contain a set member called G. On the other hand, the elements of XIN will all be set to 1, because @IN treats G as a set index variable, as opposed to a text literal.
SETS:
GIRLS /DEBBIE, SUE, ALICE/: XINDEX, XIN;
BOYS /BOB, JOE, SUE, FRED/;
ENDSETS
!XINDEX will be 0 because GIRLS does not contain the member 'G';
!XIN will be 1 because G is treated as a set index variable as
opposed to the text literal 'G';
@FOR( GIRLS( G):
XINDEX( G) = @INDEX( GIRLS, G);
XIN( G) = @IN( GIRLS, G);
);
@INSERT( set_name, primitive_1_index [, primitive_2_index ...])
This function may be used to dynamically add members to derived sets. Each of the primitive set members forming the new derived set member must have been included in their respective primitive sets. The following example illustrates:
MODEL:
SETS:
PRIMITIVE;
LATTICE( PRIMITIVE, PRIMITIVE);
ENDSETS
DATA:
PRIMITIVE = 1..10;
ENDDATA
CALC:
! Starting at 1,1, generate the lattice of points reachable
by the two integer vectors;
X1 = 1; Y1 = 3;
X2 = 7; Y2 = 5;
@INSERT( LATTICE, 1,1); ! insert the seed;
@SET( 'TERSEO', 3); !minimal output;
! Now generate all the points reachable, directly or
indirectly from the seed via the two vectors within
a finite region;
@FOR( LATTICE( I, J) | I #LE# @SIZE( PRIMITIVE):
I1 = I + X1;
J1 = J + Y1;
@IFC( #NOT# @IN( LATTICE, I1, J1):
@IFC( @IN( PRIMITIVE, I1) #AND# @IN( PRIMITIVE, J1):
@INSERT( LATTICE, I1, J1);
);
);
I1 = I + X2;
J1 = J + Y2;
@IFC( #NOT# @IN( LATTICE, I1, I1):
@IFC( @IN( PRIMITIVE, I1) #AND# @IN( PRIMITIVE, J1):
@INSERT( LATTICE, I1, J1);
);
);
);
ENDCALC
DATA:
!display the lattice set;
@TEXT() = @TABLE( LATTICE);
ENDDATA
END
Model: LATTICE
In this model, we want to find all the points on a 10-by-10 grid that are reachable, either directly or indirectly, from point (1,1) using combinations of the two vectors (1,3) and (7,5). The grid is represented by the 2-dimensional set LATTICE. Initially, LATTICE is empty, but we will add members using @INSERT if we discover they are reachable from the seed (1,1).
First, we place the seed into the LATTICE with the following insert statement:
@INSERT( LATTICE, 1, 1); ! insert the seed;
The following doubly-nested loop iterates through the entire 10x10 grid:
@FOR( LATTICE( I, J) | I #LE# @SIZE( PRIMITIVE):
@FOR( VECTORS( I2, J2):
I3 = I + I2;
J3 = J + J2;
@IFC( #NOT# @IN( LATTICE, I3, J3):
@IFC( @IN( PRIMITIVE, I3) #AND# @IN( PRIMITIVE, J3):
@INSERT( LATTICE, I3, J3);
);
);
);
);
Each of the two vectors are then added individually to the current point, with the resulting point tested to see if a) if it has not already been added to LATTICE, and b) if it lies within the 10x10 grid. If the new point passes both these tests, it gets added to the lattice using an @INSERT statement:
@INSERT( LATTICE, I3, J3);
At the end of the model we use the @TABLE output function to display the lattice we found:
1 2 3 4 5 6 7 8 9 10
1 X
2 X
3 X
4 X
5
6
7
8 X
9 X
10
Here we see that, in addition to the seed, the following points are members of the lattice: (2,4), (3,7), (4,10), (8,6) and (9,9).
@SIZE( set_name)
This returns the number of elements in the set_name set. Using the @SIZE function is preferred to explicitly listing the size of a set in a model. This serves to make your models more data independent and, therefore, easier to maintain should the size of your sets change.
To view an example of the @SIZE function, refer to the PERT/CPM example in the Sparse Derived Set Example - Explicit List section of Using Sets.
@WRAP( INDEX, LIMIT)
This allows you to "wrap" an index around the end of a set and continue indexing at the other end of the set. That is, when the last (first) member of a set is reached in a set looping function, use of @WRAP will allow you to wrap the set index to the first (last) member of the set. This is a particularly useful function in cyclical, multiperiod planning models.
Formally speaking, @WRAP returns J such that J = INDEX - K * LIMIT, where K is an integer such that J is in the interval [1,LIMIT]. Informally speaking, @WRAP will subtract or add limit to index until it falls in the range 1 to LIMIT.
For an example on the use of the @WRAP function in a staff scheduling model, refer to the Primitive Set Example section in Using Sets.