Difference between revisions of "For and While Loops"
Jhernandez3 (talk | contribs) m (→See Also) |
(Local Variables --> Local Values (terminology change)) |
||
(16 intermediate revisions by 2 users not shown) | |||
Line 4: | Line 4: | ||
== For i := a Do expr == | == For i := a Do expr == | ||
− | The | + | 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 [[Sequence_Operator|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 | + | 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. | Usually, the Intelligent Array features take care of iterating over indexes of arrays without the need for explicit looping. | ||
− | <Tip Title="Tip> Analytica’s Intelligent Array features means that you rarely need explicit iteration using | + | <Tip Title="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]].</Tip> |
− | A | + | A [[For]] loop is sometimes useful in these specialized cases: |
− | * To avoid selected evaluations of | + | * 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 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 | + | * 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. | See below for an example of each of these three cases. | ||
Line 27: | Line 26: | ||
Consider the following expression: | Consider the following expression: | ||
+ | :<code>If x < 0 Then 0 Else Sqrt(x)</code> | ||
− | + | The [[If-Then-Else]] is included in this expression to avoid the warning “Square root of a negative number.” However, if <code>x</code> 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 <code>x</code> is indexed by <code>i</code>), the expression can be rewritten as: | |
− | |||
− | The | ||
− | |||
− | |||
− | ::<code>If x[i=j]<0 then 0 else Sqrt(x[i=j])</code> | + | :<code>For j := I do</code> |
+ | ::<code>If x[i = j] < 0 then 0 else Sqrt(x[i = j])</code> | ||
Or as (see next section): | Or as (see next section): | ||
− | :<code>Using y:=x in i do</code> | + | :<code>Using y := x in i do</code> |
− | + | ::<code>If y < 0 Then 0 else Sqrt(y)</code> | |
− | ::<code>If y<0 Then 0 else Sqrt(y)</code> | ||
Situations like this can often occur during slicing operations. For example, to shift <code>x</code> one position to the right along <code>i</code>, the following expression would encounter an error: | Situations like this can often occur during slicing operations. For example, to shift <code>x</code> one position to the right along <code>i</code>, the following expression would encounter an error: | ||
− | :<code>if i<2 then x[i=1] else x[i=i-1]</code> | + | :<code>if i < 2 then x[i = 1] else x[i = i - 1]</code> |
− | The error occurs when <code>x[i=i-1]</code> is evaluated since the value corresponding to <code>i-1=0</code> is out of range. The avoid the error, the expression can be rewritten as: | + | The error occurs when <code>x[i = i - 1]</code> is evaluated since the value corresponding to <code>i - 1 = 0</code> is out of range. The avoid the error, the expression can be rewritten as: |
− | :<code>For j:=i do | + | :<code>For j := i do |
− | ::If j<2 then x[i=1] else x[i=j-1]</code> | + | ::If j < 2 then x[i = 1] else x[i = j - 1]</code> |
− | Out-of-range errors can also be avoided without using | + | 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: |
− | :<code>Sqrt(if x<0 then 0 else x)</code> | + | :<code>Sqrt(if x < 0 then 0 else x)</code> |
− | + | :<code>x[i = (if i < 2 then 1 else i - 1)]</code> | |
− | :<code>x[i=(if i<2 then 1 else i-1)]</code> | ||
===== Dimensionality reduction ===== | ===== 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 <code>A</code> indexed by <code>I</code>, and you wish to apply a function <code>f(x)</code> to each element of <code>A</code> along <code>I</code>. In a conventional programming language, this would require a loop over the elements of <code>A</code>; however, in almost all cases, Analytica’s array abstraction does this automatically — the expression is simply <code>f(A)</code>, and the result remains indexed by <code>i</code>. 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 <code>Atom</code>, as detailed in [[Parameter | + | Suppose you have an array <code>A</code> indexed by <code>I</code>, and you wish to apply a function <code>f(x)</code> to each element of <code>A</code> along <code>I</code>. In a conventional programming language, this would require a loop over the elements of <code>A</code>; however, in almost all cases, Analytica’s array abstraction does this automatically — the expression is simply <code>f(A)</code>, and the result remains indexed by <code>i</code>. 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 <code>Atom</code>, 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 <code>A</code> is an array of text values): |
− | :A: | + | :<code>Variable A :=</code> |
:{| class="wikitable" | :{| class="wikitable" | ||
− | + | ! colspan="3" style="text-align: left;" | Index_1 ▶ | |
− | ! | ||
− | |||
|- | |- | ||
!1 | !1 | ||
Line 78: | Line 71: | ||
|} | |} | ||
− | |||
:{| class="wikitable" | :{| class="wikitable" | ||
+ | ! colspan="3" style="text-align: left;" | Index_2 ▶ | ||
|- | |- | ||
|1 | |1 | ||
Line 86: | Line 79: | ||
|} | |} | ||
− | 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: | + | We wish to split the text values in <code>A</code> and obtain a two dimensional array of letters indexed by <code>Index_1</code> and <code>Index_2</code>. Since [[Split]] does not array abstract, we must do each row separately and re-index by <code>Index_2</code> before the result rows are recombined into a single array. This is accomplished by the following loop: |
− | :<code>FOR Row := Index_1 DO Array(Index_2, SplitText(A[Index_1=Row], ','))</code> | + | :<code>FOR Row := Index_1 DO Array(Index_2, SplitText(A[Index_1 = Row], ','))</code> |
This results in: | This results in: | ||
− | |||
− | |||
− | |||
:{| class="wikitable" | :{| class="wikitable" | ||
+ | ! !! colspan="3" | Index_2 ▶ | ||
|- | |- | ||
− | ! | + | ! '''Index_1 ▼ ''' |
! 1 | ! 1 | ||
! 2 | ! 2 | ||
Line 106: | Line 97: | ||
|C | |C | ||
|- | |- | ||
− | ! | + | ! 2 |
− | + | |D | |
− | + | |E | |
− | + | |F | |
|- | |- | ||
− | ! | + | ! 3 |
− | + | |G | |
− | + | |H | |
− | + | |I | |
|} | |} | ||
+ | |||
===Reducing memory requirements=== | ===Reducing memory requirements=== | ||
Line 121: | Line 113: | ||
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: | 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: | ||
− | <code>MatrixA:</code> A two dimensional array indexed by M and N. | + | :<code>MatrixA:</code> A two dimensional array indexed by M and N. |
+ | :<code>MatrixB:</code> A two dimensional array indexed by N and P. | ||
+ | :<code>Average(MatrixA*MatrixB, N)</code> | ||
− | + | During the calculation, Analytica needs memory to compute <code>MatrixA*MatrixB</code>, an array indexed by <code>M</code>, <code>N</code>, and <code>P</code>. If these indexes have sizes 100, 200, and 300 respectively, then <code>MatrixA*MatrixB</code> contains 6,000,000 numbers, requiring over 60 megabytes of memory at 10 bytes per number. | |
− | |||
− | |||
− | |||
− | During the calculation, Analytica needs memory to compute <code>MatrixA * MatrixB</code>, an array indexed by <code>M</code>, <code>N</code>, and <code>P</code>. If these indexes have sizes 100, 200, and 300 respectively, then <code>MatrixA * MatrixB</code> 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: | To reduce the memory required, use the following expression instead: | ||
− | :<code>For L := M Do Average(MatrixA[M=L]*MatrixB, N)</code> | + | :<code>For L := M Do Average(MatrixA[M = L]*MatrixB, N)</code> |
− | Each element <code>MatrixA[M=L]*MatrixB</code> has dimensions <code>N</code> and <code>P</code>, needing only 200x300x10= 600 kilobytes of memory at a time. | + | Each element <code>MatrixA[M = L]*MatrixB</code> has dimensions <code>N</code> and <code>P</code>, needing only 200x300x10 = 600 kilobytes of memory at a time. |
<tip title="Tip">For the special case of a [[Matrix_functions#Dot_product_of_two_matrices|dot product]], for an expression of the form <code>Sum(a*b, i)</code>, it performs a similar transformation internally.</tip> | <tip title="Tip">For the special case of a [[Matrix_functions#Dot_product_of_two_matrices|dot product]], for an expression of the form <code>Sum(a*b, i)</code>, it performs a similar transformation internally.</tip> | ||
Line 140: | Line 129: | ||
===While(Test) Do Body=== | ===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 <code>0</code>. If «Test» never becomes <code>False</code>, [[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 [[Ensuring Array Abstraction|While and array abstraction]]. | |
− | + | [[While]] returns the final value found in the last iteration of <code>Body</code> or [[Null]] if no iterations occur. For example: | |
− | :<code>(Var x := 1; While x > 10 Do x := x+1) ¨ Null</code> | + | :<code>(Var x := 1; While x < 10 Do x := x + 1) ¨ 10</code> |
+ | :<code>(Var x := 1; While x > 10 Do x := x + 1) ¨ Null</code> | ||
− | Using | + | Using [[While]] often follows the following pattern: |
:<code>Var x[]:= ...;</code> | :<code>Var x[]:= ...;</code> | ||
Line 162: | Line 149: | ||
== Iterate (initial, expr, until, maxIter, ''warnFlag'') == | == Iterate (initial, expr, until, maxIter, ''warnFlag'') == | ||
− | Suppose the definition of variable x contains a call to [ | + | Suppose the definition of variable x contains a call to [[Iterate]](). [[Iterate]]() initializes <code>x</code> to the value of «initial». While stopping condition «until» is <code>False</code> (zero), it evaluates expression «expr», and assigns the result to <code>x</code>. Given the optional parameter «maxIter», it stops after «maxIter» iterations and, if «warnFlag» is <code>True</code>, issues a warning — unless it has already been stopped by «until» becoming <code>True</code>. If «until» is array-valued, it only stops when all elements of «until» are <code>True</code>. |
− | [ | + | [[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 <code>x</code>. |
− | If you use [ | + | 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 Title="Tip"> You can usually write convergence algorithms more cleanly using | + | <Tip Title="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 <code>True</code>. But, with [[While]] you need to use one of the tricks described in [[Ensuring Array Abstraction|While and array abstraction]] to ensure the expression fully supports array abstraction.</Tip> |
== See Also == | == See Also == | ||
− | * [ | + | * [[Intelligent Arrays]] |
− | + | * [[For versus Var]] | |
+ | * [[Var..Do]] | ||
+ | * [[For..Do]] | ||
+ | * [[While..Do]] | ||
+ | * [[Iterate]] | ||
+ | * [[If-Then-Else]] | ||
+ | * [[Looping over a model]] | ||
+ | * [[Dynamic loop]] | ||
− | <footer>Local | + | <footer>Local Values / {{PAGENAME}} / Recursion</footer> |
Latest revision as of 00:18, 5 January 2017
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.
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.
Library
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.
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.
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.See Also
- Intelligent Arrays
- For versus Var
- Var..Do
- For..Do
- While..Do
- Iterate
- If-Then-Else
- Looping over a model
- Dynamic loop
Enable comment auto-refresher