For versus Local



Release:

4.6  •  5.0  •  5.1  •  5.2  •  5.3  •  5.4  •  5.5

Is there any difference between the following two constructs?

For ai[I] := a Do «expr»
Local ai[I] := a Do «expr»

You can assume that the array a is indexed by I and also by other indexes. «expr» is any Analytica expression that uses ai.

What For does

The construct

For ai[I] := a Do «expr»

iterates over every dimension of a except I, so that at each iteration ai is indexed only by I.

You can also say that this iterates over every I-slice of a. An I-slice is a 1-D array indexed by only by I. You can view an array a that has one or more other indexes as containing multiple I-slices, one for each combination of the other indexes.

What Local does

The construct

Local ai[I] := a Do «expr»

declares that ai is a local variable and that inside «expr» it can treat ai as having no dimension other than i. It is left up to Analytica to figure out how to ensure that ai has no extra dimension, but usually this is accomplished by evaluating «expr» multiple times, once for each I-slice of a.

An example

Suppose your model makes use of a spreadsheet. Cell range "D11:D110" holds a vector of input information. Cell "G10" contains a computed result that depends on the input cell range. Your Analytica expression sends data over as input and reads back the result:

SpreadsheetSetRange(wb, "Sheet1!D11:D110", a, I);
SpreadsheetCell(wb, "Sheet1", "G", 10)

The variable wb is defined as a call to SpreadsheetOpen. This expression works fine as long as a has no indexes other than I. But what happens if a has another dimension also? This might be index J, or if evaluated in Sample mode, it might be Run.

When the SpreadsheetSetRange call is evaluated, and since it operates only over index I, Analytica's Array Abstraction automatically iterates, calling SpreadsheetSetRange once for every combination of the indexes excluding I. Of course, once this finishes, only the last I-slice of a will be in the cells "G10:G110", since a spreadsheet cell only contains one value at a time. When it then evaluates the SpreadsheetCell call, it retrieves the result for only the final case, not for every I-slice of a as intended.

You can fix this problem by explicitly iterating using For.

For ai[I] := a Do (
SpreadsheetSetRange(wb, "Sheet1!D11:D110", ai, I);
SpreadsheetCell(wb,"Sheet1", "G", 10)
)

or you can fix the problem using Local by declaring the allowed dimensionality of ai during the evaluation of these two lines.

Local ai[I] := a;
SpreadsheetSetRange(wb, "Sheet1!D11:D110", ai, I);
SpreadsheetCell(wb,"Sheet1", "G", 10)

The end result is the same in each case, so that For and Local appear to be equivalent. In fact, you can even write the Local expression using the Do syntax to make it look just like the For expression.

Local ai[I] := a Do (
SpreadsheetSetRange(wb, "Sheet1!D11:D110", ai, I);
SpreadsheetCell(wb, "Sheet1", "G", 10)
)

A difference in perspective

Although For and Local produce the same result, each encourages a somewhat different way of thinking about your expression and your algorithm. When you use For, you are generally thinking about your expression procedurally -- that is, you are thinking explicitly about the steps the computer will take to evaluate your expressions. This is reflected by the description of what For does, which revolves around the use of the word iterates. In contrast, the use of Local encourages you to think declaratively, where you worry about providing the information the computer needs to successfully evaluate your expressions, but you don't explicitly worry about what steps it needs to take to carry out the evaluation. In the example, we tell it that the two-line expression works as long as ai has no indexes other than I.

To a large extent, the Analytica language is more consistent with a declarative way of thinking. For example, you are usually focused on the relationships in your problem, and (if you are an experienced modeler) not usually worrying as much about the indexes that you aren't operating over in your current expression. You define (global) variables, and the definition serves as an implicit description of the explicit value (i.e., the computed value). Computed values include uncertain views as well as mid values. The language is referentially transparent, so that you can see how each object is computed, unlike imperative languages that can change variables from any point in the code. All these Analytica features are all declarative style concepts. But Analytica as a modeling and programming language also has numerous features that enable you to encode algorithms procedurally -- things like explicit looping constructs, assignment to local variables, and meta-inference from button OnClick or variable OnChange events. So there is not a "correct" or "officially sanctioned" perspective. In some cases, it is a matter of taste.

Differences in evaluation

Whereas For and Local may be functionally equivalent in most situations, there can be notable differences in how they are actually evaluated. The Local construct gives the evaluation engine more flexibility to decide what steps or iterations are necessary, which means it has the option to skip iterations if it can prove they have no impact on the final result, or to evaluate the «expr» in an alternative fashion or alternative order.

The differences in the steps used to evaluate For and Local are not generally visible in the final result, but you can sometimes detect them when «expr» includes some sort of side-effect. Side-effects include user-interface interactions, operations that change something external to your model (like files, databases or spreadsheets), and changes to local variables. As a challenge, see if you can predict the result of the following contrived expressions. For both

