Evaluation and Debugging in Mathematica

Comp 110 Module 4

Understanding evaluation in Mathematica

In this module, we will discuss how Mathematica evaluates expressions and then focus on some practical techniques for writing and debugging Mathematica functions.  Much of the material below is a simplified, condensed version of Mathematica's tutorial on Evaluation.

In evaluating an expression,  Mathematica follows the principle of applying definitions to that expression until the result no longer changes.  This means that if you take the final result that Mathematica generates, and enter it as Mathematica input, you will get back the same result.  Note that Mathematica is different from most other programming languages because in Mathematica it is perfectly permissible to have undefined variables in the expression.  For example, evaluating the If expression below

If[a==0,1,2]

returns the same expression, since the value of a is unknown.  Mathematica has said that it cannot reduce this expression into a simpler expression, since the value of a is unknown.  On the other hand, the If expression

If[a-a==0,1,2]

evaluates to 1 because the value of a-a is zero independent of the value of a.  Evaluating this second If expression is beyond the power of most programming languages.  To understand this evaluation process, we first note that all expressions in Mathematica are represented internally as expressions of the form

f[a,b,c]

where f is a function and a, b, and c are expressions.  Even arithmetic expressions such as a+b+c are represented internally in this way.  To see this representation, we can use the Mathematica function FullForm

FullForm[a+b+c]

The method in which Mathematica evaluates the expression f[a,b,c]differs depending on whether f (the head of the expression f[a,b,c]) is a user-defined expression or one of a special class of built-in functions.

Standard evaluation

This section describes the standard procedure used by Mathematica to evaluate all user-defined expressions.  Note that there are some kinds of expressions, such as those used to represent Mathematica programs and control structures, which are evaluated in a non-standard way.  The treatment of such expressions is discussed later.  In the standard evaluation procedure, Mathematica first evaluates the head f of an expression, and then evaluates each element a, b, and c of the expression f[a,b,c].  These elements are, in general, themselves expressions, to which the same evaluation procedure is recursively applied.

As soon as Mathematica has evaluated the head of an expression, Mathematica checks to see whether the head is a symbol that has attributes.  If the symbol has the attributes Orderless, Flat or Listable, then immediately after evaluating the elements of the expression Mathematica performs the transformations associated with these attributes.

If a function f is Flat, then Mathematica automatically reduces expressions of the form  f[f[a,b],c] to the form  f[a,b,c].  In standard mathematical terminology, the function f is associative.  For example, the function Plus is Flat.

Attributes[Plus]

Thus, the expression Plus[Plus[a,b],c] reduces to Plus[a,b,c].

FullForm[Plus[Plus[a,b],c]]

If a function f is Orderless, then Mathematica automatically reduces the expression f[b,a] to f[a,b].  In standard mathematical terminology, the function f is commutative.  For example, the function Plus is Orderless

FullForm[Plus[b,a]]

Finally, if a function f is Listable, then Mathematica automatically reduces the expression f[{a,b,c}] to the expression {f[a],f[b],f[c]}.  For example, the function Abs is Listable.

Attributes[Abs]

Abs[{a,b,c}]

The next step in the standard evaluation procedure is to use definitions that Mathematica knows for the expression that Mathematica is evaluating. Mathematica first tries to use definitions that you have made, and if there are none that apply, Mathematica tries built-in definitions.  If Mathematica finds a definition that applies, Mathematica performs the corresponding transformation on the expression.  The result is another expression, which must then in turn be evaluated according to the standard evaluation procedure.

Below is a summary of the six steps used in evaluating an expression f[a,b,c] in the standard evaluation process.

1.  Evaluate the head f of the expression.

2.  Evaluate each element a, b, and c in turn.

3.  Apply transformations associated with the attributes Orderless, Listable, Flat.

4.  Apply any definitions for f that you have given.  These definitions are applied with the most specific having highest priority.

5.  Apply any built-in definitions for f.

6.  Evaluate the result.

As discussed above, Mathematica follows the principle that each expression is evaluated until no further definitions apply.  This means that Mathematica must continue re-evaluating results until Mathematica arrives at an expression that remains unchanged through the evaluation procedure.

