0% found this document useful (0 votes)
11 views

Dev Range Variables and For Loops in Mathcad

The document discusses different ways of using range variables and for-loops in Mathcad, noting some surprising differences between versions. Random data is generated and filtered using range variables and indexing in various ways. For-loops are also used to filter the data. The behavior of range variables is examined, finding inconsistencies between Mathcad versions.

Uploaded by

gurpreets_807665
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
11 views

Dev Range Variables and For Loops in Mathcad

The document discusses different ways of using range variables and for-loops in Mathcad, noting some surprising differences between versions. Random data is generated and filtered using range variables and indexing in various ways. For-loops are also used to filter the data. The behavior of range variables is examined, finding inconsistencies between Mathcad versions.

Uploaded by

gurpreets_807665
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 13

• version is a component that determines the version number of Mathcad

version := 0 version = "13,1,3,0"

Ranges, Sequences and For-Loops - Playground

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)

Define filter functions

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

range variable application vector application


the vector element in range? in-range numbers element in range? in-range numbers

0.2 1 0.2 1 0.2


0.991 0 0 0 0
0.822 0 0 0 0
0.224 1 0.224 1 0.224
0.106 0 0 → 0  → 0
x=
0.151 ( k) =
in2 x
1 ( k)
in2 x ⋅ x =
k 0.151
in2( x) =
1
( in2( x) ⋅ x) =
0.151
0.285 0 0 0 0
0.129 1 0.129 1 0.129
0.113 0 0 0 0
0.83 0 0 0 0

• Note: in2(x) needs vectorizing in M7, M13 and M14, but not in M11.

Filtering by Range Variable

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.

Filter Attempt One


The idea behind this is fairly straightforward. In a zero-based indexing system, rows(y) gives the index of the next
'free' element of the vector y. in2(xk) returns one if xk is matching element, so multiplying in2(xk) by rows(y) is
simply rows(y) for a match and zero for a non-match.

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 )

• Trying the column operator makes no difference to M7, M11 or M14


y := 0
y := −1
0
T
y
rows y( k⋅ 0 ) ⋅ in2( xk) := xk y = ( 0.83 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

Filter Attempt Two


The idea behind this is even simpler. A summation operator counts the number of matches that occur up to and
including the kth element. In a zero-based indexing system, this is the index of the next 'free' element. However, if
we were to merely assign the kth element of x to this index, we wouldn't necessarily get a matching element, and
indeed would probably overwrite the last matching element. To get round this, we use an if statement on the rhs of
the definition that returns the kth element if it's a match or the current last value of y otherwise. Again, the first
element of y isn't part of the list of matches and should be removed.
N− 1

i := 0 only works in M11 if this is enabled ( i)


in2 x = 4
i =0
y := 0

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

• Works in M7, M11, M12 and M13, but not M14


• In M14, disable the Seed statement above then repeatedly press ctl-F9. M14 displays a mix of -1s and
occasional matches.

Filter Attempt Three


Although the above two methods (should) work, they involve relatively complex index calculations on the left hand
side; it would be nice to simplify them The problem is that the range variable must appear on the left hand side of a
definition. In the cases above, we've actively used the range variable to calculate the index. However, this is not
the only option. We can make use of the concept of a nested vector.

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

Range variable to vector


One of the interesting uses of this particular method is that it can convert any range variable into a vector

s := 0.5 , 0.55 .. 0.75


vs := 0 Ensure variable is reset
vs := 0 vs = ( 0 ) Initialize vector
0
vs
0s (
:= stack vs , s
0 ) stack the elements
T
vs = ( 0 0.5 0.55 0.6 0.65 0.7 0.75 )
0
(
vs := submatrix vs , 1 , last vs , 0 , 0
0 ( 0) ) remove the initialization element
T
vs = ( 0.5 0.55 0.6 0.65 0.7 0.75 )

Additionally, the form below works in M12 and M13


vs := 0
j := 0

( vs j+0s j ) := ( s
T
j + 1) vs = ( 0.5 0.55 0.6 0.65 0.7 0.75 )

Filter Attempt Four


We can move away from a direct indexing method to an indirect method. We first determine how many matches
there are and create a corresponding range variable. Next we create a vector showing how many matches there are
in the index range 0 to the kth element, and then filter this vector such that it now contains the index in y of the
corresponding matching number in x. Finally, the built-in function match determines the index of each match and
we assign the number to y

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 )

Parallel calculations using range variables


One potential problem with range variables is that the values of several iterated variables may be mutually
dependent. Calculating them individually will lose that dependence, as the range variable applies to only one region.
However, by putting related expressions inside an array, Mathcad allows the user to calculate the variables in
parallel.

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

Other range variable observations

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

Reset common variables: N := 4 i := 0 .. N a := 0 b := 0 c := 0

Case One: array + scalar variable on lhs


This case works in M11 .. M14, but not M7, and works as expected; a takes on the values of i and b takes on the
last value of i

a i T (0 1 2 3 4 )
i a
:= =
b 3⋅ i b 12

Case Two: array + scalar variable on both sides


This case works in M11 .. M14, but not M7. However, c is simply 12 (3*i) in M11 and M14, but the more useful 30
(sum of 3*i) in M12 and M13. For M11 and M14, it doesn't matter if c is a scalar or single-element array.

c := 0

6
a i T (0 1 2 3 4 )
i a
:= = ( 3 ⋅ i) = 30
c 3⋅ i + c c 30
i

Passing of ranges and range variables as arguments to functions.


From M11 onwards (apart from M12), it is possible to use ranges as arguments to functions. From M12 to M??, it
is also possible to use range variables as arguments, even though they only appears on the rhs of an evaluation,
and without Mathcad evaluating them prior to their use by the function.

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

This works even if the range is not an integer sequence.


r := f( a , a + b .. c)

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

And evaluate it ...

q := f( rng( a , 2 ⋅ b , c) ) rng( a , 2 ⋅ b , c) =

7
T
q = ( 4 6.25 9 )

To confirm that rng does generate a range ...

(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

Assignment of ranges to an array


It is also possible to store ranges as elements of an array in M11..M14, but not M7.

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

The for loop


An example of the efficiency of the for operator. Consider a function, pick, that takes a vector, v, a list (vector) of
indices into v, and returns a vector of the corresponding elements of v. (pick is effectively a filter, where the
condition is that the index of a element is within lst.)
If the for loop simply took a range, then we would have to write

pick( v , lst) := w←0 holds returned vector


k←0 index for w
for i ∈ 0 .. last( lst) for i from 0 to last element of lst
j ← lst get index into v
i
w ←v store that jth value of v as the kth element of w
k j
k←k+ 1 increment k to point at next 'free' element

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.

pick( v , lst) := w←0 initialize returned vector


for i ∈ lst for each index in lst
w ←v get the corresponding value of v, store it in the next 'free' element of w
rows( w) i
w return w

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

y := r ← 0 .. 9 the second code fragment allow the creation of a range variable


(?) that is used by the for-loop iteration variable
for k ∈ r
2 3
z ← k + j ⋅ ( k + 1)
k
z
T
y = ( i 1 + 8i 4 + 27i 9 + 64i 16 + 125i 25 + 216i 36 + 343i 49 + 512i 64 + 729i 81 + 1000i )
T
y →

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

You might also like