0% found this document useful (0 votes)
117 views8 pages

Tricks of The Trade: Tutorial

Uploaded by

Damir Gruicic
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)
117 views8 pages

Tricks of The Trade: Tutorial

Uploaded by

Damir Gruicic
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/ 8

Tutorial

Tricks of the Trade

This is a column of programming tricks and techniques, most of which, we hope, will be
contributed by our readers. You are encouraged to submit ideas to this column.

Edited by Paul Abbott

Proofs Without Words


In the book Proofs Without Words: Exercises in Visual
Thinking, by Roger B. Nelson (Mathematical Association of
America, 1993) there are a number of nice examples of
visual proofs. Because Mathematica contains numeric,
symbolic, and graphic operations, it is an excellent environ-
ment for demonstrating such proofs. Steven Skiena
([email protected]) includes a number of examples of visu-
alizing combinatorical objects in his book Implementing Dis-
crete Mathematics: Combinatorics and Graph Theory with
Mathematica (Addison-Wesley, 1990). The code for his book
accompanies Mathematica as the standard package
DiscreteMath`Combinatorica`. In a similar vein, Ted Courant
showed “Pictures of Geometric Series” in the last issue of the
In[6]:= proof[7]
Journal.
Here are two examples of visual proofs. We first set the
Graphics option AspectRatio to Automatic to obtain graphics
with equal x and y scales:

In[1]:= SetOptions[Graphics, AspectRatio -> Automatic];

Sums of Odd Integers


The following graphic proof (done here using Disk and Line)
of the formula for the sums of odd integers

1 + 3 + 5 + L (2n 1) = n2

is attributed to Nicomachus of Gerasa:

In[2]:= lines[n_] := Table[


{Line[{{0, i}, {i, i}}], Line[{{i, 0}, {i, i}}]}, Sums of Squares of Fibonacci Numbers
{i, n} ] The Fibonacci numbers:
In[3]:= dots[n_] := Table[{GrayLevel[1/2 - (-1)^Max[i,j]/4], F1 = 1, F2 = 1, Fn = Fn 1 + Fn 2,
Disk[{i - 1/2, j - 1/2}, 3/8]}, {i, n}, {j, n}]
can be implemented directly:
In[4]:= proof[n_] := Show[Graphics[{lines[n], dots[n]}]]
In[1]:= fib[1] = 1;
In[5]:= proof[4]
In[2]:= fib[2] = 1;
In[3]:= fib[n_] := fib[n] = fib[n-1] + fib[n-2]

VOLUME 5, ISSUE 4 27
Here are the first 15 Fibonacci numbers: Sound “Visualization”

In[4]:= Array[fib, 15] Every user should be aware that Mathematica has excellent
graphics capabilities which greatly assist visualization. How-
Out[4]= {1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610}
ever, although Mathematica includes sound capabilities, per-
haps not too many users have taken advantage of them for
A visual proof, due to Alfred Brousseau, involves con-
visualization. Here are a few examples that illustrate the use
structing a graphic that draws and places boxes with sizes
of ListPlay, and show how to use Compile to speed up opera-
equal to the squares of successive Fibonacci numbers
tions that involve lists.
In[5]:= rect[{a_, b_}, {x_, y_}] :=
Logistic Map
{{x, y}, {a+x, y}, {a+x, b+y}, {x, b+y}, {x, y}}
The logistic map is the mapping x a mx(1 - x) where 0 £ m
In[6]:= boxes[n_] := Table[Line[rect[{fib[i], fib[i]}, £ 4 and 0 £ x £ 1 (see, for example, Roman Maeder’s article
If[EvenQ[i], {fib[i-1], 0}, {0, fib[i-1]}]]], “Function Iteration and Chaos” in the Journal 5(2): 28–40).
{i, n}] An efficient iterative implementation to help visualize the
behavior as m = 3 + t/n varies from 3 to 4 is
In[7]:= Show[Graphics[boxes[5]]]
In[1]:= logistic[n_Integer, start_:2/3] :=
Module[ {f, t, x},
f = Compile[{x, t},
(3 + t/n) x (1 - x)];
FoldList[f, N[start], Range[0, n]] ]