Index I := 1..4
Index J := 1..5
Variable A := 10*J + I
Variable B := 10*I + J

Expression 1:

Local count := 0;
For ai[I] := A Do (
For bi[I] := B Do (
count := count + 1
)
);
count

Expression 2:

Local count := 0;
Local ai[I] := A Do (
Local bi[I] := B Do (
count := count + 1
)
);
count

Each expression is counting the number of iterations that actually occurs and returning that count. This count is a side-effect of the For or Local construct, and the surprise here is that the result is different. Expression 1 using For evaluates the body 25 times, whereas Expression 2 using Local evaluates the body expression only 5 times. If you were to insert a MsgBox, you would similarly see it pop up 25 times or 5 times respectively.

Because the evaluation engine has the flexibility to change the number of or the order of iterations, when iterations are required, you should never rely on side-effects like count to come out a certain way. If your algorithm relies on a side-effect like this (an inherently procedural, not declarative, concept) that itself is sensitive to the number or order of iterations, then you need to use For. It is possible (even likely) that future releases of Analytica will find even more efficient ways to evaluate expressions that use Local. These changes will have no effect on the final result as long as you are not making use of iteration-dependent side-effects such as count. Hence, to steer clear of any future backward compatibility issues, always use For when you do have iteration-dependent side-effects. Conversely, when you have so such side-effects and evaluation speed is critical, then you should favor Local constructs.

Why only 5 evaluations by Local?

To answer this question, let's remove the iteration-dependent side-effect, and instead suppose F(ai, bi) is any valid Analytica expression that can be computed when ai and bi have no indexes other that I. Then consider the expression

Local ai[I] := A Do (
Local bi[I] := B Do (
F(ai, bi)
)
)

Given that A and B are both indexed by I, J, the final result will contain the results for F(A[@J = 1], B[@J = 1]), F(A[@J = 2], B[@J = 2]), F(A[@J = 3], B[@J = 3]), and so on. However, in the final result, F(A[@J = 1], B[@J = 2]) does not appear. This follows from the rules of array abstraction, which dictate that the result can have only one J index dimension. Whereas the For variant computes every combination, including F(A[@J = 1], B[@J = 2]), and then retains only the diagonal, the Local variant has the freedom to skip all the non-diagonal evaluations that will not be retained in the final result.

A useful instantiation of this, where F(ai, bi) is a computation carried out by our spreadsheet, might be

Local ai[I] := A Do (
SpreadsheetSetRange(wb, "Sheet1!D1:D4", ai, I);
Local bi[I] := B Do (
SpreadsheetSetRange(wb, "Sheet1!E1:E4", bi, I);
SpreadsheetCell(wb, "Sheet1", "G", 10)
)
)

Vacuous loops

A vacuous loop is an expression in which an explicit looping construct is used when it is unnecessary, that is, when array abstraction would apply the expression to all combinations automatically. you should vigorously avoid vacuous loops. They clutter, obscure and lengthen your Definitions, and they can dramatically slow down the evaluation of your models, sometimes by many orders of magnitude.

If a function or expression, F(ai, bi), is fully array-abstractable, as most Analytica expressions and functions are, then explicit iteration is vacuous, as it this example.

For ai[I] := A Do (
For bi[I] := B Do (
F(ai, bi)
)
)

When F(ai, bi) is array-abstractable, then the final result can be obtained using simply F(A, B).

Given the preceding discussion, you might assume that vacuous loops are not possible when you use Local, but you would be mistaken. Your contract with Local is that ai and bi will not have any index other than I when F(ai,bi). It maintains that contract even when F can handle the extra indexes correctly. Hence, the Local loop around an abstractable function is also vacuous, and can similarly slow down evaluation, albeit less so than a vacuous For is some cases.

Declaration using the same name

You can use the Local dimensionality construct to tell Analytica what the acceptable dimensionality of a global variable is, as follows.

Local a[I] := a;
«expr1»;
«expr2»

This says that when the value of global variable a is used anywhere in «expr1» or «expr2», it must ensure that a has no index other than I. The novel aspect of this construct is that the same name, a, is used for the local variable as for the global variable.

Using the same name is a recommended practice because it has the effect of entirely hiding the global variable from the body expressions. This prevents you from accidentally using the value of the global variable when you intended to use the dimensionally constrained local value. It is not uncommon for such a mistake to have no impact on the final result, but cause a multiplicative slowdown effect as the extra index is operated over unnecessarily in the body.

The same name Local construct is a great example where it is beneficial to think declaratively. When you see this, you should interpret it as declaring the allowable indexes for a, rather than as declaring a local variable.

One final benefit on the same name Local construct is that you can easily add it to an existing expression when you discover that your expression requires an assumption about the dimensionality of a. You can add it to the top and not worry about replacing every occurrence of a within the body expression.

See Also

Comments


You are not allowed to post comments.