For and While Loops

For i := a Do expr

The For loop successively assigns the next atom from array «a» to local index «i», and evaluates expression «expr». «expr» might refer to «i», for example to slice out a particular element of an array. «a» might be a list of values defined by m..n or Sequence(m, n, dx) or it might be a multidimensional array. Normally, it evaluates the body «expr» once for each atom in «a».

The result of the For loop is an array with all the indexes of «a» containing the values of each evaluation of «expr». If any or all evaluations of «expr» have any additional index(es), they are also indexes of the result.

Usually, the Intelligent Array features take care of iterating over indexes of arrays without the need for explicit looping.

Tip
Analytica’s Intelligent Array features means that you rarely need explicit iteration using For loops to repeat operations over each dimensions of an array, often used in conventional computer language. If you find yourself using For loops a lot in Analytica, this might be a sign that you are not using Intelligent Arrays effectively. If so, please (re)read the sections on Intelligent Arrays.

A For loop is sometimes useful in these specialized cases:

• To avoid selected evaluations of «expr» that might be invalid or out of range, and can be prevented by nesting an If-Then-Else inside a For.
• To apply an Analytica function that requires an atom or one- or two-dimensional array input to a higher-dimensioned array.
• To reduce the memory needed for calculations with very large arrays by reducing the memory requirement for intermediate results.

See below for an example of each of these three cases.

Special

Avoiding out-of-range errors

Consider the following expression:

If x < 0 Then 0 Else Sqrt(x)

The If-Then-Else is included in this expression to avoid the warning “Square root of a negative number.” However, if x is an array of values, this expression cannot avoid the warning since Sqrt(x) is evaluated before If-Then-Else selects which elements of Sqrt](x) to include. To avoid the warning (assuming x is indexed by i), the expression can be rewritten as:

For j := I do
If x[i = j] < 0 then 0 else Sqrt(x[i = j])

Or as (see next section):

Using y := x in i do
If y < 0 Then 0 else Sqrt(y)

Situations like this can often occur during slicing operations. For example, to shift x one position to the right along i, the following expression would encounter an error:

if i < 2 then x[i = 1] else x[i = i - 1]

The error occurs when x[i = i - 1] is evaluated since the value corresponding to i - 1 = 0 is out of range. The avoid the error, the expression can be rewritten as:

For j := i do 
If j < 2 then x[i = 1] else x[i = j - 1]

Out-of-range errors can also be avoided without using For by placing the conditional inside an argument. For example, the two examples above can be written without For as follows:

Sqrt(if x < 0 then 0 else x)
x[i = (if i < 2 then 1 else i - 1)]
Dimensionality reduction

For can be used to apply a function that requires an atom, one- or two- dimensional input to a multi-dimensional result. This usage is rare in Analytica since array abstraction normally does this automatically; however, the need occasionally arises in some circumstances.

Suppose you have an array A indexed by I, and you wish to apply a function f(x) to each element of A along I. In a conventional programming language, this would require a loop over the elements of A; however, in almost all cases, Analytica’s array abstraction does this automatically — the expression is simply f(A), and the result remains indexed by i. However, there are a few cases where Analytica does not automatically array abstract, or it is possible to write a user-defined function that does not automatically array abstract (e.g., by declaring a parameter to be of type Atom, as detailed in Parameter qualifiers). For example, Analytica does not array abstract over functions such as Sequence, Split, Subset, or Unique, since these return un-indexed lists of varying lengths that are unknown until the function evaluates. Suppose we have the following variables defined (note that A is an array of text values):

Variable A :=
Index_1 ▶
1 A, B, C
2 D, E, F
3 G, H, I
Index_2 ▶
1 2 3

We wish to split the text values in A and obtain a two dimensional array of letters indexed by Index_1 and Index_2. Since Split does not array abstract, we must do each row separately and re-index by Index_2 before the result rows are recombined into a single array. This is accomplished by the following loop:

FOR Row := Index_1 DO Array(Index_2, SplitText(A[Index_1 = Row], ','))

This results in:

Index_2 ▶
Index_1 ▼ 1 2 3
1 A B C
2 D E F
3 G H I

Reducing memory requirements

In some cases, it is possible to reduce the amount of memory required for intermediate results during the evaluation of expressions involving large arrays. For example, consider the following expression:

MatrixA: A two dimensional array indexed by M and N.
MatrixB: A two dimensional array indexed by N and P.
Average(MatrixA*MatrixB, N)

During the calculation, Analytica needs memory to compute MatrixA*MatrixB, an array indexed by M, N, and P. If these indexes have sizes 100, 200, and 300 respectively, then MatrixA*MatrixB contains 6,000,000 numbers, requiring over 60 megabytes of memory at 10 bytes per number.

To reduce the memory required, use the following expression instead:

For L := M Do Average(MatrixA[M = L]*MatrixB, N)

Each element MatrixA[M = L]*MatrixB has dimensions N and P, needing only 200x300x10 = 600 kilobytes of memory at a time.

Tip
For the special case of a dot product, for an expression of the form Sum(a*b, i), it performs a similar transformation internally.

While(Test) Do Body

While evaluates «Body» repeatedly as long as Test <> 0. For While ... to terminate, «Body» must produce a side-effect on a local value that is used by «Test», causing «Test» eventually to equal 0. If «Test» never becomes False, While continues to loop indefinitely. If you suspect that might be happening, type Control+. (Control+period) to interrupt execution.

«Test» must evaluate to an atomic (non-array) value; therefore, it is a good idea to force any local value used in «Test» to be atomic valued. While is one of the few constructs in Analytica that does not generalize completely to handle arrays. But, there are ways to ensure that variables and functions using While support Intelligent Arrays and probabilistic evaluation. See While and array abstraction.

While returns the final value found in the last iteration of Body or Null if no iterations occur. For example:

(Var x := 1; While x < 10 Do x := x + 1) ¨ 10
(Var x := 1; While x > 10 Do x := x + 1) ¨ Null

Using While often follows the following pattern:

Var x[]:= ...;
While (FunctionOf(x)) Do (
...
x := expr;
...
);
returnValue

Iterate (initial, expr, until, maxIter, warnFlag)

Suppose the definition of variable x contains a call to Iterate(). Iterate() initializes x to the value of «initial». While stopping condition «until» is False (zero), it evaluates expression «expr», and assigns the result to x. Given the optional parameter «maxIter», it stops after «maxIter» iterations and, if «warnFlag» is True, issues a warning — unless it has already been stopped by «until» becoming True. If «until» is array-valued, it only stops when all elements of «until» are True.

Iterate() is designed for convergence algorithms where an expression must be recomputed an unknown number of iterations. Iterate (like Dynamic) must be the main expression in a definition — it cannot be nested within another expression. But it can, and usually does, contain nested expressions as some of its parameters. Iterate() (again like Dynamic() and unlike other functions) can, and usually does, mention the variable x that it defines within the expressions for initial and until. These expressions can also refer to variables that depend on x.

If you use Iterate() in more than one node in your model, you should be careful that the two functions don’t interact adversely. In general, two nodes containing Iterate() should never be mutual ancestors of each other. Doing so makes the nesting order ambiguous and can result in inconsistent computations. Likewise, care must be taken to avoid similar ambiguities when using interacting Iterate and Dynamic loops.

Tip
You can usually write convergence algorithms more cleanly using While. One difference is that While requires its stopping condition «Test» to be an atom, where Iterate() allows an array-valued stopping condition «until». Nevertheless, it is usually better to use While because you want it to do an appropriate number of iterations for each element of «until», rather than continue until all its elements are True. But, with While you need to use one of the tricks described in While and array abstraction to ensure the expression fully supports array abstraction.