In[2]:= ListPlay[logistic[8000], SampleRate -> 2000]

It may be helpful to superimpose grid lines onto this picture:

In[8]:= grid[n_] :=
Module[{k, l},
If[OddQ[n], (k=0; l=1), (k=1; l=0)];
{Table[Line[{{0, j}, {fib[n+k], j}}],
Playing this sound one hears the period-doubling route to
{j, 0, fib[n+l]}],
chaos.
Table[Line[{{i, 0}, {i, fib[n+l]}}],
It may not be immediately obvious that f in this Module
{i, 0, fib[n+k]}]}]
will be fully compiled. One way to check is to use With to
In[9]:= Show[Graphics[{{GrayLevel[0.8], grid[7]}, boxes[7]}]] provide a suitable (integer) value for the parameter n, and
then project out the heart of the InputForm of the compiled
function (which is part [[1,3]]):

In[3]:= With[{n = 10},


Module[ {f, t, x},
f = Compile[{x, t},
(3 + t/n) x (1 - x)];
InputForm[f][[1, 3]] ] ]
Out[3]= {{1, 17}, {4, 1, 0}, {4, 2, 1}, {12, 3, 0}, {12, 10, 1},
{20, 1, 2}, {41, 2, 3}, {36, 1, 3, 4}, {20, 0, 5},
{33, 5, 4, 6}, {12, 1, 2}, {39, 0, 7}, {20, 2, 8},
{33, 8, 7, 9}, {36, 6, 0, 9, 10}, {8, 10}}

Since this output only contains “op-codes,” the function has


It is apparent from these graphics that been fully compiled.

F12 + F22 + L + Fn2 = Fn Fn 1.

28 THE MATHEMATICA JOURNAL © 1995 Miller Freeman Publications


Stabilization of Chaos In[7]:= ListPlay[chaos[2000, -0.05, 5, 3.9, 0.9]]
In the proceedings of the recent First International Mathe-
matica Symposium, A. Iglesias, M.A. Matías, J. Güémez, and
J.M. Gutiérrez present a paper entitled “Controlling Chaos
with Mathematica.” Here is a simple implementation of the
stabilization of the logistic map using feedback of level l
(that is, x a x(1 + l)) applied every Dn timesteps:

In[4]:= chaos[n_Integer, la_, dn_, mu_, start_:0.3] :=