To conclude this section, consider the recursive definition of the factorial function given below:

fact1[0]=1;
fact1[n_]:=fact1[n-1]n

To evaluate the function fact1[3], Mathematica first evaluates the head of this expression.  Since fact1 cannot be reduced further, Mathematica next attempts to evaluate 3.  Again, this expression can not be reduced further.  Next, Mathematica notes that fact1 is neither Flat, Orderless, or Listable.  Next, Mathematica attempts to apply user supplied definitions for fact1.  There are two such definitions

?fact1

Since the lefthand side of the first definition fact1[0] does not match fact1[3], Mathematica uses the second definition and simplifies the expression fact1[3] to the expression fact1[3-1] 3.  At this point, Mathematica restarts the entire evaluation process with fact1[3-1] 3.  Mathematica repeatedly applies this process until all six of the steps above fail to transform the expression yielding the final answer of 6.

Non-standard evaluation

While most built-in Mathematica functions follow the standard evaluation procedure, some important ones do not.  For example, most of the Mathematica functions associated with the construction and execution of programs use non-standard evaluation procedures.  In typical cases, the functions either never evaluate some of their arguments, or do so in a special way under their own control.

For example, Mathematica does not evaluate the lefthand side of the assignment x=y (i.e, Set[x,y] in FullForm) because if Mathematica evaluated the lefthand side of the second assignment below

a=1
a=2

then the second expression would reduce to 1=2 which would make no sense.

If expressions also have special evaluation rules.  To evaluate If[a,b,c], Mathematica first evaluates a.  If a is True, then Mathematica evaluates b;  if a is False, then Mathematica evaluates c.  For example, the following function guards against division by zero.

divGuard[a_,b_]:=If[b==0,Print["Division by zero"],(a/b)]

divGuard[1,2]
divGuard[1,0]

Note that if Mathematica evaluated both b and c in this expression automatically, recursive definitions involving If to test for base cases would never work.  (See the problem below.)

Mathematica also evaluates plotting functions in a special way.  For example, the function Plot[f,{x,…}] does not immediately evaluate f, but instead generates a sequence of values for x and then evaluates f for each value in this sequence.  While this distinction might not seem important at first glance, it can lead to some subtle errors when running plotting functions.

Problems

Recall  the function calcPlot[f,a,b] from Module 3a that takes a function f and two numbers a and b as its arguments and plots the expressions f[t] and  f'[t] simultaneously for t on the interval from a to b.

