Dev Range Variables and For Loops in Mathcad
Dev Range Variables and For Loops in Mathcad
The purpose of this worksheet is to look at some different ways of using range variables and for-loops. It is useful to
compare the results of the worksheet in different versions of Mathcad - there are some surprising differences.
Filtering
We start by looking at filtering data, where filtering in this context is the selection of those elements of a vector that
meet a given condition. In particular, we'll generate a vector comprised of uniformly-distributed random numbers
between 0 and 1 inclusive, and pick those elements lying between 0.125 and 0.25 inclusive.
Generate test data
set the seed to obtain a sequence that is both repeatable and generates a reasonable number of variates
within the chosen octile
Seed( 2000) = 1
set the size of the sample, and generate the random numbers and a corresponding range variable
N := 10
x := runif ( N , 0 , 1 )
k := 0 .. last( x)
create a function that returns one if a number lies in the range a to b, zero otherwise
in( x , a , b ) := a ≤ x ≤ b
create a specialized form of 'in' that restricts the range to the second octile
in2( x) := in( x , 0.125 , 0.25)
examine the vector of random numbers and the effect of applying the filter
• Note: in2(x) needs vectorizing in M7, M13 and M14, but not in M11.
1
The idea of a filter is that it will apply a condition to a set of numbers and remove all non-matching numbers (or
select all matching numbers). We'll look at a few ways that we can do this.
If this product is used as an index into y on the lhs of a definition, it effectively adds an element to the end of y. By
setting that element to be the matching number itself, we should be able to build up a vector of matching elements
as Mathcad iterates through the range variable. The first element of y can be discarded as Mathcad will store any
non-matching numbers in it.
clear the receiving variable, y (ie, the variable that will hold the selected numbers)
y := 0
initialize the first element of y to be a number that lies outside of the expected range
y := −1
0
iterate through x and set the next row of y to hold the next matching number
T
y := x y = ( 0.83 0.2 0.224 0.151 0.129 )
rows( y) ⋅ in2 ( xk) k
• This works in M12 and M13 but not in M7, M11 or M14.
• M11 only returns the value of the last number in x (element zero should hold a non-matching number), although
M7 and M14 do return the last matching value
• The question is why doesn't this return a list of matching numbers in M11 or M14? The M12 and M13 results
seem to make more sense. I would expect rows(y) to be updated at each iteration. To support the belief that it
should, let's skip ahead a bit and implement the algorithm as a for loop; this seems to give the required list of
matching numbers:
even more skipping - a function to do the job
y := y ← −1 filter( f , v) := w←0
0
for a ∈ v
for k ∈ 0 .. last( x)
w ← a if f( a)
y ←x
rows( y) ⋅ in2 ( xk)
rows( w)
k
w
y
T T
y = ( 0.83 0.2 0.224 0.151 0.129 ) filter( in2 , x) = ( 0.2 0.224 0.151 0.129 )
T
y := y ← −1 y = ( 0.83 0.2 0.224 0.151 0.129 )
0
for k ∈ 0 .. last( x)
y
rows y ( k⋅ 0
) ⋅ in2( xk) ← xk
y
2
• It appears as if M7 and M11 may be evaluating non-indexed expressions as constants; hence rows(y) is
calculated at entry into the iteration loop and is replaced by 0 throughout. We can examine this by setting an
element
• In this example, I would have expected stack to pick up the expanded version of z, but it doesn't - in any
version of Mathcad.
i := 0 .. 3 z := 0
T 0 0 0 0
z := stack( z , i) z =
i 0 1 2 3
• However, clearing z and setting z0 to z (thereby converting z to a vector) produces a completely different
result. M11 agrees with M12 & M13, but M7 and M14 give different results.
n := 0 z := 0 z0 := 0 zi+ n := stack( z , i)
0
T 0 T 0
z1 = 1 z2 = 0 2
0 0
1
0 0 0 0
z := 0 z0 := 0 ( )
zi+ 1 := stack z i , i
T
z =
0 1 2 3
Another related example of range variables not doing what I'd expect; r clearly does not pick up the 'current' value of
rows(r):
range variable
r := 0 j := 0 .. 7
r := if ( j < 3 , j , rows( r) + j) T
j r = (0 1 2 3 4 5 6 7 )
program (iteration variable)
T
r := r←0 r = ( 0 1 2 6 8 10 12 14 )
for j ∈ 0 .. 7
r j ← if ( j < 3 , j , rows( r) + j)
r
y := −1
0
( ( ) )
3
y k ( ( k)
:= if in2 x , x , y
k last( y) )
in2 ( xi)
T
y = ( −1 0.2 0.224 0.151 0.129 )
i =0
Let's look at the rhs of the definition first. It is similar to the rhs of the second filter in that it adds the kth element if
there is a match and returns the existing value otherwise. The difference is that we use the stack operator to add
the kth element to a vector of matching values Again, the first element isn't part of the list of matches and should
be removed. However, the necessity to use k on the lhs means that we can't simply update y by direct assignment.
Instead, we zero times k as the index (ie, zero) and store the vector of matches as the first element of y.
y := 0
y := −1
0
y
0k ( ( k) ( 0 k) 0)
:= if in2 x , stack y , x , y
y := submatrix( y , 1 , last( y ) , 0 , 0)
T
y = ( 0.2 0.224 0.151 0.129 )
0 0
( vs j+0s j ) := ( s
T
j + 1) vs = ( 0.5 0.55 0.6 0.65 0.7 0.75 )
4
T
x = ( 0.2 0.991 0.822 0.224 0.106 0.151 0.285 0.129 0.113 0.83 )
N− 1
n := ( i)
in2 x n=4 i := 0 .. n − 1
i=0
k
( i)
T
m := in2 x m = (1 1 1 2 2 3 3 4 4 4 )
k
i=0
→ T
m := ( m⋅ in2( x) ) − 1 m = ( 0 −1 −1 1 −1 2 −1 3 −1 −1 )
(0 )
(3 ) T
match( i , m) = y := x y = ( 0.2 0.224 0.151 0.129 )
(5 ) i match( i , m) 0
(7 )
Alternative implementations
One
k
( k) ( i)
T
m := in2 x ⋅ in2 x − 1 m = ( 0 −1 −1 1 −1 2 −1 3 −1 −1 )
k
i =0
n := max( m) i := 0 .. n
T
y := x y = ( 0.2 0.224 0.151 0.129 )
i match( i , m) 0
Two
m := in2 x
0 ( 0) k := 1 .. last( x)
( k)
T
m := m + in2 x m := m − 1 m = (0 0 0 1 1 2 2 3 3 3 )
k k− 1
(0 1 2 )
i := 0 .. max( m)
T T (3 4 )
y := x y = ( 0.2 0.224 0.151 0.129 ) match( i , m) =
i match( i , m) 0 (5 6 )
(7 8 9 )
For example, consider an infection model with four variables: i for the number of individuals affected, s for the
number susceptible, d for the number eliminated and r for the number recovered and hence immune. The time
development of this model is given by the equations below:
t := 0 .. 4 time
initialize iterate
5
i i 0.0001⋅ s ⋅ i
0 t+1 t t
50
s s s − 0.0001⋅ s ⋅ i
0 22000 t+ 1 t t t
:= :=
d 0 d d + 0.55⋅ i
0 t+ 1 t t
0
r r r + 0.45⋅ i
0 t+ 1 t t
t = i = s = d = r =
t t t t
• Note: The design aim for this facility was to provide parallel calculation, ie Mathcad would calculate the right
hand side of the equation first and then assign the result to the left hand side. Unfortunately, Mathcad 11 and
M13 broke this convention and calculate serially - and in opposite senses: M11 scans across then down, while
M13 scans down then across . M7, M12 and M14 restore parallel calculation. This means that caution must
be observed when dealing with such equations - consider a simple swap ...
p r 11 12 q 21 p q p 21
:= = := =
q s 21 22 p 11 q p q 21
p r 11 12 s q 22 21 p r s q p r 22 12
:= = := =
q s 21 22 r p 12 11 q s r p q s 12 22
Although a range variable is supposed to appear on both sides of a definition, there are some exceptions to this
rule; the variations are version dependent to some degree.
When the lhs is an array, only one of the elements must refer to the range variable
a i T (0 1 2 3 4 )
i a
:= =
b 3⋅ i b 12
c := 0
6
a i T (0 1 2 3 4 )
i a
:= = ( 3 ⋅ i) = 30
c 3⋅ i + c c 30
i
Let's define a simple function to generate a vector of squares of its arguments. The for-loop iteration, k, variable
takes on successive values of its argument, x. It then squares them and adds them to the vector y. If x is a scalar,
f returns a single-element array, if x is a vector, range or range variable (M13..M14), it returns a vector.
f( x) := y←0
for k ∈ x
2
y ←k
rows( y)
y
f( 3 ) = ( 9 )
i := 2 .. 5
a := 2 b := 0.25 c := a + 1 j := a , a + b .. c
This passes the range to the This invokes the range variable This passes the range variable
function iteration mechanism to the function
4 (4 ) 4
9 (9 ) 9
f( 2 .. 5 ) = f( i) = g := f( i) g=
16 ( 16 ) 16
25 ( 25 ) 25
4 (4 ) 4
5.063 ( 5.063 ) 5.063
f( a , a + b .. c) = 6.25 f( j) = ( 6.25 ) g := f( j) g= 6.25
7.563 ( 7.563 ) 7.563
9 (9 ) 9
T
r = ( 4 5.063 6.25 7.563 9 )
We can also define a function that returns a range defined from a to c, step b:
rng( a , b , c) := a , a + b .. c
q := f( rng( a , 2 ⋅ b , c) ) rng( a , 2 ⋅ b , c) =
7
T
q = ( 4 6.25 9 )
(4 )
k := rng( a , 2 ⋅ b , c) f( k) = ( 6.25 )
(9 )
But not all constructs work equally across Mathcad versions....M13 doesn't like v, but M11 and M14 do.
p := r ← rng( a , 2 ⋅ b , c) ( v r ) := p
v←0
for k ∈ r
v ←k
rows( v)
(v r)
2 2
p= 2.5 2.5 v= r=
3 3
i := 0 .. N − 1 A := i .. N B := 0 .. i
i i
0
1 0
1 2 0
T 2 3 T 0 1
A = 2 3 B = (0 ) 1
3 4 1 2
3 4 2
4 3
4
• evaluating Ai gives a normal range variable evaluation of A using i as the index, but each 'element' of the
display is actually an evaluated range (M11 shows NaNs).
8
0
1
2
3
4
1
2
A =
i 3
4
2
3
4
3
4
• Mathcad 11 displays A as range specifications, whereas M12..M14 display the evaluated form - it would be
nice to retain the M11 format for *all* display evaluations of a range variable by itself. This would have the
benefit of clearly distinguishing a range variable from a vector.
• The problem is finding a way to use these vectors in interesting fashions. It doesn't seem possible to directly
use an indexed element of a range array
V := 0 V := A
Ai i
V := 9
A0
ii := 0 V := ii ← A
ii+0⋅ i i
but assigning an element of A to a range variable, and then using the variable, does work.
T
a := A V := 9 V = ( 9 9 9 9 9 )
0 a
Sequence Generation
Consider the sequence 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, ..., where any integer, n, appears n times. There are
several ways of generating this sequence, and we'll start with a simple function:
s( n ) := k←0
for i ∈ 1 .. n
for j ∈ 1 .. i
v ←i
k
k←k+ 1
v
T
s( 5 ) = ( 1 2 2 3 3 3 4 4 4 4 5 5 5 5 5 )
We can also make use of the range array, B, given above. In this case, the variable k takes on each range and
9
uses it to generate the appropriate part of the sequence.
w←0 = (1 2 2 3 3 3 4 4 4 4 )
n←0
for k ∈ B
n←n+1
for i ∈ k
w ←n
rows( w)
T
w
w return w
However, the for loop allows the iteration variable to pick up values from any legal sequence, so in this case, i can
directly access the values of lst.
T
lst := match( i , m) lst = ( 0 3 5 7 )
i 0
T
y := pick( x, lst) y = ( 0.2 0.224 0.151 0.129 )
To be completed ...
x := 0
N := 4
i := 0 .. N k := i
10
T
x :== i
i
Example One
x := a←i this code fragment allows the use of an range variable on the rhs
i
only of a local assignment
2
c←a
3
d ← (i + 1)
c + j ⋅d
T
x = ( i 1 + 8i 4 + 27i 9 + 64i 16 + 125i )
T
x → ( i 1 + 8⋅ i 4 + 27⋅ i 9 + 64⋅ i 16 + 125⋅ i )
Example Two
Example Three
z := r ← 0 .. 9 but the third code fragment indicates that it is not possible to use
just the range variable within a block of code
p ← a←r
r
2
c←a
3
d ← (r + 1)
c + j ⋅d
p
T
z →
nor is it possible to assign a sequence to a variable, which would be useful as r could be constructed and
reused.
y := r ← ( 0 .. 9 ) , 10 y := r ← 0 .. 9
for k ∈ r for k ∈ ( 0 .. 9 ) , 10
2 3 2 3
z ← k + j ⋅ ( k + 1) z ← k + j ⋅ ( k + 1)
k k
z z
although we can use a vector, there doesn't appear to be an obvious way to get the for-loop to treat a vector
as an iteration sequence ...
T
sq( n ) := sort( ceil( runif ( n , 0 , 10) ) ) sq( 5 ) = ( 1 3 4 5 9 ) vector
11
T
y0 := r ← sq( 7) y0 = ( 1 2 3 3 4 7 9 )
for k ∈ r
2 3
z ← k + j ⋅ (k + 1)
k
r
T
y0 := r ← stack[ 1 , 3 , 5 , ( 4 , 7 .. 13) , 21] y0 = can't add a range using stack
for k ∈ r
2 3
z ← k + j ⋅ (k + 1)
k
r
T
y0 := r ← stack( 1 , 3 , 5 , 999 , 21) y0 =
although can add range to a vector
r ← 4 , 7 .. 13
3
for k ∈ r the for-loop doesn't try to expand it
2 3 (which is reasonable), so need an
z ← k + j ⋅ (k + 1) additional capability to create an
k
iteration sequence (and possibly
r
use it at worksheet level)
y1 := r ← 0 .. 9 y2 := r ← 0 .. 9 q := y2
y2
for k ∈ r r2 ← r
2 3 r2
z ← k + j ⋅ (k + 1)
k
r
i
1 + 8i
4 + 27i
9 + 64i
16 + 125i
y1 = y= 25 + 216i y2 =
36 + 343i
49 + 512i
64 + 729i
81 + 1000i
100 + 1331i
12
2
0 2.5 0
3 1
1 0 2
2 0 3
3 0 4
p := y1 p= q=
y1 4 0 5
5 0 6
6 0 7
7 0 8
8 0 9
9 0
q =
2 .. 3
13