PROC OPTMODEL defines several new types of expressions for the manipulation of sets. Aggregation operators combine values of an expression that is evaluated over the members of an index set. Other operators create new sets by combining existing sets, or they test relationships between sets. PROC OPTMODEL also supports an IF expression operator that can conditionally evaluate expressions. These and other such expressions are described in this section.
AND { index-set } logic-expression
The AND aggregation operator evaluates the logical expression logic-expression jointly for each member of the index set index-set. The index set enumeration finishes early if the logic-expression evaluation produces a false value (zero or missing). The expression returns 0 if a false value is found or returns 1 otherwise. The following statements demonstrate both a true and a false result:
proc optmodel; put (and{i in 1..5} i < 10); /* returns 1 */ put (and{i in 1..5} i NE 3); /* returns 0 */
CARD ( set-expression )
The CARD function returns the number of members of its set operand. For example, the following statements produce the output
3
since the set has 3 members:
proc optmodel; put (card(1..3));
set-expression-1 CROSS set-expression-2
The CROSS expression returns the crossproduct of its set operands. The result is the set of tuples formed by concatenating the tuple value of each member of the left operand with the tuple value of each member of the right operand. Scalar set members are treated as tuples of length 1. The following statements demonstrate the CROSS operator:
proc optmodel; set s1 = 1..2; set<string> s2 = {'a', 'b'}; set<number, string> s3=s1 cross s2; put 's3 is ' s3; set<number, string, number> s4 = s3 cross 4..5; put 's4 is ' s4;
This code produces the output in Figure 5.34.
Figure 5.34: CROSS Expression Output
set-expression-1 DIFF set-expression-2
The DIFF operator returns a set that contains the set difference of the left and right operands. The result set contains values that are members of the left operand but not members of the right operand. The operands must have compatible set types. The following statements evaluate and print a set difference:
proc optmodel; put ({1,3} diff {2,3}); /* outputs {1} */
IF logic-expression THEN expression-2 ELSE expression-3
The IF-THEN/ELSE expression evaluates the logical expression logic-expression and returns the result of evaluating the second or third operand expression according to the logical test result. If the logic-expression is true (nonzero and nonmissing), then the result of evaluating expression-2 is returned. If the logic-expression is false (zero or missing), then the result of evaluating expression-3 is returned. The other subexpression that is not selected is not evaluated.
An ELSE clause is matched during parsing with the nearest IF-THEN clause that does not have a matching ELSE. The ELSE clause can be omitted for numeric expressions; the resulting IF-THEN is handled as if a default ELSE 0 clause were supplied.
Use the IF-THEN/ELSE expression to handle special cases in models. For example, an inventory model based on discrete time periods might require special handling for the first or last period. In the following example the initial inventory for the first period is assumed to be fixed:
proc optmodel; number T; var inv{1..T}, order{1..T}; number sell{1..T}; number inv0; . . . /* balance inventory flow */ con iflow{i in 1..T}: inv[i] = order[i] - sell[i] + if i=1 then inv0 else inv[i-1]; . . .
The IF-THEN/ELSE expression in the example models the initial inventory for a time period i. Usually the inventory value is the inventory at the end of the previous period, but for the first time period the inventory
value is given by the inv0
parameter. The iflow
constraints are linear because the IF-THEN/ELSE test subexpression does not depend on variables and the other subexpressions
are linear.
IF-THEN/ELSE can be used as either a set expression or a scalar expression. The type of expression depends on the subexpression between the THEN and ELSE keywords. The type used affects the parsing of the subexpression that follows the ELSE keyword because the set form has a lower operator precedence. For example, the following two expressions are equivalent because the numeric IF-THEN/ELSE has a higher precedence than the range operator (..):
IF logic THEN 1 ELSE 2 .. 3 (IF logic THEN 1 ELSE 2) .. 3
But the set form of IF-THEN/ELSE has lower precedence than the range expression operator. So the following two expressions are equivalent:
IF logic THEN 1 .. 2 ELSE 3 .. 4 IF logic THEN (1 .. 2) ELSE (3 .. 4)
The IF-THEN and IF-THEN/ELSE operators always have higher precedence than the logic operators. So, for example, the following two expressions are equivalent:
IF logic THEN numeric1 < numeric2 (IF logic THEN numeric1) < numeric2
It is best to use parentheses when in doubt about precedence.
expression IN set-expression
expression NOT IN set-expression
The IN expression returns 1 if the value of the left operand is a member of the right operand set. Otherwise, the IN expression returns 0. The NOT IN operator logically negates the returned value. Unlike the DATA step, the right operand is an arbitrary set expression. The left operand can be a tuple expression . The following example demonstrates the IN and NOT IN operators:
proc optmodel; set s = 1..10; put (5 in s); /* outputs 1 */ put (-1 not in s); /* outputs 1 */ set<num, str> t = {<1,'a'>, <2,'b'>, <2,'c'>}; put (<2, 'b'> in t); /* outputs 1 */ put (<1, 'b'> in t); /* outputs 0 */
{ index-set }
The index set expression returns the set of members of an index set. This expression is distinguished from a set constructor (see the section Set Constructor Expression) because it contains a list of set expressions.
The following statements use an index set with a selection expression that excludes the value 3:
proc optmodel; put ({i in 1..5 : i NE 3}); /* outputs {1,2,4,5} */
set-expression-1 INTER set-expression-2
The INTER operator returns a set that contains the intersection of the left and right operands. This is the set that contains values that are members of both operand sets. The operands must have compatible set types.
The following statements evaluate and print a set intersection:
proc optmodel; put ({1,3} inter {2,3}); /* outputs {3} */
INTER { index-set } set-expression
The INTER aggregation operator evaluates the set-expression for each member of the index set index-set. The result is the set that contains the intersection of the set of values that were returned by the set-expression for each member of the index set. An empty index set causes an expression evaluation error.
The following statements use the INTER aggregation operator to compute the value of {1,2,3,4} {2,3,4,5} {3,4,5,6}:
proc optmodel; put (inter{i in 1..3} i..i+3); /* outputs {3,4} */
MAX { index-set } expression
The MAX aggregation operator evaluates the numeric expression expression for each member of the index set index-set. The result is the maximum of the values that are returned by the expression. Missing values are handled with the SAS numeric sort order; a missing value is treated as smaller than any nonmissing value. If the index set is empty, then the result is the negative number that has the largest absolute value representable on the machine.
The following example produces the output 0.5
:
proc optmodel; put (max{i in 2..5} 1/i);
MIN { index-set } expression
The MIN aggregation operator evaluates the numeric expression expression for each member of the index set index-set. The result is the minimum of the values that are returned by the expression. Missing values are handled with the SAS numeric sort order; a missing value is treated as smaller than any nonmissing value. If the index set is empty, then the result is the largest positive number representable on the machine.
The following example produces the output 0.2
:
proc optmodel; put (min{i in 2..5} 1/i);
OR { index-set } logic-expression
The OR aggregation operator evaluates the logical expression logic-expression for each member of the index set index-set. The index set enumeration finishes early if the logic-expression evaluation produces a true value (nonzero and nonmissing). The result is 1 if a true value is found, or 0 otherwise. The following statements demonstrate both a true and a false result:
proc optmodel; put (or{i in 1..5} i = 2); /* returns 1 */ put (or{i in 1..5} i = 7); /* returns 0 */
PROD { index-set } expression
The PROD aggregation operator evaluates the numeric expression expression for each member of the index set index-set. The result is the product of the values that are returned by the expression. This operator is analogous to the operator used in mathematical notation. If the index set is empty, then the result is 1.
The following example uses the PROD operator to evaluate a factorial:
proc optmodel; number n = 5; put (prod{i in 1..n} i); /* outputs 120 */
expression-1 .. expression-n BY expression
The range expression returns the set of numbers from the specified arithmetic progression. The sequence proceeds from the left operand value up to the right operand limit. The increment between numbers is 1 unless a different value is specified with a BY clause. If the increment is negative, then the progression is from the left operand down to the right operand limit. The result can be an empty set.
For compatibility with the DATA step iterative DO loop construct, the keyword TO can substitute for the range (..) operator.
The limit value is not included in the resulting set unless it belongs in the arithmetic progression. For example, the following range expression does not include 30:
proc optmodel; put (10..30 by 7); /* outputs {10,17,24} */
The actual numbers that the range expression "f..l by i
" produces are in the arithmetic sequence
where
and represents the relative machine precision. The limit is adjusted to avoid arithmetic roundoff errors.
PROC OPTMODEL represents the set specified by a range expression compactly when the value is stored in a parameter location, used as a set operand of an IN or NOT IN expression, used by an iterative DO loop , or used in an index set . For example, the following expression is evaluated efficiently:
999998.5 IN 1..1000000000
{ expression-1 , …expression-n }
The set constructor expression returns the set of the expressions in the member list. Duplicated values are added to the set only once. A warning message is produced when duplicates are detected. The constructor expression consists of zero or more subexpressions of the same scalar type or of tuple expressions that match in length and in element types.
The following statements output a three-member set and warn about the duplicated value 2:
proc optmodel; put ({1,2,3,2}); /* outputs {1,2,3} */
The following example produces a three-member set of tuples, using PROC OPTMODEL parameters and variables. The output is displayed in Figure 5.35.
proc optmodel; number m = 3, n = 4; var x{1..4} init 1; string y = 'c'; put ({<'a', x[3]>, <'b', m>, <y, m/n>});
/ members /
The set literal expression provides compact specification of simple set values. It is equivalent in function to the set constructor expression but minimizes typing for sets that contain numeric and string constant values. The set members are specified by members, which are literal values. As with the set constructor expression , each member must have the same type.
The following statement specifies a simple numeric set:
/1 2.5 4/
The set contains the members 1
, 2.5
, and 4
. A string set could be specified as follows:
/Miami 'San Francisco' Seattle 'Washington, D.C.'/
This set contains the strings ’Miami’
, ’San Francisco’
, ’Seattle’
, and ’Washington, D.C.’
. You can specify string values in set literals without quotation marks when the text follows the rules for a SAS name. Strings
that begin with a digit or contain blanks or other special characters must be specified with quotation marks.
Specify tuple members of a set by enclosing the tuple elements within angle brackets (<
elements>
). The tuple elements can be specified with numeric and string literals. The following example includes the tuple elements
<’New York’, 4.5>
and <’Chicago’, -5.7>
:
/<'New York' 4.5> <Chicago -5.7>/
SETOF { index-set } expression
The SETOF aggregation operator evaluates the expression expression for each member of the index set index-set. The result is the set that is formed by collecting the values returned by the operand expression. The operand can be a tuple expression . For example, the following statements produce a set of tuples of numbers with their squared and cubed values:
proc optmodel; put (setof{i in 1..3}<i, i*i, i**3>);
Figure 5.36 shows the displayed output.
SLICE ( < element-1, …element-n > , set-expression )
The SLICE expression produces a new set by selecting members in the operand set that match a pattern tuple. The pattern tuple is specified by the element list in angle brackets. Each element in the pattern tuple must specify a numeric or string expression. The expressions are used to match the values of the corresponding elements in the operand set member tuples. You can also specify an element by using an asterisk (*). The sequence of element values that correspond to asterisk positions in each matching tuple is combined into a tuple of the result set. At least one asterisk element must be specified.
The following statements demonstrate the SLICE expression:
proc optmodel; put (slice(<1,*>, {<1,3>, <1,0>, <3,1>})); put (slice(<*,2,*>, {<1,2,3>, <2,4,3>, <2,2,5>}));
These statements produce the output in Figure 5.37.
For the first PUT statement, <1,*>
matches set members <1,3> and <1,0> but not <3,1>. The second element of each matching set tuple, corresponding to the asterisk
element, becomes the value of the resulting set member. In the second PUT statement, the values of the first and third elements
of the operand set member tuple are combined into a two-position tuple in the result set.
The following statements use the SLICE expression to help compute the transitive closure of a set of tuples representing a
relation by using Warshall’s algorithm. In these statements the set parameter dep
represents a direct dependency relation.
proc optmodel; set<str,str> dep = {<'B','A'>, <'C','B'>, <'D','C'>}; set<str,str> cl; set<str> cn; cl = dep; cn = (setof{<i,j> in dep} i) inter (setof{<i,j> in dep} j); for {node in cn} cl = cl union (slice(<*,node>,cl) cross slice(<node,*>,cl)); put cl;
The local dummy parameter node
in the FOR statement iterates over the set cn
of possible intermediate nodes that can connect relations transitively. At the end of each FOR iteration, the set parameter
cl
contains all tuples from the original set in addition to all transitive tuples found in the current or previous iterations.
The output in Figure 5.38 includes the indirect and direct transitive dependencies from the set dep
.
Figure 5.38: Warshall's Algorithm Output
A special form of index-set-item uses the SLICE expression implicitly. See the section More on Index Sets for details.
SUM { index-set } expression
The SUM aggregation operator evaluates the numeric expression expression for each member in the index set index-set. The result is the sum of the values that are returned by the expression. If the index set is empty, then the result is 0. This operator is analogous to the operator that is used in mathematical notation. The following statements demonstrate the use of the SUM aggregation operator:
proc optmodel; put (sum {i in 1..10} i); /* outputs 55 */
set-expression-1 SYMDIFF set-expression-2
The SYMDIFF expression returns the symmetric set difference of the left and right operands. The result set contains values that are members of either the left or right operand but are not members of both operands. The operands must have compatible set types.
The following example demonstrates a symmetric difference:
proc optmodel; put ({1,3} symdiff {2,3}); /* outputs {1,2} */
< expression-1, …expression-n >
A tuple expression represents the value of a member in a set of tuples. Each scalar subexpression inside the angle brackets represents the value of a tuple element. This form is used only with IN , SETOF , and set constructor expressions.
The following statements demonstrate the tuple expression:
proc optmodel; put (<1,2,3> in setof{i in 1..2}<i,i+1,i+2>); put ({<1,'a'>, <2,'b'>} cross {<3,'c'>, <4,'d'>});
The first PUT statement checks whether the tuple <1, 2, 3> is a member of a set of tuples. The second PUT statement outputs the crossproduct of two sets of tuples that are constructed by the set constructor.
These statements produce the output in Figure 5.39.
set-expression-1 UNION set-expression-2
The UNION expression returns the set union of the left and right operands. The result set contains values that are members of either the left or right operand. The operands must have compatible set types. The following example performs a set union:
proc optmodel; put ({1,3} union {2,3}); /* outputs {1,3,2} */
UNION { index-set } set-expression
The UNION aggregation expression evaluates the set-expression for each member of the index set index-set. The result is the set union of the values that are returned by the set-expression. If the index set is empty, then the result is an empty set.
The following statements demonstrate a UNION aggregation. The output is the value of {1,2,3,4} {2,3,4,5} {3,4,5,6}.
proc optmodel; put (union{i in 1..3} i..i+3); /* outputs {1,2,3,4,5,6} */
set-expression-1 WITHIN set-expression-2
set-expression NOT WITHIN set-expression
The WITHIN expression returns 1 if the left operand set is a subset of the right operand set and returns 0 otherwise. (That is, the operator returns true if every member of the left operand set is a member of the right operand set.) The NOT WITHIN form logically negates the result value. The following statements demonstrate the WITHIN and NOT WITHIN operators:
proc optmodel; put ({1,3} within {2,3}); /* outputs 0 */ put ({1,3} not within {2,3}); /* outputs 1 */ put ({1,3} within {1,2,3}); /* outputs 1 */