calcPlot[f_,a_,b_]:=Plot[{f[t],f'[t]},{t,a,b}]

Tracing Mathematica evaluations

The standard way in which Mathematica works is to take any expression you give as input, evaluate the expression completely, and then return the result.  When you are trying to understand what Mathematica is doing, however, it is often worthwhile to look not just at the final result of the evaluation, but also at intermediate steps in the evaluation process.  Section 2.5.10 of the Mathematica book describes the function Trace that returns a list of intermediate expressions used during evaluation

Trace[expr] -- generates a list of all expressions used in evaluating expr

Trace[expr,form] -- include only expressions that match the pattern form

The function Trace returns a list that represents the “history” of a Mathematica computation.  The expressions in the list are given in the order that they were generated during the computation.  In most cases, the list returned by Trace has a nested structure, which represents the structure of the computation.

The basic idea is that each sublist in the list returned by Trace represents the evaluation chain for a particular Mathematica expression.  The elements of this chain correspond to different forms of the same expression.  Usually, however, the evaluation of one expression requires the evaluation of a number of other expressions, often subexpressions.  Each subsidiary evaluation is represented by a sublist in the structure returned by Trace.

Below is an example of Trace applied to the evaluation of the expression fact1[3].  Note that the last entry in the list is the value of fact1[3].

Trace[fact1[3]]

Unfortunately, understanding the output of Trace takes a bit of practice.  To help you to understand this output, I have written a function stepper that takes a simple expression and prints out the list of intermediate expressions produced during Mathematica's evaluation of an expression.  The difference between the output of Trace and stepper is that stepper prints out the entire expression at each intermediate step.  For example, evaluating the expression (1+2)*(4-2) yields the intermediate steps

stepper[(1+2)*(4-2)]

stepper can also be used to step through simple recursively defined functions in Mathematica.  In particular, these function should involve only arithmetic operations and If's.  For example, the evaluation of the expression fact1[3] proceeds in the following manner.  (Note that stepper takes an optional second argument that represents the number of steps to process during evaluation.)

stepper[fact1[3],30,FullForm]

Implementation of the function stepper

SetAttributes[stepper,HoldFirst]

stepper[x_,n_:∞,form_:StandardForm]:=Module[{expr=HoldForm[x],i=0,nextExp},
Print[expr//form];
nextExp=singleStep[expr];
While[i<n&&Not[nextExp===expr],
Print[nextExp//form];expr=nextExp;nextExp=singleStep[expr];i++]]

singleStep[x_]:=With[{tr=With[{temp=Extract[x,{1},Unevaluated]},Trace[temp]]},
If[tr=={},x,
With[{ff=getFirst[tr]},ReplacePart[x,ff[[2]],Position[x,Extract[ff,{1,1},HoldPattern]],1]]]]

getFirst[tr_]:=If[tr[[1,0]]===HoldForm,{tr[[1]],tr[[2]]},getFirst[tr[[1]]]]

Clear[fib]
fib[0]:=1
fib[1]:=1
fib[n_]:=fib[n-1]+fib[n-2]

stepper[fib[3]]

Clear[a,b,c,d,e]
If[a,b,If[c,d,If[e,f,g]]]

fact2[n_]:=If[n==0,1,fact2[n-1]n]

fact3[n_,acc_]:=If[n==0,acc,fact3[n-1,n*acc]]

if[True,b_,c_]:=b
if[False,b_,c_]:=c

fact2[n_]:=if[n==0,1,fact2[n-1]n]

Writing and debugging Mathematica expressions

This section summarizes some basic strategies that make writing and debugging Mathematica programs as painless as possible.  Beginning students often expect their programs to work at the first try.  In practice this never happens, even for expert programmers.  (The number of consecutive lines of code that I can write without making an error is zero.  Professional programmers in the US write on average one correct line of code per hour.)  Therefore, most of your time will be spent not in writing your code, but in debugging your code.  Given Mathematica's subtle method of evaluation, the first question that most novice Mathematica programmers ask is: "How do I debug Mathematica functions?"  Before trying to answer this question, we first attempt to answer an even more fundamental question: "How do I write functions that are easy to debug in Mathematica?"  By learning how to write programs that are easier to debug, you can avoid many of the headaches of debugging large, intricate Mathematica functions.

Writing functions that are easy to debug

During the course of using Mathematica to solve problems, a natural tendency of the novice programmer is to try to write a single, large function that solves the problem.   The programmer then attempts to evaluate this function hoping against hope that the program works correctly on the first try.  Invariably, the function has several errors.  In most programming languages, these erroneous expressions would normally raise a warning message.  However, in Mathematica, these erroneous expressions are happily processed by Mathematica as symbolic expressions.  The result can be extremely large, hard to parse, incorrect answers.  For example, the function listAbs given below takes a list of real numbers and should return a list their absolute values.

listAbs[l_]:=Table[If[l≥0,l[[i]],-l[[i]]],{i,Length[l]}]

However, listAbs has a bug that causes it to return some mysterious looking output.

listAbs[{-3,-2,-1,0,1,2,3}]

Although listAbs is a trivial function, we should probably have written a helper function that computes the absolute value of a single number, or, even better, used the Documentation Center to locate Mathematica's built-in absolute value function.  This example also illustrates the rule that we should also test Mathematica functions on small test data before proceeding to large data.  (Imagine the size of the output if the input list had thousands of elements.)

myAbs[x_]:=If[x≥0,x,-x]
listAbs[l_]:=Table[myAbs[l[[i]]],{i,Length[l]}]

In general, the main strategy in writing Mathematica functions that are easy to debug is to make sure that the functions are short and easy to understand.  In particular,

1.  Break complicated functions into a collection of short helper functions.  Test and debug these helper functions first.

2.  Use descriptive names for functions and local variables.  Avoid using several variants of the same name unless the quantities involved are related.

3.  Avoid using global variables.  Instead use local variables defined using With and Module.

Note that using local variables helps to avoid having a variable accidentally modified during the course of defining several functions.

Debugging functions in Mathematica

Given that the solution to any problem should be expressed as a sequence of short, simple functions, the basic approach to debugging Mathematica functions is to test each function as soon as it is written.  Don't try to test another function that relies on this helper function until the helper function is thoroughly debugged.  The test data for the functions should test all of the possible cases associated with the problem.  For example, a function that computes the absolute value of a number should be tested on both positive and negative numbers.

If Mathematica prints an error message during an attempt to evaluate an expression, read the message carefully.  The message often gives clues concerning the reason for the error.  For example, a plotting function might complain that the value that the function is attempting to plot is not a number.  This message is a sign that you should test the function you are trying to plot outside of the plotting function and make sure that this function is returning a number.

If the function returns an answer, you should check whether or not the answer is correct.   (Of course, one important precondition for testing your function is to actually know whether the answer that your function produces is the correct!)  If the answer is incorrect, you should evaluate the function on more test data and look for a pattern in the wrong answers.

If the answer is incorrect, another possibility is to use the function Trace (and its variants) to understand the behavior of the function.  Unfortunately, the output of Trace is usually too cryptic to understand.  A simpler approach is to insert calls to the Print function inside the function in question.  These calls to Print should print out the value of important variables during the course of computation.  For example, the function sumSquares[n] computes the sum of the squares of the integers from 1 to n using the iterative function Do.

sumSquares[n_]:=Module[{sum=0},
Do[sum=sum+(i^2),{i,n}];
sum]

sumSquares[10]

We can print out the intermediate values of the variable sum by inserting the function Print[i," ",sum] before the expression sum=sum+(i^2).

The operator ; is used to build a sequence of expressions to be evaluated.  The expression a;b;c is Mathematica shorthand for the function CompoundExpression[a,b,c].  CompoundExpression evaluates a, then b, then c and finally returns the value of c.  In general, CompoundExpression only makes sense in the context where evaluating the expressions a and b has some side effects such as Printing out some information or Setting some global variables.  For example,

sumSquares[n_]:=Module[{sum=0},
Do[Print[i," ",sum];sum=sum+(i^2),{i,n}];
sum]

sumSquares[10]

For functions that resist these debugging techniques, I use the following technique of last resort:  break existing functions into simpler and simpler pieces and test these pieces individually until you find the error.  Then reassemble the resulting pieces into the corrected function.  Note that this debugging technique is a corollary of the approach of writing complicated  Mathematica functions as a collection of simple functions.

One cause of hard to debug errors is the use of multiple definitions for a single function name.  In general, I suggest that you be careful using this feature of Mathematica.  For example, consider this attempt to memoize the Fibonacci function

badFib[0]=1;
badFib[1]=1;
badFib[n_]:=badFib[n]=badFib[n-1]*badFib[n-2]

Our first attempt at testing badFib yields

badFib[5]

Of course, the problem is that I used * instead of + in the definition of badFib.  So try to fix the definition above and reevaluate badFib[5].  Mathematica still returns 1!  At first glance this error seems mysterious, since we have corrected the definition of badFib.  The problem is that badFib is still using old memoized values for badFib.  The solution to this problem is to Clear[badFib] and to reevaluate the definitions.  In general, when you suspect a problem involving multiple definitions for a single variable, here are three steps to combat the problem:

1.  Use ?name to examine all definitions associated with name.

2.  Use Clear[name] to clear all definitions.  This operation is especially important if the function name is memoized.

3.  Quit the kernel to clear all definitions;  then reevaluate all expressions.

factHelp[n_,acc_]:=If[n==0,acc,factHelp[n-1,n*acc]]

listMax[l_]:=If[l=={},0,If[First[l]>listMax[Rest[l]],First[l],listMax[Rest[l]]]]

listMax[{2,1,3}]

circlePlot[n]=Show[Graphics[Table[Line[{Sin[θ],Cos[θ]},{Θ,0,2π,((2π)/m)}]]],AspectRatio->1]