Difference between revisions of "Procedural Programming"
Jhernandez3 (talk | contribs) m |
Jhernandez3 (talk | contribs) |
||
Line 425: | Line 425: | ||
|style="width:100px;" |I | |style="width:100px;" |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 Title="Tip">For the special case of a dot product (page 224), for an expression of the form Sum(a*b, i), it performs a similar transformation internally.</Tip> | ||
<footer>Analytica Enterprise / {{PAGENAME}} / Function List</footer> | <footer>Analytica Enterprise / {{PAGENAME}} / Function List</footer> |
Revision as of 13:05, 14 December 2015
This section shows you how to use the procedural features of the Analytica modeling language, including:
- Begin-End, (), and “;” for grouping expressions
- Declaring local variables and assigning to them
- For and While loops and recursion
- Local indexes
- References and data structures
- Handles to objects
- Dialog functions
- Miscellaneous functions
A procedural program is list of instructions to a computer. Each instruction tells the computer what to do, or it might change the sequence to execute the instructions. Most Analytica models are non-procedural — that is, they consist of an unsequenced set of definitions of variables.
Each definition is a simple expression that contain functions, operators, constants, and other variables, but no procedural constructs controlling the sequence of execution. In this way, Analytica is like a standard spreadsheet application, in which each cell contains a simple formula with no procedural constructs. Analytica selects the sequence in which to evaluate variables based on the dependencies among them, somewhat in the same way spreadsheets determine the sequence to evaluate their cells. Controlling the evaluation sequence via conditional statements and loops is a large part of programming in a language like in Fortran, Visual Basic, or C++. Non-procedural languages
like Analytica free you from having to worry about sequencing. Non-procedural models or programs are usually much easier to write and understand than procedural programs because you can understand each definition (or formula) without worrying about the sequence of execution.
However, procedural languages enable you to write more powerful functions that are hard or impossible without their procedural constructs. For this reason, Analytica offers a set of programming constructs, described in this chapter, providing a general procedural programming language for those who need it.
You can use these constructs to control the flow of execution only within the definition of a variable or function. Evaluating one variable or function cannot (usually) change the value of another variables or functions. Thus, these procedural constructs do not affect the simple non-procedural relationship among variables and functions. The only exception is that a function called from an event handler such as OnChange
or a button OnClick
attribute can change the definition of a global variable (see Button Creation for more details).
Sections
- A Procedural Programming Example
- Summary of Programming Constructs
- Grouping Expressions
- Local Variable Declarations and Assignments
- For and While Loops
- Recursion
- Local Indexes
- Ensuring Array Abstraction
- References and Data Structures
- Object Handling
- Dialog Functions
- Miscellaneous Functions
An example of procedural programming
The following function, Factors(), computes the prime factors of an integer x. It illustrates many of the key constructs of procedural programming.
See below for an explanation of each of these constructs, and cross references to where they are.
Numbers identify features below |
Function Factors(x) Definition: |
---|---|
1 | VAR result := [1];
|
2 | VAR n := 2;
|
3 | WHILE n <= x DO
|
4 | BEGIN
|
2 | VAR r := Floor(x/n);
|
5 | (result := Concat(result, [n]);
|
6 | x := r)
|
4,7 | END; /* End While loop */
|
7, 8 | result /* End Definition */
|
This definition illustrates these features:
VAR x := e
construct defines a local variable x, and sets an initial value e. See “Defining a local variable: Var v := e” on page 376 for more.- You can group several expressions (statements) into a definition by separating them by “;” (semicolons). Expressions can be on the same line or successive lines. See “Begin-End, (), and “;” for grouping expressions” on page 376.
While test Do body
construct tests conditionTest
, and, if True, evaluatesBody
, and repeats until conditionTest
is False. See “While(Test) Do Body” on page 381.- Begin
e1; e2; … End
groups several expressions separated by semicolons “;” — in this case as the body of a While loop. See “Begin-End, (), and “;” for grouping expressions” on page 376. (e1; e2; …)
is another way to group expressions — in this case, as the action to be taken in the Then case. See “Begin-End, (), and “;” for grouping expressions” on page 376.x := e
lets you assign the value of an expressione
to a local variablex
or, as in the first case, to a parameter of a function. See “Assigning to a local variable: v := e” on page 377.- A comment is enclosed between /* and */ as an alternative to { and }.
- A group of expressions returns the value of the last expression — here the function
Factors
returns the value ofresult
— whether the group is delimited by Begin and End, by parentheses marks ( and ), or, as here, by nothing.
Summary of programming constructs
Construct | Meaning: | For more, see |
---|---|---|
e1; e2; … ei
|
Semicolons join a group of expressions to be evaluated in sequence | |
BEGIN e1; e2; …
|
A group of expressions to be evaluated in sequence | |
(e1; e2; … ei)
|
Another way to group expressions | |
m .. n
|
Generates a list of successive integers from m to n
|
|
Var x := e
|
Define local variable x and assign initial value e
|
|
Index i := e
|
Define local index i and assign initial value e
|
|
x := e
|
Assigns value from evaluating e to local variable x .
Returns value |
|
While Test Do Body
|
While Test is True, evaluate Body and repeat.
Returns last value of |
|
{ comments }
/* comments */ |
Curly brackets { } and /* */ are alternative ways to enclose comments to be ignored by the parser. | |
'text'
"text" |
You can use single or double quotes to enclose a literal text value, but they must match. | |
For x := a DO e
|
Assigns to loop variable x , successive atoms from array a and repeats evaluation expression e for each value of x .
Returns an array of values of |
|
For x[i, j…] := a DO e
|
Same, but it assigns to x successive sub-arrays of a , each indexed by the indices, [i, j …
|
|
\ e
|
Creates a reference to the value of expression e .
|
|
1
|
VAR result := [1];
|
|
\ [i, j …] e
|
Creates an array indexed by any indexes of e other than i, j … of references to sub-arrays of e each indexed by i, j ….
|
|
# r
|
Returns the value referred to by reference r .
|
Begin-End, (), and “;” for grouping expressions
As illustrated above, you can group several expressions (statements) as the definition of a variable or function simply by separating them by semicolons (;). To group several expressions as a condition or action of If a Then b Else c
or While a Do b
, or, indeed, anywhere a single expression
is valid, you should enclose the expressions between Begin
and End
, or between parentheses characters ( and ).
The overall value of the group of statements is the value from evaluating the last expression. For example:
(VAR x := 10; x := x/2; x - 2) → 3
Analytica also tolerates a semicolon (;
) after the last expression in a group. It still returns the value of the last expression. For example:
(VAR x := 10; x := x/2; x/2;) → 2.5
The statements can be grouped on one line, or over several lines. In fact, Analytica does not care where new-lines, spaces, or tabs occur within an expression or sequence of expressions — as long as they are not within a number or identifier.
Declaring local variables and assigning to them
Defining a local variable:
This construct creates a local variable v
and initializes it with the value from evaluating expression e
. You can then use v
in subsequent expressions within this context — that is, in following expressions in this group, or nested within expressions in this group. You cannot refer to a local variable outside its context — for example, in the definition of another variable or function.
If v
has the same identifier (name) as a global variable, any subsequent mention of v
in this context refers to the just-defined local variable, not the global.
Examples
Instead of defining a variable as:
Sum(Array_a*Array_b, N)/(1+Sum(Array_a*Array_b, N))
Define it as:
VAR t := Sum(Array_a*Array*b, N); t/(1+t)
To compute a correlation between Xdata
and Ydata
, instead of:
Sum((Xdata-Sum(Xdata, Data_index)/Nopts)*(Ydata-
Sum(Ydata, Data_index)/Nopts), Data_index)/
Sqrt(Sum((Xdata-Sum(Xdata, Data_index)/
Nopts)^2, Data_index) * Sum((Ydata -
Sum(Ydata, Data_index)/Nopts)^2, Data_index))
Define the correlation as:
VAR mx := Sum(Xdata, Data_index)/Nopts;
VAR my := Sum(Ydata, Data_index)/Nopts;
VAR dx := Xdata - mx;
VAR dy := Ydata - my;
Sum(dx*dy, Data_index)/Sqrt(Sum(dx^2, Data_index)*Sum(dy^2,
Data_index))
The latter expression is faster to execute and easier to read. The correlation expression in this example is an alternative to Analytica’s built-in Correlation() function when data is dimensioned by an index other than the system index Run
.
Assigning to a local variable:
The :=
(assignment operator) sets the local variable v
to the value of expression e
.
The assignment expression also returns the value of e
, although it is usually the effect of the assignment that is of primary interest.
The equal sign =
does not do assignment. It tests for equality between two values.
Within the definition of a function, you can also assign a new value to any parameter. This only changes the parameter and does not affect any global variables used as actual parameters in the call to the function.
ComputedBy(x)
This function indicates that the value of a variable is computed as a side-effect of another variable, x
. Suppose v
is defined as ComputedBy(x)
, and the value of v
needs to be computed, then Analytica will evaluate x
. During the evaluation of x
, x
must set the value of v
using an assignment operator.
Even though v
is a side-effect of x
, its definition is still referentially transparent, which means that its definition completely describes its computed value.
ComputedBy
is useful when multiple items are computed simultaneously within an expression. It is particularly useful from within an Iterate() function when several variables need to be updated in each iteration.
Variable rot := ... {a 2-D rotation matrix indexed by Dim and Dim2}
Variable X_rot := ComputedBy(Y_rot)
Variable Y_rot :=
BEGIN
Var v := Array(Dim,[X,Y]);
Var v_r := sum( rot*v, Dim );
X_rot := v_r[Dim2='x'];
v_r[Dim2='y'];
END
Assigning to a slice of a local variable
Slice assignment means assigning a value into an element or slice of an array contained by a local variable, for example:
x[i = n] := e
x
must be a local variable, i
is an index (local or global), n
evaluates to be a value or values of i
, and e
is any expression. If x
was not array or was an array not indexed by i
, the slice assignment adds i as a dimension of x
. The result returned from the assignment operator is the value e
, not the full value of x
, which can be a source of confusion but is that way so you can chain assignments, e.g.:
x[i=3] := x[i=5] := 7
You can write some algorithms much more easily and efficiently using slice assignment. For example:
Function Fibonacci_series(f1, f2, n: Number Atom) :=
INDEX m := 1..n;
VAR result := 0;
result[m = 1] := f1;
result[m = 2] := f2;
FOR I := 3..n DO result[m = i] := result[m = i -1] + result[m = i - 2];
result
In the first slice assignment:
result[m = 1] := f1;
result
was not previously indexed by m
. So the assignment adds the index m
to result, making it into an array with value f1 for m=1
and its original value, 0, for all other values of m
.
More generally, in a slice assignment:
x[i = n] := e
If x
was already indexed by i
, it sets x[i=n]
to the value of e
. All other slices of x
over i
retain their previous values. If x
was indexed by other indexes, say j
, the result is indexed by i
and j
. The assigned slice x[i=n]
has the value e
for all values of the other index(es) j
.
You can index by position as well as name in a slice assignment, for example:
x[@i = 2] := e
This assigns the value of e
as the second slice of x
over index i
.
To set a cell in a multi-dimensional array, include multiple subscript coordinates, e.g.:
x[i=2,j=5,k=3] := 7
Slice assignment array abstracts when x
, n
, or e
have extra dimensions, and the abstraction is coordinated when an index is shared by x
, n
, or e
. Using abstraction, it is possible to assign to many cells in a single assignment operation.
For and While loops and recursion
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 xample 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 anIf-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 f or 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):
- A: Index_1 ▼
1 1 A, B, C 2 D, E, F 3 G, H, I
- Index_1 ▼
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_1 ▼, Index_2 ▶
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.
Enable comment auto-refresher
Lchrisman