Module[ {f},
f = Compile[{x, {i, _Integer}},
Module[{y = mu x (1 - x)},
If[Mod[i, dn] == 0, y (1 + la), y] ] ];
FoldList[f[#1, #2]&, start, Range[n]] ]
Recurring Decimals
With no feedback (l = 0.0):
Here are the sounds of a few recurring decimal expansions:
In[5]:= ListPlay[chaos[2000, 0.0, 5, 3.9]]
In[8]:= ListPlay[First[RealDigits[N[1/11, 500]]]]

Applying feedback of level l = -0.05 every five time steps


In[9]:= ListPlay[First[RealDigits[N[1/7, 500]]]]
starting from the default value x = 0.3, we see (and hear)
that the chaos is quickly stabilized:

In[6]:= ListPlay[chaos[2000, -0.05, 5, 3.9]]

A look at the decimal expansion

In[10]:= N[1/7, 18]


With a different starting parameter (x = 0.9), stabilization Out[10]= 0.1428571428571428571
takes longer:
shows that the digit sequence repeats every six digits.
Non-recurrent decimals will just sound like noise,

VOLUME 5, ISSUE 4 29
In[11]:= ListPlay[First[RealDigits[N[Sqrt[2], 500]]]] BinarySearch[l_List, k_Integer,
low_Integer, high_Integer, f_] :=
Module[{mid = Floor[ (low + high)/2 ]},
If [low > high, Return[low - 1/2]];
If [f[ l[[mid]] ] == k, Return[mid]];
If [f[ l[[mid]] ] > k,
BinarySearch[l, k, 1, mid-1, f],
BinarySearch[l, k, mid+1, high, f] ] ]

The first recursive call to BinarySearch should read:

BinarySearch[l, k, low, mid-1, f]

This error makes the routine run much slower and causes it
to use a great deal more memory but, as the routine works,
as do rational numbers with digit sequences longer than the
no one would notice much difference when searching short
sample range:
lists. Here are some test results of the corrected routine run
In[12]:= Short[N[1/499, 505], 2] on an IBM PC 486/66 with 8 MB of RAM:
Out[12]//Short=
No. of list elements Best times (seconds)
0.002004008016032064128256513026052104208416833667334669\ 10000 1.373
<<423>>539078156312625250501002004008 20000 2.691
In[13]:= ListPlay[First[RealDigits[%]]] 50000 9.173
100000 63.933
200000 out of memory
400000 out of memory

Even correcting the misprint does not make the routine


fast enough for our purposes. In the following routine,
instead of using recursion, which can be quite expensive in
execution time, we use a While loop.

BinarySearch[l_List, k_Integer,
low_Integer, high_Integer, f_]:=
Module[{lo, mid, hi, el},
lo = low;
hi = high;
While[lo <= hi,
Binary Searching If[(el = f[l[[mid = Floor[(lo + hi)/2]]]]) == k,
Geza Makay Return[mid]];
[email protected] If[el > k, hi = mid-1, lo = mid+1] ];
Return[lo - 1/2] ]
The binary search is the fastest way to find an element in an
ordered list. The standard package DiscreteMath`Combinator-
ica` contains an implementation, Since computing f (the function that extracts the key from
an element of the list) twice may take a long time, we calcu-
In[1]:= Needs[“DiscreteMath`Combinatorica`”] late it once and save the result for later use. The improve-
ment in speed is quite impressive:
In[2]:= ?BinarySearch
BinarySearch[l,k,f] searches sorted list l for key k and No. of list elements Best times (seconds)
returns the position of l containing k, with f a 10000 0.124
function which extracts the key from an element of l. 20000 0.206
50000 0.417
but it turns out to be too slow for long lists. Looking at the 100000 0.928
source code of this routine reveals a misprint: 200000 107.5
400000 out of memory

30 THE MATHEMATICA JOURNAL © 1995 Miller Freeman Publications


But it is still not fast enough. Notice that the time to com- For the final versions of BinarySearch, we used a While loop
plete a search depends (more or less) linearly on the length of 100 times to give more precise timing values, which will (of
the list although, in theory, it should be logarithmic. The course) make the times higher than they actually are:
sudden jump of the execution time at 200,000 elements is
due to insufficient RAM and the use of virtual memory cer- In[10]:= i = 0;
tainly makes the computation much slower. It is not hard to While[i < 100,
find out what makes the modified routine slow: When calling BinarySearch[alist, rand, 1, n, Identity];
the routine, the first argument (a list) is evaluated and copied i++] // Timing
for the use of the routine. Since we do not want to modify Out[10]= {1.76667 Second, Null}
the list in the routine, it is sufficient to pass the list as an
unevaluated symbol (using the HoldFirst attribute). This gives In summary: To speed up routines, do not use recursion
us the following routine: and keep long lists (such as interpolating functions or arrays
of numbers) in unevaluated form.
In[3]:= Unprotect[BinarySearch];
Steve Skiena, author of the Combinatorica package,
In[4]:= SetAttributes[BinarySearch, HoldFirst] responds: I am certainly chagrined to see the bug/typo on
my part which turned BinarySearch into a linear-time algo-
In[5]:= BinarySearch[l_, k_Integer, rithm (independent of the evaluation issue). The reason it
low_Integer, high_Integer, f_] := went over five years undetected within Combinatorica is
Module[{lo = low, mid, hi = high, el, ls = l}, because it is not used for any purpose other than exposition,
If[Length[ls] > 0, so there is no other function which depends upon it which
While[lo <= hi, would reveal its poor run-time behavior.
If[(el = A more interesting excuse is that BinarySearch is a notori-
f[ls[[mid = Floor[(lo + hi)/2]]]]) == k, ously difficult ‘simple’ algorithm to implement correctly.
Return[mid] ]; Knuth states that “many good programmers have done it
If[el > k, hi = mid-1, lo = mid+1] ], wrong” and that that although binary search was first pub-
Return[-1] ]; lished in 1946, the first completely correct implementation
Return[lo - 1/2] ] was apparently not published until 1962!
The speedups associated with keeping the list in unevalu-
This routine is much faster than the previous ones: ated form certainly speak for themselves.

No. of list elements Best times (seconds) Split


10000 0.065 The following utility routine takes a list and a test and splits
20000 0.07 the list at points failing the test by making successive pair-
50000 0.077 wise comparisons. It makes use of pattern matching and the
100000 0.081 recursive possibilities provided by rule-based programming.
200000 0.115 The implementation starts by separating the list into two
400000 0.23 parts:

Note that this routine gives run-times depending logarith- In[1]:= Split[{a_, b___}, test_:SameQ] := Split[{{a}, {b}}, test]
mically on the length of the list only if the list is passed as a
variable in the first argument. Otherwise, the assignment of The matching condition applies the test to the last element of
the local variable ls=l evaluates the expression l, which gives the first list and the first element of the last list and proceeds
run-times linear in the length of the list. recursively:
To test these routines, we used a randomly generated
sorted list of integers, In[2]:= Split[{{a___, b_}, {c_, d___}}, t_] /; t[b, c] :=
Split[{{a, b, c}, {d}}, t]
In[6]:= n = 50000;
In[3]:= Split[{{a___, b_}, {c_, d___}}, t_] :=
In[7]:= alist = NestList[# + Random[Integer, 100]&, 0, n]; {{a, b}} ~Join~ Split[{c, d}, t]

and a random number to search for: Note that conditional tests can appear on the left-hand side
of an expression.
In[8]:= rand := Random[Integer, Last[alist]]
The recursion terminates when the last element is an
In[9]:= BinarySearch[alist, rand, 1, n, Identity] // Timing empty list:
15
Out[9]= {0.25 Second, —} In[4]:= Split[{a_, {}}, t_] := {a}
2
Here are two examples. The first uses the default (SameQ)
test:

VOLUME 5, ISSUE 4 31
In[5]:= Split[{a, a, a, a, b, b, c, c, c, c, d}] In[6]:= Catch[Fold[
Out[5]= {{a, a, a, a}, {b, b}, {c, c, c, c}, {d}} If[#1 > 6.0, Throw[#2 - 1], #1 + 1/#2 ]&,
0.0, Range[300]]]
A second application partitions a list of random numbers, Out[6]= 227

In[6]:= Table[Random[], {10}] Alternatively, the sum can be computed using


Out[6]= {0.515833, 0.107546, 0.114147, 0.682227, 0.304036,
0.905033, 0.380635, 0.044102, 0.360654, 0.845311} In[7]:= NSum[1/i, {i, %}]
Out[7]= 6.00437
by breaking the list when successive elements differ by more
than 0.4: and the inequality tested using

In[7]:= Split[%, Abs[#1 - #2] <= 0.4 &] In[8]:= % - 1/%% < 6.0 < %
Out[7]= {{0.515833}, {0.107546, 0.114147}, {0.682227, 0.304036}, Out[8]= True
{0.905033}, {0.380635, 0.044102, 0.360654}, {0.845311}}
Note that when using Nest or Fold one needs to choose suf-
ficiently large iteration parameters to ensure that a solution is
Catch and Throw encountered before the iteration terminates.
Instead of using Do, For, or While for repetitive computation
involving testing, one can use Nest, Fold, or FixedPoint along Vector and Matrix Norms
with Catch and Throw. Here are four examples. Mathematica does not include any of the standard vector or
The first power of 2 that is greater than 109: matrix norms in the kernel or the standard packages. Per-
haps one reason for this is that any specific norm is very
In[1]:= Catch[ easy to construct using built-in list or matrix operations – in
FixedPoint[If[# > 10^9, Throw[#], 2 #]&, 2]] fact all the definitions below are “one-liners.” Section 16.8.1
Out[1]= 1073741824 of the Handbook of Applied Mathematics, edited by Carl E.
Pearson (Van Nostrand Reinhold, 1990), lists a number of
As a check: vector and matrix norms.
The following vector and matrix will be used in the
In[2]:= %/2 < 10^9 < % numerical examples:
Out[2]= True
In[1]:= vec = Table[Random[], {5}]
The smallest positive integer n such that n! is greater than Out[1]= {0.650118, 0.653373, 0.522767, 0.464846, 0.484166}
108: In[2]:= (mat = Table[Random[], {2}, {2}]) // MatrixForm
Out[2]//MatrixForm=
In[3]:= Catch[Fold[
If[#1 > 10^8, Throw[#2 - 1], Times[##]]&, 0.832695 0.0522864
1, Range[20]]] 0.206315 0.644941
Out[3]= 12
Vector norms
As a check: The Hölder norm is defined by
In[4]:= 11! < 10^8 < 12! Ê n ˆ1 p
Out[4]= True Á
Á
Ë
Âi =1
p
xi ˜ , p ≥ 1
˜
¯
The first arithmetic sum (1 + 2 + 3 + L + n) that exceeds
and can be implemented directly as
10000:
In[3]:= HolderNorm[x_?VectorQ, p_ /; p >= 1] :=
In[5]:= Catch[Fold[
(Plus @@ Flatten[Abs[x]^p])^(1/p)
If[#1 > 10000, Throw[#1], Plus[##]]&,
0, Range[200]]]
For example,
Out[5]= 10011
In[4]:= HolderNorm[vec, 3]
The number of terms n in the harmonic sum (1 + 1ˇ2 +
Out[4]= 0.969231
1ˇ3 + L + 1ˇn) required to exceed 6.0:

32 THE MATHEMATICA JOURNAL © 1995 Miller Freeman Publications


One can visualize the Hölder norm as a function of p: Matrix norms
The following definitions apply to square matrices. It is easy
In[5]:= Plot[HolderNorm[vec, p], {p, 1, 10}] to test if a matrix is square using

In[14]:= SquareQ[a_?MatrixQ] := Equal @@ Dimensions[a]


2.5
In[15]:= SquareQ[mat]
2 Out[15]= True

1.5
The maximum norm of an n ¥ n matrix a is simply the
maximum absolute value occuring in the matrix multiplied
2 4 6 8 10 by n:

In[16]:= MaximumNorm[a_?MatrixQ] :=
The e-norm is simply the maximum absolute value of the Length[a] Max[Abs[a]] /; SquareQ[a]
elements of the vector x:
In[17]:= MaximumNorm[mat]
In[6]:= eNorm[x_?VectorQ] := Max[Abs[x]] Out[17]= 1.66539
In[7]:= eNorm[vec]
The definition of the Hölder matrix norm is identical to
Out[7]= 0.653373
the vector case except for the extra restriction on p:
and may be interpreted as a special case of the Hölder norm Ê ˆ1 p

for p Æ •: Á
ÁÁ
Ë
Âa
i, j
ij ˜˜
¯
, 1£ p £ 2
In[8]:= HolderNorm[vec, 1000]
Out[8]= 0.65648 The implementation is direct:

The e¢-norm is defined by In[18]:= HolderNorm[a_?MatrixQ, p_ /; 1 <= p <= 2] :=


(Plus @@ Flatten[Abs[a]^p])^(1/p) /;
n SquareQ[a]
Âx
i =1
i
In[19]:= HolderNorm[mat, 1.5]
Out[19]= 1.24159
and implemented via
Let’s again visualize the Hölder norm as a function of p:
In[9]:= ePrimeNorm[x_?VectorQ] := Plus @@ Flatten[Abs[x]]

In[10]:= ePrimeNorm[vec] In[20]:= Plot[HolderNorm[mat, p], {p, 1, 2}];


Out[10]= 2.77527
1.7

1.6
The e¢-norm is a special case of the Hölder norm with
p = 1: 1.5

1.4
In[11]:= HolderNorm[vec, 1] 1.3
Out[11]= 2.77527 1.2

The Euclidean norm is a special case of the Hölder norm 1.2 1.4 1.6 1.8 2
with p = 2:
The Euclidean norm is a special case of the Hölder norm
n with p = 2,
2
Âx i
2
i =1
Âa i, j
ij

In[12]:= EuclideanNorm[x_?VectorQ] := HolderNorm[x, 2]

In[13]:= EuclideanNorm[vec] and we take advantage of this fact in the implementation:


Out[13]= 1.25433

VOLUME 5, ISSUE 4 33
In[21]:= EuclideanNorm[a_?MatrixQ] := HolderNorm[a, 2] In[26]:= ?SingularValues

In[22]:= EuclideanNorm[mat] SingularValues[m] gives the singular value decomposition


Out[22]= 1.07454 for a numerical matrix m. The result is a list
{u, w, v}, where w is the list of nonzero singular
There is an alternative equivalent definition of the values, and m can be written as
Euclidean norm: Conjugate[Transpose[u]].DiagonalMatrix[w].v

tr(A† A) one can write

where † indicates the conjugate transpose and the trace of a In[27]:= SpectrumNorm[a_?MatrixQ] :=
matrix, tr, is the sum of its diagonal elements: Max[SingularValues[a][[2]]] /; SquareQ[a]

In[28]:= SpectrumNorm[mat]
In[23]:= tr[a_?MatrixQ] := Plus @@ Transpose[a, {1, 1}]
Out[28]= 0.902607
In[24]:= (tr[Conjugate[Transpose[mat]] . mat])^(1/2)
Out[24]= 1.07454 Condition numbers
For a square matrix A, the condition number Cf(A) is defined
The spectrum norm is the largest singular value of the by f(A)f(A–1), where f is a specified norm. This number
matrix. The singular values of a matrix are defined by gives a bound on the sensitivity of changes in the solution x
of the equation Ax = b, with respect to changes in b.
In[25]:= Sqrt[Eigenvalues[Conjugate[Transpose[mat]] . mat]]
From the definition, here is an implementation that uses
Out[25]= {0.902607, 0.583035} the Euclidean norm by default:

Alternatively, using the built-in function In[29]:= ConditionNumber[a_?MatrixQ, norm_:EuclideanNorm] :=


norm[a] norm[Inverse[a]] /; SquareQ[a]

In[30]:= ConditionNumber[mat]
Out[30]= 2.19406

We can easily work with other norms:

In[31]:= ConditionNumber[mat, SpectrumNorm]


Out[31]= 1.54812

In[32]:= ConditionNumber[mat, MaximumNorm]


Out[32]= 5.27034

In[33]:= ConditionNumber[mat, HolderNorm[#, 1]&]


Out[33]= 5.72829

The Hilbert matrix is a good example of an ill-conditioned


matrix (having a large condition number):

In[34]:= Hilbert[n_] := Table[1/(i+j-1), {i, n}, {j, n}]

In[35]:= (mat = Hilbert[3]) // MatrixForm


Out[35]//MatrixForm=

1 1
- -
1 2 3
1 1 1
- - -
2 3 4
1 1 1
- - -
3 4 5

In[36]:= ConditionNumber[N[mat]]
Out[36]= 526.159

34 THE MATHEMATICA JOURNAL © 1995 Miller Freeman Publications

You might also like