Flow Control Statements

Six forms of flow control

Execution statements such as assignment statements, SOLVE statements or data management statements are normally executed in their order of appearance in the body of a procedure. However, the presence of control flow statements can redirect the flow of execution as the need arises. AIMMS provides six forms of flow control:

  • the IF-THEN-ELSE statement for conditional execution,

  • the WHILE statement for repetitive conditional execution,

  • the REPEAT statement for repetitive unconditional execution,

  • the FOR statement for repetitive domain-driven execution,

  • the SWITCH statement for branching on set and integer values,

  • the HALT and RETURN statement for terminating the current execution,

  • the SKIP and BREAK statements for terminating the current repetitive execution, and

  • the BLOCK statement for visually grouping together multiple statements.

Syntax

flow-control-statement:

Flow control statements and special numbers

In the condition of flow control statements such as IF-THEN-ELSE, WHILE and REPEAT it is needed to know whether the result is equal to 0.0 or not in order to take the appropriate branch of execution. The special number NA has the interpretation “not yet available” thus it is also not yet known whether it is equal to 0.0 or not. The special number UNDF is the result of an illegal operation, so its value cannot be known. Therefor, AIMMS will issue an error message if the result of a condition in these statements evaluates to NA or UNDF. Special numbers and their interpretation as logical values are discussed in full detail in Real Values and Arithmetic Extensions and Logical Expressions.

The IF-THEN-ELSE Statement

The conditional IF-THEN-ELSE statement is used to choose between the execution of several groups of statements depending on the outcome of one or more logical conditions. The syntax of the IF-THEN-ELSE statement is given in the following diagram.

Syntax

if-then-else-statement:

image/svg+xmlIF logical-expression THEN statement ELSEIF ELSE statement ENDIF ;

AIMMS will evaluate all logical conditions in succession and stops at the first condition that is satisfied. The statements associated with that particular branch are executed. If none of the conditions is satisfied, the statements of the ELSE branch, if present, will be executed.

Example

The following code illustrates the use of the IF-THEN-ELSE statement.

if ( not SupplyDepot ) then
    DialogMessage( "Select a supply depot before solving the model" );
elseif ( Exists[ p, Supply(SupplyDepot,p) < Sum( i, Demand(i,p) ) ] ) then
    DialogMessage( "The selected supply depot has insufficient capacity" );
else
    solve TransportModel ;
endif ;

Note that in this particular example the evaluation of the ELSEIF condition only makes sense when a SupplyDepot exists. This is automatically enforced because the IF condition is not satisfied. Similarly, successful execution of the ELSE branch apparently depends on the failure of both the IF and ELSEIF conditions.

The WHILE and REPEAT Statements

The WHILE and REPEAT statements group a series of execution statements and execute them repeatedly. The execution of the repetitive loop can be terminated by a logical condition that is part of the WHILE statement, or by means of a BREAK statement from within both the WHILE and REPEAT statements.

Syntax

while-statement:

image/svg+xmlWHILE logical-expression DO loop-string statement ENDWHILE ;

repeat-statement:

image/svg+xmlREPEAT loop-string statement ENDREPEAT ;

Loop strings are discussed in Advanced Use of WHILE and REPEAT.

Termination by WHILE condition

The execution of a WHILE statement is subject to a logical condition that is verified each time the statements in the loop are executed. If the condition is false initially, the statements in the loop will never be executed. In case the WHILE loop does not contain a BREAK, HALT or RETURN statement, the statements inside the loop must in some way influence the outcome of the logical condition for the loop to terminate.

Termination by a BREAK statement

An alternative way to terminate a WHILE or REPEAT statement is the use of a BREAK statement inside the loop. BREAK statements make it possible to abort the execution at any position inside the loop. This freedom allows you to formulate more natural termination conditions than would otherwise be possible with just the logical condition in the WHILE statement. After aborting the loop, AIMMS will continue with the first statement following it.

Skipping the remainder of a loop

In addition to the BREAK statement, AIMMS also offers a SKIP statement. With it you instruct AIMMS to skip the remaining statements inside the current iteration of the loop, and immediately return to the top of the WHILE or REPEAT statement to execute the next iteration. The SKIP statement is an elegant alternative to placing the statements inside the loop following the SKIP statement in a conditional IF statement.

Syntax

skip-break-statement:

image/svg+xmlBREAK SKIP loop-string WHEN logical-expression ;

The WHEN clause

By adding a WHEN clause to either a BREAK or SKIP statement, you make its execution conditional to a logical expression. In practice, the execution of a BREAK or SKIP statement is almost always subject to some condition.

Example WHILE statement

