# IF a THEN b ELSE c with arrays

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

The construct, IF a THEN b ELSE c, generalizes appropriately if any or all of «a», «b», and «c» are arrays. In other words, it fully supports Intelligent Arrays. For example, if condition «a» is an array of Booleans (true or false values), it returns an array with the same index, containing «b» or «c» as appropriate:

Variable X := -2 .. 2
If X > 0 THEN 'Positive' ELSE IF X < 0 THEN 'Negative' ELSE 'Zero' →
X ▶
-2 -1 0 1 2
'Negative' 'Negative' 'Zero' 'Positive' 'Positive'

If «b» and/or «c» are arrays with the same index(es) as «a», it returns the corresponding the values from «b» or «c» according to whether «a» is true or false:

IF X >= 0 THEN Sqrt(X) ELSE 'Imaginary' →
X ▶
-2 -1 0 1 2
'Imaginary' 'Imaginary' 0 1 1.414

In this case, the expression Sqrt(X) is also indexed by X. The IF expression evaluates Sqrt(X) for each value of X, even the negative ones, which return NAN, even though they are replaced by Imaginary in the result.

To avoid evaluating all of «b» or «c»: When IF a THEN b ELSE c is evaluated, the expression «a» is first evaluated completely. If its result is true, or if it results in an array where any value in the array is true, then the expression «b» is evaluated completely as an array operation, meaning the expression is evaluated for all values of all indexes contained within «b». Similarly, if «a» is false or «a» is an array and any element is false, the expression is «c» is evaluated in its entirety. Once both are computed, the appropriate values are picked out of these results according to the result of «a». Sometimes, you want to avoid evaluating elements of «b» or «c» corresponding to elements of «a» that give errors or Null results, to avoid wasting computation time on intermediate results that won’t be used in the final result, or because the computations cause evaluation errors, not just warnings. In such cases, you can use explicit iteration, using a For or While loop over index(es) of «a».

Omitting ELSE: If you omit the ELSE c part, it usually gives a warning when it is first evaluated. If you click Ignore Warnings, it returns Null for elements for which «a» is false:

IF X >= 0 THEN Sqrt(X) →
X ▶
-2 -1 0 1 2
«Null» «Null» 0 1 1.414

After you have clicked Ignore Warnings, it does not give the warning again, even after you save and reopen the model.

Tip
Usually, you should omit the ELSE c part of an IF construct only in a compound expression, when the IF a THEN b is not the last expression, but rather is followed by ";". In this situation, the Null result is not part of the result of the compound expression, and it gives no warning, as shown in this example:
BEGIN
VAR A[] := Min([X, Y]);
IF A < 0 THEN A := 0;
Sqrt(A)
END

Caveats of conditional side effects: In the expression above, the empty brackets following A define A as array with no indexes (i.e., as atomic). Analytica will ensure that within the body of the expression where A is used, A will always be atomic, even if X or Y are array-valued. To do this, Analytica might need to iterate the expression. If you feel compelled to embed an assignment inside a THEN or ELSE clause, you should always make sure that the condition being tested is a scalar and not an array. In this case, because A has been declared to be 0-dimensional, the expression A < 0 is guaranteed to be scalar. If you cannot guaranteed that the IF clause will always be scalar, even if other indexes are added to your model in the future, then you should avoid using assignment from within a THEN or ELSE clause since Analytica evaluates IF-THEN-ELSE and an array operation. Without the brackets declaring A to be scalar, the IF clause would say “IF any value of A is less than zero THEN evaluate the assignment”, so the result would be an array of zeroes even if there is only a single negative number in X and Y. A better way to encode a conditional assignment, which properly array abstracts and has the intended effect, is as follows:

BEGIN
VAR A := Min([X, Y] ;
A := IF A < 0 THEN 0 ELSE A;
Sqrt(A)
END

The dimensions of the result: If «a» is an array containing some True and some False values, IF a THEN b ELSE c, evaluates both «b» and «c». The result contains the union of the indexes of all operands, «a», «b», and «c». But, if «a» is an atom or array whose value(s) are all true (1), it does not bother to evaluate «c» and returns an array with the indexes of «a» and «b». Similarly, if all atoms in «a» are false (0), it does not bother to evaluate «b» and returns an array with the indexes of «a» and «c». This means that the values in the condition «a» can affect whether «b» and/or «c» are evaluated, and which indexes are included in the result.

### IFALL a THEN b ELSE c

If you don’t want the dimensions of the result to vary with the value(s) in «a», use the IFALL construct. This is like the IF construct, except that it always evaluates «a», «b», and «c», and so the result always contains the union of the indexes of all of three operands.

IFALL requires the ELSE c clause. If omitted, it gives a syntax error.

### IFONLY a THEN b ELSE c

IFALL has the advantage over IF (and IFONLY) that the dimensions of the result are always the same, no matter what the values of the condition «a». The downside is that if «a» is an array and all its atoms are True (or all are False), it wastes computational effort calculating «c» (or «b») even though its value is not needed for the result. IFALL also might waste memory (and therefore also time) by including the index(es) that are only in «c» (or «b») even though the result has the same values over those indexes. The standard IF construct might also waste some memory when all of the values of array «a» are True (or all are False), because the result includes any index(es) of «a» that are not indexes of «b» (or «c»), even though the result must be the same over such index(es).

In these situations you can use a third conditional construct, IFONLY a THEN b ELSE c. Like IF, when all atoms of «a» are True (or all False), it evaluates only «b» (or only «c»). But, unlike IF, the result in these cases does include any index(es) of «a» that are not indexes of «b» (or «c», respectively). Thus, IFONLY can be more memory-efficient.

### When to use IF, IFALL, or IFONLY

In most cases, you can just use IF without worrying about IFALL or IFONLY. The only reason to use IFALL is when you want to avoid the possibility that the dimensions of results can vary with values of inputs. The only reason to use IFONLY is when memory is tight and it’s common for condition «a» to be all true or all false.

If condition «a» is an atom or array containing only true (only False) values, IF and IFONLY evaluate only «b» (only «c»), whereas IFALL always evaluates both «b» and «c». The result of IFONLY contains the indexes of only «b» (only «c»). The result of IF contains the indexes of «a» and «b» (or «c»). The result of IFALL always contains the indexes of «a», «b», and «c», and so its dimensions do not depend on the values of «a».

If condition «a» is an array containing mixed true and false atoms, all three constructs behave identically: They evaluate «a», «b», and «c», and the result contains the union of the indexes of «a», «b», and «c».

IFALL requires the ELSE part. It is optional for IF and IFONLY, but strongly recommended except when it is one of multiple statements, and not the last, in a compound expression, enclosed in parentheses or BEGIN ... END.