Contents Previous Next Index
16 Testing, Debugging, and Efficiency
New programs, whether developed in Maple or any other language, sometimes work incorrectly. Problems that occur when a program is run can be caused by syntax errors introduced during implementation, logic errors in the design of the algorithm, or errors in the translation of an algorithm's description into code. Many errors can be subtle and hard to find by visually inspecting your program. Maple provides error detection commands and a debugger to help you find these errors.
Maple has several commands to help you find errors in procedures. Among these are commands to trace procedure execution, check assertions, raise exceptions and trap errors, and verify procedure semantics and syntax.
Additionally, the Maple debugger lets you stop in an executing Maple procedure, inspect and modify the values of local and global variables, and continue the execution process, either to completion, or one statement or block at a time. You can stop the execution process when Maple reaches a particular statement, when it assigns a value to a specified local or global variable, or when a specified error occurs. This facility lets you investigate the inner workings of a program.
Even when a program is working correctly, you may want to analyze its performance to try to improve its efficiency. Maple commands are available to analyze the time and memory consumption involved in running a program.
16.1 In This Chapter
Using the Maple debugger
Detailed debugger information
Additional commands for error detection
Measuring and improving program efficiency
16.2 The Maple Debugger: A Tutorial Example
The Maple debugger is a tool that you can use to detect errors in your procedures. Using this facility, you can follow the step-by-step execution of your code to determine why it is not returning the results that you expect.
This section illustrates how to use the Maple debugger as a tool for debugging a Maple procedure. The debugger commands are introduced and described as they are applied. For more information about the debugger commands, see Maple Debugger Commands.
You can use the command-line Maple debugger or you can use the interactive Maple debugger available in the standard interface.
Figure 16.1: The Maple Debugger in the Standard Interface
In the standard interface, the interactive Maple debugger is opened automatically by Maple when a breakpoint or watchpoint is encountered during the execution of a program. An interactive debugger window is displayed, which contains the following components:
a main text box that displays a procedure name and the debugger output
a field for entering commands and an associated Execute button
buttons that perform common debugging functions
While the interactive debugger has a different user interface, it otherwise functions identically to the command-line Maple debugger. For more information, refer to the InteractiveDebugger help page.
This section introduces various debugger commands. To present and describe all of the options available for these commands, the command-line debugger will be used instead of the interactive debugger. Note that the Common Debugger Commands buttons in the interactive debugger always implement the corresponding commands with their default options. To run a debugger command with non-default options in the interactive debugger, enter the command and options in the Enter a debugger command: field and click the Execute button.
Example
Consider the following procedure, sieve, which is used as a case study. It implements the Sieve of Eratosthenes: given a parameter n, return a count of the prime numbers less than or equal to n. To debug the sieve procedure, breakpoints and watchpoints will be used to stop the the execution of the procedure at selected points or on selected events.
sieve := proc(n::integer) local i, k, flags, count,twicei; count := 0; for i from 2 to n do flags[i] := true; end do; for i from 2 to n do if flags[i] then twicei := 2*i; for k from twicei by i to n do flags[k] = false; end do; count := count+l; end if; end do; count; end proc:
Numbering the Procedure Statements I
To use the Maple debugger, you can enter several debugger commands. Many of these debugger commands refer to statements in the procedures that you are debugging. Statement numbers allow such references. The showstat command displays a Maple procedure along with numbers preceding each line that begins a new statement.
showstat(sieve);
sieve := proc(n::integer) local i, k, flags, count, twicei; 1 count := 0; 2 for i from 2 to n do 3 flags[i] := true end do; 4 for i from 2 to n do 5 if flags[i] then 6 twicei := 2*i; 7 for k from twicei by i to n do 8 flags[k] = false end do; 9 count := count+l end if end do; 10 count end proc
Note: The numbers preceding each line differ from line numbers that may be displayed in a text editor. For example, keywords that end a statement (such as end do and end if) are not considered separate Maple commands and are therefore not numbered.
Invoking the Debugger I
To invoke the Maple debugger, execute a procedure and then stop the execution process within the procedure. To execute a Maple procedure, call it by using a Maple command at the top level or call it from another procedure. The simplest way to stop the execution process is to set a breakpoint in the procedure.
Setting a Breakpoint
Use the stopat command to set a breakpoint in the sieve procedure.
stopat(sieve);
sieve
This command sets a breakpoint before the first statement in the procedure sieve. When you subsequently execute the sieve procedure, Maple stops before executing the first statement and waits for you to provide instructions on what to do next. When the execution process stops, the debugger prompt is displayed (DBG>).
Note: If a procedure has a remember table or a cache table, you may have to run the restart command before running a second or subsequent stopat command. For more information about remember tables and cache tables, see The remember, cache, and system Options or refer to the remember or CacheCommand help pages.
In the following example, the sieve procedure is called.
sieve(10);
sieve: 1* count := 0; DBG>
Several pieces of information are displayed after the debugger prompt.
The previously computed result. This particular execution process stopped at the first statement before making any computations, so no result appears.
The name of the procedure in which the execution process has stopped (sieve).
The execution process stopped before statement number 1. An asterisk (*) follows this statement number to indicate that a breakpoint was set before the statement.
At the debugger prompt, you can evaluate Maple expressions and call debugger commands. Maple evaluates expressions in the context of the stopped procedure. You have access to the same procedure parameters, and local, global, and environment variables as the stopped procedure. For example, since the sieve procedure was called with parameter value 10, the formal parameter n has the value 10.
DBG> n 10 sieve: 1* count := 0;
For each expression that Maple evaluates,
the result of the expression is displayed; if there is no result, the most recent previous result is displayed (this output can be suppressed by using a colon to terminate the command entered at the DBG> prompt)
the name of the stopped procedure
the statement number where the procedure stopped followed by the statement, and
a new debugger prompt.
Note: To remove a breakpoint from a procedure, use the unstopat command.
Controlling the Execution of a Procedure during Debugging I
Debugger commands control how the procedure is executed once the debugger is started. Some commonly used debugger commands are next, step, into, list, outfrom, and cont.
The next command runs the next statement at the current nesting level. After the statement is run, control is returned to the debugger. If the statement is a control structure (for example, an if statement or a loop), the debugger runs any statements within the control structure that it would normally run. It stops the execution process before the next statement after the control structure. Similarly, if the statement contains calls to procedures, the debugger executes these procedure calls in their entirety before the execution process stops.
DBG> next 0 sieve: 2 for i from 2 to n do ... end do; DBG>
The 0 in the first line of the output represents the result of the statement that was run--that is, the result of count := 0. A "*" does not appear next to the statement number because there is no breakpoint set immediately before statement 2. The debugger does not show the body of the for loop, which itself consists of statements with their own statement numbers, unless the execution process actually stops within its body. Maple represents the body of compound statements by ellipses (...).
Running the next command again results in the following output.
DBG> next true sieve: 4 for i from 2 to n do ... end do; DBG>
The execution process now stops before statement 4. Statement 3 (the body of the previous for loop) is at a deeper nesting level. The loop is executed n-1 times. The debugger displays the last result computed in the loop (the assignment of the value true to flags[10]).
Tip: If you want to repeat the previous debugger command, as shown in the second next command above, you can press Enter at the DBG> prompt. You can also view your recent command history using the up and down arrow keys on your keyboard.
To step into a nested control structure (such as an if statement or for loop) or a procedure call, use the step debugger command.
DBG> step true sieve: 5 if flags[i] then ... end if DBG> step true sieve: 6 twicei := 2*i; DBG>
If you use the step debugger command when the next statement to run is not a deeper structured statement or procedure call, it has the same effect as the next debugger command.
DBG> step 4 sieve: 7 for k from twicei by i to n do ... end do; DBG>
At any time during the debugging process, you can use the showstat debugger command to display the current status of the debugging process.
DBG> showstat sieve := proc(n::integer) local i, k, flags, count, twicei; 1* count := 0; 2 for i from 2 to n do 3 flags[i] := true end do; 4 for i from 2 to n do 5 if flags[i] then 6 twicei := 2*i; 7 ! for k from twicei by i to n do 8 flags[k] = false end do; 9 count := count+l end if end do; 10 count end proc DBG>
Maple displays a debugger prompt to indicate that you are still working within the Maple debugger. The asterisk (*) indicates the unconditional breakpoint. An exclamation point (!) that follows a statement number (see line 7) indicates the statement at which the procedure is stopped.
To continue the debugging process, run another debugger command. For example, you can use into or step to enter the innermost loop.
The behavior of the into debugger command is between that of the next and step commands. The execution process stops at the next statement in the current procedure independent of whether it is at the current nesting level or in the body of a control structure (an if statement or a loop). That is, the into command steps into nested statements, but not procedure calls. It executes called procedures completely and then stops.
DBG> into 4 sieve: 8 flags[k] = false DBG>
A debugger command that is related to showstat is the list command. It displays the previous five statements, the current statement, and the next statement to indicate where the procedure has stopped.
DBG> list sieve := proc(n::integer) local i, k, flags, count, twicei; ... 3 flags[i] := true end do; 4 for i from 2 to n do 5 if flags[i] then 6 twicei := 2*i; 7 for k from twicei by i to n do 8 ! flags[k] = false end do; 9 count := count+l end if end do; ... end proc DBG>
You can use the outfrom debugger command to finish the execution process at the current nesting level or at a deeper level. Execution of the procedure is stopped once a statement at a shallower nesting level is reached, that is, after a loop terminates, a branch of an if statement executes, or the current procedure call returns.
DBG> outfrom true = false sieve: 9 count := count+l DBG> outfrom l sieve: 5 if flags[i] then ... end if DBG>
The cont debugger command continues the execution process until either the procedure stops normally or encounters another breakpoint.
DBG> cont
9⁢l
The procedure does not give the expected output. Although you may find the reason obvious from the previous debugger command examples, in other cases, it may not be easy to find procedure errors. Therefore, continue to use the debugger. First, use the unstopat command to remove the breakpoint from the sieve procedure.
unstopat(sieve);
Invoking the Debugger II
The procedure sieve maintains the changing result in the variable count. Therefore, a logical place to look during debugging is wherever Maple modifies count. The easiest way to do this is by using a watchpoint, which starts the debugger whenever Maple modifies a variable that you identify.
Setting a Watchpoint
Use the stopwhen command to set watchpoints. In this case, the execution process will stop whenever Maple modifies the variable count in the procedure sieve.
stopwhen([sieve,count]);
sieve,count
The stopwhen command returns a list of all the currently watched variables (that is, the variables that you provided to the stopwhen command).
Execute the sieve procedure again.
count := 0 sieve: 2 for i from 2 to n do ... end do; DBG>
The execution process stops because Maple modified count and the debugger displays the assignment statement count := 0. Similar to breakpoints, the debugger then displays the name of the procedure and the next statement to be run in the procedure. Note that the execution process stops after Maple assigns a value to count.
This first assignment to count is correct. Use the cont debugger command to continue the execution process.
DBG> cont count := l sieve: 5 if flags[i] then ... end if DBG>
At first glance, this may look correct. Assume that the output is correct and continue the execution process.
DBG> cont count := 2*l sieve: 5 if flags[i] then ... end if DBG>
This output appears to be incorrect because Maple should have simplified 2*1. Note that it printed 2*l (two times the letter l) instead. By examining the source text for the procedure, you can see that the letter "l" was entered instead of the number "1". Since the source of the error has been discovered, you can stop the procedure. Use the quit debugger command to stop the debugger, and then use the unstopwhen command to remove the watchpoint from the procedure.
DBG> quit Interrupted
unstopwhen();
After correcting the source code for sieve, run the restart command, re-execute that source code (for example, read it into your command-line session or re-execute that code region in your worksheet), and execute the procedure again.
restart;
sieve := proc(n::integer) local i, k, flags, count,twicei; count := 0; for i from 2 to n do flags[i] := true; end do; for i from 2 to n do if flags[i] then twicei := 2*i; for k from twicei by i to n do flags[k] = false; end do; count := count+1; end if; end do; count; end proc:
9
This result is still incorrect. There are four primes less than 10, namely 2, 3, 5, and 7. Therefore, start the debugger once more, stepping into the innermost parts of the procedure to investigate. Since you do not want to start executing the procedure from the start, set the breakpoint at statement 6.
stopat(sieve,6);
true sieve: 6* twicei := 2*i; DBG> step 4 sieve: 7 for k from twicei by i to n do ... end do; DBG> step 4 sieve: 8 flags[k] = false DBG> step true = false sieve: 8 flags[k] = false DBG>
The last step reveals the error. The previously computed result should have been false (from the assignment of flags[k] to the value false), but instead the value true = false was returned. An equation was used instead of an assignment. Therefore, Maple did not set flags[k] to false.
Once again, stop the debugger and correct the source text.
The following code represents the corrected procedure.
sieve := proc(n::integer) local i, k, flags, count,twicei; count := 0; for i from 2 to n do flags[i] := true end do; for i from 2 to n do if flags[i] then twicei := 2*i; for k from twicei by i to n do flags[k] := false; end do; count := count+1; end if; end do; count; end proc:
Execute the sieve procedure again to test the corrections.
4
The sieve procedure returns the correct result.
16.3 Maple Debugger Commands
This section provides additional details about the commands used in The Maple Debugger: A Tutorial Example and a description of other debugger commands.
Numbering the Procedure Statements II
The showstat command has the following syntax. The procedureName parameter is optional.
showstat( procedureName );
If showstat is called with no arguments, all procedures that contain breakpoints are displayed.
You can also use the showstat command to display a single statement or a range of statements by using the following syntax.
showstat( procedureName, number );
showstat( procedureName, range );
In these cases, the statements that are not displayed are represented by ellipses (...). The procedure name, its parameters, and its local and global variables are always displayed.
f := proc(x) if x <= 2 then print(x); end if; print(-x); end proc:
showstat(f, 2..3);
f := proc(x) ... 2 print(x) end if; 3 print(-x) end proc
Invoking the Debugger III
This section provides additional information about breakpoints and watchpoints.
Setting Breakpoints
The stopat command has the following syntax, where procedureName is the name of the procedure in which to set the breakpoint, statementNumber is the line number of the statement in the procedure before which the breakpoint is set, and condition is a Boolean expression which must be true to stop the execution process. The statementNumber and condition arguments are optional.
stopat( procedureName, statementNumber, condition );
The condition argument can refer to any global variable, local variable, or parameter of the procedure. These conditional breakpoints are indicated by a question mark (?) if the showstat command is used to display the procedure.
Since the stopat command sets the breakpoint before the specified statement, when Maple encounters a breakpoint, the execution process stops and Maple starts the debugger before the statement.
Note: This means that you cannot set a breakpoint after the last statement in a statement sequence--that is, at the end of a loop body, an if statement body, or a procedure.
If two identical procedures exist, depending on how you created them, they may share breakpoints. If you entered the procedures individually, with identical procedure bodies, they do not share breakpoints. If you created a procedure by assigning it to the body of another procedure, their breakpoints are shared.
f := proc(x) x^2 end proc: g := proc(x) x^2 end proc: h := op(g): stopat(g);
g,h
showstat();
g := proc(x) 1* x^2 end proc h := proc(x) 1* x^2 end proc
Removing Breakpoints
The unstopat command has the following syntax, where procedureName is the name of the procedure that contains the breakpoint, and statementNumber is the line number of the statement where the breakpoint is set. The statementNumber parameter is optional.
unstopat( procedureName, statementNumber );
If statementNumber is omitted in the call to unstopat, all breakpoints in the procedure procedureName are cleared.
Setting Explicit Breakpoints
You can set an explicit breakpoint by inserting a call to the DEBUG command in the source text of a procedure. The DEBUG command has the following syntax. The argument parameter is optional.
DEBUG( argument );
If no argument is included in the DEBUG command, execution in the procedure stops at the statement following the location of the DEBUG command, and then the debugger is started.
Note: The showstat command does not mark explicit breakpoints with an "*" or a "?".
f := proc(x,y) local a; a:=x^2; DEBUG(); a:=y^2; end proc:
showstat(f);
f := proc(x, y) local a; 1 a := x^2; 2 DEBUG(); 3 a := y^2 end proc
f(2,3);
4 f: 3 a := y^2 DBG> quit Interrupted
If the argument of the DEBUG command is a Boolean expression, the execution process stops only if the Boolean expression evaluates to true. If the Boolean expression evaluates to false or FAIL, the DEBUG command is ignored.
f := proc(x,y) local a; a:=x^2; DEBUG(a<1); a:=y^2; DEBUG(a>1); print(a); end proc:
9 f: 5 print(a) DBG> quit Interrupted
If the argument of the DEBUG command is a value other than a Boolean expression, the debugger prints the value of the argument (instead of the last result) when the execution process stops at the following statement.
f := proc(x) x^2; DEBUG("This is my breakpoint. The current value of x is:", x); x^3; end proc:
f(2);
"This is my breakpoint. The current value of x is:", 2 f: 3 x^3 DBG>
Removing Explicit Breakpoints
The unstopat command cannot remove explicit breakpoints. You must remove breakpoints that were set by using DEBUG by editing the source text for the procedure.
DBG> unstopat [f] f: 3 x^3 DBG> showstat f := proc(x) 1 x^2; 2 DEBUG("This is my breakpoint. The current value of x is:", x); 3 ! x^3 end proc DBG> quit Interrupted
Note: If you display the contents of a procedure by using the print command (or lprint) and the procedure contains a breakpoint that was set by using stopat, the breakpoint appears as a call to DEBUG.
f := proc(x) x^2 end proc:
stopat(f);
f,g,h
print(f);
procxDEBUG⁡;x^2end proc
Setting Watchpoints
The stopwhen command can take the following forms.
stopwhen( globalVariableName );
stopwhen( [procedureName, variableName] );
The first form specifies that the debugger should be started when the global variable globalVariableName is changed. Maple environment variables, such as Digits, can also be monitored by using this method.
stopwhen(Digits);
Digits
The second form starts the debugger when the (local or global) variable variableName is changed in the procedure procedureName.
When any form of stopwhen is called, Maple returns a list of the current watchpoints.
The execution process stops after Maple assigns a value to the watched variable. The debugger displays an assignment statement instead of the last computed result (which would otherwise be the right-hand side of the assignment statement).
Clearing Watchpoints
The syntax to call unstopwhen is the same as that for stopwhen. Similar to the stopwhen command, the unstopwhen command returns a list of all (remaining) watchpoints.
If no arguments are included in the call to unstopwhen, then all watchpoints are cleared.
Setting Watchpoints on Specified Errors
You can use an error watchpoint to start the debugger when Maple returns a specified error message. When a watched error occurs, the procedure stops executing and the debugger displays the statement in which the error occurred.
Error watchpoints are set by using the stoperror command. The stoperror command has the following syntax
stoperror( "errorMessage" );
where errorMessage is a string or a symbol that represents the error message returned from the evaluation of a Maple expression. If the argument is a string, the debugger will be started when an error for which the given string is a prefix is encountered. A list of the current error watchpoints is returned.
If no argument is entered in the call to stoperror, the list of current (error) watchpoints is returned.
stoperror();
stoperror( "numeric exception: division by zero" );
numeric exception: division by zero
If the special name `all` is used instead of a specific error message as the parameter to the stoperror command, a procedure stops executing when any error that would not be trapped occurs.
Errors trapped by an error trapping construct (try...catch statement) do not generate an error message. Therefore, the stoperror command cannot be used to catch them. For more information about the try...catch structure, see Trapping Errors. If the special name `traperror` is used instead of a specific error message as the parameter to the stoperror command, a procedure stops executing when any error that is trapped occurs. If the errorMessage parameter is entered in the form traperror["message"] to stoperror, the debugger starts only if the error specified by "message" is trapped.
When a procedure stops executing because of an error which causes an exception, continued execution is not possible. Any of the execution control commands, such as next or step (see Controlling the Execution of a Procedure during Debugging I and Controlling the Execution of a Procedure during Debugging II), process the error as if the debugger had not intervened. For example, consider the following two procedures. The first procedure, f, calculates 1/x. The other procedure, g, calls f but traps the "division by zero" error that occurs when x = 0.
f := proc(x) 1/x end proc: g := proc(x) local r; try f(x); catch: infinity; end try; end proc:
If procedure g is executed at x=9, the reciprocal is returned.
g(9);
19
At x=0, as expected, a value of infinity is returned.
g(0);
∞
The stoperror command stops the execution process when you call f directly.
stoperror("numeric exception: division by zero");
f(0);
Error, numeric exception: division by zero f: 1 1/x DBG> cont Error, (in f) numeric exception: division by zero
The call to f from g is within a try...catch statement, so the "division by zero" error does not start the debugger.
Instead, try using the stoperror(traperror) command.
unstoperror( "numeric exception: division by zero" );
stoperror( `traperror` );
traperror
This time, Maple does not stop at the error in f.
Error, (in f) numeric exception: division by zero
However, Maple starts the debugger when the trapped error occurs.
Error, numeric exception: division by zero f: 1 1/x DBG> step Error, numeric exception: division by zero g: 3 infinity DBG> step
In the case that a particular error message is specified in the form traperror["message"], the debugger is started only if the error specified by "message" is trapped.
Clearing Watchpoints on Specified Errors
Error watchpoints are cleared by using the top-level unstoperror command. The syntax to call the unstoperror command is the same as for the stoperror command. Like the stoperror command, the unstoperror command returns a list of all (remaining) error watchpoints.
If no argument is included in the call to unstoperror, all error watchpoints are cleared.
unstoperror();
Controlling the Execution of a Procedure during Debugging II
After stopping the execution of a procedure and starting the debugger, you can examine the values of variables or perform other experiments (see the following section, Changing the State of a Procedure during Debugging). After you have examined the state of the procedure, you can continue the execution process by using several different debugger commands.
The most commonly used debugger commands are into, next, step, cont, outfrom, return, and quit.
The return debugger command causes execution of the currently active procedure call to complete. The execution process stops at the first statement after the current procedure.
The other commands are described in the tutorial in The Maple Debugger: A Tutorial Example. For more information on these and other debugger commands, refer to the debugger help page.
Changing the State of a Procedure during Debugging
When a breakpoint or watchpoint stops the execution of a procedure, the Maple debugger is started. In the debugger mode, you can examine the state of the global variables, local variables, and parameters of the stopped procedure. You can also determine where the execution process stopped, evaluate expressions, and examine procedures.
While in the debugger mode, you can evaluate any Maple expression and perform assignments to local and global variables. To evaluate an expression, enter the expression at the debugger prompt. To perform assignments to variables, use the standard Maple assignment statement.
f
f(10);
f: 1* x^2 DBG> sin(3.0); .1411200081 f: 1* x^2 DBG> cont
100
The debugger evaluates any variable names that you use in the expression in the context of the stopped procedure. Names of parameters or local variables evaluate to their current values in the procedure. Names of global variables evaluate to their current values. Environment variables, such as Digits, evaluate to their values in the stopped procedure's environment.
If an expression corresponds to a debugger command (for example, your procedure has a local variable named step), you can still evaluate it by enclosing it in parentheses.
f := proc(step) local i; for i to 10 by step do i^2 end do; end proc:
stopat(f,2);
f(3);
f: 2* i^2 DBG> step 1 f: 2* i^2 DBG> (step) 3 f: 2* i^2 DBG> quit Interrupted
When the execution process is stopped, you can modify local and global variables by using the assignment statement (:=). The following example sets a breakpoint in the loop only when the index variable is equal to 5.
sumn := proc(n) local i, sum; sum := 0; for i to n do sum := sum + i end do; end proc:
showstat(sumn);
sumn := proc(n) local i, sum; 1 sum := 0; 2 for i to n do 3 sum := sum+i end do end proc
stopat(sumn,3,i=5);
sumn
sumn(10);
10 sumn: 3? sum := sum+i
Reset the index to 3 so that the breakpoint is encountered again.
DBG> i := 3 sumn: 3? sum := sum+i DBG> cont 17 sumn: 3? sum := sum+i DBG> cont
62
Maple has added the numbers 1, 2, 3, 4, 3, and 4 and returned 17 as the result. By continuing the execution of the procedure, the numbers 5, 6, 7, 8, 9, and 10 are added and 62 is returned as the result.
Examining the State of a Procedure during Debugging
You can use two debugger commands to return information about the state of the procedure execution. The list debugger command shows you the location where the execution process stopped within the procedure and the where debugger command shows you the stack of procedure activations.
The list debugger command has the following syntax.
list procedureName statementNumber[..statNumber]
The list debugger command is similar to the showstat command, except that you do not need to specify arguments. If no arguments are included in the call to list, only the five previous statements, the current statement, and the next statement to be executed are displayed. This provides some context in the stopped procedure. In other words, it indicates the static position where the execution process stopped.
The where debugger command shows you the stack of procedure activations. Starting from the top level, it shows you the statement that is executing and the parameters it passed to the called procedure. The where debugger command repeats this for each level of procedure call until it reaches the current statement in the current procedure. In other words, it indicates the dynamic position where execution stopped. The where command has the following syntax.
where numLevels
To illustrate these commands, consider the following example. The procedure check calls the sumn procedure from the previous example.
check := proc(i) local p, a, b; p := ithprime(i); a := sumn(p); b := p*(p+1)/2; evalb( a=b ); end proc:
There is a (conditional) breakpoint in sumn.
sumn := proc(n) local i, sum; 1 sum := 0; 2 for i to n do 3? sum := sum+i end do end proc
When check calls sumn, the breakpoint starts the debugger.
check(9);
The where debugger command shows that
check was called from the top level with argument 9,
check called sumn with argument 23, and
the execution process stopped at statement number 3 in sumn.
DBG> where TopLevel: check(9) [9] check: a := sumn(p) [23] sumn: 3? sum := sum+i DBG> cont
true
The next example illustrates the use of where in a recursive function.
fact := proc(x) if x <= 1 then 1 else x * fact(x-1) end if; end proc:
showstat(fact);
fact := proc(x) 1 if x <= 1 then 2 1 else 3 x*fact(x-1) end if end proc
stopat(fact,2);
fact
fact(5);
fact: 2* 1 DBG> where TopLevel: fact(5) [5] fact: x*fact(x-1) [4] fact: x*fact(x-1) [3] fact: x*fact(x-1) [2] fact: x*fact(x-1) [1] fact: 2* 1 DBG>
If you do not want to view the entire history of the nested procedure calls, use the numLevels parameter in the call to the where debugger command to print a specified number of levels.
DBG> where 3 fact: x*fact(x-1) [2] fact: x*fact(x-1) [1] fact: 2* 1 DBG> quit Interrupted
The showstop command (and the showstop debugger command) displays a report of all the currently set breakpoints, watchpoints, and error watchpoints. Outside the debugger at the top level, the showstop command has the following syntax.
showstop();
The next example illustrates the use of the showstop command.
f := proc(x) local y; if x < 2 then y := x; print(y^2); end if; print(-x); x^3; end proc:
In the following example, breakpoints are set.
stopat(f):
stopat(f,2):
stopat(int);
f,int
In the following example, watchpoints are set.
stopwhen(f,y):
Digits,f,y
In the following example, an error watchpoint is set.
The showstop command reports all the breakpoints and watchpoints.
Breakpoints in: f int Files with breakpoints: "" Watched variables: Digits y in procedure f Watched errors: "numeric exception: division by zero"
Using Top-Level Commands at the Debugger Prompt
The showstat, stopat, unstopat, stopwhen, unstopwhen, stoperror, and showstop commands can be used at the debugger prompt. The following list describes the syntax rules for top-level commands used at the debugger prompt.
Do not enclose the arguments of the command in parentheses.
Do not separate the arguments of the command with a comma. The arguments must be separated by a space character.
Do not use colons or semicolons to end statements.
The procedure name is not required by any command. Commands that use a procedure name assume the currently stopped procedure if one is not specified.
For the stoperror command, double quotes are not required.
Except for these rules, the debugger prompt call for each command is of the same form and takes the same arguments as the corresponding top-level command call.
Restrictions
At the debugger prompt, the only permissible Maple statements are debugger commands, expressions, and assignments. The debugger does not permit statements such as if, while, for, read, and save. However, you can use `if` to simulate an if statement and seq to simulate a loop.
The debugger cannot set breakpoints in, or step into, built-in commands, such as diff and has. These commands are implemented in C and compiled into the Maple kernel. Debugging information about these commands is not accessible to Maple. However, if a built-in command calls a library command, for example, the diff command calling `diff/sin`, you can use a breakpoint to stop in the latter.
If a procedure contains two identical statements that are expressions, the debugger cannot always determine the statement at which the execution process stopped. If this situation occurs, you can still use the debugger and the execution process can continue. The debugger issues a warning that the displayed statement number may be incorrect.
Note: This issue occurs because Maple stores all identical expressions as a single occurrence of the expression. The debugger cannot determine at which invocation the execution process stopped.
16.4 Detecting Errors
This section describes some simple commands that you can use for detecting errors in procedures that are written in Maple. If you are not successful in finding the error by using these commands, you can use the Maple debugger, which is discussed in The Maple Debugger: A Tutorial Example and Maple Debugger Commands, to display the stepwise execution of a procedure.
Tracing a Procedure
The simplest tools available for error detection in Maple are the printlevel environment variable, and the trace and tracelast commands. You can use these facilities to trace the execution of both user-defined and Maple library procedures. However, they differ in the type of information that is returned about a procedure.
The printlevel variable is used to control how much information is displayed when a program is executed. By assigning a large integer value to printlevel, you can monitor the execution of statements to selected levels of nesting within procedures. The default value of printlevel is 1. Larger, positive integer values cause the display of more intermediate steps in a computation. Negative integer values suppress the display of information.
The printlevel environment variable is set by using the following syntax, where n is the level to which Maple commands are evaluated.
printlevel := n;
To determine what value of n to use, note that statements within a particular procedure are recognized in levels that are determined by the nesting of conditional or repetition statements, and by the nesting of procedures. Each loop or if condition increases the evaluation level by 1, and each procedure call increases the evaluation level by 5. Alternatively, you can use a sufficiently large value of n to ensure that all levels are traced. For example, printlevel := 1000 displays information in procedures up to 200 levels deep.
f := proc(x) local y; y := x^2; g(y) / 4; end proc: g := proc(x) local z; z := x^2; z * 2; end proc:
812
printlevel := 5;
{--> enter f, args = 3 y := 9 81/2 <-- exit f (now at top level) = 81/2} 81/2
printlevel := 10;
{--> enter f, args = 3 y := 9 {--> enter g, args = 9 z := 81 162 <-- exit g (now in f) = 162} 81/2 <-- exit f (now at top level) = 81/2} 81/2
The amount of information that is displayed depends on whether the call to the procedure was terminated with a colon or a semicolon. If a colon is used, only the entry and exit points of the procedure are printed. If a semicolon is used, the results of the statements are also printed.
To reset the value of the printlevel variable, reassign its value to 1.
printlevel := 1;
By assigning a large value to printlevel, the trace of all subsequent Maple procedure calls is displayed. To display the trace of specific procedures, you can use the trace command. The trace command has the following syntax, where arguments is one or more procedure names.
trace(arguments);
The trace command returns an expression sequence containing the names of the traced procedures. To begin tracing, call the procedure.
trace(f, g);
f,g
f(3):
{--> enter f, args = 3 {--> enter g, args = 9 <-- exit g (now in f) = 162} <-- exit f (now at top level) = 81/2}
Similar to printlevel, the amount of information that is displayed during tracing when trace is used depends on whether the call to the procedure was terminated with a colon or a semicolon. If a colon is used, only entry and exit points of the procedure are printed. If a semicolon is used, the results of the statements are also printed.
To turn off the tracing of specific procedures, use the untrace command.
untrace(f, g);
Note: You can use debug and undebug as alternate names for trace and untrace.
If running a procedure results in the display of an error message, you can use the tracelast command to determine the last statement executed and the values of variables at the time of the error. The tracelast command has the following syntax.
tracelast;
After an error message is displayed, the following information is returned from a call to tracelast.
The first line displays which procedure was called and what values were used for the parameters.
The second line displays the # symbol, the procedure name with the line number of the statement that was executed, and the statement that was executed.
Finally, if there are any local variables in the procedure, they are displayed with their corresponding values.
f := proc(x) local i, j, k; i := x; j = x^2; seq(k, k=i..j); end proc:
f(2, 3);
Error, (in f) range bounds in seq must be numeric or character
f called with arguments: 2, 3 #(f,3): seq(k,k = i .. j) Error, (in f) unable to execute seq locals defined as: i = 2, j = j, k = k
You can find the error in this procedure by studying the results of the tracelast command--the assignment to the local variable j incorrectly uses an equal sign (=) instead of an assignment symbol ( := ).
The information provided by tracelast can become unavailable whenever Maple does a garbage collection. Therefore, it is advisable to use tracelast immediately after an error occurs. For more information about garbage collection in Maple, see Garbage Collection.
Using Assertions
An assertion is a verification of the state of Maple at the time the assertion is made. You can include assertions in your procedure to guarantee pre- and post-conditions, and loop invariants during execution by using the ASSERT command. You can also use assertions to guarantee the value returned by a procedure or the value of local variables inside a procedure. The ASSERT command has the following syntax.
ASSERT( condition, message );
If condition evaluates to false, an error is generated and message is printed. If the first argument evaluates to true, ASSERT returns NULL.
To check assertions, turn on assertion checking before executing a procedure that contains an ASSERT command. To query the current state of assertion checking, or turn assertion checking on or off, use the kernelopts command.
The default state for assertion checking is no assertion checking (assertlevel=0).
Programming note: You should use assertions to verify that your program is working as intended. You should not use assertions to validate computations or values which are not completely in the control of your program, such as user input.
Turn assertion checking on:
kernelopts(assertlevel=1);
0
Note that when you set a kernelopts variable, such as when you turn assertion checking on or off, kernelopts returns its previous value.
At any time during the Maple session, you can check the setting for assertion checking by entering the following command.
kernelopts(assertlevel);
1
If assertion checking is on and a procedure that contains an ASSERT statement is executed, the condition represented by the ASSERT statement is checked.
f := proc(x, y) local i, j; i := 0; j := 0; while (i <> x) do ASSERT(i > 0, "invalid index"); j := j + y; i := i + 1; end do; j; end proc;
f≔procx,ylocali,j;i ≔ 0;j ≔ 0;whilei<>xdoASSERT⁡0<i,invalid index;j ≔ j+y;i ≔ i+1end do;jend proc
Error, (in f) assertion failed, invalid index
Use the kernelopts command again to turn assertion checking off. (Again, kernelopts returns its previous value.) When assertion checking is off, the overhead of processing an ASSERT statement in a procedure is minimal.
kernelopts(assertlevel=0);
For information on assertion checking and procedures, see Return Type) and Variables in Procedures.
Related to assertions are Maple warning messages. The WARNING command causes a specified warning message to display. The warning is preceded by the string '"Warning, "'. The WARNING command has the following syntax.
WARNING( msgString, msgParam1, msgParam2, ... );
The msgString parameter is the text of the warning message and msgParami are optional parameters to substitute into msgString, if any. For more information on message parameters, see Handling Exceptions.
f := proc(x) if x < 0 then WARNING("sqrt(%1) is complex", x); end if; sqrt(x); end proc;
f≔procxifx<0thenWARNING⁡sqrt(%1) is complex,xend if;sqrt⁡xend proc
f(-2);
Warning, sqrt(-2) is complex
I⁢2
By default, warning messages are displayed. You can hide warning messages by using the interface(warnlevel=0) command. In this case, the warning is not displayed and the call to WARNING has no effect.
interface(warnlevel=0);
3
Handling Exceptions
An exception is an event that occurs during the execution of a procedure that disrupts the normal flow of instructions. Many kinds of actions can cause exceptions, for example, attempting to read from a file that does not exist. Maple has two mechanisms available when such situations occur:
the error statement to raise an exception, and
the try...catch...finally block to handle exceptions.
Raising Exceptions
The error statement raises an exception. Execution of the current statement sequence is interrupted, and the block and procedure call stack is popped until either an exception handler is encountered, or execution returns to the top level (in which case the exception becomes an error). The error statement has the following syntax.
error msgString, msgParam1, msgParam2, ...
The msgString parameter is a string that gives the text of the error message. It can contain numbered parameters of the form %n or %-n, where n is an integer. These numbered parameters are used as placeholders for actual values. In the event that the exception is printed as an error message, the actual values are specified by the msgParam values.
For example,
error "%1 has a %-2 argument, %3, which is missing", f, 4, x;
Error, f has a 4th argument, x, which is missing
A numbered parameter of the form %n displays the nth msgParam in line-printed notation (that is, as lprint would display it). A numbered parameter of the form %-n displays the nth msgParam, assumed to be an integer, in ordinal form. For example, the %-2 in the previous error statement is displayed as "4th". The special parameter %0 displays all the msgParams, separated by a comma and a space.
The error statement evaluates its arguments and then creates an exception object which is an expression sequence with the following elements.
The name of the procedure in which the exception was raised. If the exception occurred in a procedure local to a module, then the name of the innermost visible (non-local) calling procedure is used. If the exception occurred at the top level (not within a procedure), then the first element of the exception object will be the constant 0.
The msgString.
The msgParams, if any.
The created exception object is assigned to the global variable lastexception as an expression sequence. For more information on lastexception, refer to the error help page.
Note: The actual arguments to the error statement are also assigned to lasterror for compatibility with older versions of Maple.
Note: To view the value of the lastexception variable within the debugger, use the showexception debugger command.
The error statement normally causes an immediate exit from the current procedure to the Maple session. Maple prints an error message of the following form.
Error, (in procName) msgText
In this case, msgText is the text of the error message (which is constructed from the msgString and optional msgParams of the error statement), and procName is the name of the procedure in which the error occurred, or the name of the innermost non-local procedure in the current call stack if the procedure is a module local. If the procedure does not have a name, procName is displayed as unknown. If the error occurs at the top level, outside any procedure, the (in procName) part of the message is omitted.
The error statement is commonly used when parameter declarations are not sufficient to check that the actual parameters to a procedure are of the correct type. The following pairup procedure takes a list L of the form [x_1, y_1, x_2, y_2, ..., x_n, y_n] as input, and creates from it a list of the form [[x_1, y_1], [x_2, y_2], ..., [x_n, y_n]]. A simple type check cannot determine if list L has an even number of elements, so you must check this explicitly by using an error statement.
pairup := proc(L::list) local i, n; n := nops(L); if irem(n, 2) = 1 then error "list must have an even number of " "entries, but had %1", n; end if; [seq( [L[2*i-1], L[2*i]], i=1..n/2 )]; end proc:
pairup([1, 2, 3, 4, 5]);
Error, (in pairup) list must have an even number of entries, but had 5
pairup([1, 2, 3, 4, 5, 6]);
1,2,3,4,5,6
For information on trapping errors using a try...catch statement, see Trapping Errors.
Checking Syntax
The Maple maplemint command generates a list of semantic errors for a specified procedure, if any. The semantic errors for which maplemint checks include parameter name conflicts, local and global variable name conflicts, unused variable declarations, and unreachable code. The maplemint command has the following syntax.
maplemint( procedureName );
In the case where the specified procedure is free of semantic errors, maplemint returns NULL.
f := proc() local a, i; global c; for i from 1 to 10 do print(i); for i from 1 to 5 do if a = 5 then a := 6; return true; print(`test`); end if; end do; end do; end proc:
maplemint(f);
Procedure f() These variables were used as the same loop variable for nested loops: i These names were used as global names but were not declared: test These global variables were declared, but never used: c These local variables were used before they were assigned a value: a There is unreachable code following a RETURN or return statement at statement 7: print(test)
Similar to maplemint, Maple also has an external program utility called mint. The mint program is called from outside Maple; it is used to check both semantic and syntax errors in an external Maple source file.
16.5 Creating Efficient Programs
After a Maple procedure is debugged, you would normally want to improve the performance of the code. Maple commands are available to analyze the time and memory consumption involved in executing individual statements. Maple also provides commands to monitor the efficiency of procedures.
During the performance improvement phase, note that Maple is based on a small kernel written in C and on large libraries of Maple code which are interpreted. Therefore, whenever performance is critical, it is generally most efficient to perform computations by using the built-in commands in the kernel. The phrase option builtin is used to identify the built-in commands. For example, the add command is a built-in command in Maple. To determine if a command is built-in, use the print command with the command name as its argument.
print(add);
procoptionbuiltin=add;end proc
The option builtin phrase identifies add as a built-in command, and the identifier following builtin is either a name or number that identifies this particular command in the kernel.
For more information about efficiency in Maple programming, refer to the efficiency help page.
Displaying Time and Memory Statistics
A simple way to measure the time requirements of an executed command at the interactive level is to use the time command. The time command has the following syntax.
time( expr )
The following statements all return the sum of the same sequence of numbers. However, by using the time command, it is clear that the second expression, which uses the add command, is the most efficient method with respect to time consumption.
time( `+`(seq(2^i, i=1..10^5) ) );
3.269
time( add(2^i, i=1..10^5) );
0.866
Two options are available to compare these expression with the equivalent for...do statement. The first is to wrap the statement in an anonymous function call:
time( proc() local S, i; S:=0: for i from 1 to 10^5 do S := S + 2^i end do: end proc() );
0.957
Another solution is to use the other form of the time command with no arguments, which returns the total CPU time used since the start of the Maple session. The time is reported in seconds and the value returned is a floating-point number.
time()
To find the time used to execute a particular statement or group of statements, use the following statements.
st := time():
... statements to be timed ...
time() - st;
Therefore, you could use the following set of statements to calculate the amount of time (in seconds) required to add the first 10,000 powers of 2 by using the add command.
st:=time(): S:=0: for i from 1 to 10^5 do S := S + 2^i end do: time()-st;
0.964
CPU time is not the only important measure of efficiency. For most code, the amount of memory used is equally important. This can be measured with the command
kernelopts(':-bytesused')
For parallel code, the real or wall clock time is also important. The time command with the index real measures real time used:
time[':-real']()
time[':-real']( expr )
A uniform interface to all of these metrics is available in the CodeTools package.
CodeTools:-Usage(expression, options)
By default, CodeTools:-Usage prints the time and memory usage in evaluating the expression. If you want to save the results, you can specify an output option, which ensures that values that can be saved are returned.
CodeTools:-Usage( `+`(seq(sign(i)*2^abs(i), i=-10^4..10^4)), 'output'='all');
Record⁡realtime=0.107,cputime=0.110,gctime=0.01569000000,gcrealtime=0.01176619530,bytesused=52976104,bytesalloc=101715968,output=1
CodeTools:-Usage( `+`(Threads:-Seq(sign(i)*2^abs(i), i=-10^4..10^4)), 'output'='all');
Record⁡realtime=2.779,cputime=158.379,gctime=1.421882000,gcrealtime=0.1763780117,bytesused=53099856,bytesalloc=980213760,output=1
CodeTools:-Usage( add(sign(i)*2^abs(i), i=-10^4..10^4), 'output'='all');
Record⁡realtime=0.161,cputime=0.495,gctime=0.409091000,gcrealtime=0.0758688450,bytesused=38978056,bytesalloc=0,output=1
CodeTools:-Usage( Threads:-Add(sign(i)*2^abs(i), i=-10^4..10^4), 'output'='all');
Record⁡realtime=16.978,cputime=995.051,gctime=6.134018000,gcrealtime=0.6797471046,bytesused=39037528,bytesalloc=469762048,output=1
CodeTools:-Usage( proc() local S, i; S:=0: for i from -10^4 to 10^4 do S := S + sign(i)*2^abs(i) end do: end proc(), 'output'='all');
Record⁡realtime=0.619,cputime=2.815,gctime=2.727438000,gcrealtime=0.5310511594,bytesused=51444584,bytesalloc=0,output=1
For most computers, the third expression above will have the lowest cputime and bytesused values. Depending on the parallelism available, the fourth expression, which uses Threads:-Add, may have the lowest realtime value. The first two expressions will have the highest bytesused values since they both create large sequences of 2*10^4 numbers before adding them to 1.
Profiling a Procedure
The Profiling subpackage of CodeTools can be used to display run-time information about a procedure (or procedures). The run-time information is displayed in tabular form and it contains the number of calls to the procedures, the CPU time used, and the number of bytes used by each call. To turn on profiling, use the Profile command.
CodeTools:-Profiling:-Profile( procedureNames )
Then, to display the run-time information collected for the profiled procedures use the SortBy command.
CodeTools:-Profiling:-SortBy( )
To display the line-by-line profiling information for the specified procedure, use the PrintProfiles command. If no argument is given to PrintProfiles, the run-time information for all profiled procedures is displayed.
CodeTools:-Profiling:-PrintProfiles( procedureName )
To illustrate the use of profiling in Maple, consider the following procedures that compute the nth Fibonacci number. Both procedures contain the same code except that Fibonacci1 uses option remember.
For more information about option remember, see The remember, cache, and system Options.
Fibonacci1:=proc(n) option remember; if n<2 then n else Fibonacci1(n-1)+Fibonacci1(n-2) end if; end proc:
Fibonacci2:=proc(n) if n<2 then n else Fibonacci2(n-1)+Fibonacci2(n-2) end if; end proc:
Turn on profiling for both procedures.
with(CodeTools:-Profiling):
Profile(Fibonacci1);
Profile(Fibonacci2);
Execute the procedures.
Fibonacci1(25);
75025
Fibonacci2(25);
Use the SortBy command to display the run-time information about Fibonacci1 and Fibonacci2.
SortBy();
function calls time time% words words% --------------------------------------------------------------------------- Fibonacci1 26 0.000 0.00 481 0.04 Fibonacci2 242785 0.788 100.00 1213923 99.96 --------------------------------------------------------------------------- total: 242811 0.788 100.00 1214404 100.00
Use PrintProfiles to display the line-by-line run-time information.
PrintProfiles(Fibonacci1);
Fibonacci1 Fibonacci1 := proc(n) |Calls Seconds Words| PROC | 26 0.000 481| 1 | 26 0.000 78| if n < 2 then 2 | 2 0.000 0| n else 3 | 24 0.000 403| Fibonacci1(n-1)+Fibonacci1(n-2) end if end proc
PrintProfiles(Fibonacci2);
Fibonacci2 Fibonacci2 := proc(n) |Calls Seconds Words| PROC |242785 0.788 1213923| 1 |242785 0.377 728355| if n < 2 then 2 |121393 0.080 0| n else 3 |121392 0.331 485568| Fibonacci2(n-1)+Fibonacci2(n-2) end if end proc
By studying the run-time information, particularly the number of calls to each procedure, you can see that it is more efficient to use option remember in a recursive procedure.
To turn off profiling, use the UnProfile command. If no argument is given to UnProfile, all procedures currently profiled are returned to their original state.
UnProfile( procedureName )
When a procedure is unprofiled, all run-time information for that procedure is lost.
UnProfile();
Warning, total execution time is 0
Warning, total words used is 0
function calls time time% words words% --------------------------------------------------------------------------- --------------------------------------------------------------------------- total: 0 0.000 100.00 0 100.00
The CodeTools:-Profiling package has several other useful commands, including LoadProfiles and SaveProfiles, which can be used to save and load profile information to and from a file. By using these commands, you can collect profiling information from commands run with restart commands in between. In the following code, both calls to myproc will be profiled and the data collected as if they had been executed right after each other.
CodeTools:-Profiling:-Profile(myproc);
myproc( input1 );
CodeTools:-Profiling:-SaveProfiles( "myproc.profile", 'overwrite' );
CodeTools:-Profiling:-LoadProfiles( "myproc.profile" );
myproc( input2 );
The older profile facility is also still available but it is slower and does not provide line-by-line profiling information. It is still useful for profiling the use of built-in procedures, which are not supported by CodeTools:-Profiling. For more information, refer to the profile help page.
In some cases, it is useful to collect profiling information on every procedure which is invoked during the evaluation of a Maple expression. In this situation, use the exprofile command with the profile kernel option. The output of exprofile can be verbose for moderately complicated code.
a:=proc(); b(100); end proc:
b:=proc(n); if n>0 then c(n-2); end if; end proc:
c:=proc(n); if n>0 then b(n+1); end if; end proc:
kernelopts(profile=true):
writeto('output');
a();
kernelopts(profile=false);
writeto(terminal);
exprofile('output',alpha);
16.6 Managing Resources
Maple provides several commands for managing computer resources during computation. In particular, the timelimit command controls the maximum amount of time available for a computation, gc starts the garbage collection process, and kernelopts provides communication with the Maple kernel.
Setting a Time Limit on Computations
The timelimit command is used to limit the amount of CPU time for a computation. The timelimit command has the following syntax, where time is the time limit (in seconds) to evaluate expression.
timelimit( time, expression )
If the expression is successfully evaluated within the specified time, timelimit returns the value of the expression. If the time limit is reached before the expression is evaluated, timelimit raises an exception.
f := proc() local i; for i to 100000 do 2^i end do end proc:
timelimit(0.25, f());
The exception raised by timelimit can be caught with a try...catch construct.
try timelimit(0.25, f()); catch "time expired": NULL; end try;
Multiple calls to timelimit can be nested, causing both limits to be active at once.
g := proc(t) try timelimit(t, f()); catch "time expired": error "time expired in g"; end try; end proc:
timelimit(10, g(0.25) );
timelimit(0.25, g(10) );
Note that in the second of these examples, the inner call, g(10) would normally have finished without triggering the time limit exception. The outer time limit of 0.25 cpu seconds prevented the inner call from completing. Thus, the time-out event did not occur inside g and so is not trapped by the catch clause in g. This illustrates that a try-catch construct cannot capture a time limit exception event generated by a timelimit call in a surrounding scope.
For more information on catching time expired exceptions and nested time limits, refer to the timelimit help page.
Garbage Collection
Garbage collection deletes all objects that are no longer in use by the program and are occupying space in memory. In Maple, garbage collection will also recover storage from the remember tables of procedures that use an option system or option builtin by removing entries that have no other references to them.
For more information about procedure options, see Options.
Garbage collection is also used to clear cache tables that have temporary entries when a memory usage threshold is reached.
The Maple garbage collection command is gc. It has the following syntax.
gc()
Garbage collection occurs automatically when the memory management system determines that memory resources are low. Alternatively, the gc command explicitly schedules a garbage collection cycle and returns a value of NULL. However, the use of gc is discourage since the underlying memory management system attempts to balance memory usage and performance by tracking the memory behavior of the program. The decision of when to initiate a garbage collection can be skewed by directly calling gc.
The kernelopts command is used to query garbage collection information such as the number of bytes returned after the last garbage collection and the number of times the garbage collection process has run.
kernelopts( gcbytesavail );
kernelopts( gcbytesreturned );
kernelopts( gctimes );
Other Kernel Options for Managing Resources
The kernelopts command is provided as a mechanism of communication between the user and the Maple kernel. You have already seen several uses of kernelopts in this guide, including how to use kernelopts to check assertions in procedures. Specifically, this command is used to set and query variables that affect kernel computations in Maple.
The following kernelopts options can be used to limit Maple's use of system resources.
The cpulimit, datalimit, and stacklimit options can be used to set limits on the resources available to Maple and must be used carefully. Unlike the timelimit command, once one of these limits is reached, Maple may shut down without warning without prompting you to save your work. This makes these limit options most useful for running in non-interactive sessions.
On some platforms, including all Windows platforms, the detection of limit violations is tied to garbage collection and therefore the detection of limit violations will be inaccurate for code that rarely starts the garbage collection process. If the garbage collection process does not occur, Maple does not detect limit violations.
These options can also be set using the -T command-line option. For more information, refer to the maple help page.
The filelimit and processlimit limit options can similarly be used to limit the number of open files and external processes that Maple can use at one time. Some internal Maple commands open files or run processes and thus will fail if these limits are too low.
If the option limitjvmheap is set to true then the Java external calling virtual machine is limited to the amount of memory given in the limit option jvmheaplimit.
The option cacheclearlimit is used to set a threshold at which Maple is allowed to clear temporary elements from cache tables during garbage collection.
An informational kernelopts option is memusage which will display how much memory is currently in use, listed by DAG type.
kernelopts( memusage );
Note: There is a Maplet application that provides a graphical user interface to a subset of the kernel options. This Maplet can be opened by calling Maplets:-Examples:-KernelOpts().
16.7 Testing Your Code
Occasionally, code may be incorrect after it is first written or changed. For that reason, it is very important that code is tested. In Maple, you can create tests for code in many ways. This section introduces some useful Maple commands for testing and provides suggestions on how to create useful tests.
Verifying Results with verify
One common difficulty in producing good tests is verifying that the computed results match the expected result. Maple provides the general and powerful command verify to make this possible in many cases.
The default mode of the verify command is simple evalb equality checking.
verify(10, 20);
verify(10, 10.00);
More complicated objects require more complicated tests.
verify(10+x, 10.00+x);
verify(Array(1..3,[1,2,3]), Array([1,2,3],'readonly'));
The verify command called with a third argument provides numerous different structured verifiers, many of which are similar to the structured type of the expressions being compared. For full details, refer to the verify and verify/structured help pages.
verify(10+x, 10.00+x, 'float(10)' );
verify(Array(1..3,[1,2,3]), Array([1,2,3],readonly), 'Array');
verify({0.32}, {0.320002, 0.319996},'set(float(1e5))');
A Simple Test Harness
An easy way to test code is to write a series of verify statements into a text file which can then be read directly by the command-line interface or the read command.
For the sieve example introduced in The Maple Debugger: A Tutorial Example, the following statements can be saved in a file called sieveTest.mpl:
verify(sieve(1), 0); verify(sieve(2), 1); verify(sieve(10), 4); verify(sieve(100), 25); verify(sieve(1223), 200); verify(sieve(-1), 0); verify(sieve(-1000), 0);
If the sieve function works properly, reading or running this file from the command line
maple -s -q < sieveTest.mpl
should produce output that contains true values.
true true true true true true true
This output is easy to inspect visually for correctness. If the number of tests in one file is large, you may want to produce errors for failures, and let successful tests proceed without further action. The command CodeTools:-Test is a front-end to verify that provides this functionality as well as allowing you to test expected errors and customize verifications. The output format is quite flexible. In the following example, we use the quiet option to suppress output for passed tests, and the label option to give each test a unique identifier, so we can easily identify failures. Here is the new version of the test harness:
with(CodeTools): Test(sieve(1), 0, quiet, label=10); Test(sieve(2), 1, quiet, label=20); Test(sieve(10), 4, quiet, label=30); Test(sieve(100), 25, quiet, label=40); Test(sieve(1223), 200, quiet, label=50); Test(sieve(-1), 0, quiet, label=60); Test(sieve(-1000), 0, quiet, label=70); Test(sieve(sqrt(2)), "invalid input", testerror, quiet, label=80); Test(sieve(1), -1, quiet, label=90);
which should produce just one line of output:
Error, (in CodeTools:-Test) TEST FAILED: 90
This new test harness has the advantage that failures are highlighted as errors, so they stand out visually. If you remove the quiet option, you will also get a short message for each test that passes. That can be useful to ensure that false positive results are less likely to occur due to tests being skipped.
Writing Good Tests
Much has been written on the subject of writing good sets of tests. In general, it is best to test as many of the corner cases as possible in addition to a few typical cases.
For example, if a procedure takes a list as input, there should be a test case for the empty list.
For more comprehensive references on testing software, see for example:
B. Beizer. Software Testing Techniques. Van Nostrand Reinhold, second edition, 1990.
C. Kaner, J. Falk, H.Q. Nguyen. Testing Computer Software. Wiley, second edition, 1999.
G.J. Myers. The Art of Software Testing. Wiley, second edition, 2004.
Test Coverage
Good suites of tests exercise every statement in the code that is being tested. Maple provides a package to measure the coverage of a suite of tests in CodeTools:-Profiling:-Coverage.
To use this code, activate profiling of the procedure (or procedures) you want to test as described in Profiling a Procedure. Then run your test suite and use the command CodeTools:-Profiling:-Coverage:-Print to get a report on which lines in your procedures were not run while running the test suite.
For example, we could add the following to the test file for sieve in the previous section:
with(CodeTools): Profiling:-Profile(sieve); ... Profiling:-Coverage:-Print();
When run, in addition to the test output, this produces the message:
sieve (8): all statements covered
which informs us that the procedure was called 8 times and every statement in the procedure was executed at least once. If statements had been missed, those missed statements would be printed.
The command CodeTools:-Profiling:-Coverage:-Percent provides much more compact output, and in this case would produce:
sieve 100.00%
16.8 Exercises
The following procedure tries to compute 1−xa.
f := proc(a::integer, x::anything) if a<0 then a := -a end if; 1-x^a; end proc:
Determine what is wrong with this procedure.
Hint: Use the Maple debugger described in The Maple Debugger: A Tutorial Example and Maple Debugger Commands to isolate the error.
The following recurrence relation defines the Chebyshev polynomials of the first kind, Tn⁡x.
T0⁡x=1,T1⁡x=x,Tn⁡x=2⁢x⁢Tn−1⁡x−Tn−2⁡x
The following procedure computes Tn⁡x in a loop for any given integer n.
T := proc(n::integer, x) local t1, tn, t; t1 := 1; tn := x; for i from 2 to n do t := expand(2*x*tn - t1); t1 := tn; tn := t; end do; tn; end proc:
This procedure has several errors. Which variables must be declared local? What happens if n is zero or negative? Identify and correct all errors, using the Maple debugger where appropriate. Modify the procedure so that it returns unevaluated if n is a symbolic value.
Download Help Document