This example computes the machine epsilon, which is the smallest number that, when added to 1.0, gives a value different from 1.0. It is a measure of the accuracy of the floating point arithmetic, and it is machine dependent. We assume that meps is a scalar parameter, and that the numeric comparison tolerances are set to zero (see also Numerical Comparison).

meps := 1.0;

while (1.0 + meps/2 > 1.0) do
    meps /= 2;
endwhile;

Since the parameter meps is determined iteratively, and the loop condition will eventually be satisfied, this example illustrates an appropriate use of the WHILE loop.

Example REPEAT statement

By applying a BREAK statement, the machine epsilon can be computed equivalently using the following REPEAT statement.

meps := 1.0;

repeat
    break when (1.0 + meps/2 = 1.0) ;
    meps /= 2;
endrepeat;

The BREAK statement could also have been formulated in an equivalent but less elegant manner without a WHEN clause:

if (1.0 + meps/2 = 1.0) then
   break;
endif;

Advanced Use of WHILE and REPEAT

Advanced uses

Next to the common use of the WHILE and REPEAT statements described in the previous section, AIMMS offers some special constructs that help you

  • keep track of the number executed iterations automatically, and

  • control nested arrangements of WHILE and REPEAT statements.

Nonconvergent loops

There are practical examples in which the terminating condition of a repetitive statement may not be met at all or at least not within a reasonable amount of work or time. A good example of this behavior are solution algorithms for which convergence is likely but not guaranteed. In these cases, it is common practice to terminate the execution of the loop when the total number of iterations exceeds a certain limit.

In AIMMS, such conditions can be formulated easily without the need to

  • introduce an additional parameter,

  • add a statement to initialize it, and

  • increase the parameter every iteration of the loop.

Each repetitive statement keeps track of its iteration count automatically and makes the number of times the loop is entered available by means of the predefined operator LoopCount. Upon entering a repetitive statement AIMMS will set its value to 1, and will increase it by 1 at the end of every iteration.

Example

Whether the following sequence will converge depends on the initial value of x. In the case where there is no convergence or if convergence is too slow, the loop in the following example will terminate after 100 iterations.

while ( Abs(x-OldValue) >= Tolerance and LoopCount <= 100 ) do
    OldValue := x ;
    x        := x^2 - x ;
endwhile ;

Naming nested loops

So far, we have considered single loops. However, in practice it is quite common that repetitive statements appear in nested arrangements. To provide finer control over the flow of execution in such situations, AIMMS allows you to label a particular repetitive statement with a loop string.

Use of loop strings

Using a loop string in conjunction with the BREAK and SKIP statements, it is possible to break out from several nested repetitive statements with a single BREAK statement. The loop string argument can also be supplied to the LoopCount operator so the break can be conditional on the number of iterations of any loop. Without specifying a loop string, BREAK, SKIP and LoopCount refer to the current loop by default.

Example

The following example illustrates the use of loop strings and the LoopCount operator in nested repetitive statements. It outlines an algorithm in which the domain of definition of a particular problem is extended in every loop based on the current solution, after which the new problem is solved by means of a sequential solution process.

repeat "OuterLoop"
     ... ! Determine initial settings for sequential solution process

    while( Abs( Solution - OldSolution ) <= Tolerance ) do
        OldSolution := Solution ;

        ... ! Set up and solve next sequential step ...

        ! ... but terminate algorithm when convergence is too slow
        break "OuterLoop" when LoopCount >= LoopCount("OuterLoop")^2 ;
    endwhile;

    ... ! Extend the domain of definition based on current solution,
        ! or break from the loop when no extension is possible anymore.
endrepeat;

The FOR Statement

The FOR statement is related to the use of iterative operators in expressions. An iterative operator such as SUM or MIN applies a particular operation to all expressions defined over a particular domain. Similarly, the FOR statement executes a group of execution statements for all elements in its domain. The syntax of the FOR statement is given in the following diagram.

Syntax

for-statement:

image/svg+xmlUNORDERED SPARSE ORDERED FOR ( binding-domain ) DO loop-string statement ENDFOR ;

Execution is sequential

The binding domain of a FOR statement can only contain free indices, which are then bound by the statement. All statements inside a FOR statement are executed in sequence for the specific elements in the binding domain. Unless specified otherwise, the ordering of elements in the binding domain, and hence the execution order of the FOR statement, is the same as the order of the corresponding binding set(s).

Integer domains

FOR statements with an integer domain in the form of an enumerated set behave in a similar manner as the FOR statement in programming languages like C or Pascal. Like the example below, FOR statements of this type are mostly of an algorithmic nature, and the indices bound by the FOR statement typically serve as an iteration count.

Example

for ( n in { 1 .. MaxPriority } ) do

    x.NonVar( i | x.Priority(i) < n ) := 1;
    x.Relax ( i | x.Priority(i) = n ) := 0;
    x.Relax ( i | x.Priority(i) > n ) := 1;

    Solve IntegerModel;
