The OPTMODEL Procedure
Programming Statements
- Assignment Statement
- CALL Statement
- CLOSEFILE Statement
- COFOR Statement
- CONTINUE Statement
- CREATE DATA Statement
- DO Statement
- DO Statement, Iterative
- DO UNTIL Statement
- DO WHILE Statement
- DROP Statement
- END Statement
- ENDSOURCE Statement
- ENDSUBMIT Statement
- EXPAND Statement
- FILE Statement
- FIX Statement
- FOR Statement
- IF Statement
- LEAVE Statement
- Null Statement
- PRINT Statement
- PROFILE Statement
- PUT Statement
- QUIT Statement
- READ DATA Statement
- RESET OPTIONS Statement
- RESTORE Statement
- SAVE MPS Statement
- SAVE QPS Statement
- SOLVE Statement
- SOURCE Statement
- STOP Statement
- SUBMIT Statement
- UNFIX Statement
- USE PROBLEM Statement
PROC OPTMODEL supports several programming statements. You can perform various actions with these statements, such as reading or writing data sets, setting parameter values, generating text output, or invoking a solver.
Statements are read from the input and are executed immediately when complete. Certain statements can contain one or more substatements. The execution of substatements is held until the statements that contain them are submitted. Parameter values that are used by expressions in programming statements are resolved when the statement is executed; this resolution might cause errors to be detected. For example, the use of undefined parameters is detected during resolution of the symbolic expressions from declarations.
A statement is terminated by a semicolon. The positions at which semicolons are placed are shown explicitly in the following statement syntax descriptions.
The programming statements can be grouped into the categories shown in Table 7.
Table 7: Types of Programming Statements in PROC OPTMODEL
| Control | Looping | General | Input/Output | Model |
|---|---|---|---|---|
| DO | COFOR | Assignment | CLOSEFILE | DROP |
| END | CONTINUE | CALL | CREATE DATA | EXPAND |
| IF | DO Iterative | ENDSOURCE | FILE | FIX |
| Null (;) | DO UNTIL | ENDSUBMIT | RESTORE | |
| QUIT | DO WHILE | PROFILE | PUT | SOLVE |
| STOP | FOR | RESET OPTIONS | READ DATA | UNFIX |
| LEAVE | SOURCE | SAVE MPS | USE PROBLEM | |
| SUBMIT | SAVE QPS |
Assignment Statement
identifier-expression = expression;
The assignment statement assigns a variable or parameter value. The type of the target identifier-expression must match the type of the right-hand-side expression.
For example, the following statements set the current value for variable x to 3:
proc optmodel;
var x;
x = 3;
Note: Parameters that were declared with the equal sign (=) initialization forms must not be reassigned a value with an assignment statement. If this occurs, PROC OPTMODEL reports an error.
CALL Statement
The CALL statement invokes the named library subroutine. The values that are determined for each argument expression are passed to the subroutine when the subroutine is invoked. The subroutine can update the values of PROC OPTMODEL parameters and variables when an argument is an identifier-expression (see the section Identifier Expressions). For example, the following statements set the parameter array a to a random permutation of 1 to 4:
proc optmodel;
number a{i in 1..4} init i;
number seed init -1;
call ranperm(seed, a[1], a[2], a[3], a[4]);
Note: The maximum length of the string value returned from an output argument is equal to the character length of the argument before the call. An undefined STRING parameter that is used as an output argument has a character length of 8.
For a list of CALL routines, see SAS Functions and CALL Routines: Reference. You can also call subroutines that are compiled by the FCMP procedure. For more information, see the section FCMP Routines.
CLOSEFILE Statement
CLOSEFILE file-specifications;
The CLOSEFILE statement closes files that were opened by the FILE statement. Each file is specified by a logical name, a physical file name in quotation marks, or an expression enclosed in parentheses that evaluates to a physical file name. See the section FILE Statement for more information about file specifications.
The following example shows how the CLOSEFILE statement is used with a logical file name:
filename greet 'hello.txt';
proc optmodel;
file greet;
put 'Hi!';
closefile greet;
Generally you must close a file with a CLOSEFILE statement before external programs can access the file. However, any open files are automatically closed when PROC OPTMODEL terminates.
COFOR Statement
COFOR { index-set } statement;
The COFOR statement executes its statement for each member of the specified index-set, similar to how the FOR statement executes. However, in a COFOR statement, PROC OPTMODEL can execute the SOLVE statement concurrently with other statements. The execution of the COFOR substatement is interleaved between loop iterations so that other iterations can be processed while an iteration waits for a SOLVE statement to complete. Multiple solvers can run concurrently. This interleaving is managed so that in many cases a FOR loop can be replaced by a COFOR loop to achieve concurrency with minimal or no other changes to the code.
The following code shows a simple example:
proc optmodel printlevel=0 sessref=mysess;
var x {1..6} >= 0;
minimize z = sum {j in 1..6} x[j];
con a1: x[1] + x[2] + x[3] <= 4;
con a2: x[4] + x[5] + x[6] <= 6;
con a3: x[1] + x[4] >= 5;
con a4: x[2] + x[5] >= 2;
con a5: x[3] + x[6] >= 3;
cofor{i in 3..5} do;
fix x[1]=i;
solve;
put i= x[1]= _solution_status_=;
end;
Figure 7 shows the PROC OPTMODEL output. The order of the output from different iterations can vary between runs, depending on the order in which the SOLVE statements complete. Because the SESSREF= option is used, the solvers execute in the distributed computing environment. A FOR statement could have been used instead of COFOR; the FOR statement would produce a consistent output order but only one solver would be executed at a time. Note that because the solver execution in this example is trivial, the benefits from concurrency are limited.
Figure 7: A Simple COFOR Loop
i=4 x[1]=4 _SOLUTION_STATUS_=OPTIMAL i=5 x[1]=5 _SOLUTION_STATUS_=INFEASIBLE i=3 x[1]=3 _SOLUTION_STATUS_=OPTIMAL |
A COFOR statement can contain other control and looping statements, including nested COFOR loops. The maximum number of threads that can be used is controlled by the PROC OPTMODEL and SAS options that are in effect when the outermost COFOR loop is entered, as described in the section Threaded and Distributed Processing. The outermost COFOR statement allocates threads for execution on the computer that is running PROC OPTMODEL. When you use a SESSREF= or SESSUUID= option to request distributed computing, the outermost COFOR statement also creates a distributed execution environment. Solvers within the COFOR loop can then run remotely in single-machine mode on the compute nodes (as shown in the solver output).
The COFOR statement supports simultaneous processing of several SOLVE statements. Processing proceeds through the iteration body statements as it would through a FOR loop until a SOLVE statement is executed. After the problem is generated, the solver starts processing in a background thread (or remote computing node in the distributed case) and the COFOR loop switches execution to another iteration of the loop, assuming enough threads and iterations are available. (Note that you need at least two threads on the computer that is running the COFOR loop to enable overlap of statement execution with solver execution.) Execution could switch to an existing iteration where the solver has completed. Alternatively, a new iteration of a COFOR loop could be started. All output from an iteration, except within a SUBMIT block, is displayed together after the iteration has completed. Output from a SUBMIT block is displayed as the block is executed.
You can change the NTHREADS= option value within a COFOR loop by using the RESET OPTIONS statement. The new value affects SOLVE statements that are executed subsequently in the same iteration but not those in other iterations. When a COFOR statement is running in distributed mode, the default value of the NTHREADS= option is the number of threads per compute node, as determined when the outermost COFOR loop starts. Otherwise the default value of the NTHREADS= option is 1. Executing SOLVE statements in the background by using a single thread usually provides the best performance on a single computer.
The order in which the solvers complete is unpredictable. So it is usually not useful for a problem that is solved within an iteration to depend on the results of SOLVE statements that are executed in other iterations of the COFOR loop. It is advisable to limit global parameter updates to operations where order is not important, such as accumulating counts, sums, or unions or writing mutually exclusive subsets of an array. It is possible to execute multiple SOLVE statements within a loop iteration, and subsequent solver invocations within an iteration can use results from prior solvers in the same iteration.
In many cases, a COFOR loop iteration solves a specialized version of a common problem structure. This requires it to modify problem attributes that are also used in other iterations, such as coefficient values or the fixed status of variables. Changes to problem attributes are not made visible to other iterations of a COFOR loop in order to avoid confusing behavior due to interleaved execution. For example, the value printed for x[1] in Figure 7 is the local value for the iteration, not the most recent global value. Changes to these attributes create or update a copy of the value that is local to the iteration. These attribute values along with the local dummy parameters provide a local context for the iteration.
The following problem attributes are automatically made local to the modifying iteration when they are changed within a COFOR loop:
the current problem, selected by USE PROBLEM
the value of variables and their suffix values
the fixed status of variables
the constraint suffix values
the dropped status of constraints
the .LABEL suffix
NUMBER, STRING, and SET parameters that determine values that are used in the bounds or body expressions of problem declarations (CONSTRAINT, IMPVAR, MIN, MAX, or VAR)
NUMBER, STRING, and SET parameters that determine values that are used in solver arguments within the same outermost COFOR loop
the predeclared string parameters _SOLVER_OPTIONS_ and _solver_OPTIONS_ (for each solver)
To illustrate these rules, consider the following code, which uses the NLP solver to solve a MINLP portfolio optimization problem by selecting random subsets of the assets to optimize:
proc optmodel printlevel=0 nthreads=4;
/* assets and related parameters */
set ASSETS;
num return {ASSETS};
num cov {ASSETS, ASSETS} init 0;
read data means into ASSETS=[_n_] return;
read data covdata into [asset1 asset2] cov cov[asset2,asset1]=cov;
num riskLimit init 0.00025;
num minThreshold init 0.1;
num numTrials = 10;
/* number of random trials */
set TRIALS = 1..numTrials;
/* declare NLP problem for fixed set of assets */
set ASSETS_THIS;
var AssetPropVar {ASSETS} >= minThreshold <= 1;
max ExpectedReturn = sum {i in ASSETS} return[i] * AssetPropVar[i];
con RiskBound:
sum {i in ASSETS_THIS, j in ASSETS_THIS}
cov[i,j] * AssetPropVar[i] * AssetPropVar[j] <= riskLimit;
con TotalPortfolio:
sum {asset in ASSETS} AssetPropVar[asset] = 1;
/* parameters to track best solution */
num infinity = constant('BIG');
num best_objective init -infinity;
set INCUMBENT;
/* iterate over trials */
num start {TRIALS};
num finish {TRIALS};
num overall_start;
overall_start = time();
call streaminit(1);
cofor {trial in TRIALS} do;
start[trial] = time() - overall_start;
put;
put trial=;
ASSETS_THIS = {i in ASSETS: rand('UNIFORM') < 0.5};
put ASSETS_THIS=;
for {i in ASSETS diff ASSETS_THIS}
fix AssetPropVar[i] = 0;
solve with NLP / logfreq=0;
put _solution_status_=;
if _solution_status_ ne 'INFEASIBLE' then do;
if best_objective < ExpectedReturn then do;
best_objective = ExpectedReturn;
INCUMBENT = ASSETS_THIS;
end;
end;
finish[trial] = time() - overall_start;
end;
put best_objective= INCUMBENT=;
create data plotdata from [trial] start finish;
proc sgplot data=plotdata;
highlow y=trial low=start high=finish / type=bar x2axis;
yaxis reverse;
x2axis label="seconds";
run;
All the COFOR loop iterations use the same problem, _START_. However, the changes to the problem are local to the iteration that makes them. For example, the FIX statement does not affect variables in other iterations. The value of the ASSETS_THIS parameter is used by the RiskBound constraint, so the change to it is local. Because AssetPropVar is a VAR, the changes to its value are also local.
On the other hand, the values of the best_objective and INCUMBENT parameters do not affect any problem declarations. Therefore, their global values are used, enabling the code in the COFOR loop to select and save the best result. Similarly, the start and finish parameters are not used in the problem and allow the overlapping of iterations to be illustrated. Figure 8 from the SGPLOT procedure shows how the iterations have overlapped execution times.
Figure 8: Overlapped COFOR Iterations

