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
andRETURN
statement for terminating the current execution,the
SKIP
andBREAK
statements for terminating the current repetitive execution, andthe
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:
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:
repeat-statement:
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:
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
andREPEAT
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:
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:
selector:
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:
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:
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.
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.
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.