endfor;

This example tries to solve a mixed-integer mathematical program heuristically in stages. The algorithm first only solves for those integer variables that have a particular integer priority, and then changes them to non-variables before going on to the next priority. The suffices used in this example are discussed in Variable Declaration and Attributes.

Non-integer domains

FOR statements with non-integer binding domains are typically used to process the data of a model for all elements in a data-related domain. The use of a FOR statement in such a situation is only necessary if the statements inside it form a unit, for which sequential execution for each element in the domain of the entire group of statements is essential. An example follows.

Example

for ( i in Cities ) do
    SmallestTransportCity := ArgMin( j, Transport(i,j) ) ;
    DiscardedTransports   += Transport( i, SmallestTransportCity ) ;
    Transport( i, SmallestTransportCity ) := 0 ;
endfor;

In this example the three assignments form an inseparable unit. For each particular value of i, the second and third assignment depend on the correct value of SmallestTransport in the first assignment.

Use FOR only when needed

If you are familiar with programming language like PASCAL and C, then the use of FOR statements will seem quite natural. In AIMMS, however, FOR statements are often not needed, especially in the context of indexed assignments. Indexed assignments bind the free indices in their domain implicitly, resulting in sequential execution of that particular assignment for all elements in its domain. In general, such an index binding assignment is executed much more efficiently than the same assignment placed inside an equivalent FOR statement. In general, you should use FOR statements only when really necessary.

AIMMS issues a warning

AIMMS will provide a warning when it detects unnecessary FOR statements in your model. Typically FOR statement are not required when the loop only contains assignments that do not refer to scalar identifiers (either numeric or element-valued) to which assignments have been made inside the loop as well. For instance, in the last example the FOR statement is essential, because the assignment and use of the element parameter LargestTransportCity is inside the loop.

Example

The following example shows an unnecessary use of the FOR statement.

solve OptimizationModel;

! Mark variables with large marginal values
for (i) do
    if ( Abs[x.Marginal(i)] > HighPrice ) then
        Mark(i) := x.Marginal(i);
    else
        Mark(i) := 0.0;
    endif;
endfor;

While this statement may seem very natural to C or Pascal programmers, in a sparse execution language like AIMMS it should preferably be written by the following simpler, and faster, assignment statement.

Mark(i) := x.Marginal(i) OnlyIf ( Abs[x.Marginal(i)] > HighPrice );

The SPARSE, ORDERED and UNORDERED keywords

With the optional keywords SPARSE, ORDERED and UNORDERED you can indicate that AIMMS should follow one of three possible strategies to execute the FOR statement. If you do not explicitly specify a strategy, AIMMS will follow the SPARSE strategy by default, and issue a warning when this strategy leads to severe inefficiencies. You can find an explanation of each of the strategies, as well as a description of the cases in which you may want to choose a specific strategy in Ordered Sets and the Condition of a FOR Statement.

FOR as a repetitive statement

Like the WHILE and the REPEAT statements, FOR is a repetitive statement. Thus, you can use the SKIP and BREAK statements and the LoopCount operator. In addition, you can identify a FOR statement with a loop string thereby controlling execution in nested arrangements as discussed in the previous section.

Use of SKIP and BREAK

The SKIP statement skips the remaining statements in the FOR loop and continues to execute the loop for the next element in the binding domain. The BREAK statement will abort the execution of the FOR statement all together.

The SWITCH Statement

The SWITCH statement

The SWITCH statement is used to choose between the execution of different groups of statements depending on the value of a scalar parameter reference. The syntax of the SWITCH statement is given in the following two diagrams.

Syntax

switch-statement:

image/svg+xmlSWITCH reference DO selector : statement ENDSWITCH ;

selector:

image/svg+xmlquoted-element integer element-range DEFAULT ,

Integers and set element

The SWITCH statement can switch on two types of scalar parameter references: set element-valued or integer-valued. When you try to switch on references to string-valued or non-integer numerical parameters, AIMMS will issue a compile time error

Switch selectors

Each selector in a SWITCH statement must be a comma-separated list of values or value ranges, matching the type of the selecting scalar parameter. Expressions and ranges used in a SWITCH statement must only contain constant integers and set elements. Set elements used in a switch selector must be known at compile time, i.e. the data initialization of the corresponding set must be a part of the model description.

The DEFAULT selector last

The optional DEFAULT selector matches every reference. Since AIMMS executes only those statements associated with the first selector matching the value of the scalar reference, it is clear that the DEFAULT selector should be placed last.

Example

The following SWITCH statement takes different actions based on the model status returned by a SOLVE statement.

solve OptimizationModel;