Changes to problem attributes from completed iterations are made visible after the loop is finished. They appear in the context that contained the COFOR statement. If multiple iterations modify the same problem attribute value, then the value from the iteration that completed last is the one made visible.
The LEAVE statement can be used to terminate execution of a COFOR loop. This completes the current iteration of the COFOR loop. The currently active solvers for the COFOR loop are terminated, and the output of the incomplete iterations is discarded. The CONTINUE statement within a COFOR loop can also be used to complete the current iteration, but it has no effect on other iterations.
Using the LEAVE statement to terminate is useful, for example, when a sufficiently good solution is found for a problem. The preceding code has been modified as follows to keep generating solutions until a time limit is reached. The code sets a time limit and then executes the LEAVE statement to stop processing when the limit is exceeded. The COFOR loop uses a very large iteration range to allow it to run indefinitely.
proc optmodel printlevel=0;
set ASSETS;
num return {ASSETS};
num cov {ASSETS, ASSETS} init 0;
read data means into ASSETS=[_n_] return;
read data covdata into [asset1 asset2] cov cov[asset2,asset1]=cov;
num riskLimit init 0.00025;
num minThreshold init 0.1;
/* declare NLP problem for fixed set of assets */
set ASSETS_THIS;
var AssetPropVar {ASSETS} >= minThreshold <= 1;
max ExpectedReturn = sum {i in ASSETS} return[i] * AssetPropVar[i];
con RiskBound:
sum {i in ASSETS_THIS, j in ASSETS_THIS}
cov[i,j] * AssetPropVar[i] * AssetPropVar[j] <= riskLimit;
con TotalPortfolio:
sum {asset in ASSETS} AssetPropVar[asset] = 1;
num infinity = constant('BIG');
num best_objective init -infinity;
set INCUMBENT;
/* run for 30 seconds */
num last_time;
last_time = time() + 30;
num n_trials init 0;
call streaminit(1);
cofor {trial in 1..1e9} do;
put;
put trial=;
ASSETS_THIS = {i in ASSETS: rand('UNIFORM') < 0.5};
put ASSETS_THIS=;
for {i in ASSETS diff ASSETS_THIS} fix AssetPropVar[i] = 0;
solve with NLP / logfreq=0;
put _solution_status_=;
if _solution_status_ ne 'INFEASIBLE' then do;
if best_objective < ExpectedReturn then do;
best_objective = ExpectedReturn;
INCUMBENT = ASSETS_THIS;
end;
end;
n_trials = n_trials + 1;
if time() >= last_time then leave;
end;
put n_trials=;
put best_objective= INCUMBENT=;
quit;
CONTINUE Statement
CONTINUE ;
The CONTINUE statement terminates the current iteration of the loop statement (iterative DO, DO UNTIL, DO WHILE, FOR, or COFOR) that immediately contains the CONTINUE statement. Execution resumes at the start of the loop after checking WHILE or UNTIL tests. The FOR, COFOR, or iterative DO loops apply new iteration values.
CREATE DATA Statement
The CREATE DATA statement creates a new SAS data set and copies data into it from PROC OPTMODEL parameters and variables. The CREATE DATA statement can create a data set with a single observation or a data set with observations for every location in one or more arrays. The data set is closed after the execution of the CREATE DATA statement.
Note: Currently, the source data are always located on the client.
The arguments to the CREATE DATA statement are as follows:
Each column or key-column defines output data set variables and a data source for a column. For example, the following statement generates the output SAS data set resdata from the PROC OPTMODEL array opt, which is indexed by the set indset:
create data resdata from [solns]=indset opt;
The output data set variable solns contains the index elements in indset.
Columns
Columns can have the following forms:
Figure 9: CREATE DATA with COL Expression
| Obs | i | xa | xb | xc |
|---|---|---|---|---|
| 1 | 1 | 2 | 2 | 2 |
| 2 | 2 | 2 | 2 | 2 |
| 3 | 3 | 2 | 2 | 2 |
Note: When no key-columns are specified, the output data set has a single observation.
The following statements incorporate several of the preceding examples to create and print a data set by using PROC OPTMODEL parameters:
proc optmodel;
number m = 7, n = 5;
create data example from m n ratio=(m/n) col("s"||n)=(m+n);
proc print;
run;
The output from the PRINT procedure is shown in Figure 10.
Figure 10: CREATE DATA for Single Observation
| Obs | m | n | ratio | s5 |
|---|---|---|---|---|
| 1 | 7 | 5 | 1.4 | 12 |
Column Options
Each column or key-column that defines a data set variable can be followed by zero or more of the following modifiers:
The following statements demonstrate the use of column options, including the use of multiple options for a single column:
proc optmodel;
num sq{i in 1..10} = i*i;
create data squares from [i/format=hex2./length=3] sq/format=6.2;
proc print;
run;
The output from the PRINT procedure is shown in Figure 11.
Figure 11: CREATE DATA with Column Options
| Obs | i | sq |
|---|---|---|
| 1 | 01 | 1.00 |
| 2 | 02 | 4.00 |
| 3 | 03 | 9.00 |
| 4 | 04 | 16.00 |
| 5 | 05 | 25.00 |
| 6 | 06 | 36.00 |
| 7 | 07 | 49.00 |
| 8 | 08 | 64.00 |
| 9 | 09 | 81.00 |
| 10 | 0A | 100.00 |
Key Columns
Key-columns declare index values that enable multiple observations to be written from array columns. An observation is created for each unique index value combination. The index values supply the index for array columns that do not have an explicit index.
Key-columns define the data set variables where the index value elements are written. They can also declare local dummy parameters for use in expressions in the columns. Key-columns are syntactically similar to columns, but are more restricted in form. The following forms of key-columns are allowed:
A key-set in the CREATE DATA statement explicitly specifies the set of index values. You can specify key-set as an identifier expression, an index set expression, a set constructor expression, or an arbitrary set expression enclosed in parentheses. When you specify key-set as an index set expression, the index-set dummy parameters remain visible in columns and override dummy parameters that are declared in the key-columns items with the same name.
The following statements create a data set from the PROC OPTMODEL parameter m, a matrix whose only nonzero entries are located at (1, 1) and (4, 1):
proc optmodel;
number m{1..5, 1..3} = [[1 1] 1 [4 1] 1 [*] 0];
set ISET = setof{i in 1..2} i**2;
create data example
from [i j] = {i in ISET, {1, 2}} m;
proc print data=example noobs;
run;
The dummy parameter i in the key-set expression takes precedence over the dummy parameter i declared in the key-columns item. The output from these statements is shown in Figure 12.
Figure 12: CREATE: key-set with SETOF Aggregation Expression
| i | j | m |
|---|---|---|
| 1 | 1 | 1 |
| 1 | 2 | 0 |
| 4 | 1 | 1 |
| 4 | 2 | 0 |
If no key-set is specified, then the set of index values is formed from the union of the index sets of the implicitly indexed columns. The number of index elements for each implicitly indexed array must match the number of key-columns. The type of each index element (string versus numeric) must match the element of the same position in other implicit indices.
The arrays for implicitly indexed columns in a CREATE DATA statement do not need to have identical index sets. A missing value is supplied for the value of an implicitly indexed array location when the implied index value is not in the array’s index set.
In the following statements, the key-set is unspecified. The set of index values is , which is the union of the index sets of
x and y. These index sets are not identical, so missing values are supplied when necessary. The results of these statements are shown in Figure 13.
proc optmodel;
number x{1..2} init 2;
var y{2..3} init 3;
create data exdata from [keycol] x y;
proc print;
run;
Figure 13: CREATE: Unspecified key-set
| Obs | keycol | x | y |
|---|---|---|---|
| 1 | 1 | 2 | . |
| 2 | 2 | 2 | 3 |
| 3 | 3 | . | 3 |
The types of the output data set variables match the types of the source values. The output variable type for a key-columns value matches the corresponding element type in the index value tuple. A numeric element matches a numeric data set variable, and a string element matches a character variable. For regular columns, the source expression type determines the output data set variable type. A numeric expression produces a numeric variable, and a string expression produces a character variable. Character variables that do not include a FIXED= column option are stored in VARCHAR form when the data set engine supports that form. Otherwise, character variables are stored in fixed CHAR form.
Lengths of character variables in the output data set are determined automatically when the LENGTH= column option is not specified. The length is set to accommodate the longest string value output in that column.
You can use the iterated columns form to output selected rows of multiple arrays, assigning a different data set variable to each column. For example, the following statements output the last two rows of the two-dimensional array, a, along with corresponding elements of the one-dimensional array, b:
proc optmodel;
num m = 3; /* number of rows/observations */
num n = 4; /* number of columns in a */
num a{i in 1..m, j in 1..n} = i*j; /* compute a */
num b{i in 1..m} = i**2; /* compute b */
set<num> subset = 2..m; /* used to omit first row */
create data out
from [i]=subset {j in 1..n}<col("a"||j)=a[i,j]> b;
The preceding statements create a data set out, which has observations and
variables. The variables are named
i, a1 through an, and b, as shown in Figure 14.
Figure 14: CREATE DATA Set: The Iterated Column Form
| Obs | i | a1 | a2 | a3 | a4 | b |
|---|---|---|---|---|---|---|
| 1 | 2 | 2 | 4 | 6 | 8 | 4 |
| 2 | 3 | 3 | 6 | 9 | 12 | 9 |
See the section Data Set Input/Output for more examples of using the CREATE DATA statement.
DO Statement
DO ; statements ; END;
The DO statement groups a sequence of statements together as a single statement. Each statement within the list is executed sequentially. The DO statement can be used for grouping with the IF, FOR, and COFOR statements.
DO Statement, Iterative
The iterative DO statement assigns the values from the sequence of specification items to a previously declared parameter or variable, name. The specified statement sequence is executed after each assignment. This statement corresponds to the iterative DO statement of the DATA step.
Each specification provides either a single number or a single string value, or a sequence of such values. Each specification takes the following form:
The expression in the specification provides a single value or set of values to assign to the target name. Multiple values can be provided for the loop by giving multiple specification items that are separated by commas. For example, the following statements output the values 1, 3, and 5:
proc optmodel;
number i;
do i=1,3,5;
put i;
end;
In this case, the same effect can be achieved with a single range expression in place of the explicit list of values, as in the following statements:
proc optmodel;
number i;
do i=1 to 5 by 2;
put 'value of i assigned by the DO loop = ' i;
i=i**2;
put 'value of i assigned in the body of the loop = ' i;
end;
The output of these statements is shown in Figure 15.
Figure 15: DO Loop: Name Parameter Unaffected
value of i assigned by the DO loop = 1 value of i assigned in the body of the loop = 1 value of i assigned by the DO loop = 3 value of i assigned in the body of the loop = 9 value of i assigned by the DO loop = 5 value of i assigned in the body of the loop = 25 |
Unlike the DATA step, a range expression requires the limit to be specified. Additionally the BY part, if any, must follow the limit expression. Moreover, although the name parameter can be reassigned in the body of the loop, the sequence of values that is assigned by the DO loop is unaffected.
The argument expression can also be an expression that returns a set of numbers or strings. For example, the following statements produce the same sequence of values for i as the previous statements but use a set parameter value:
proc optmodel;
set s = {1,3,5};
number i;
do i = s;
put i;
end;
Each specification can include a WHILE or UNTIL clause. A WHILE or UNTIL clause applies to the expression that immediately precedes the clause. The sequence that is specified by an expression can be terminated early by a WHILE or UNTIL clause. A WHILE logic-expression is evaluated for each sequence value before the nested statements. If the logic-expression returns a false (zero or missing) value, then the current sequence is terminated immediately. An UNTIL logic-expression is evaluated for each sequence value after the nested statements. The sequence from the current specification is terminated if the logic-expression returns a true value (nonzero and nonmissing). After early termination of a sequence due to a WHILE or UNTIL expression, the DO loop execution continues with the next specification, if any.
To demonstrate use of the WHILE clause, the following statements output the values 1, 2, and 3. In this case the sequence of values from the set s is stopped when the value of i reaches 4.
proc optmodel;
set s = {1,2,3,4,5};
number i;
do i = s while(i NE 4);
put i;
end;
DO UNTIL Statement
DO UNTIL ( logic-expression ) ; statements ; END;
The DO UNTIL loop executes the specified sequence of statements repeatedly until the logic-expression, evaluated after the statements, returns true (a nonmissing nonzero value).
For example, the following statements output the values 1 and 2:
proc optmodel;
number i;
i = 1;
do until (i=3);
put i;
i=i+1;
end;
Multiple criteria can be introduced using expression operators, as in the following example:
do until (i=3 and j=7);
For a list of expression operators, see Table 10.
DO WHILE Statement
DO WHILE ( logic-expression ) ; statements ; END;
The DO WHILE loop executes the specified sequence of statements repeatedly as long as the logic-expression, evaluated before the statements, returns true (a nonmissing nonzero value).
For example, the following statements output the values 1 and 2:
proc optmodel;
number i;
i = 1;
do while (i<3);
put i;
i=i+1;
end;
Multiple criteria can be introduced using expression operators, as in the following example:
do while (i<3 and j<7);
For a list of expression operators, see Table 10.
DROP Statement
DROP constraint-list;
The DROP statement causes the solver to ignore a list of constraints, constraint arrays, or constraint array locations. The space-delimited constraint-list specifies the names of the dropped constraints. Each constraint, constraint array, or constraint array location is named by an identifier-expression. An entire constraint array is dropped if an identifier-expression omits the index for an array name. Each identifier-expression can be prefixed by an indexing set to specify the constraint indices.
The following example statements use the DROP statement:
proc optmodel;
var x{1..10};
con c1: x[1] + x[2] <= 3;
con disp{i in 1..9}: x[i+1] >= x[i] + 0.1;
drop c1; /* drops the c1 constraint */
drop disp[5]; /* drops just disp[5] */
drop {i in 1..3} disp; /* drops disp[1] disp[2] disp[3] */
drop disp; /* drops all disp constraints */
The following line drops both the c1 and disp[5] constraints:
drop c1 disp[5];
Constraints can be added back to the model by using the RESTORE statement.
END Statement
END ;
The END statement marks the end of the list of statements that are executed by a DO group statement, iterative DO statement, DO UNTIL statement, or DO WHILE statement.
ENDSOURCE Statement
ENDSOURCE ;
The ENDSOURCE statement is the default marker for the end of a SOURCE block that begins with a SOURCE statement.
ENDSUBMIT Statement
ENDSUBMIT ;
The ENDSUBMIT statement marks the end of the SAS code block that begins with a SUBMIT statement.
EXPAND Statement
The EXPAND statement prints the specified constraint, variable, implicit variable, or objective declaration expressions in the current problem after expanding aggregation operators, substituting the current value for parameters and indices, and resolving constant subexpressions. identifier-expression is the name of a variable, objective, or constraint. If the name is omitted and no options are specified, then all variables, the last objective, used implicit variables, and undropped constraints in the current problem are printed. The following statements show an example EXPAND statement:
proc optmodel;
number n=2;
var x{1..n};
min z1=sum{i in 1..n}(x[i]-i)**2;
max z2=sum{i in 1..n}(i-x[i])**3;
con c{i in 1..n}: x[i]>=0;
fix x[2]=3;
expand;
These statements produce the output in Figure 16.
Figure 16: EXPAND Statement Output
Var x[1] Fix x[2] = 3 Maximize z2=(-x[1] + 1)**3 + (-x[2] + 2)**3 Constraint c[1]: x[1] >= 0 Constraint c[2]: x[2] >= 0 |
Specifying an identifier-expression restricts output to the specified declaration. A non-array name prints only the specified item. If an array name is used with a specific index, then information for the specified array location is output. Using an array name without an index restricts output to all locations in the array.
You can use the following options to further control the EXPAND statement output:
The following options restrict the types of declarations output when no specific non-array item or array location is requested. By default, variables, implicit variables, the current objective, and constraints are output. Only the requested declaration types are output when one or more of the following options are used.
For example, you can see the effect of a FIX statement on the problem that is presented to the solver by using the SOLVE option. You can modify the previous example as follows:
proc optmodel;
number n=2;
var x{1..n};
min z1=sum{i in 1..n}(x[i]-i)**2;
max z2=sum{i in 1..n}(i-x[i])**3;
con c{i in 1..n}: x[i]>=0;
fix x[2]=3;
expand / solve;
These statements produce the output in Figure 17.
Figure 17: Expansion with Fixed Variable
Var x[1] >= 0 Fix x[2] = 3 Maximize z2=(-x[1] + 1)**3 - 1 |
Compare the results in Figure 17 to those in Figure 16. The constraint c[1] has been converted to a variable bound. The subexpression that uses the fixed variable has been resolved to a constant.
FILE Statement
The FILE statement selects the current output file for the PUT statement. By default PUT output is sent to the SAS log. Use the FILE statement to manage a group of output files. The specified file is opened for output if it is not already open. The output file remains open until it is closed with the CLOSEFILE statement.
The file-specification names the output file. It can use any of the following forms:
The LRECL= option sets the line length of the output file. The LRECL= option is ignored if the file is already open or if the PRINT or LOG file is specified.
The LRECL= value can be specified in these forms:
The LRECL= value cannot exceed the largest four-byte signed integer, which is .
The following example shows how to use the FILE statement to handle multiple files:
proc optmodel;
file 'file.txt' lrecl=80; /* opens file.txt */
put 'This is line 1 of file.txt.';
file print; /* selects the listing */
put 'This goes to the listing.';
file 'file.txt'; /* reselects file.txt */
put 'This is line 2 of file.txt.';
closefile 'file.txt'; /* closes file.txt */
file log; /* selects the SAS log */
put 'This goes to the log.';
/* using expression to open and write a collection of files */
str ofile;
num i;
num l = 40;
do i = 1 to 3;
ofile = ('file' || i || '.txt');
file (ofile) lrecl=(l*i);
put ('This goes to ' || ofile);
closefile (ofile);
end;
The following statements illustrate the usefulness of using a logical name associated with a file by FILENAME statement:
proc optmodel;
/* assigns a logical name to file.txt */
/* see FILENAME statement in */
/* SAS Global Statements: Reference */
filename myfile 'file.txt' mod;
file myfile;
put 'This is line 3 of file.txt.';
closefile myfile;
file myfile;
put 'This is line 4 of file.txt.';
closefile myfile;
Notice that the FILENAME statement opens the file referenced for append. Therefore, new data are appended to the end every time the logical name, myfile, is used in the FILE statement.
FIX Statement
The FIX statement causes the solver to treat a list of variables, variable arrays, or variable array locations as fixed in value. The variable-list consists of one or more variable names separated by spaces. Each member of the variable-list is fixed to the same expression. For example, the following statements fix the variables x and y to 3:
proc optmodel;
var x, y;
num a = 2;
fix x y=a+1;
A variable is specified with an identifier-expression (see the section Identifier Expressions). An entire variable array is fixed if the identifier-expression names an array without providing an index. Each identifier-expression can be prefixed by an indexing set to specify the variable indices.
You can specify a new value for the variables by using the expression. If a single identifier-expression is specified, prefixed by an indexing set, then the expression is evaluated for each element in the indexing set. Otherwise the expression is evaluated exactly once. For example, the following statements fix all locations in array x to 0 except x[10], which is fixed to 1:
proc optmodel;
var x{1..10};
fix x = 0;
fix x[10] = 1;
The same effect can be achieved using a single statement:
fix {i in 1..10} x[i] = if i eq 10 then 1 else 0;
If expression is omitted, the variable is fixed at its current value. For example, you can fix some variables to be their optimal values after the SOLVE statement is invoked. Note: The fixed value is equal to the current value for a fixed variable. The fixed value is updated if a new value is assigned to a fixed variable.
The effect of FIX can be reversed by using the UNFIX statement.
FOR Statement
FOR { index-set } statement;
The FOR statement executes its substatement for each member of the specified index-set. The index set can declare local dummy parameters. You can reference the value of these parameters in the substatement. For example, consider the following statements:
proc optmodel;
for {i in 1..2, j in {'a', 'b'}} put i= j=;
These statements produce the output in Figure 18.
Figure 18: FOR Statement Output
i=1 j=a i=1 j=b i=2 j=a i=2 j=b |
As another example, the following statements set the current values for variable x to random values between 0 and 1:
proc optmodel;
var x{1..10};
for {i in 1..10}
x[i] = ranuni(-1);
Multiple statements can be controlled by specifying a DO statement group for the substatement.
Caution: Avoid modifying the parameters that are used by the FOR or COFOR statement index set from within the substatement. The set value that is used for the left-most index set item is not affected by such changes. However, the effect of parameter changes on later index set items cannot be predicted.
IF Statement
The IF statement evaluates the logical expression and then conditionally executes the THEN or ELSE substatements. The substatement that follows the THEN keyword is executed when the logical expression result is nonmissing and nonzero. The ELSE substatement, if any, is executed when the logical expression result is a missing value or zero. The ELSE part is optional and must immediately follow the THEN substatement. When IF statements are nested, an ELSE is always matched to the nearest incomplete unmatched IF-THEN. Multiple statements can be controlled by using DO statements with the THEN or ELSE substatements.
Note: When an IF-THEN statement is used without an ELSE substatement, substatements of the IF statement are executed when possible as they are entered. Under certain circumstances, such as when an IF statement is nested in a FOR loop, the statement is not executed during interactive input until the next statement is seen. By following the IF-THEN statement with an extra semicolon, you can cause it to be executed upon submission, since the extra semicolon is handled as a null statement.
LEAVE Statement
LEAVE ;
The LEAVE statement terminates the execution of the entire loop body (iterative DO, DO UNTIL, DO WHILE, FOR, or COFOR) that immediately contains the LEAVE statement. Execution resumes at the statement that follows the loop. The following example demonstrates a simple use of the LEAVE statement:
proc optmodel;
number i, j;
do i = 1..5;
do j = 1..4;
if i >= 3 and j = 2 then leave;
end;
print i j;
end;
The results from these statements are displayed in Figure 19.
Figure 19: LEAVE Statement Output
| i | j |
|---|---|
| 1 | 4 |
| i | j |
|---|---|
| 2 | 4 |
| i | j |
|---|---|
| 3 | 2 |
| i | j |
|---|---|
| 4 | 2 |
| i | j |
|---|---|
| 5 | 2 |
For values of i equal to 1 or 2, the inner loop continues uninterrupted, leaving j with a value of 4. For values of i equal to 3, 4, or 5, the inner loop terminates early, leaving j with a value of 2.
Null Statement
;
The null statement is treated as a statement in the PROC OPTMODEL syntax, but its execution has no effect. It can be used as a placeholder statement.
PRINT Statement
PRINT print-items;
The PRINT statement outputs string and numeric data in tabular form. The statement specifies a list of arrays or other data items to print. Multiple items can be output together as data columns in the same table.
If no format is specified, the PRINT statement handles the details of formatting automatically (see the section Formatted Output for details). The default format for a numerical column is the fixed-point format (w.d format), which is chosen based on the values of the PDIGITS= and PWIDTH= options (see the section PROC OPTMODEL Statement) and on the values in the column. The PRINT statement uses scientific notation (the Ew. format) when a value is too large or too small to display in fixed format. The default format for a character column is the $w. format, where the width is set to be the length of the longest string (ignoring trailing blanks) in the column.
print-item can be specified in the following forms:
The following example demonstrates the use of several print-item forms:
proc optmodel;
num x = 4.3;
var y{j in 1..4} init j*3.68;
print y; /* identifier-expression */
print (x * .265) dollar6.2; /* (expression) [format] */
print {i in 2..4} y; /* {index-set} identifier-expression */
print {i in 1..3}(i + i*.2345692) best7.;
/* {index-set} (expression) [format] */
print "Line 1"; /* string */
The output is displayed in Figure 20.
Figure 20: Print-item Forms
| [1] | y |
|---|---|
| 1 | 3.68 |
| 2 | 7.36 |
| 3 | 11.04 |
| 4 | 14.72 |
| $1.14 |
| [1] | y |
|---|---|
| 2 | 7.36 |
| 3 | 11.04 |
| 4 | 14.72 |
| [1] | |
|---|---|
| 1 | 1.23457 |
| 2 | 2.46914 |
| 3 | 3.70371 |
| Line 1 |
Adjacent print items that have similar indexing are grouped together and output in the same table. Items have similar indexing if they specify arrays that have the same number of indices and have matching index types (numeric versus string). Nonarray items are considered to have the same indexing as other nonarray items. The resulting table has a column for each array index followed by a column for each print item value. This format is called list form. For example, the following statements produce a list form table:
proc optmodel;
num a{i in 1..3} = i*i;
num b{i in 3..5} = 4*i;
print a b;
These statements produce the listing output in Figure 21.
Figure 21: List Form PRINT Table
| [1] | a | b |
|---|---|---|
| 1 | 1 | |
| 2 | 4 | |
| 3 | 9 | 12 |
| 4 | 16 | |
| 5 | 20 |
The array index columns show the set of valid index values for the print items in the table. The array index column for the ith index is labeled [i]. There is a row for each combination of index values that was used. The index values are displayed in sorted ascending order.
The data columns show the array values that correspond to the index values in each row. If a particular array index is invalid or the array location is undefined, then the corresponding table entry is displayed as blank for numeric arrays and as an empty string for string arrays. If the print items are scalar, then the table has a single row and no array index columns.
If a table contains a single array print item, the array is two-dimensional (has two indices), and the array is dense enough, then the array is shown in matrix form. In this format there is a single index column that contains the row index values. The label of this column is blank. This column is followed by a column for every unique column index value for the array. The latter columns are labeled by the column value. These columns contain the array values for that particular array column. Table entries that correspond to array locations that have invalid or undefined combinations of row and column indices are blank or (for strings) printed as an empty string.
The following statements generate a simple example of matrix output:
proc optmodel;
print {i in 1..6, j in i..6} (i*10+j);
The PRINT statement produces the output in Figure 22.
Figure 22: Matrix Form PRINT Table
| 1 | 2 | 3 | 4 | 5 | 6 | |
|---|---|---|---|---|---|---|
| 1 | 11 | 12 | 13 | 14 | 15 | 16 |
| 2 | 22 | 23 | 24 | 25 | 26 | |
| 3 | 33 | 34 | 35 | 36 | ||
| 4 | 44 | 45 | 46 | |||
| 5 | 55 | 56 | ||||
| 6 | 66 |
The PRINT statement prints single two-dimensional arrays in the form that uses fewer table cells (headings are ignored). Sparse arrays are normally printed in list form, and dense arrays are normally printed in matrix form. In a PROC OPTMODEL statement, the PMATRIX= option enables you to tune how the PRINT statement displays a two-dimensional array. The value of this option scales the total number of nonempty array elements, which is used to compute the tables cells needed for list form display. Specifying values for the PMATRIX= option less than 1 causes the list form to be used in more cases, while specifying values greater than 1 causes the matrix form to be used in more cases. If the value is 0, then the list form is always used. The default value of the PMATRIX= option is 1. Changing the default can be done with the RESET OPTIONS statement.
The following statements illustrate how the PMATRIX= option affects the display of the PRINT statement:
proc optmodel;
num a{i in 1..6, i..i} = i;
num b{i in 1..3, j in 1..3} = i*j;
print a;
print b;
reset options pmatrix=3;
print a;
reset options pmatrix=0.5;
print b;
The output is shown in Figure 23.
Figure 23: PRINT Statement: Effects of PMATRIX= Option
| [1] | [2] | a |
|---|---|---|
| 1 | 1 | 1 |
| 2 | 2 | 2 |
| 3 | 3 | 3 |
| 4 | 4 | 4 |
| 5 | 5 | 5 |
| 6 | 6 | 6 |
| b | |||
|---|---|---|---|
| 1 | 2 | 3 | |
| 1 | 1 | 2 | 3 |
| 2 | 2 | 4 | 6 |
| 3 | 3 | 6 | 9 |
| a | ||||||
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 1 | 1 | |||||
| 2 | 2 | |||||
| 3 | 3 | |||||
| 4 | 4 | |||||
| 5 | 5 | |||||
| 6 | 6 | |||||
| [1] | [2] | b |
|---|---|---|
| 1 | 1 | 1 |
| 1 | 2 | 2 |
| 1 | 3 | 3 |
| 2 | 1 | 2 |
| 2 | 2 | 4 |
| 2 | 3 | 6 |
| 3 | 1 | 3 |
| 3 | 2 | 6 |
| 3 | 3 | 9 |
From Figure 23, you can see that, by default, the PRINT statement tries to make the display compact. However, you can change the default by using the PMATRIX= option.
PROFILE Statement
The PROFILE statement controls the PROC OPTMODEL profiler, which enables you to collect and display timing and execution count information for PROC OPTMODEL processing. The profiler can be very useful for finding bottlenecks during execution, such as constraints that require large amounts of time during problem generation. When the profiler is enabled, PROC OPTMODEL records the time for processing a declaration, the time for executing statements, and the number of times that statements are executed. See Example 9.7: Sparse Modeling for an example use of the PROFILE statement.
The mode argument specifies the action that the PROFILE statement performs. You can use the following values for mode:
The options control how PROC OPTMODEL collects and displays profiler information. You can specify the following options:
The elapsed time that is required to process an item includes the processing of other profiled items that it depends on. For example, the processing of a constraint during problem generation might require the evaluation of parameter values. The PROFILE statement reports net time so that the total of profiled times represents actual processing time.
The equation that is used to compute net time is
Elapsed time is the elapsed clock time this is required for processing an item. Nested time is the total of the elapsed times that are spent within the same thread to process other profiled items, such as substatements or declaration values. Wait time represents the time that a single thread is allocated but idle because it is waiting for other threads to perform the required processing. The total of net times can exceed the elapsed wall clock time when multiple threads are used.
PUT Statement
The PUT statement writes text data to the current output file. The syntax of the PUT statement in PROC OPTMODEL is similar to the syntax of the PROC IML and DATA step PUT statements. The PUT statement contains a list of items that specify data for output and provide instructions for formatting the data.
The current output file is initially the SAS log. This can be overridden with the FILE statement. An output file can be closed with the CLOSEFILE statement.
Normally the PUT statement outputs the current line after processing all items. Final @ or @@ operators suppress this automatic line output and cause the current column position to be retained for use in the next PUT statement.
put-item can take any of the following forms.
QUIT Statement
QUIT ;
The QUIT statement terminates the OPTMODEL execution. The statement is executed immediately, so it cannot be a nested statement. A QUIT statement is implied when a DATA or PROC statement is read.
READ DATA Statement
The READ DATA statement reads data from a SAS data set into PROC OPTMODEL parameter and variable locations. The arguments to the READ DATA statement are as follows:
Note: Currently, the data are always saved on the client. Transferring large data sets from a CAS libref can have significant network overhead and require a large amount of client memory.
The following example uses the READ DATA statement to copy data set variables j and k from the SAS data set indata into parameters of the same name. The READ= data set option specifies a password.
proc optmodel;
number j, k;
read data indata(read=secret) into j k;
Key Columns
If any read-key-columns are specified, then the READ DATA statement reads all observations from the input data set. If no read-key-columns are specified, then only the first observation of the data set is read. The data set is closed after reading the requested information.
Each read-key-column specifies a data set variable that supplies the column value. The values of the specified data set variables from each observation are combined into a key tuple. This combination is known as the observation key. The observation key is used to index array locations specified by the read-columns items. The observation key is expected to be unique for each observation read from the data set.
The syntax for a read-key-column is as follows:
Each read-key-column specifies an element of the observation key tuple. The source-name specifies the data set variable name, either as a name or by using a COL expression (as described later for read-columns). Use the special data set variable name _N_ to refer to the observation’s iteration number, starting at 1.
A local dummy parameter can be created for each read-key-column. You can use the dummy parameter to reference the key tuple element value in subsequent read-columns items. A dummy parameter is created when the read-key-column specifies a name preceding the equal sign (=). The dummy parameter has the specified name. Also, a dummy parameter is created when the source-name is a nonliteral name and no alternate name has been specified using the equal sign. In this case, the dummy parameter has the same name as the data set variable.
You can specify a set-name to save the set of observation keys into a set parameter. If the observation key consists of a single scalar value, then the set member type must match the scalar type. Otherwise the set member type must be a tuple with element types that match the corresponding observation key element types.
The READ DATA statement initially assigns an empty set to the target set-name parameter. As observations are read, a tuple for each observation key is added to the set. A set used to index an array destination in the read-columns can be read at the same time as the array values. Consider a data set, invdata, created by the following statements:
data invdata;
input item $ invcount;
datalines;
table 100
sofa 250
chair 80
;
The following statements read the data set invdata, which has two variables, item and invcount. The READ DATA statement constructs a set of inventory items, Items. At the same time, the parameter location invcount[item] is assigned the value of the data set variable invcount in the corresponding observation.
proc optmodel;
set<string> Items;
number invcount{Items};
read data invdata into Items=[item] invcount;
print invcount;
The output of these statements is shown in Figure 24.
Figure 24: READ DATA Statement: Key Column
| [1] | invcount |
|---|---|
| chair | 80 |
| sofa | 250 |
| table | 100 |
When observations are read, the values of data set variables are copied to parameter locations. Numeric values are copied unchanged. For character values, trim-option controls how leading and trailing blanks are processed. trim-option is ignored when the value type is numeric. Specify any of the following keywords for trim-option:
Columns
read-columns specify data set variables to read and PROC OPTMODEL parameter locations to which to assign the values. The types of the input data set variables must match the types of the parameters. Array parameters can be implicitly or explicitly indexed by the observation key values.
Normally, missing values from the data set are assigned to the parameters that are specified in the read-columns. The NOMISS keyword suppresses the assignment of missing values, leaving the corresponding parameter locations unchanged. Note that the parameter location does not need to have a valid index in this case. This permits a single statement to read data into multiple arrays that have different index sets.
read-columns have the following forms:
Figure 25: READ DATA Statement: Identifier Expressions
| [1] | p | q |
|---|---|---|
| 1 | 1 | 2 |
| 2 | 3 | 4 |
You can use an iterated column read-option to read multiple data set variables into the same array. For example, a data set might store an entire row of array data in a group of data set variables. The following statements demonstrate how to read a data set that contains demand data divided by day:
data dmnd;
input loc $ day1 day2 day3 day4 day5;
datalines;
East 1.1 2.3 1.3 3.6 4.7
West 7.0 2.1 6.1 5.8 3.2
;
proc optmodel;
set DOW = 1..5; /* days of week, 1=Monday, 5=Friday */
set<string> LOCS; /* locations */
number demand{LOCS, DOW};
read data dmnd
into LOCS=[loc]
{d in DOW} < demand[loc, d]=col("day"||d) >;
print demand;
These statements read a set of demand variables named DAY1–DAY5 from each observation, filling in the two-dimensional array demand. The output is shown in Figure 26.
Figure 26: Demand Data
| demand | |||||
|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | |
| East | 1.1 | 2.3 | 1.3 | 3.6 | 4.7 |
| West | 7.0 | 2.1 | 6.1 | 5.8 | 3.2 |
RESET OPTIONS Statement
RESET OPTIONS options;
RESET OPTION options;
The RESET OPTIONS statement sets PROC OPTMODEL option values or restores them to their defaults. Options can be specified by using the same syntax as in the PROC OPTMODEL statement. The RESET OPTIONS statement provides two extensions to the option syntax. If an option normally requires a value (specified with an equal sign (=) operator), then specifying the option name alone resets it to its default value. You can also specify an expression enclosed in parentheses in place of a literal value. See the section OPTMODEL Options for an example.
The RESET OPTIONS statement can be placed inside loops or conditional statements. The statement is applied each time it is executed.
RESTORE Statement
RESTORE constraint-list;
The RESTORE statement adds a list of constraints, constraint arrays, or constraint array locations that were dropped by the DROP statement back into the solver model, or includes constraints in a problem where they were not previously present. The space-delimited constraint-list specifies the names of the constraints. Each constraint, constraint array, or constraint array location is named by an identifier-expression. An entire constraint array is restored if an identifier-expression omits the index from an array name. Each identifier-expression can be prefixed by an indexing set to specify the constraint indices.
For example, the following statements declare a constraint array and then drop it:
con c{i in 1..4}: x[i] + y[i] <=1;
drop c;
The following statement restores the first constraint:
restore c[1];
The following statement restores the second and third constraints:
restore {i in 2..3} c[i]; /* like "restore c[2] c[3];" */
If you want to restore all of the constraints, you can submit the following statement:
restore c;
SAVE MPS Statement
The SAVE MPS statement saves the structure and coefficients for a linear programming model into a SAS data set. This data set can be used as input data for the OPTLP or OPTMILP procedure.
Note: The OPTMODEL presolver (see the section Presolver) is automatically bypassed so that the statement saves the original model without eliminating fixed variables, tightening bounds, and so on.
The SAS-data-set argument specifies the output data set name and options. You can specify the data set name and options directly or as the string value of an expression enclosed in parentheses. The output data set uses the MPS format
described in Chapter 8, The MPS-Format Data Table. An _ID_ column is automatically added to the data set when it is created using a CAS engine libref. The generated data set contains observations that define different parts of the linear program.
Variables, constraints, and objectives are referenced in the data set by using label text from the corresponding .label suffix value. The default text is based on the name in the model. See the section Suffixes for more details. Labels are limited by default to 32 characters and are abbreviated to fit. You can change the maximum length for labels by using the MAXLABLEN= option. When needed, a programmatically generated number is added to labels to avoid duplication.
The LINEARIZE keyword transforms objectives and constraints into linear form where possible. PROC OPTMODEL replaces certain nonlinear subexpressions with linear equivalents, using added variables and constraints. See the section Linearization for a description of the supported transformations.
If the OBJECTIVE keyword is used, the objective name becomes the current problem objective. If the NOOBJECTIVE keyword is used or the current problem does not have an objective, then the data set includes a default constant zero objective. Otherwise, the current problem objective is included in the data set.
When an integer variable has been assigned a nondefault branching priority or direction, the MPS data set includes a BRANCH section. See Chapter 8, The MPS-Format Data Table, for more details.
The following statements show an example of the SAVE MPS statement. The LIBNAME statement for the mylib libref allows the creation of CAS tables. The model is specified using the OPTMODEL procedure. Then it is saved as the MPS data set mylib.MPSData, as shown in Figure 27. Next, PROC OPTLP is used to solve the resulting linear program.
libname mylib datalimit=all;
proc optmodel;
var x >= 0, y >= 0;
con c: x >= y;
con bx: x <= 2;
con by: y <= 1;
min obj=0.5*x-y;
save mps mylib.MPSData;
quit;
proc optlp data=mylib.MPSData pout=mylib.PrimalOut dout=mylib.DualOut;
run;
Figure 27: The MPS Data Set Generated by SAVE MPS Statement
| FIELD1 | FIELD2 | FIELD3 | FIELD4 | FIELD5 | FIELD6 | _ID_ |
|---|---|---|---|---|---|---|
| NAME | MPSData | . | . | 1 | ||
| ROWS | . | . | 2 | |||
| N | obj | . | . | 3 | ||
| G | c | . | . | 4 | ||
| L | bx | . | . | 5 | ||
| L | by | . | . | 6 | ||
| COLUMNS | . | . | 7 | |||
| x | obj | 0.5 | c | 1 | 8 | |
| x | bx | 1.0 | . | 9 | ||
| y | obj | -1.0 | c | -1 | 10 | |
| y | by | 1.0 | . | 11 | ||
| RHS | . | . | 12 | |||
| .RHS. | bx | 2.0 | . | 13 | ||
| .RHS. | by | 1.0 | . | 14 | ||
| ENDATA | . | . | 15 |
SAVE QPS Statement
The SAVE QPS statement saves the structure and coefficients for a quadratic programming model into a SAS data set. This data set can be used as input data for the OPTQP procedure.
Note: The OPTMODEL presolver (see the section Presolver) is automatically bypassed so that the statement saves the original model without eliminating fixed variables, tightening bounds, and so on.
The SAS-data-set argument specifies the output data set name and options. You can specify the data set name and options directly or as the string value of an expression enclosed in parentheses. The output data set uses the QPS format
described in Chapter 8. An _ID_ column is automatically added to the data set when it is created using a CAS engine libref. The generated data set contains observations that define different parts of the quadratic program.
Variables, constraints, and objectives are referenced in the data set by using label text from the corresponding .label suffix value. The default text is based on the name in the model. See the section Suffixes for more details. Labels are limited by default to 32 characters and are abbreviated to fit. You can change the maximum length for labels by using the MAXLABLEN= option. When needed, a programmatically generated number is added to labels to avoid duplication.
If the OBJECTIVE keyword is used, the objective name becomes the current problem objective. If the NOOBJECTIVE keyword is used or the current problem does not have an objective, then the data set includes a default constant zero objective. Otherwise, the current problem objective is included in the data set. The quadratic coefficients of the objective function appear in the QSECTION section of the output data set.
The following statements show an example of the SAVE QPS statement. The LIBNAME statement for the mylib libref allows the creation of CAS tables. The model is specified using the OPTMODEL procedure. Then it is saved as the QPS data set mylib.QPSData, as shown in Figure 28. Next, PROC OPTQP is used to solve the resulting quadratic program.
libname mylib datalimit=all;
proc optmodel;
var x{1..2} >= 0;
min z = 2*x[1] + 3 * x[2] + x[1]**2 + 10*x[2]**2
+ 2.5*x[1]*x[2];
con c1: x[1] - x[2] <= 1;
con c2: x[1] + 2*x[2] >= 100;
save qps mylib.QPSData;
quit;
proc optqp data=mylib.QPSData pout=mylib.PrimalOut dout=mylib.DualOut;
run;
Figure 28: QPS Data Set Generated by the SAVE QPS Statement
| FIELD1 | FIELD2 | FIELD3 | FIELD4 | FIELD5 | FIELD6 | _ID_ |
|---|---|---|---|---|---|---|
| NAME | QPSData | . | . | 1 | ||
| ROWS | . | . | 2 | |||
| N | z | . | . | 3 | ||
| L | c1 | . | . | 4 | ||
| G | c2 | . | . | 5 | ||
| COLUMNS | . | . | 6 | |||
| x[1] | z | 2.0 | c1 | 1 | 7 | |
| x[1] | c2 | 1.0 | . | 8 | ||
| x[2] | z | 3.0 | c1 | -1 | 9 | |
| x[2] | c2 | 2.0 | . | 10 | ||
| RHS | . | . | 11 | |||
| .RHS. | c1 | 1.0 | . | 12 | ||
| .RHS. | c2 | 100.0 | . | 13 | ||
| QSECTION | . | . | 14 | |||
| x[1] | x[1] | 2.0 | . | 15 | ||
| x[1] | x[2] | 2.5 | . | 16 | ||
| x[2] | x[2] | 20.0 | . | 17 | ||
| ENDATA | . | . | 18 |
SOLVE Statement
The SOLVE statement invokes a PROC OPTMODEL solver. The current model is first resolved to the numeric form that is required by the solver. The resolved model and possibly the current values of any optimization variables are passed to the solver. The solver is executed in the distributed computing environment when you specify the SESSREF= or SESSUUID= option. After the solver finishes executing, the SOLVE statement prints a short table that shows a summary of results from the solver (see the section ODS Table and Variable Names) and updates the _OROPTMODEL_ macro variable.
Here are the arguments to the SOLVE statement:
Table 8: Default Solvers and Algorithms in PROC OPTMODEL
| Problem | Solver | Algorithm |
|---|---|---|
| Constraint programming | CLP | Constraint propagation and backtracking search |
| Conic optimization | Conic | Interior point conic |
| Linear programming | LP | Simplex |
| Mixed integer linear programming | MILP | Branch-and-cut |
| General nonlinear programming | NLP | Interior point NLP |
| Quadratic programming | QP | Interior point QP |
Table 9: Solver Option Types
The SOLVE statement uses the value of the predeclared _SOLVER_OPTIONS_ and _solver_OPTIONS_ string parameters to provide default solver options. Any options that are specified by these parameters are added before options that are specified in the SOLVE statement, with options from _SOLVER_OPTIONS_ appearing first. These options are included even when the SOLVE statement does not contain a WITH clause to specify a solver; in this case, solver is the name of the default solver as shown in Table 8.
Initially the predeclared string parameters _SOLVER_OPTIONS_ and _solver_OPTIONS_ (for each solver) are empty strings, but you can assign them. You must use keywords or literal values to specify option values in these strings. Redundant white space is allowed. For example, the following statements set up some simple defaults:
_SOLVER_OPTIONS_ = "MAXTIME = 600"; /* options for all solvers */
_LP_OPTIONS_ = "PRESOLVER=AGGRESSIVE"; /* options for LP solver */
Optimization techniques that use initial values obtain them from the current values of the optimization variables unless the NOINITVAR option is specified. When the solver finishes executing, the current value of each optimization variable is replaced by the optimal value found by the solver. These values can then be used as the initial values for subsequent solver invocations.
Note: If a solver fails, any currently pending statement is stopped and processing continues with the next complete statement read from the input. For example, if a SOLVE statement that is enclosed in a DO group (see the section DO Statement) fails, then the subsequent statements in the group are not executed and processing resumes at the point immediately following the DO group. Neither an infeasible result, an unbounded result, nor reaching an iteration limit is considered to be a solver failure.
Note: The information that appears in the macro variable _OROPTMODEL_ (see the section Macro Variable _OROPTMODEL_) varies by solver.
Note: The RELAXINT keyword is applied immediately before the problem is passed to the solver, after any processing by the PROC OPTMODEL presolver. So the problem presented to the solver might not be equivalent to the one produced by setting the .RELAX suffix of all variables to a nonzero value. In particular, the bounds of integer variables are still adjusted to be integral, and PROC OPTMODEL’s presolver might use integrality to tighten bounds further.
SOURCE Statement
source-block;
ENDSOURCE | keyword ;
The SOURCE statement assigns the text of the source-block as a string value to the parameter location that is specified by identifier-expression. You can use the SOURCE statement to save source code blocks for later processing, as an alternative to quoted SAS strings. In contrast with quoted strings, line breaks are preserved and nested quotes are specified normally. SOURCE statements can be read from SAS macros. Macros and SAS global statements are processed when the source block is read.
The END= option specifies that the source block is terminated by an alternative to the default keyword, ENDSOURCE. You can use this option to nest SOURCE blocks.
The following code demonstrates how to use the SOURCE statement with the SUBMIT statement inside a macro definition:
%macro submit_test;
proc optmodel;
string testCode;
%do i=1 %to 3;
source testCode end=endsrc;
data _null_;
i = &i;
put i=;
run;
endsrc;
submit / code=testCode;
%end;
quit;
%mend submit_test;
%submit_test;
STOP Statement
STOP ;
The STOP statement halts the execution of all statements that contain it, including DO statements and other control or looping statements. Execution continues with the next top-level source statement. The following statements demonstrate a simple use of the STOP statement:
proc optmodel;
number i, j;
do i = 1..5;
do j = 1..4;
if i = 3 and j = 2 then stop;
end;
end;
print i j;
The output is shown in Figure 29.
Figure 29: STOP Statement: Output
| i | j |
|---|---|
| 3 | 2 |
When the counters i and j reach 3 and 2, respectively, the STOP statement terminates both loops. Execution continues with the PRINT statement.
SUBMIT Statement
SAS statements;
ENDSUBMIT ;
The SUBMIT statement allows SAS code to be executed before PROC OPTMODEL processing continues. For example, you can use the SUBMIT statement to invoke other SAS procedures to perform analysis or to display results. The following statements use PROC SORT to order a list of nodes by decreasing priority; the nodes can be used for further processing:
proc optmodel;
set<str> NODES;
num priority{NODES};
/* set up priority data... */
/* sort nodes by descending priority */
create data temppri from [id] priority;
submit;
proc sort;
by descending priority;
run;
endsubmit;
/* load nodes by priority */
str nodesByPri{i in 1..card(NODES)};
read data temppri into [_n_] nodesByPri=id;
/* use the sorted list... */
The SUBMIT statement must appear as the last or only statement on a line, unless you specify the CODE= option in the options list. The SUBMIT statement is followed by lines of SAS statements, terminated by the ENDSUBMIT statement on a line of its own. The SAS statements between the SUBMIT and ENDSUBMIT statements are referred to as a SUBMIT block. The SUBMIT block is sent to the SAS language processor each time the SUBMIT statement is executed.
When you specify the CODE= option in options, the SUBMIT block is specified by the string value of the option. The ENDSUBMIT statement is not used in this case.
The SUBMIT block can include SAS global statements and procedure and invocations. Macros are not expanded until the SUBMIT block is executed. So you can change macro variables to modify the behavior of the SUBMIT block each time it is processed.
The arguments list specifies macro variables to initialize before the SUBMIT block is executed. List items are separated by spaces. Each of the arguments takes one of the following forms:
The following statements use a SUBMIT argument to modify the output each time the SUBMIT block is invoked:
for {i in 1..5} do;
submit a=i;
%put Value of a is &a..;
endsubmit;
end;
The options in the SUBMIT statement are used to specify a SUBMIT block or to retrieve status information after a SUBMIT block is executed. Each item in the space-delimited options list has one of the following forms:
Each output-argument item specifies a macro variable to copy after the block is executed. Each item takes one of the following two forms:
The following statements show how to use the options in the SUBMIT statement to retrieve the result of a SUBMIT block execution:
proc optmodel;
num success, syscc;
submit / OK = success out syscc;
data example;
set notfound;
j = i*i;
run;
endsubmit;
print success syscc;
The DATA step fails, so the success parameter is set to 0 and syscc is set to the error code in the &SYSCC macro variable. The output is shown in Figure 30.
Figure 30: SUBMIT Statement Error Handling
| success | syscc |
|---|---|
| 0 | 1012 |
Note: The SUBMIT block runs in the same environment that the OPTMODEL procedure is running in. The SUBMIT block can change the values for options, LIBNAME librefs, FILENAME filerefs, titles, footnotes, and macro variables. The OPTMODEL procedure sees these changes but might not process them until the next top-level source statement is read.
Note: A SUBMIT block can reset the ODS environment of the OPTMODEL procedure. For example, the ODS SELECT and EXCLUDE lists could be cleared after the SUBMIT block executes.
Note: A SUBMIT statement can appear only in open code unless you specify the CODE= option. An error message is displayed if the SUBMIT statement is read from a macro. You can avoid this limitation by using a SOURCE statement to specify the SUBMIT block. Specify the CODE= option with the parameter location of the saved block. Otherwise, you can place the SUBMIT statement, SUBMIT block, and ENDSUBMIT in a separate file and use the %INCLUDE statement to include the file in the macro.
UNFIX Statement
The UNFIX statement reverses the effect of FIX statements. The solver can vary the specified variables, variable arrays, or variable array locations specified by variable-list. The variable-list consists of one or more variable names separated by spaces.
Each variable name in the variable-list is an identifier expression (see the section Identifier Expressions). The UNFIX statement affects an entire variable array if the identifier expression omits the index from an array name. Each identifier-expression can be prefixed by an indexing set to specify the variable indices.
The expression specifies a new initial value that is stored in each element of the variable-list. If a single identifier-expression is specified, prefixed by an indexing set, then the expression is evaluated for each element in the indexing set. Otherwise the expression is evaluated exactly once.
The following example demonstrates the UNFIX command:
proc optmodel;
var x{1..3};
fix x; /* fixes entire array to 0 */
unfix x[1]; /* x[1] can now be varied again */
unfix x[2] = 2; /* x[2] is given an initial value 2 */
/* and can be varied now */
unfix x; /* all x indices can now be varied */
After the following statements are executed, the variables x[1] and x[2] are not fixed. They each hold the value 4. The variable x[3] is fixed at a value of 2.
proc optmodel;
var x{1..3} init 2;
num a = 1;
fix x;
unfix x[1] x[2]=a+3;
unfix {1..2} x = a+3; /* equivalent to prior UNFIX statement */
USE PROBLEM Statement
USE PROBLEM identifier-expression;
The USE PROBLEM programming statement makes the problem specified by the identifier-expression be the current problem. If the problem has not been previously used, the problem is created using the PROBLEM declaration corresponding to the name. The problem must have been previously declared.
[1] The OPTMODEL procedure never uses the black-box or network solver as a default. If the QP solver detects nonconvexity (nonconcavity) for a minimization (maximization) problem, then PROC OPTMODEL calls the NLP solver instead.