switch OptimizationModel.ProgramStatus do
    'Optimal', 'LocallyOptimal' :
            ObservedModelStatus := 'Solved' ;

    'Unbounded', 'Infeasible', 'IntegerInfeasible', 'LocallyInfeasible' :
            ObservedModelStatus := 'Infeasible' ;

    'IntermediateInfeasible', 'IntermediateNonInteger', 'IntermediateNonOptimal' :
            ObservedModelStatus := 'Interrupted' ;

    default :
            ObservedModelStatus := 'Not solved' ;
endswitch ;

The HALT Statement

Terminating execution

With a HALT statement you can stop the current execution. You can use it, for example, if your model has run into an unrecoverable error condition during its execution, or if you simply want to skip the remaining statements because they are no longer relevant in a particular situation.

Compare to RETURN

Instead of the HALT statement you can also use the RETURN statement (see also Internal Procedures) to terminate the current execution. The HALT statement directly jumps back to the user interface, but a RETURN statement in a procedure only passes back control to the calling procedure and continues execution from there.

Syntax

The syntax of the HALT statement follows.

halt-statement:

image/svg+xmlHALT WITH string-expression WHEN logical-expression ;

Printing a message

You can optionally specify a string in the HALT statement that will be printed in a message dialog box when execution has stopped. This is useful, for instance, to pass on an appropriate message to the user when a particular error condition has occurred.

The WHEN clause

You can make the execution of the HALT statement conditional on a WHEN clause. If present, the current run will only be aborted if the condition after the WHEN clause is satisfied.

Example

The following example terminates the current run if the SOLVE statement does not solve to optimality. When aborting, the user will be notified with an explanatory message.

solve LinearOptimizationModel;

halt with "Execution aborted: model not solved to optimality"
     when OptimizationModel.ProgramStatus <> 'Optimal' ;

Alternative

Note that the type of model termination initiated by calling the HALT statement cannot be guarded against using AIMMS’ error handling facilities (see Raising and Handling Warnings and Errors). An alternative to the HALT statement, which enables error handling, is the RAISE statement discussed in Raising Errors and Warnings. When you want to let the HALT act as a RAISE statement, you can switch the option halt_acts_as_raise_error on.

The BLOCK Statement

The BLOCK statement

A sequence of statements can be grouped together into a single statement using the BLOCK statement, possibly serving one or more of the following purposes:

  • to emphasize the logical structure of the model,

  • to execute a group of statements with different option settings, or

  • to permit error handling on a group of statements (see Raising and Handling Warnings and Errors).

The syntax of the BLOCK statement is as follows.

Syntax

block-statement:

image/svg+xmlBLOCK WHERE option := expression , ; statement ONERROR identifier DO statement ENDBLOCK ;

Emphasizing logical structure in the model

Consider the following BLOCK statement containing a group of statements.

block ! Initialize measured compositions as observable.
    CompositionObservable(nmf,c in MeasuredComponents(nmf)) := 1;
    CompositionObservable(mf,mc) := 0;

    if ( not CheckComputableFlows ) then
        UnobservableComposition(nmf,c) := 1$(not CompositionObservable(nmf,c));
        return 0;
    endif;

    CompositionCount(pu,c) :=
        Count((f,g) | Admissable(pu,c,f,g) and CompositionObservable(g,c));
    NewCount := Card ( CompositionObservable );
endblock ;

In the AIMMS syntax editor, a block can be displayed in either a collapsed or an expanded state. When collapsed, the block will be displayed as follows, using a single line comment following the BLOCK keyword as its description.

image

When in a collapsed state, AIMMS will show the contents of the block in a tooltip if the mouse pointer is placed over the collapsed block, as illustrated in the figure below.

image1

Executing with different option settings

During the execution of a block statement, the options in the WHERE clause will have the specified values set at the beginning of the block statement, and the old values restored at the end of the block statement. More on the format of option names and value settings can be found in The OPTION and PROPERTY Statements. The example below prints various parameters using various settings of the option Listing_number_precision.

! The default value of the option Listing_number_precision is 3.
block ! Start printing numbers using 6 decimals.
    where Listing_number_precision := 6 ;

    display A, B ;

    block ! Start printing numbers without decimals.
        where Listing_number_precision := 0 ;
        display C, D ; ! The output looks as if C and D are integers.
    endblock ;

    display E, F ; ! Back to printing numbers using 6 decimals.

endblock ;

display G, H ; ! Back to printing numbers using 3 decimals.

In the above example, a nested block statement is used to set the scope of option settings; the inner block statement temporarily overrides the option setting of the outer block statement, which overrides the global option settings.

The OnError clause

The OnError clause is one of the means of handling runtime errors in AIMMS. It is discussed in detail in Handling Errors.