Maple7prguide PDF
Maple7prguide PDF
Maple7prguide PDF
Programming Guide
c 2001 by Waterloo Maple Inc.
ii •
c 2001, 2000, 1998, 1996 by Waterloo Maple Inc.
All rights reserved. This work may not be translated or copied in whole
or in part without the written permission of the copyright holder, except
for brief excerpts in connection with reviews or scholarly analysis. Use in
connection with any form of information storage and retrieval, electronic
adaptation, computer software, or by similar or dissimilar methodology
now known or hereafter developed is forbidden.
1 Introduction 1
1.1 Getting Started . . . . . . . . . . . . . . . . . . . . . . . . 2
Locals and Globals . . . . . . . . . . . . . . . . . . . . . . 7
Inputs, Parameters, Arguments . . . . . . . . . . . . . . . 8
1.2 Basic Programming Constructs . . . . . . . . . . . . . . . 11
The Assignment Statement . . . . . . . . . . . . . . . . . 11
The for Loop . . . . . . . . . . . . . . . . . . . . . . . . . 13
The Conditional Statement . . . . . . . . . . . . . . . . . 15
The while Loop . . . . . . . . . . . . . . . . . . . . . . . 19
Modularization . . . . . . . . . . . . . . . . . . . . . . . . 20
Recursive Procedures . . . . . . . . . . . . . . . . . . . . . 22
Exercise . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
1.3 Basic Data Structures . . . . . . . . . . . . . . . . . . . . 25
Exercise . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Exercise . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
A MEMBER Procedure . . . . . . . . . . . . . . . . . . . . . 28
Exercise . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Binary Search . . . . . . . . . . . . . . . . . . . . . . . . . 29
Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Plotting the Roots of a Polynomial . . . . . . . . . . . . . 31
1.4 Computing with Formulæ . . . . . . . . . . . . . . . . . . 33
The Height of a Polynomial . . . . . . . . . . . . . . . . . 34
Exercise . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
The Chebyshev Polynomials, Tn (x) . . . . . . . . . . . . . 36
Exercise . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
Integration by Parts . . . . . . . . . . . . . . . . . . . . . 37
Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Computing with Symbolic Parameters . . . . . . . . . . . 39
Exercise . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
1.5 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . 42
iii
iv • Contents
2 Fundamentals 45
2.1 Evaluation Rules . . . . . . . . . . . . . . . . . . . . . . . 46
Parameters . . . . . . . . . . . . . . . . . . . . . . . . . . 47
Local Variables . . . . . . . . . . . . . . . . . . . . . . . . 50
Global Variables . . . . . . . . . . . . . . . . . . . . . . . 51
Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . 52
2.2 Nested Procedures . . . . . . . . . . . . . . . . . . . . . . 54
Local Versus Global Variables . . . . . . . . . . . . . . . . 55
The Quick-Sort Algorithm . . . . . . . . . . . . . . . . . . 56
Creating a Uniform Random Number Generator . . . . . 59
2.3 Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
Types that Modify Evaluation Rules . . . . . . . . . . . . 62
Structured Types . . . . . . . . . . . . . . . . . . . . . . . 66
Type Matching . . . . . . . . . . . . . . . . . . . . . . . . 68
2.4 Choosing a Data Structure: Connected Graphs . . . . . . 70
Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
2.5 Remember Tables . . . . . . . . . . . . . . . . . . . . . . . 76
The remember Option . . . . . . . . . . . . . . . . . . . . 76
Adding Entries Explicitly . . . . . . . . . . . . . . . . . . 77
Removing Entries from a Remember Table . . . . . . . . . 78
2.6 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . 79
3 Advanced Programming 81
3.1 Procedures Which Return Procedures . . . . . . . . . . . 82
Creating a Newton Iteration . . . . . . . . . . . . . . . . . 82
A Shift Operator . . . . . . . . . . . . . . . . . . . . . . . 85
3.2 When Local Variables Leave Home . . . . . . . . . . . . . 86
Creating the Cartesian Product of a Sequence of Sets . . 89
Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
3.3 Interactive Input . . . . . . . . . . . . . . . . . . . . . . . 94
Reading Strings from the Terminal . . . . . . . . . . . . . 95
Reading Expressions from the Terminal . . . . . . . . . . 95
Converting Strings to Expressions . . . . . . . . . . . . . 97
3.4 Extending Maple . . . . . . . . . . . . . . . . . . . . . . . 98
Defining New Types . . . . . . . . . . . . . . . . . . . . . 99
Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
Neutral Operators . . . . . . . . . . . . . . . . . . . . . . 100
Exercise . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
Extending Certain Commands . . . . . . . . . . . . . . . 106
3.5 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . 109
Contents • v
5 Procedures 201
5.1 Procedure Definitions . . . . . . . . . . . . . . . . . . . . 201
Mapping Notation . . . . . . . . . . . . . . . . . . . . . . 202
Unnamed Procedures and Their Combinations . . . . . . 203
Procedure Simplification . . . . . . . . . . . . . . . . . . . 204
5.2 Parameter Passing . . . . . . . . . . . . . . . . . . . . . . 204
Declared Parameters . . . . . . . . . . . . . . . . . . . . . 206
The Sequence of Arguments . . . . . . . . . . . . . . . . . 207
5.3 Local and Global Variables . . . . . . . . . . . . . . . . . 208
Evaluation of Local Variables . . . . . . . . . . . . . . . . 210
5.4 Procedure Options and the Description Field . . . . . . . 212
Options . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212
The Description Field . . . . . . . . . . . . . . . . . . . . 214
5.5 The Value Returned by a Procedure . . . . . . . . . . . . 215
Assigning Values to Parameters . . . . . . . . . . . . . . . 215
Explicit Returns . . . . . . . . . . . . . . . . . . . . . . . 218
Error Returns . . . . . . . . . . . . . . . . . . . . . . . . . 219
Trapping Exceptions . . . . . . . . . . . . . . . . . . . . . 221
Returning Unevaluated . . . . . . . . . . . . . . . . . . . . 225
Exercise . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
5.6 The Procedure Object . . . . . . . . . . . . . . . . . . . . 227
Last Name Evaluation . . . . . . . . . . . . . . . . . . . . 227
The Type and Operands of a Procedure . . . . . . . . . . 228
Saving and Retrieving Procedures . . . . . . . . . . . . . 231
5.7 Explorations . . . . . . . . . . . . . . . . . . . . . . . . . 232
Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . 232
5.8 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . 233
Index 611
1 Introduction
As a Maple user, you may fall into any number of categories. You may
have used Maple only interactively. You may already have written many
of your own programs. Even more fundamentally, you may or may not
have programmed in another computer language before attempting your
first Maple program. Indeed, you may have used Maple for some time
without realizing that the same powerful language you regularly use to
enter commands is itself a complete programming language.
Writing a Maple program can be very simple. It may only involve
putting a proc() and an end proc around a sequence of commands that
you use every day. On the other hand, the limits for writing Maple pro-
cedures with various levels of complexity depend only on you. Ninety to
ninety-five percent of the thousands of commands in the Maple language
are themselves Maple programs. You are free to examine these programs
and modify them to suit your needs, or extend them so that Maple can
tackle new types of problems. You should be able to write useful Maple
programs in a few hours, rather than the few days or weeks that it often
takes with other languages. This efficiency is partly due to the fact that
Maple is interactive; this interaction makes it easier to test and correct
programs.
Coding in Maple does not require expert programming skills. Unlike
traditional programming languages, the Maple language contains many
powerful commands which allow you to perform complicated tasks with
a single command instead of pages of code. For example, the solve com-
mand computes the solution to a system of equations. Maple comes with a
large library of routines, including graphical display primitives, so putting
useful programs together from its powerful building blocks is easy.
The aim of this chapter is to provide basic knowledge for proficiently
writing Maple code. To learn quickly, read until you encounter some ex-
ample programs and then write your own variations. This chapter includes
many examples along with exercises for you to try. Some of them highlight
1
2 • Chapter 1: Introduction
The prompt character > indicates that Maple is waiting for input.
Throughout this book, the command-line (or one-dimensional) input
format is used. For information on how to toggle between Maple notation
and standard math notation, please refer to the first chapter of the
Getting Started Guide.
Your input can be as simple as a single expression. A command is
followed immediately by its result.
> 103993/33102;
103993
33102
1
section 10.6 discusses specific commands to control printing.
1.1 Getting Started • 3
> 103993
> / 33102
> ;
103993
33102
You can even put the terminating semicolon on a separate line. Noth-
ing evaluates until you complete the command. Maple may, however, parse
the command for errors at this stage.
Associate names with results by using the assignment statement, :=.
> a := 103993/33102;
103993
a :=
33102
Once assigned a value in this manner, you can use the name a as if
it were the value 103993/33102. For example, you can use Maple’s evalf
command to compute an approximation to 103993/33102 divided by 2.
> evalf(a/2);
1.570796326
The program takes the input, called x within the procedure, and ap-
proximates the value of x divided by two. Since this is the last calculation
done within the procedure, the half procedure returns this approxima-
tion. Give the name half to the procedure using the := notation, just as
you would assign a name to any other object. Once you have defined a
new procedure, you can use it as a command.
4 • Chapter 1: Introduction
> half(2/3);
.3333333333
> half(a);
1.570796326
1.500000000
> evalf(a/2);
f := proc()
local a;
a := 103993/33102 ; evalf(1/2 ∗ a)
end proc
Maple’s interpretation of this procedure definition appears immedi-
ately after the command lines that created it. Examine it carefully and
note the following:
• You see a display of the procedure definition (just as for any other
Maple command) only after you complete it with an end proc and a
semicolon. Even the individual commands that make up the procedure
do not display until you complete the entire procedure and enter the
last semicolon.
Execute the procedure f—that is, cause the statements forming the
procedure to execute in sequence—by typing its name followed by paren-
theses. Enclose any input to the procedure, in this case none, between the
parentheses.
> f();
1.570796326
2
For example, the semicolon in the definition of a procedure between the last com-
mand and the end proc is optional.
6 • Chapter 1: Introduction
Sometimes you may not want Maple to display the result of con-
structing a complicated procedure definition. To suppress the display, use
a colon (:) instead of a semicolon (;) at the end of the definition.
> g := proc() local a;
> a := 103993/33102;
> evalf(a/2);
> end proc:
e := 3
> e;
If you try this with the procedure g, Maple displays only the name g
instead of its true value. Both procedures and tables potentially contain
many subobjects. This model of evaluation, referred to as last name
evaluation, hides the detail. To obtain the true value of the name g, use
the eval command, which forces full evaluation.
> g;
> eval(g);
proc()
local a;
a := 103993/33102 ; evalf(1/2 ∗ a)
end proc
To print the body of a Maple library procedure, set the interface
variable verboseproc to 2. See ?interface for details on interface
variables.
1.1 Getting Started • 7
b := 2
1.570796326
8 • Chapter 1: Introduction
1.570796326
103993
33102
> k(103993,33102);
103993
33102
41.07142857
−5 + 12 I
2 3
− I
13 13
−.2724000000 I
You could instead use the Re and Im commands to pick out the real
and imaginary parts, respectively, of a complex number. Hence, you can
also calculate the norm of a complex number in the following manner.
> znorm := proc(z)
> sqrt( Re(z)^2 + Im(z)^2 );
> end proc;
√
The norm of 2 + 3i is still 13.
> znorm( 2+3*I );
√
13
Finally, you can also compute the norm by re-using the abnorm pro-
cedure. The abznorm procedure below uses Re and Im to pass information
to abnorm in the form it expects.
> abznorm := proc(z)
> local r, i;
> r := Re(z);
> i := Im(z);
> abnorm(r, i);
> end proc;
abznorm := proc(z)
local r, i;
r := <(z) ; i := =(z) ; abnorm(r, i)
end proc
Use abznorm to calculate the norm of 2 + 3i.
> abznorm( 2+3*I );
√
13
variable := value ;
This syntax assigns the name on the left-hand side of := to the com-
puted value on the right-hand side. You have seen this statement used in
many of the earlier examples.
The use of := here is similar to the assignment statement in program-
ming languages, such as Pascal. Other programming languages, such as C
and Fortran, use = for assignments. Maple does not use = for assignments,
since it is such a natural choice for representing mathematical equations.
If you want to write a procedure called plotdiff which plots an
expression f (x) together with its derivative f 0 (x) on the interval [a, b],
you can accomplish this task by computing the derivative of f (x) with
the diff command and then plotting both f (x) and f 0 (x) on the same
interval with the plot command.
> y := x^3 - 2*x + 1;
12 • Chapter 1: Introduction
y := x3 − 2 x + 1
yp := 3 x2 − 2
–2
plotdiff := proc(y, x, a, b)
local yp;
yp := diff(y, x) ; plot([y, yp], x = a..b)
end proc
The procedure name is plotdiff. It has four parameters: y, the ex-
pression it differentiates; x, the name of the variable it uses to define the
expression; and a and b, the beginning and the end of the interval over
which it generates the plot. The procedure returns a Maple plot object
which you can either display, or use in further plotting routines.
By specifying that yp is a local variable, you ensure that its usage in
the procedure does not clash with any other usage of the variable that
you may have made elsewhere in the current session.
1.2 Basic Programming Constructs • 13
0.5
0 1 2 3 4 5 6
t
–0.5
–1
You may instead perform the same calculations by using a for loop.
> total := 0:
> for i from 1 to 5 do
> total := total + i;
> end do;
14 • Chapter 1: Introduction
total := 1
total := 3
total := 6
total := 10
total := 15
For each cycle through the loop, Maple increments the value of i by
one and checks whether i is greater than 5. If it is not, then Maple executes
the body of the loop again. When the execution of the loop finishes, the
value of total is 15.
> total;
15
The following procedure uses a for loop to calculate the sum of the
first n natural numbers.
> SUM := proc(n)
> local i, total;
> total := 0;
> for i from 1 to n do
> total := total+i;
> end do;
> total;
> end proc:
The purpose of the total statement at the end of SUM is to ensure that
SUM returns the value total. Calculate the sum of the first 100 numbers.
> SUM(100);
5050
5050
1.2 Basic Programming Constructs • 15
2.3
ABS(a)
16 • Chapter 1: Introduction
The single quotes tell Maple not to evaluate ABS. You can modify the ABS
procedure by using the type(..., numeric) command to test whether
x is a number.
> ABS := proc(x)
> if type(x,numeric) then
> if x<0 then -x else x end if;
> else
> ’ABS’(x);
> end if;
> end proc:
The above ABS procedure contains an example of a nested if statement,
that is, one if statement appearing within another. You need an even
more complicated nested if statement to implement the function
0 if x ≤ 0
x if 0 < x ≤ 1
hat(x) =
2 − x if 1 < x ≤ 2
0 if x > 2.
> else 0;
> end if;
> else
> ’HAT’(x);
> end if;
> end proc:
You may use as many elif branches as you need.
ABS(a b)
ABS(a) ABS(b)
You can use the type(..., ‘*‘) command to test whether an ex-
pression is a product and use the map command to apply ABS to each
operand of the product.
> ABS := proc(x)
> if type(x, numeric) then
> if x<0 then -x else x end if;
> elif type(x, ‘*‘) then
> map(ABS, x);
> else
> ’ABS’(x);
> end if;
> end proc:
> ABS( a*b );
ABS(a) ABS(b)
2 ABS(a)
You may want to improve ABS further so that it can calculate the
absolute value of a complex number.
The error message indicates what went wrong inside the for statement
while trying to execute the procedure. The test in the for loop failed
because "hello world" is a string, not a number, and Maple could
not determine whether to execute the loop. The following implemen-
tation of SUM provides a much more informative error message. The
type(...,integer) command determines whether n is an integer.
> SUM := proc(n)
> local i,total;
> if not type(n, integer) then
> error("input must be an integer");
> end if;
> total := 0;
> for i from 1 to n do total := total+i end do;
> total;
> end proc:
Now the error message is more helpful.
1.2 Basic Programming Constructs • 19
Using type to check inputs is such a common task that Maple provides
a simple means of declaring the type of an argument to a procedure. For
example, you can rewrite the SUM procedure in the following manner. An
informative error message helps you to find and correct a mistake quickly.
> SUM := proc(n::integer)
> local i, total;
> total := 0;
> for i from 1 to n do total := total+i end do;
> total;
> end proc:
Maple tests the condition and executes the commands inside the loop
over and over again until the condition fails.
You can use the while loop to write a procedure that divides an inte-
ger n by two as many times as is possible. The iquo and irem commands
calculate the quotient and remainder, respectively, using integer division.
> iquo( 7, 3 );
> irem( 7, 3 );
20 • Chapter 1: Introduction
> divideby2(48);
The while and for loops are both special cases of a more general
repetition statement; see section 4.3.
Modularization
When you write procedures, identifying subtasks and writing these as
separate procedures is a good idea. Doing so makes your procedures easier
to read, and you may be able to reuse some of the subtask procedures in
another application.
Consider the following mathematical problem. Suppose you have a
positive integer, in this case, forty.
> 40;
40
5
1.2 Basic Programming Constructs • 21
16
Divide.
> divideby2( % );
49
Recursive Procedures
Just as you can write procedures that call other procedures, you can also
write a procedure that calls itself. This is called recursive programming .
As an example, consider the Fibonacci numbers, which are defined in the
following procedure.
The time command tells you the number of seconds a procedure takes
to execute. Fibonacci is not very efficient.
> time( Fibonacci(20) );
1.2 Basic Programming Constructs • 23
.450
The reason is that Fibonacci recalculates the same results over and
over again. To find f20 , it must find f19 and f18 ; to find f19 , it must
find f18 again and f17 ; and so on. One solution to this efficiency problem
is to tell Fibonacci to remember its results. That way, Fibonacci only
has to calculate f18 once. The remember option makes a procedure store
its results in a remember table. Section 2.5 further discusses remember
tables.
> Fibonacci := proc(n::nonnegint)
> option remember;
> if n<2 then
> n;
> else
> Fibonacci(n-1)+Fibonacci(n-2);
> end if;
> end proc:
This version of Fibonacci is much faster.
> time( Fibonacci(20) );
0.
.133
> fnew;
> end if;
> end proc:
.133
When you write recursive procedures, you must weigh the benefits of
remember tables against their use of memory. Also, you must make sure
that your recursion stops.
Using the return statement can make your recursive procedures easier
to read; the usually complicated code that handles the general step of the
recursion does not end up inside a nested if statement.
Exercise
1. The Fibonacci numbers satisfy the following recurrence.
and
F (2n + 1) = F (n + 1)2 + F (n)2 where n > 1
Use these new relations to write a recursive Maple procedure which
computes the Fibonacci numbers. How much recomputation does this
procedure do?
1.3 Basic Data Structures • 25
You can easily represent the data for this problem as a list. nops gives
the total number of entries in a list X, while the ith entry of the list is
denoted X[i].
> X := [1.3, 5.3, 11.2, 2.1, 2.1];
> nops(X);
> X[2];
5.3
You can add the numbers in a list by using the add command.
> add( i, i=X );
22.0
26 • Chapter 1: Introduction
4.400000000
1 1 1
a+ b+ c
3 3 3
Exercise
1. Write a Maple procedure called sigma which, given n > 1 data val-
ues, x1 , x2 , . . . , xn , computes their standard deviation. The following
equation gives the standard deviation of n > 1 numbers,
v
u n
u1 X
σ=t (xi − µ)2
n
i=1
You create lists and many other objects in Maple out of more primitive
data structures called sequences . The list X defined previously contains
the following sequence.
> Y := X[];
You can select elements from a sequence in the same way you select
elements from a list.
1.3 Basic Data Structures • 27
> Y[3];
11.2
> Y[2..4];
> Y[2..-2];
W := a, b, c
> Y, W, Y;
1.3, 5.3, 11.2, 2.1, 2.1, a, b, c, 1.3, 5.3, 11.2, 2.1, 2.1
[[1.3, 5.3, 11.2, 2.1, 2.1], [a, b, c], [1.3, 5.3, 11.2, 2.1, 2.1]]
You can select elements from a set in the same way you select elements
from a list or a sequence, but the order of the elements in a set is session
dependent. Do not make any assumptions about this order.
You may also use the seq command to build sequences.
> seq( i^2, i=1..5 );
1, 4, 9, 16, 25
Exercise
1. Write a Maple procedure which, given a list of lists of numerical data,
computes the means of each column of the data.
A MEMBER Procedure
You may want to write a procedure that determines whether a certain
object is an element of a list or a set. The procedure below uses the
return statement discussed in section 1.2.
> MEMBER := proc( a::anything, L::{list, set} )
> local i;
> for i from 1 to nops(L) do
> if a=L[i] then return true end if;
> end do;
> false;
> end proc:
Here 3 is a member of the list.
> MEMBER( 3, [1,2,3,4,5,6] );
true
1.3 Basic Data Structures • 29
The type of loop that MEMBER uses occurs so frequently that Maple
has a special version of the for loop for it.
> MEMBER := proc( a::anything, L::{list, set} )
> local i;
> for i in L do
> if a=i then return true end if;
> end do;
> false;
> end proc:
The symbol x is not a member of this set.
> MEMBER( x, {1,2,3,4} );
false
Instead of using your own MEMBER procedure, you can use the built-in
member command.
Exercise
1. Write a Maple procedure called POSITION which returns the position
i of an element x in a list L. That is, POSITION(x,L) should return
an integer i > 0 such that L[i]=x. Return 0 if x is not in the list L.
Binary Search
One of the most basic and well-studied computing problems is that of
searching. A typical problem involves searching a list of words (a dictio-
nary, for example) for a specific word w.
Many possible solutions are available. One approach is to search the
list by comparing each word in turn with w until Maple either finds w or
it reaches the end of the list.
> Search := proc(Dictionary::list(string), w::string)
> local x;
> for x in Dictionary do
> if x=w then return true end if
> end do;
> false
> end proc:
However, if the Dictionary is large, say 50 000 entries, this approach can
take a long time.
You can reduce the execution time required by sorting the Dictionary
before you search it. If you sort the dictionary into ascending order then
you can stop searching as soon as you encounter a word greater than w.
On average, you only have to look halfway through the dictionary.
30 • Chapter 1: Introduction
false
true
false
Exercises
1. Can you demonstrate that the BinarySearch procedure always ter-
minates? Suppose the dictionary has n entries. How many words in
the dictionary D does BinarySearch look at in the worst case?
1.3 Basic Data Structures • 31
1.5
0.5
–1 –0.5 0.5 1
You can use this approach to write a procedure which plots the com-
plex roots of a polynomial. Consider the polynomial x3 − 1.
> y := x^3-1;
y := x3 − 1
R := [−.5000000000 − .8660254038 I,
−.5000000000 + .8660254038 I, 1.]
You need to turn this list of complex numbers into a list of points in
the plane. The Re and Im commands pick the real and imaginary parts,
respectively.
> points := map( z -> [Re(z), Im(z)], R );
32 • Chapter 1: Introduction
0.8
0.6
0.4
0.2
–0.4 –0.2 0 0.2 0.4 0.6 0.8 1
–0.2
–0.4
–0.6
–0.8
0.5
–3 –2 –1 1
–0.5
–1
> rootplot( y );
0.5
–1 –0.5 0 0.5 1
–0.5
–1
When you write procedures, you often have several choices of how to
represent the data with which your procedures work. The choice of data
structure can have great impact on how easy it is to write your procedure
and its resulting efficiency. Section 2.4 describes an example of choosing
a data structure.
The ai s are the coefficients . They can be numbers or even expressions in-
volving variables. The crucial point is that each coefficient is independent
of (does not contain) x.
34 • Chapter 1: Introduction
p := 32 x6 − 48 x4 + 18 x2 − 1
> HGHT(p,x);
48
You cannot map the abs command, or any other command, onto a
sequence. One solution is to turn the sequence into a list or a set.
> S := map( abs, {%} );
48
79
If the polynomial is in expanded form, you can also find its height
in the following manner. You can map a command directly onto a poly-
nomial. The map command applies the command to each term in the
polynomial.
> map( f, p );
f(79 x71 ) + f(56 x63 ) + f(49 x44 ) + f(63 x30 ) + f(57 x24 )
+ f(−59 x18 )
Thus, you can map abs directly onto the polynomial.
> map( abs, p );
> coeffs( % );
79
Hence, you can calculate the height of a polynomial with this one-
liner.
> p := randpoly(x, degree=50) * randpoly(x, degree=99);
9214
Exercise
1. Write a pprocedure
Pn that computes the Euclidean norm of a polynomial;
2
that is, i=0 i | .
|a
> T(4,x);
2 x (2 x (2 x2 − 1) − x) − 2 x2 + 1
8 x4 − 8 x2 + 1
Exercise
1. The Fibonacci polynomials, Fn (x), satisfy the linear recurrence
Integration by Parts
Maple’s indefinite integral evaluator is very powerful. This section de-
scribes how you could write your own procedure for integrating formulæ
of the form
p(x)f (x),
where p(x) is a polynomial in x and f (x) is a special function. Here
p(x) = x2 and f (x) = ex .
> int( x^2*exp(x), x );
x2 ex − 2 x ex + 2 ex
1 4 1 3p 3 p 3
x arcsin(x) + x 1 − x2 + x 1 − x2 − arcsin(x)
4 16 32 32
38 • Chapter 1: Introduction
You can verify this formula by differentiating both sides of the equa-
tion.
> diff(%,x);
> evalb(%);
true
x5 ex − 5 x4 ex + 20 x3 ex − 60 x2 ex + 120 x ex − 120 ex
1.4 Computing with Formulæ • 39
You can simplify this answer by using the collect command to group
the terms involving exp(x) together.
> collect(%, exp(x));
You can now write a procedure which calculates p(x)ex dx for any
R
(24 − 23 x + 10 x2 − 3 x3 ) ex
Exercises
1. Modify the procedure IntExpPolynomial to be more efficient by pro-
cessing only the non-zero coefficients of p(x).
2. The procedure IntExpPolynomial is quadratic in degree. Modify this
procedure again to make it linear in degree.
(x − 1) ex
(x2 − 2 x + 2) ex
(x3 − 3 x2 + 6 x − 6) ex
With sufficient time and ingenuity you would find the formula
n
(−1)n−i xi
Z X
xn ex dx = n! ex .
i!
i=0
This formula holds only for non-negative integers n. Use the assume
facility to tell Maple that the unknown n has certain properties.
> assume(n, integer);
> additionally(n >= 0);
Note that a simple type check is not sufficient to determine that n is an
integer.
> type(n, integer);
false
You need to use the is command, which is part of the assume facility.
1.4 Computing with Formulæ • 41
true, true
Thus, you can rewrite the IntExpMonomial procedure from section 1.4
in the following manner.
> IntExpMonomial := proc(n::anything, x::name)
> local i;
> if is(n, integer) and is(n >= 0) then
> n! * exp(x) * sum( ( (-1)^(n-i)*x^i )/i!, i=0..n );
> else
> error("Expected a non-negative integer but received", n);
> end if;
> end proc:
This version of IntExpMonomial accepts both explicit and symbolic input.
> IntExpMonomial(4, x);
1 2 1 3 1 4
24 ex (1 − x + x − x + x )
2 6 24
In the next example, Maple evaluates the sum in terms of the gamma
function. The tilde (~) on n indicates that n carries an assumption.
> IntExpMonomial(n, x);
n~! ex (
x(n~+1) ((−x)(−1−n~) e(−x) Γ(2 + n~) − %1)
(−1)n~ e(−x) +
(n~ + 1)!
) + n~! ex (−(−1)n~ e(−x) + x(n~+1) (n~ + 1)
((−x)(−1−n~) e(−x) Γ(2 + n~) − %1)/(x (n~ + 1)!)+
(−x)(−1−n~) (−1 − n~) e(−x) Γ(2 + n~)
x(n~+1) (
x
− (−x)(−1−n~) e(−x) Γ(2 + n~)
(n~ + 1) (−x)(−1−n~) (−1 − n~) e(−x) Γ(n~ + 1, −x)
−
x
+ %1 − (n~ + 1) (−x)(−1−n~) e(−x) (−x)n~ ex )/(n~ + 1)!)
%1 := (n~ + 1) (−x)(−1−n~) e(−x) Γ(n~ + 1, −x)
> simplify(%);
ex xn~
Clearly, the use of symbolic constants in this way greatly extends the
power of the system.
Exercise
1. Extend the facility above to compute xn eax+b dx, where n is an
R
integer and a and b are constants. You must handle the case n = −1
separately since Z x
e
dx = −Ei(1, −x) .
x
Use the ispoly command from the Maple library to test for the ex-
pression ax + b which is linear in x.
1.5 Conclusion
This chapter introduced the basics of Maple programming. It first showed
you how to take a few lines of code and turn them into a useful procedure
simply by inserting them between proc() and end proc statements. Then
it introduced local and global variables and how to use them. As well, you
1.5 Conclusion • 43
2. nested procedures;
5. remember tables.
45
46 • Chapter 2: Fundamentals
a := b
> b := c;
b := c
> a + 1;
c+1
> eval(a);
When you enter commands at the prompt, Maple usually evaluates the
names as if you had enclosed each one in an eval(). The main exception
is that evaluation stops whenever evaluating to one more level would turn
the name into one of a table, an array, a procedure, or a module. The
command a + 1 above is almost identical to eval(a) + 1.
In procedures, some rules are different. If you use the previous assign-
ments within a procedure, you may get unexpected results.
> f := proc()
> local a,b;
> a := b;
> b := c;
> a + 1;
> end proc;
> f();
b+1
Parameters
Chapter 1 introduced you to local and global variables, but proce-
dures have a more fundamental type of variable: parameters. Param-
eters are variables whose name appears between the parentheses of a
proc()expression. They have a special role within procedures, as Maple
replaces them with arguments when you execute the procedure.
48 • Chapter 2: Fundamentals
Examine the following procedure which squares its first argument and
assigns the answer to the second argument, which must be a name.
> sqr1 := proc(x::anything, y::name)
> y := x^2;
> end proc;
d2
> ans;
d2
The procedure squares the value of d and assigns the result to the
name ans. Try the procedure again, but this time use the name a which
Maple earlier assigned the value b. Remember to reset ans to a name
first.
> ans := ’ans’;
ans := ans
c2
> ans;
c2
From the answer, Maple clearly remembers that you assigned b to the
name a, and c to the name b. When did this evaluation occur?
To determine when, you must examine the value of x as soon as Maple
enters the procedure. Use the debugger to get Maple to stop just after
entering sqr1.
> stopat(sqr1);
2.1 Evaluation Rules • 49
[sqr1 ]
DBG> cont
c2
> unstopat(sqr1):
g :=
proc() local a, b, ans ; a := b ; b := c ; sqr1(a, ans ) end proc
> g();
50 • Chapter 2: Fundamentals
b2
Whether you call a procedure from the interactive level or from inside
a procedure, Maple evaluates the arguments before invoking the proce-
dure. Once Maple evaluates the arguments, it replaces all occurrences
of the procedure’s formal parameters with the actual arguments. Then
Maple invokes the procedure.
Because Maple only evaluates parameters once, you cannot use them
like local variables. The author of procedure cube, below, forgot that
Maple does not re-evaluate parameters.
> cube := proc(x::anything, y::name)
> y := x^3;
> y;
> end proc:
When you call cube as below, Maple does assign ans the value 23 , but
the procedure returns the name ans rather than its value.
> ans := ’ans’;
ans := ans
ans
> ans;
Maple replaces each y with ans, but Maple does not evaluate these
occurrences of ans again. Thus, the final line of cube returns the name
ans, not the value that Maple assigned to ans.
Use parameters to pass information into the procedure. You may think
of parameters as objects evaluated to zero levels.
Local Variables
Local variables are temporary storage places within a procedure. You can
create local variables by using the local declaration statement at the
beginning of a procedure. If you do not declare whether a variable is
local or global, Maple decides for you. If you make an assignment to a
2.1 Evaluation Rules • 51
b+1
Maple always uses last name evaluation for tables, arrays, modules,
and procedures. Therefore, if you assign a table, an array, a module, or
a procedure to a local variable, Maple does not evaluate that variable
unless you use eval. Maple creates the local variables of a procedure each
time you call the procedure. Thus, local variables are local to a specific
invocation of a procedure.
If you have not written many programs you might think that one level
evaluation of local variables is a serious limitation, but in fact code which
requires further evaluation of local variables is difficult to understand,
and is unnecessary. Moreover, because Maple does not attempt further
evaluations, it saves many steps, causing procedures to run faster.
Global Variables
Global variables are available from inside any procedure in Maple as well
as at the interactive level. Indeed, any name you use at the interactive
level is a global variable, allowing you to write a procedure which assigns
a value to a variable that is accessible again later from within another
procedure, from within the same procedure, or at the interactive level.
52 • Chapter 2: Fundamentals
> h := proc()
> global x;
> x := 5;
> end proc:
> h();
> x;
Moreover, if you write yet another procedure which uses the global
variable x, then the two procedures may use the same x in incompatible
ways.
Whether within a procedure or at the interactive level, Maple always
applies the same evaluation rules to global variables. It evaluates all global
names fully, except when the value of such a variable is a table, an array,
or a procedure, in which case, Maple halts its evaluation at the last name
in the chain of assignments. This evaluation rule is called last name
evaluation.
Hence, Maple evaluates parameters to zero levels, local variables
to one level, and global variables fully, except for last name evaluation.
As with local variables, the rules for determining which variables are
global are fully described in Section 2.2.
Exceptions
This section describes two exceptions of particular note to the rules for
evaluation.
The Ditto Operator The ditto operator , %, which recalls the last result,
is local to procedures but Maple evaluates it fully. When you invoke a
procedure, Maple initializes the local version of % to NULL.
2.1 Evaluation Rules • 53
> f := proc()
> local a,b;
> print( "Initially [%] has the value", [%] );
> a := b;
> b := c;
> a + 1;
> print( "Now [%] has the value", [%] );
> end proc:
> f();
The same special rules apply to the %% and %%% operators. Using local
variables instead of ditto operators makes your procedures easier to read
and debug.
10
> g();
54 • Chapter 2: Fundamentals
10
1 1
[1, , , 2]
2 4
1 1
[1, , , 2]
2 4
8 4 2 16
[ , , , ]
v v v v
If, using the above rule, Maple cannot determine whether a variable
should be global or local, the following default decisions are made for you.
If a variable appears on the left-hand side of an explicit assignment
or as the controlling variable of a for loop, then Maple assumes that
you intend the variable to be local. Otherwise, Maple assumes that the
variable is global to the whole session. In particular, Maple assumes by
default that the variables you only pass as arguments to other procedures,
which may set their values, are global.
four smaller arrays. You sort the entire array by repeatedly partitioning
the smaller arrays.
The partition procedure uses an array to store the list because you
can change the elements of an array directly. Thus, you can sort the array
in place and not waste any space generating extra copies.
The quicksort procedure is easier to understand if you look at the
procedure partition in isolation first. This procedure accepts an array
of numbers and two integers. The two integers are element numbers of the
array, indicating the portion of the array to partition. While you could
possibly choose any of the numbers in the array to partition around, this
procedure chooses the last element of the section of the array for that
purpose, namely A[n]. The intentional omission of global and local
statements is to show which variables Maple thinks are local and which
global by default. It is recommended, however, that you not make this
omission in your procedures.
> partition := proc(A::array(1, numeric),
> m::posint, n::posint)
> i := m;
> j := n;
> x := A[j];
> while i<j do
> if A[i]>x then
> A[j] := A[i];
> j := j-1;
> A[i] := A[j];
> else
> i := i+1;
> end if;
> end do;
> A[j] := x;
> eval(A);
> end proc:
Warning, ‘i‘ is implicitly declared local to procedure
‘partition‘
Warning, ‘j‘ is implicitly declared local to procedure
‘partition‘
Warning, ‘x‘ is implicitly declared local to procedure
‘partition‘
After partitioning the array a below, all the elements less than 3
precede 3 but they are in no particular order; similarly, the elements
larger than 3 come after 3.
> a := array( [2,4,1,5,3] );
a := [2, 4, 1, 5, 3]
[2, 1, 3, 5, 4]
[2, 1, 3, 5, 4]
> p := j;
> end proc:
>
> if m<n then # if m>=n there is nothing to do
> p:=partition(m, n);
> quicksort(A, m, p-1);
> quicksort(A, p+1, n);
> end if;
>
> eval(A);
> end proc:
Warning, ‘i‘ is implicitly declared local to procedure
‘partition‘
Warning, ‘j‘ is implicitly declared local to procedure
‘partition‘
Warning, ‘x‘ is implicitly declared local to procedure
‘partition‘
a := [2, 4, 1, 5, 3]
[1, 2, 3, 4, 5]
> eval(a);
[1, 2, 3, 4, 5]
5, 6, 5, 7, 4, 6, 5, 4, 5, 5, 7, 7, 5, 4, 6, 5, 4, 5, 7, 5
12210706011
2000000000
6.648630719
.828316845400000, −.328875163100000,
.790988967100000, .624953401700000,
.362773633800000, .679519822000000,
−.0465278542000000, −.291055180800000
The proper design choice here is that U should depend only on the
value of Digits when you invoke U. The version of uniform below ac-
complishes this by placing all the computation inside the procedure that
uniform returns.
> uniform := proc( r::constant..constant )
>
> proc()
> local intrange, f;
> intrange := map( x -> round(x*10^Digits),
> evalf(r) );
> f := rand( intrange );
> evalf( f()/10^Digits );
> end proc;
> end proc:
62 • Chapter 2: Fundamentals
.476383408581006, .554836962987261,
.147655743361511, .273247304736175,
.148172828708797, −.258115633420094,
.558246581434993, .518084711267009
This section introduced you to the rules Maple uses to decide which
variables are global or local. You have also seen the principal implications
of these rules. In particular, it introduced you to the tools available for
writing nested procedures.
2.3 Types
Types that Modify Evaluation Rules
Section 2.1 introduces the details of how Maple evaluates different kinds
of variables within a procedure: Maple evaluates global variables fully (ex-
cept for last-name evaluation) and local variables to one level. Maple eval-
uates the arguments to a procedure, depending upon the circumstances,
before invoking the procedure, and then simply substitutes the actual
parameters for the formal parameters within the procedure without any
further evaluation. All these rules seem to imply that nothing within the
procedure in any way affects the evaluation of arguments which occurs
before Maple invokes the procedure. In reality, the exceptions provide
convenient methods for controlling the evaluation of arguments which
make your procedures behave more intuitively. They also prevent eval-
uation which would result in the loss of information you wish available
within your procedure.
Maple uses different evaluation rules for some of its own commands,
for example, the evaln command. You have no doubt used this command
to clear the value of previously defined variables. If this command were
to evaluate its argument normally, it would be of no use for this purpose.
2.3 Types • 63
x := π
> cos(x);
−1
If Maple behaved the same way when you type evaln(x), then Maple
would pass the value π to evaln, losing all references to the name x.
Therefore, Maple evaluates the argument to evaln in a special way: it
evaluates the argument to a name, not to the value that name may have.
> x := evaln(x);
x := x
> cos(x);
cos(x)
You will find it useful to write your own procedures which exhibit
this behavior. You may want to write a procedure which returns a value
by assigning it to one of the arguments. Section 2.1 describes such a
procedure, sqr1, but each time you call sqr1 you must take care to pass
it an unassigned name.
> sqr1:= proc(x::anything, y::name)
> y := x^2;
> end proc:
This procedure works fine the first time you call it. However, you must
make sure that the second argument is indeed a name; otherwise, an error
results. In the example below, the error occurs because, upon the second
attempt, ans has the value 9.
> ans;
ans
64 • Chapter 2: Fundamentals
> ans;
You have two ways around this problem. The first is to use either
single quotes or the evaln command to ensure that Maple passes a name
and not a value. The second is to declare the parameter to be of type
evaln.
Just like the evaln command, declaring a parameter to be of type
evaln causes Maple to evaluate that argument to a name, so you do not
have to worry about evaluation when you use the procedure.
> cube := proc(x::anything, y::evaln)
> y := x^3;
> end proc:
> ans;
125
> ans;
125
In the above case, Maple passes the name ans to the cube procedure
instead of the value 9.
Using the evaln declaration is generally a good idea. It ensures that
your procedures do what you expect instead of returning cryptic error
messages. However, some Maple programmers like to use the single quotes.
When the call to the procedure is within a procedure itself, the presence
2.3 Types • 65
exprseq
Now you can map over sequences as well as lists, sets, and other
expressions.
> S := 1,2,3,4;
66 • Chapter 2: Fundamentals
S := 1, 2, 3, 4
Both evaln and uneval greatly extend the flexibility of Maple’s pro-
gramming language and the types of procedures you can write.
Structured Types
Sometimes a simple type check, either through declared formal parameters
or explicitly with the type command, does not provide enough informa-
tion. A simple check tells you that 2x is an exponentiation but it does not
distinguish between 2x and x2 .
> type( 2^x, ‘^‘ );
true
true
true
false
2
{x2 + 2 x + 1 = 0, z = }
x
1
[ = 9]
x
[x2 + 2 x + 1, x = −1]
Type Matching
Section 1.4 describes the following pair of procedures that implement
indefinite integration of any polynomial multiplied by ex .
> IntExpMonomial := proc(n::nonnegint, x::name)
> if n=0 then return exp(x) end if;
> x^n*exp(x) - n*IntExpMonomial(n-1, x);
> end proc:
> IntExpPolynomial := proc(p::polynom, x::name)
> local i, result;
> result := add( coeff(p, x, i)*IntExpMonomial(i, x),
> i=0..degree(p, x) );
> collect(result, exp(x));
> end proc:
> i=0..degree(p, x) );
> if type(xx, name) then
> collect(result, exp(x));
> else
> eval(result, x=b) - eval(result, x=a);
> end if;
> end proc:
true
> y, a, b;
myvar , 1, 6
−118 e2 + 308 e
(−4 x2 + 8 x − 8 + x3 ) ex
Make a new graph G and add a few cities (or vertices , in the termi-
nology of graph theory).
> new(G):
> cities := {Zurich, Rome, Paris, Berlin, Vienna};
2.4 Choosing a Data Structure: Connected Graphs • 71
Add roads between Zurich and each of Paris, Berlin, and Vienna. The
connect command names the roads e1, e2, and e3.
> connect( {Zurich}, {Paris, Berlin, Vienna}, G );
e1 , e2 , e3
Add roads between Rome and Zurich and between Berlin and both
Paris and Vienna.
> connect( {Rome}, {Zurich}, G);
e4
e5 , e6
Paris
Rome
Berlin
Vienna
Zurich
If you look at the drawing above, you can convince yourself that, in
this particular case, you could travel between any two cities. Instead of
visual inspection, you can also use the connectivity command.
72 • Chapter 2: Fundamentals
true
The data structures that the networks package uses are quite in-
volved, because that package supports more general structures than you
need in this example. The question then is: how would you represent the
cities and roads? Since cities have distinct names and the order of the
cities is irrelevant, you could represent the cities as a set of names.
> vertices(G);
{e1 , e2 , e3 , e4 , e5 , e6 }
You can also represent a road as the set consisting of the two cities
the road connects.
> ends(e2, G);
{Zurich, Berlin}
{Zurich, Berlin}
You can represent the data as a table of neighbors; one entry should
be in the table for each city.
> T := table( map( v -> (v)=neighbors(v,G), cities ) );
Similarly, you can find the neighbors of the neighbors, and thus you can
quickly determine how far you can travel.
The connected procedure below determines whether you can travel
between any two cities. It uses the indices command to extract the set
of cities from the table.
> indices(T);
Since the indices command returns a sequence of lists, you must use
the op and map command to generate the set.
> map( op, {%} );
true
You can add the cities Montreal, Toronto, and Waterloo, and the
highway between them.
> T[Waterloo] := {Toronto};
TWaterloo := {Toronto}
2.4 Choosing a Data Structure: Connected Graphs • 75
TMontreal := {Toronto}
Now you can no longer travel between any two cities; for example,
you cannot travel from Paris to Waterloo.
> connected(T);
false
Exercises
1. The system of cities and roads above splits naturally into two com-
ponents: the Canadian cities and the roads between them, and the
European cities and the roads between them. In each component you
can travel between any two cities but you cannot travel between the
two components. Write a procedure that, given a table of neighbors,
splits the system into such components. You may want to think about
the form in which the procedure should return its result.
2. The connected procedure above cannot handle the empty table of
neighbors.
> connected( table() );
table([0 = 0, 1 = 1, 2 = 1, 3 = 2])
f (x ) := result :
You must add entries in the remember table after making the pro-
cedure. The option remember statement does not create the remember
table, but rather asks Maple to automatically add entries to it. The pro-
cedure works without this option, but less efficiently.
You could even write a procedure which chooses which values to add
to its remember table. The following version of fib only adds entries to
its remember table when you call it with an odd-valued argument.
> fib := proc(n::nonnegint)
> if type(n,odd) then
> fib(n) := fib(n-1) + fib(n-2);
> else
> fib(n-1) + fib(n-2);
> end if;
> end proc:
> fib(0) := 0:
> fib(1) := 1:
> fib(9);
34
78 • Chapter 2: Fundamentals
T7 := T7
Now the fib procedure’s remember table has only five entries.
> op(4, eval(fib) );
table([0 = 0, 1 = 1, 3 = 2, 5 = 5, 9 = 34])
You should use remember tables only with procedures whose results
depend exclusively on parameters. The procedure below depends on the
value of the environment variable Digits.
2.6 Conclusion • 79
> f := proc(x::constant)
> option remember;
> evalf(x);
> end proc:
> f(Pi);
3.141592654
Digits := 44
> f(Pi);
3.141592654
2.6 Conclusion
A thorough understanding of the concepts in this chapter will provide you
with an excellent foundation for understanding Maple’s language. The
time you spend studying this chapter will save you hours puzzling over
trivial problems in subroutines and procedures which appear to behave
erratically. With the knowledge contained here, you should now see the
source of such problems with clarity. Just as you may have done after
finishing chapter 1, you may wish to put this book down for a while and
practice creating more of your own procedures.
Chapter 3 introduces you to more advanced techniques in Maple pro-
gramming. For example, it discusses procedures which return procedures,
procedures which query the user for input, and packages which you can
design yourself.
The remaining chapters of this manual are independent from one an-
other. You can focus on the topics of interest to you, for example, the
Maple debugger or Maple graphics programming. If you wish a more for-
mal presentation of the Maple language, take a look at chapters 4 and 5.
80 • Chapter 2: Fundamentals
3 Advanced Programming
81
82 • Chapter 3: Advanced Programming
2
1.5
1
0.5
x0 x1
0 1 2 3 4 5 6 7 8
–0.5 x
–1
f (xk )
xk+1 = xk −
f 0 (xk )
x0 := 2.0
x0 := 4.828427124
x0 := 4.032533198
x0 := 4.000065353
x0 := 4.000000000
g := x → x − cos(x)
x → x − cos(x)
SirIsaac := (x → x) −
x → 1 + sin(x)
Note that SirIsaac does not contain references to the name g; thus,
you can change g without breaking SirIsaac. You can find a good ap-
proximate solution to x − cos(x) = 0 in a few iterations.
> x0 := 1.0;
x0 := 1.0
x0 := .7503638679
x0 := .7391128909
x0 := .7390851334
x0 := .7390851332
3.1 Procedures Which Return Procedures • 85
A Shift Operator
Consider the problem of writing a procedure that takes a function, f , as
input and returns a function, g, such that g(x) = f (x + 1). You can write
such a procedure in the following manner.
> shift := (f::procedure) -> ( x->f(x+1) ):
Try performing a shift on sin(x).
> shift(sin);
x → sin(x + 1)
Maple’s lexical scoping rules declare the f within the inner procedure
to be the same f as the parameter within the outer procedure. Therefore,
the command shift works as written.
The version of shift above works with univariate functions but it
does not work with functions of two or more variables.
> h := (x,y) -> x*y;
h := (x, y) → x y
> hh := shift(h);
hh := x → h(x + 1)
> hh(x,y);
Error, (in h) h uses a 2nd argument, y, which is
missing
> hh := shift(h);
86 • Chapter 3: Advanced Programming
hh := x → h(x + 1, args2..−1 )
> hh(x,y);
(x + 1) y
y z2
h := (x, y, z) →
x
> hh(x,y,z);
y z2
x+1
Since a set in Maple contains unique elements, you can easily verify
that each a that make_a returns is unique.
> test := { a, a, a };
test := {a}
test := {a, a}
test := {a, a, a, a, a, a, a}
You can use local variables to make Maple print things it would not
ordinarily be able to display. The above set test is an example. Another
example is expressions which Maple would ordinarily simplify automat-
ically. For example, Maple automatically simplifies the expression a + a
to 2a, so displaying the equation a+a = 2a is not easy. You can create the
illusion that Maple is showing you these steps using procedure make_a,
above.
> a + make_a() = 2*a;
88 • Chapter 3: Advanced Programming
a + a = 2a
To Maple, these two variables are distinct, even though they share the
same name.
You cannot easily assign a value to such escapees. Whenever you type
a name in an interactive session, Maple thinks you mean the global vari-
able of that name. While this prevents you from using the assignment
statement, it does not prevent you from using the assignment command.
The trick is to write a Maple expression which extracts the variable you
want. For example, in the equation above, you may extract the local a by
removing the global a from the left-hand side of the equation.
> eqn := %;
eqn := a + a = 2 a
another _a := a
You may then assign the global name a to this extracted variable and
so verify the equation.
> assign(another_a = a);
> eqn;
2a = 2a
> evalb(%);
true
> assume(b>0);
> x := b + 1;
x := b~ + 1
b~ + 1
When you clear the definition of the named variable the association
between the name and the local name with the tilde is lost, but expressions
created with the local name still contain it.
> b := evaln(b);
b := b
> x;
b~ + 1
If you later wish to reuse your expression, you must either perform a
substitution before removing the assumption or perform some manipula-
tions of your expressions similar to the equation eqn.
c := [3, 1]
[γ, x]
Before you call the element procedure you must initialize all the coun-
ters to 1, except the first one, which should be 0.
> c := array( [0, 1] );
c := [0, 1]
[α, x]
[β, x]
[γ, x]
[α, y]
[β, y]
[γ, y]
FAIL
Again, you can find all six elements of {α, β, γ} × {x, y}.
> f := CartesianProduct( {alpha, beta, gamma}, {x,y} );
[α, x]
[β, x]
[γ, x]
[α, y]
[β, y]
[γ, y]
FAIL
[x, N, 23]
[y, N, 23]
[x, Z, 23]
[y, Z, 23]
[x, R, 23]
element := 45
> g();
[x, R, 56]
94 • Chapter 3: Advanced Programming
These examples demonstrate not only that local variables can escape
the bounds of the procedures which create them, but that this mechanism
allows you to write procedures which create specialized procedures.
Exercises
1. The procedure that CartesianProduct generates does not work if one
of the sets is empty.
> f := CartesianProduct( {}, {x,y} );
> f();
[1, 1, 1, 1, 1], [1, 1, 1, 2], [1, 1, 3], [1, 2, 2], [1, 4], [2, 3], [5].
readline( filename )
> DetermineSign(u-1);
> y
true
readstat( prompt )
n−1
390625
x=0
Below, the user first tries to use the number 1 as the limit variable.
Since 1 is not a name, GetLimitInput asks for another limit variable.
> GetLimitInput( exp(u*x) );
x=∞
s := “a*x^2 + 1”
> y := parse( s );
98 • Chapter 3: Advanced Programming
y := a x2 + 1
When you parse the string s you get an expression. In this case, you
get a sum.
> type(s, string), type(y, ‘+‘);
true, true
The parse command does not evaluate the expression it returns. You
must use eval to evaluate the expression explicitly. Below, Maple does
not evaluate the variable a to its value, 2, until you explicitly use the eval
command.
> a := 2;
a := 2
> z := parse( s );
z := a x2 + 1
> eval(z);
2 x2 + 1
See section 10.7 for more details about the parse command.
The techniques you have seen in this section are all very simple, but
you can use them to create powerful applications such as Maple tutorials,
procedures that drill students, or interactive lessons.
true
true
false
true
true
false
false
Exercises
1. Modify the ‘type/LINEAR‘ procedure so that you can use it to test
if an expression is linear in a set of variables. For example, x + ay + 1
is linear in both x and y, but xy + a + 1 is not.
Neutral Operators
Maple understands a number of operators, for example +, *, ^, and, not,
and union. All of these operators have special meaning to Maple: some
represent algebraic operations, such as addition or multiplication; some
represent logical operations; and some represent operations performed on
sets. Maple also has a special class of operators, the neutral operators ,
on which it does not impose any meaning. Instead, Maple allows you
to define the meaning of any neutral operator. The name of a neutral
operator begins with the ampersand character, &. Section 4.4 describes
the naming conventions for neutral operators.
3.4 Extending Maple • 101
(7 &^ 8) &^ 9
false
false
7 &^ 8
Maple uses the infix notation only if your neutral operator has exactly
two arguments.
> &^(4), &^(5, 6), &^(7, 8, 9);
unit by using the interface function. See ?interface for more informa-
tion.
> interface(imaginaryunit=j);
type/Hamiltonian :=
{name, ∗, +, realcons , specfunc(anything , &^)}
The ‘&^‘ procedure multiplies the two Hamiltonians, x and y. If either
x or y is a real number or variable, then their product is the usual product
denoted by * in Maple. If x or y is a sum, ‘&^‘ maps the product onto
the sum; that is, ‘&^‘ applies the distributive laws: x(u + v) = xu + xv
and (u + v)x = ux + vx. If x or y is a product, ‘&^‘ extracts any real
factors. You must take special care to avoid infinite recursion when x
or y is a product that does not contain any real factors. If none of the
multiplication rules apply, ‘&^‘ returns the product unevaluated.
> ‘&^‘ := proc( x::Hamiltonian, y::Hamiltonian )
> local Real, unReal, isReal;
> isReal := z -> evalb( is(z, real) = true );
>
> if isReal(x) or isReal(y) then
> x * y;
>
> elif type(x, ‘+‘) then
> # x is a sum, u+v, so x&^y = u&^y + v&^y.
> map(‘&^‘, x, y);
>
> elif type(y, ‘+‘) then
> # y is a sum, u+v, so x&^y = x&^u + x&^v.
> map2(‘&^‘, x, y);
>
> elif type(x, ‘*‘) then
> # Pick out the real factors of x.
> Real, unReal := selectremove(isReal, x);
> # Now x&^y = Real * (unReal&^y)
> if Real=1 then
> if type(y, ‘*‘) then
> Real, unReal := selectremove(isReal, x);
> Real * ’‘&^‘’(x, unReal);
> else
> ’‘&^‘’(x, y);
3.4 Extending Maple • 103
You can place all the special multiplication rules for the symbols I,
J, and K in the remember table of ‘&^‘. See section 2.5.
> ‘&^‘(I,I) := -1: ‘&^‘(J,J) := -1: ‘&^‘(K,K) := -1:
> ‘&^‘(I,J) := K: ‘&^‘(J,I) := -K:
> ‘&^‘(I,K) := -J: ‘&^‘(K,I) := J:
> ‘&^‘(J,K) := I: ‘&^‘(K,J) := -I:
20 + 41 I + 20 J − 3 K
20 − 15 I − 4 J + 43 K
> 56 &^ I;
56 I
a &^ J
a~ J
Exercise
1. The inverse of a general Hamiltonian, a + bi + cj + dk, is (a − bi − cj −
dk)/(a2 + b2 + c2 + d2 ). You can demonstrate this fact by assuming
that a, b, c, and d are real and define a general Hamiltonian, h.
> assume(a, real); assume(b, real);
h := a~ + b~ I + c~ J + d ~ K
a~ − b~ I − c~ J − d ~ K
hinv :=
a~2 + b~2 + c~2 + d ~2
Now all you have to check is that h &^ hinv and hinv &^ h both
simplify to 1.
> h &^ hinv;
3.4 Extending Maple • 105
a~ (a~ − b~ I − c~ J − d ~ K)
%1
b~ (I a~ + b~ − c~ K + d ~ J)
+
%1
c~ (J a~ + b~ K + c~ − d ~ I)
+
%1
d ~ (K a~ − b~ J + c~ I + d ~)
+
%1
%1 := a~ + b~ + c~2 + d ~2
2 2
> simplify(%);
a~ (a~ − b~ I − c~ J − d ~ K)
%1
a~ b~ I + b~2 + b~ c~ K − b~ d ~ J
+
%1
a~ c~ J − b~ c~ K + c~2 + c~ d ~ I
+
%1
a~ d ~ K + b~ d ~ J − c~ d ~ I + d ~2
+
%1
%1 := a~ + b~ + c~2 + d ~2
2 2
> simplify(%);
You can then extend the diff command so that you can differentiate
polynomials represented in that way. If you write a procedure with a
name of the form ‘diff/F ‘ then diff invokes it on any unevaluated
calls to F. Specifically, if you use diff to differentiate F (arguments )
with respect to x, then diff invokes ‘diff/F ‘ as follows.
‘diff/F ‘( arguments, x )
POLYNOM(x, 1, 2, 3, 4, 5, 6, 7, 8, 9)
the special symbols I, J, and K that are part of the definition of the
Hamiltonians.
> x &^ I &^ J;
(x &^ I) &^ J
x &^ K
You can extend the simplify command so that it applies the as-
sociative law to unevaluated products of Hamiltonians. If you write a
procedure with a name of the form ‘simplify/F ‘, then simplify in-
vokes it on any unevaluated function calls to F. Thus, you must write a
procedure ‘simplify/&^‘ that applies the associative law to Hamiltoni-
ans.
The procedure below uses the typematch command to determine if
its argument is of the form (a&^b)&^c and, if so, it picks out the a, b,
and c.
> s := x &^ y &^ z;
s := (x &^ y) &^ z
true
> a, b, c;
x, y, z
You can give the user details about the simplifications your procedure
makes through the userinfo command. The ‘simplify/&^‘ procedure
prints out an informative message if you set infolevel[simplify] or
infolevel[all] to at least 2.
108 • Chapter 3: Advanced Programming
> simplify(%);
−x
infolevel simplify := 5
> simplify(%);
simplify/&^: "applying the associative law"
simplify/&^: "applying the associative law"
The help pages for expand, series, and evalf provide details on how
you may extend those commands. See also section 8.4.
3.5 Conclusion • 109
You may employ any or all of the above methods, as you see fit.
Maple’s design affords you the opportunity to customize it to suit your
needs, allowing you great flexibility.
3.5 Conclusion
The topics in this chapter and chapters 1 and 2 form the building blocks
of the programming features in Maple. Although the topics in this chapter
are more specialized than those of earlier chapters, they are still very im-
portant and are among the most useful. In particular, the first two sections
which delve into the workings of procedures which return procedures and
local variables are fundamental as you move on to more advanced pro-
gramming. The later topics, including interactive input and extending
Maple, while not as fundamental, are also extremely beneficial.
The remaining chapters in this book fall into two categories. Chapters
4 and 5 present in a formal manner the structure of the Maple language
and the details of procedures. The other chapters address specific topics,
such as plotting, numerical programming, and the Maple debugger.
110 • Chapter 3: Advanced Programming
4 The Maple Language
Syntax The syntax defines what input constitutes a valid Maple expres-
sion, statement, or procedure. It answers such questions as:
These are all questions about language syntax. They are concerned
solely with the input of expressions and programs to Maple, not what
Maple does with them.
If the input is not syntactically correct, Maple reports a syntax error.
Consider some interactive examples.
Two adjacent minus signs are not valid.
> --1;
111
112 • Chapter 4: The Maple Language
but you must place at least one digit between the decimal point and the
exponent suffix.
> 2.e-3;
1 1x
x z,
2 2 z
4.1 Language Elements • 113
Syntax Errors in Files Maple reports syntax errors which occur when
reading in files and indicates the line number. Write the following program
in a file called integrand.
f:= proc(x)
t:= 1 - x^2
t*sqrt(t)
end proc:
Tokens
Maple’s language definition combines characters into tokens. Tokens
consist of keywords (reserved words), programming-language operators,
names, strings, natural integers, and punctuation marks.
Reserved Words Table 4.2 lists the reserved words in Maple. They have
special meanings, and thus you cannot use them as variables in programs.
Many other symbols in Maple have predefined meanings. For example,
mathematical functions such as sin and cos, Maple commands such as
expand and simplify, and type names such as integer and list. How-
ever, you can safely use these commands in Maple programs in certain
contexts. But the reserved words in table 4.2 have a special meanings,
and thus you cannot change them.
You should not confuse the double quote character, ", which delimits
a string, with the back quote character, ‘, which forms a symbol or the
single quote, ’, which delays evaluation. A string’s length has no practical
limit in Maple. On most Maple implementations, this means that a string
can contain more than half a million characters.
To make the double quote character appear in a string, type a back-
slash character and a double quote (") where you want the double quote
character to appear.
4.1 Language Elements • 117
> "a\"b";
“a\“b”
“a\\b”
“while”
The enclosing double quotes themselves do not form part of the string.
> length("abcde");
S := “This is a string”
> S[6..9];
“is a”
118 • Chapter 4: The Maple Language
> S[-6..-1];
“string”
“o”, “v”, “e”, “r”, “ ”, “a”, “ ”, “s”, “t”, “r”, “i”, “n”, “g”
3141592653589793238462643
−12345678901234567890
> +12345678901234567890;
12345678901234567890
Token Separators
You can separate tokens by using either white space or punctuation
marks. This tells Maple where one token ends and the next begins.
> a: = b;
ax + xy
The only instances in which white space can become part of a token
are names and strings, formed by enclosing a sequence of characters in
back quotes and double quotes, respectively. In these cases, the white
space characters are as significant as any other character.
On a line, unless you are in the middle of a string, Maple considers all
characters which follow a sharp character “#” to be part of a comment .
Since white space and newline characters are functionally the same,
you can continue statements from line to line.
> a:= 1 + x +
> x^2;
a := 1 + x + x2
The problem of continuation from one line to the next is less trivial
when long numbers or long strings are involved since these two classes of
tokens are not restricted to a few characters in length. The general mech-
anism in Maple to specify continuation of one line onto the next is as
follows: if the special character backslash, \, immediately precedes a new-
line character, then the parser ignores both the backslash and the newline.
If a backslash occurs in the middle of a line, Maple usually ignores it; see
?backslash for exceptions. You can use this to break up a long sequence
of digits into groups of smaller sequences, to enhance readability.
> "The input should be either a list of\
> variables or a set of variables";
G := .5772156649015328606065120900824024\
3104215933593992
Punctuation Marks Table 4.5 lists the punctuation marks .
; and : Use the semicolon and the colon to separate statements. The
distinction between these marks is that a colon during an interactive
session prevents the result of the statement from printing.
> f:=x->x^2;
f := x → x2
’sin’(π)
> %;
sin(π)
4.1 Language Elements • 121
> %;
() The left and right parentheses group terms in an expression and group
parameters in a function call.
> (a+b)*c; cos(Pi):
[] Use the left and right square brackets to form indexed (subscripted)
names and to select components from aggregate objects such as arrays,
rtables, tables, and lists. See section 4.4.
> a[1]: L:=[2,3,5,7]: L[3]:
[] and {} Use the left and right square brackets also to form lists, and
the left and right braces to form sets. See section 4.4.
> L:=[2,3,5,2]: S:={2,3,5,2}:
<> and | The left and right angle brackets in conjunction with the ver-
tical bar are used to construct rtable-based Matrices and Vectors.
> <<1,2,3> | <4,5,6>>:
4.3 Statements
There are eight types of statements in Maple. They are the
1. assignment statement
2. selection statement
3. repetition statement
4. read statement
5. save statement
6. empty statement
7. quit statement
8. expressions
Section 4.4 discusses expressions at length.
Throughout the remainder of this section, expr stands for any expres-
sion, statseq stands for a sequence of statements separated by semicolons.
4.3 Statements • 123
name := expr ;
name_1, ..., name_n := expr_1, ..., expr_n ;
This assigns, or sets, the value of the variable name to be the re-
sult of executing the expression expr. Multiple assignments can also be
performed.
2y − 1
> x := 3; x^2 + 1;
x := 3
10
a1 2
a1 := 3
9
fCu := 1.512
φ := t → t2
124 • Chapter 4: The Maple Language
Note that the following does not define a function; instead an entry
is created in the remember table for phi. See section 2.5.
> phi(t) := t^2;
φ(t) := t2
name [ sequence ]
Note that since an indexed name is itself a valid name, you can add
a succession of subscripts.
> A[1,2];
A1, 2
> A[i,3*j-1];
Ai, 3 j−1
The use of the indexed name A[1,2] does not imply that A is an array,
as in some languages. The statement
> a := A[1,2] + A[2,1] - A[1,1]*A[2,2];
name || natural
name || string
name || ( expression )
Since a name can appear on the left-hand side, Maple allows a suc-
cession of concatenations. Some examples of the use of the concatenation
operator for name formation are given in Table 4.6.
The concatenation operator is a binary operator which requires a
name or a string as its left operand. Although Maple usually evaluates
expressions from left to right, it evaluates concatenations from right to
left. Maple evaluates the right-most operand, then concatenates to the left
operand. If it evaluates the right operand to an integer, string or name,
then the result of the concatenation is a string or name (depending on the
type of the left-most operand). If it evaluates the right operand to some
other type of object, say a formula, then the result of the operation is an
unevaluated concatenated object.
> p || n;
pn
> "p" || n;
“pn”
> n := 4: p || n;
p4
> p || (2*n+1);
p9
126 • Chapter 4: The Maple Language
> p || (2*m+1);
p||(2 m + 1)
> x || (1..5);
x1 , x2 , x3 , x4 , x5
> X || ("a".."g");
Maple never fully evaluates the left-most object, but rather evaluates
it to a name. Concatenations can also be formed with the cat command.
cat( sequence )
Note that all the arguments of the cat command are evaluated nor-
mally (as for any other function call); therefore
> cat( "a", "b", "c" );
“abc”
is equivalent to
4.3 Statements • 127
> "" || a || b || c;
“abc”
However, now the areas of Maple that rely on the sine function will
not work properly.
> plot( 1, 0..2*Pi, coords=polar );
mysqr := x → x2
evaln(name )
a := a
> i := 4;
i := 4
a4 := a4
a4 := a4
In the special case where name is a string you may also unassign a vari-
able by delaying evaluation of the right-hand side with single quotes (’).
See section 4.4.
> a := ’a’;
a := a
4.3 Statements • 129
Related Functions You can use the assigned command to test if a name
has an assigned value.
> assigned(a);
false
true
> a;
a := 3
> b;
2
130 • Chapter 4: The Maple Language
−1 5
sol := {y = ,x= }
2 2
Maple assigns the variables x and y according to the set sol of equa-
tions.
> assign(sol);
> x;
5
2
> assigned(x);
true
> x := -2:
> if x<0 then 0 else 1 end if;
The expr must evaluate to one of the Boolean values true, false, or
FAIL; see section 4.4.
> if x then 0 else 1 end if;
Error, invalid boolean expression
−2
The selection statement may be nested, that is, the statement se-
quence in the then clause or else clause may be any statement, including
an if statement.
Compute the sign of a number.
> if x > 1 then 1
> else if x=0 then 0 else -1 end if
> end if;
−1
132 • Chapter 4: The Maple Language
The elif expr then statseq construct may appear more than
once.
Here you can implement the sign function by using an elif clause.
> x := -2;
x := −2
−1
In this form, you can view the selection statement as a case statement
with the optional else clause as the default case. For example, if you are
writing a program that accepts a parameter n with four possible values,
0, 1, 2, 3, then you might write
> n := 5;
n := 5
The for-from Loop A typical for-from loop has the following form.
> for i from 2 to 5 do i^2 end do;
9
16
25
You may omit any of the clauses for name , from expr , by expr , to
expr , or while expr . You may omit the sequence of statements statseq.
Except for the for clause, which must always appear first, the other
clauses may appear in any order. If you omit a clause, it has a default
value, which is shown in Table 4.7.
You could also write the previous example as the following.
134 • Chapter 4: The Maple Language
25
16
9
4
To find the first prime number greater than 107 you could write
> for i from 10^7 while not isprime(i) do end do;
10000019
Notice that the body of the loop is empty. Maple allows for the empty
statement. Try improving the program by considering only the odd num-
bers.
> for i from 10^7+1 by 2 while not isprime(i) do end do;
> i;
10000019
3
4
6
5
This is equivalent to
Such a loop statement will loop forever unless the break construct
(see section 4.3) or a return statement (see section 5.5) terminates it, or
if Maple encounters the quit statement, or if an error occurs.
The while Loop The while loop is a for loop with all its clauses omit-
ted except the while clause, that is
x := 256
x := 64
x := 16
x := 4
x := 1
The while loop works as follows. First, Maple evaluates the while
condition. If it evaluates to true, Maple executes the body of the loop.
This loop repeats until the while condition evaluates to false or FAIL.
Note that Maple evaluates the while condition before it executes the
body of the loop. An error occurs if the while condition does not evaluate
to one of true, false, or FAIL.
> x := 1/2:
> while x>1 do x := x/2 end do;
> x;
1
2
The for-in Loop Suppose you have a list of integers L and want to find
the integers in the list that are at most 7. You could write
> L := [7,2,5,8,7,9];
L := [7, 2, 5, 8, 7, 9]
> for i in L do
> if i <= 7 then print(i) end if;
> end do;
2
5
7
4.3 Statements • 137
The loop index (the name specified in the for clause of the statement)
takes on the operands of the first expr. See section 4.4 for a description
of the operands associated with each data type. You can test the value
of the index in the optional while clause, and, of course, the value of the
index is available when you execute the statseq. Note that the value of
the index variable name remains assigned at the end of the loop if the
object contains at least one operand.
The break and next Commands Within the Maple language reside
two additional loop control constructs: break and next. When Maple
evaluates the special name break, the result is to exit from the innermost
repetition statement within which it occurs. Execution then proceeds with
the first statement following this repetition statement.
> L := [2, 5, 7, 8, 9];
L := [2, 5, 7, 8, 9]
> for i in L do
> print(i);
> if i=7 then break end if;
> end do;
5
7
When Maple evaluates the special name next, it then proceeds im-
mediately to the next iteration. For example, suppose you want to skip
over the elements in a list that are equal to 7.
> L := [7,2,5,8,7,9];
138 • Chapter 4: The Maple Language
L := [7, 2, 5, 8, 7, 9]
> for i in L do
> if i=7 then next end if;
> print(i);
> end do;
5
8
9
Saving a Maple Session The save statement allows you to save the
values of a sequence of variables. It takes the general form
> restart:
> r0 := x^3:
> r1 := diff(r0,x):
> r2 := diff(r1,x):
The next statement saves r0, r1 and r2 in the ASCII file my_file:
> save r0, r1, r2, "my_file":
read filename ;
reads a file into the Maple session. The filename must evaluate to the
name of the file. The file must be either a Maple internal format file (a
.m file), or a text file.
If the file is a plain text file, then it must contain a sequence of valid
Maple statements, separated by semicolons or colons. The effect of reading
the file is identical to entering the same sequence of statements interac-
tively. The system displays the result of executing each statement that it
reads in from the file.
4.4 Expressions
Expressions are the fundamental entities in the Maple language. The var-
ious types of expressions include constants, names of variables and un-
knowns, formulæ, Boolean expressions, series, and other data structures.
Technically speaking, procedures are also valid expressions since you may
use them wherever an expression is legal. Chapter 5 describes them sep-
arately.
140 • Chapter 4: The Maple Language
3 * sin
x
sin ^ 2
x
cos 2
The first node of the expression tree labeled “+” is a sum. This in-
dicates the expression’s type. This expression has three branches corre-
sponding to the three terms in the sum. The nodes of each branch tell
you each term’s type in the sum. And so on down the tree until you get
to the leaves of the tree, which are names and integers in this example.
When programming with expressions, you need a way to determine
what type of expression you have, how many operands or branches an
expression has, and a way of selecting those operands. You also need a
way of building new expressions, for example, by replacing one operand
of an expression with a new value. Table 4.8 lists the primitive functions
for doing this.
> type(f, ‘+‘);
4.4 Expressions • 141
true
false
> nops(f);
sin(x)
sin(x) + 3
t := 2 cos(x)2 sin(x)
true
> nops(t);
true
true
true
op(i ..j, f )
op([i, j, k ], f )
op([i, j, k1 ..k2 ], f )
op(f )
sin
Integer
17
4
Next it evaluates the expression tree, then simplifies the result. Evalu-
ation means substituting values for variables and invoking any functions.
Here x evaluates to π/6. Hence, with these substitutions the expression
is as follows.
sin(π/6) + 2 cos(π/6)2 sin(π/6) + 3
Invoking the sin and cos functions, Maple obtains a new expression
tree, √
1/2 + 2 × (1/2 3)2 × 1/2 + 3.
144 • Chapter 4: The Maple Language
x := 1
x := 23
Integer
> op(x);
23
true
The type of a string is string. A string also has only one operand;
the string itself.
> s := "Is this a string?";
true
> nops(s);
> op(s);
x := A12, 3
true
> nops(x);
> op(x);
2, 3
> op(0,x);
A1
> y:=%;
y := A1
true
1, A, 1
c := p||(2 m + 1)
true
||
> nops(c);
> op(c);
p, 2 m + 1
integer /natural
Maple does arithmetic with fractions and integers exactly. Maple al-
ways immediately simplifies a fraction so that the denominator is positive,
and reduces the fraction to lowest terms by canceling out the greatest com-
mon divisor of the numerator and denominator.
> -30/12;
−5
2
2
x :=
3
> type(x,rational);
148 • Chapter 4: The Maple Language
true
2, 3
2, 3
natural .natural
natural .
.natural
natural exponent
natural .natural exponent
.natural exponent
where the exponent suffix is the letter “e” or “E” followed by a signed
integer with no spaces in the middle. A floating-point number is an
unsigned_float or a signed float (+unsigned_float or -unsigned_float
indicates a signed float).
> 1.2, -2., +.2;
1.2, −2., .2
Note that
4.4 Expressions • 149
> 1.e2;
−.8 6= .02
x := 231.3
> SFloatMantissa(x);
2313
> SFloatExponent(x);
−1
Float(m, e )
.3783783784
In general, you may use the evalf command to force the evaluation
of a non-floating-point expression to a floating-point expression where
possible.
> x := ln(2);
x := ln(2)
> evalf(x);
.6931471806
> evalf[15](x);
.693147180559945
I, I, I
x := 2 + 3 I
2, 3
x := 1 + I
y := 2.0 − 1. I
> x+y;
3.0 + 0. I
1
a−I
> evalc(%);
a I
+ 2
a2 +1 a +1
If you prefer to use another letter, say j, for the imaginary unit, use
the interface command as follows.
> interface(imaginaryunit = j);
> solve( {z^2=-1}, {z} );
{z = j}, {z = −j}
{z = I}, {z = −I}
Labels
A label in Maple has the form
%natural
that is, the unary operator % followed by a natural integer. The per-
centage sign takes on double duty, as a label and as the ditto operator,
which represents the result of the last one, two, or three commands.
A label is only valid after Maple’s pretty-printer introduces it. The
purpose is to allow the naming (labeling) of common subexpressions,
which serves to decrease the size of the printed output, making it more
comprehensible. After the pretty-printer introduces it, you may use a label
just like an assigned name in Maple.
> solve( {x^3-y^3=2, x^2+y^2=1}, {x, y} );
1
{y = %1, x = − %1 (−4 %13 − 3 − %12 + 6 %1 + 2 %14 )}
3
%1 := RootOf(3 _Z 2 + 3 − 3 _Z 4 + 2 _Z 6 + 4 _Z 3 )
After you obtain the above printout, the label %1 is an assigned name
and its value is the RootOf expression shown.
> %1;
RootOf(3 _Z 2 + 3 − 3 _Z 4 + 2 _Z 6 + 4 _Z 3 )
Two options are available for adjusting this facility. The option
interface(labelwidth=n )
specifies that Maple should not display expressions less than n characters
wide (approximately) as labels. The default is 20 characters. You may
turn off this facility entirely using
> interface(labelling=false);
154 • Chapter 4: The Maple Language
Sequences
A sequence is an expression of the form
a := A, B, C
> a,b,a;
A, B, C, b, A, B, C
s := x, y, z
The command
> nops(s);
Error, wrong number (or type) of parameters in function
nops
The elements of the above sequence are values, not equations, because
you did not use sets in the call to solve. Putting the solutions in a set
removes duplicates.
> s := {s};
√ √
s := {1, 2, − 2}
The seq Command The seq command creates sequences, a key tool for
programming. Section 4.5 describes it in detail. The syntax takes either
of the following general forms.
seq(f, i = a .. b )
seq(f, i = X )
1, 4, 9, 16
> seq(i,i="d".."g");
a := x3 + 3 x2 + 3 x + 1
> seq(i,i=a);
x3 , 3 x2 , 3 x, 1
3, 2, 1, 0
> seq(i,i="maple");
diff(ln(x), x $ n)
4.4 Expressions • 157
1 1 2 6 24
, − 2, 3, − 4, 5
x x x x x
f $ i = a .. b
f $ n
$ a .. b
f $ dummy = 1 .. n
x, x, x
dummy $ dummy = a .. b
If the values of a and b are numerical constants, this form is short for
creating a numerical sequence a, a+1, . . . , b (or up to the last value not
exceeding b).
> $0..4;
0, 1, 2, 3, 4
{ sequence }
[ sequence ]
{x, y1 , x1 }
> [y[1],x,x[1],y[1]];
[y1 , x, x1 , y1 ]
t := [1, x, y, x − y]
> op(2,t);
> t[2];
x
4.4 Expressions • 159
Maple’s ordering for sets is the order in which it stores the expressions
in memory. The user should not make assumptions about this ordering.
For example, in a different Maple session, the set above might appear in
the ordering {y[1], x, x[1]}. You can sort elements of a list by using
the sort command.
name [ sequence ]
A := [w, x, y, z]
> A[2];
L := [x, y, z, x, y, z]
> S := {s,s};
S := {z, x, y}
160 • Chapter 4: The Maple Language
> S[2];
> L[2..3];
[y, z]
> S[];
z, x, y
L := [t, u, v, w, x, y, z]
> L[-3];
> L[-3..-2];
[x, y]
You can also use select, remove, and selectremove to select ele-
ments from a list or set. See section 4.5.
Functions
A function call in Maple takes the form
f ( sequence )
sin(x)
4.4 Expressions • 161
> min(2,3,1);
> g();
g()
> a[1](x);
a1 (x)
f(1)
> s := 2,3;
s := 2, 3
> f(s,x);
f(2, 3, 1)
> f := g;
f := g
> f(s,x);
g(2, 3, 1)
162 • Chapter 4: The Maple Language
g := (a, b, c) → a + b + c
> f(s,x);
m := min(1, y, z)
> op(0,m);
min
> op(m);
1, y, z
> type(m,function);
true
> f := n!;
f := n!
true
factorial
> op(f);
4.4 Expressions • 163
• name
• procedure definition
• integer
• float
• function
t2 (1 − t2 )
instead of
> h := proc(t) t*(1-t) end proc;
> h(t^2);
t2 (1 − t2 )
(f (3) )(x)
> expand(%);
f(f(f(x)))
> f(g)(0);
f(g)(0)
> D(cos)(0);
• The operands of the power xa are the base x and the exponent a.
> whattype(x-y);
> whattype(x^y);
3 1 3
5, , .1714285714, + I
2 4 4
166 • Chapter 4: The Maple Language
3.737192819, 0. + 4.810477381 I
nb = nq+r/d → nq × nr/d .
0, 2 x, x, x2 , 1, x, 1, x
undefined
> infinity/infinity;
undefined
> 0/0;
Error, numeric exception: division by zero
4.4 Expressions • 167
ax + bx → (a + b)x
xa × xb → xa+b
a(x + y) → ax + ay
The first two simplifications mean that Maple adds like terms in poly-
nomials automatically. The third means that Maple distributes numerical
constants (integers, fractions, and floats) over sums, but does not do the
same for non-numerical constants.
> 2*x + 3*x, x*y*x^2, 2*(x + y), z*(x + y);
5 x, x3 y, 2 x + 2 y, z (x + y)
> 2^(1/2)+3^(1/2)+2^(1/2)*3^(1/2);
√ √ √ √
2+ 3+ 2 3
√ √ √
The reason is that combining 2 3 to 6 would introduce a third
unique square root. Calculating with roots is, in general, difficult and
expensive, so Maple is careful not to create new roots. You may use the
combine command to combine roots if you desire.
Non-Commutative Multiplication
The non-commutative multiplication operator &* acts as an inert operator
(for example, the neutral operators described in section 4.4), but the
parser understands its binding strength to be equivalent to the binding
strength of * and /.
The evalm command in the Maple Library interprets &* as the table-
based matrix multiplication operator.1 The evalm command also under-
stands the form &*() as a generic matrix identity.
> with(LinearAlgebra):
> A := matrix(2,2,[a,b,c,d]);
´ µ
a b
A :=
c d
> B := matrix(2,2,[e,f,g,h]);
´ µ
e f
B :=
g h
1
The Maple library interprets . (dot) as the rtable-based Matrix multiplication
operator.
4.4 Expressions • 169
f(g(x))
> (sin@cos)(Pi/2);
(f (2) )(x)
> expand(%);
f(f(x))
> (D@@n)(f);
(D(n) )(f )
1
sin(x)2 , (sin(2) )(x), , arcsin(x)
sin(x)
170 • Chapter 4: The Maple Language
1, 120
3.323350970
720
4.4 Expressions • 171
Notice that you need back quotes around mod since it is a reserved word.
You may invoke the commands modp and mods directly if you desire.
For example
> modp(9,5), mods(9,5);
4, −1
The mod operator understands the inert operator &^ for powering.
That is, i&^j mod m calculates ij mod m. Instead of first computing the
integer ij , which may be too large to compute, and then reducing modulo
m, Maple computes the power using binary powering with remainder.
> 2^(2^100) mod 5;
Error, numeric exception: overflow
The mod operator knows many functions for polynomial and matrix arith-
metic over finite rings and fields. For example, Factor for polynomial
factorization, and Nullspace for matrix null-space.
> 1/2 mod 5;
4 x2 + 3 x + 3
4 (x + 4) (x + 3)
Do not confuse, for example, the commands factor and Factor. The
former evaluates immediately; the latter is an inert command which Maple
does not evaluate until you make the call to mod.
The mod command also knows how to compute over a Galois field
GF (pk ), that is, the finite field with pk elements. See the ?mod online
documentation for a list of the commands that mod knows, and for further
examples.
• Any Maple name not requiring back quotes, preceded by the & char-
acter; for example, &wedge.
The following characters cannot appear in an &-name after the initial &:
& | ( ) [ ] { } ; : ’ ‘ # \ %
as well as newline and blank characters.
Maple singles out the particular neutral operator symbol &* as a spe-
cial token representing the non-commutative multiplication operator. The
4.4 Expressions • 173
special property of &* is that the parser understands its binding strength
to be equivalent to Maple’s other multiplication operators. All other
neutral operators have binding strength greater than the standard al-
gebraic operators. See ?precedence for the order of precedence of all
programming-language operators. See section 4.4 which describes where
to use &* in Maple.
You can use neutral operators as unary prefix operators, infix binary
operators, or function calls. In any of these cases, they generate function
calls with the name of the function being that of the neutral operator.
(In the usual pretty-printing mode, these particular function calls are
printed in binary operator format when exactly two operands exist and in
unary operator format when exactly one operand exists, but the internal
representation is an unevaluated function.) For example,
> a &~ b &~ c;
(a &~ b) &~ c
> op(%);
a &~ b, c
> op(0,%%);
&~
e := x + 3 y = z
> 2*e;
2x + 6y = 2z
> lhs(e);
x + 3y
The type command also understands the types <>, <, and <=. Maple
automatically converts inequalities involving > or >= to < and <=, respec-
tively. All the relational types have two operands.
> e := a > b;
e := b < a
> op(e);
b, a
4.4 Expressions • 175
“less”
In the case of the relations = and <>, the operands may be arbitrary
expressions (algebraic or non-algebraic). This equality test for expressions
deals only with syntactic equality of the Maple representations of the
expressions, which is not the same as mathematical equivalence.
> evalb( x + y = y + x );
true
false
true
true
true
false
The precedence of the logical operators and, or, and not is analo-
gous to multiplication, addition, and exponentiation, respectively. Here
no parentheses are necessary.
> (a and b) or ((not c) and d);
The type names for the logical operators and, or, and not are and,
or, and not, respectively. The first two have two operands, the latter one
operand.
> b := x and y or z;
b := x and y or z
> whattype(b);
or
> op(b);
x and y, z
a and b and c
If the result of evaluating a is false, you know that the result of the
whole Boolean expression will be false, regardless of what b and c evaluate
to. These evaluation rules are commonly known as McCarthy evaluation
rules. They are quite crucial for programming. Consider the following
statement
If Maple always evaluated both operands of the and clause, then when
x is 0, evaluation would result in a division by zero error. The advantage
of the above code is that Maple will attempt to check the second condition
only when x 6= 0.
true
> is(a-1,positive);
FAIL
Depending on the desired action in the case where condition has the value
FAIL, either the first or the second of these two if statements may be
correct for a particular context.
Tables
The table data type in Maple is a special data type for representing data
in tables. Create a table either explicitly via the table command or im-
plicitly by assignment to an indexed name. For example, the statements
> a := table([(Cu,1) = 64]);
a := table([(Cu, 1) = 64])
aCu, 1 := 64
have the same effect. They both create a table object with one compo-
nent. The purpose of a table is to allow fast access to data with
> a[Cu,1];
64
The type of a table object is table. The first operand is the indexing
function. The second operand is a list of the components. Note that tables
(and arrays, which are a special case of a table) have special evaluation
rules; in order to access the table (or array) object, you must first apply
the eval command.
> op(0,eval(a));
table
[(Cu, 1) = 64]
> A[1,2] := 4;
A1, 2 := 4
> print(A);
180 • Chapter 4: The Maple Language
´ µ
3 4
4 A2, 2
The ranges 1..2,1..2 specify two dimensions and bounds for the
integers. You may include entries in the array command or insert them
explicitly as shown. You may leave entries unassigned. In this example,
the (2, 2) entry is unassigned.
> op(0,eval(A));
array
As for tables, the first operand is the indexing function (if any).
> op(1,eval(A));
symmetric
1..2, 1..2
[(1, 1) = 3, (1, 2) = 4]
The example above displays only two entries in the array A since
Maple knows the (2, 1) entry implicitly through the indexing function.
Series
The series data type in Maple represents an expression as a truncated
power series with respect to a specified indeterminate, expanded about a
particular point. Although you cannot input a series directly into Maple
as an expression, you can create a series data type with the taylor or
series commands which have the following syntax
4.4 Expressions • 181
taylor( f, x =a, n )
taylor( f, x )
series( f, x =a, n )
series( f, x )
1 2 1 3
s := 1 + x + x + x + O(x4 )
2 6
true
The odd (first, third, . . . ) operands are the coefficients of the series
and the even operands are the corresponding integer exponents.
> op(s);
1 1
1, 0, 1, 1, , 2, , 3, O(1), 4
2 6
If Maple knows that the series is exact then it will not contain an order
term. An example of this occurs when you apply the series command
to a polynomial whose degree is less than the truncation degree for the
series. A very special case is the zero series, which Maple immediately
simplifies to the integer zero.
The series data structure represents generalized power series, which
include Laurent series with finite principal parts. More generally, Maple
allows the series coefficients to depend on x provided their growth is less
than polynomial in x. O(1) represents such a coefficient, rather than an
arbitrary constant. An example of a non-standard generalized power series
is
> series( x^x, x=0, 3 );
1
1 + ln(x) x + ln(x)2 x2 + O(x3 )
2
Maple can compute more general series expansions than the series
data type supports. The Puisseux series is such an example. In these
cases, the series command does not return a series data type, it returns
a general algebraic expression.
> s := series( sqrt(sin(x)), x );
√ 1 (5/2) 1
s := x− x + x(9/2) + O(x(13/2) )
12 1440
false
true
Ranges
You often need to specify a range of numbers. For example, when you
want to integrate a function over a range. In Maple, use the ellipsis oper-
ation to form ranges.
4.4 Expressions • 183
expression_1 .. expression_2
Specify the operator “..” using two consecutive periods. The ellipsis
operator simply acts as a place holder in the same manner as using the
relational operators in an algebraic context, primarily as a notational
tool. A range has type “..” or range. A range has two operands, the
left-limit and the right-limit, which you can access with the lhs and rhs
commands.
> r:=3..7;
r := 3..7
> op(0,r);
..
> lhs(r);
You can use the range construct, with Maple’s built-in command op,
to extract a sequence of operands from an expression. The notation
op(a..b, c)
is equivalent to
184 • Chapter 4: The Maple Language
seq(op(i,c),i=a..b)
For example,
> a := [ u, v, w, x, y, z ];
a := [u, v, w, x, y, z]
> op(2..5,a);
v, w, x, y
You can also use the range construct in combination with the con-
catenation operator to form a sequence as follows.
> x || (1..5);
x1 , x2 , x3 , x4 , x5
Unevaluated Expressions
Maple normally evaluates all expressions, but sometimes you need to tell
Maple to delay evaluating an expression.
An expression enclosed in a pair of single quotes
’expression ’
a := 1
x := 1 + b
a := 1
x := a + b
x := 5
which assigns the value 5 to the name x even though this expression con-
tains quotes. The evaluator simply strips off the quotes, but the simplifier
transforms the expression 2 + 3 into the constant 5.
The result of evaluating an expression with two levels of quotes is an
expression of type uneval. This expression has only one operand, namely
the expression inside the outermost pair of quotes.
> op(’’x - 2’’);
x−2
uneval
x := x
’f’(sequence )
’sin’(π)
> %;
sin(π)
> %;
You will find this facility useful when writing procedures which im-
plement simplification rules. See section 3.4.
Constants
Maple has a general concept of symbolic constants , and initially assigns
the global variable constants the following expression sequence of names
> constants;
false
> type(g,constant);
true
Structured Types
Sometimes a simple type check does not give enough information. For
example, the command
> type( x^2, ‘^‘ );
true
tells you that x^2 is an exponentiation but it does not tell you whether
or not the exponent is, say, an integer. In such cases, you need structured
types .
> type( x^2, name^integer );
true
false
false
true
true
true
true
true
true
e2
false
4.4 Expressions • 189
true
You should use single quotes (’) around Maple commands in type
expressions to delay evaluation.
> type( int(f(x), x), int(anything, anything) );
Error, testing against an invalid type
1
anything 2
2
which is not a valid type. If you put single quotes around the int com-
mand, the type checking works as intended.
> type( int(f(x), x), ’int’(anything, anything) );
true
true
true
true
190 • Chapter 4: The Maple Language
true
You can also test the number (and types) of arguments. The type
anyfunc(t1, ..., tn ) matches any function with n arguments of the
listed types.
> type( f(1,x), anyfunc(integer, name) );
true
false
Another useful variation is to use the And, Or, and Not type construc-
tors to create Boolean combinations of types.
> type(Pi, ’And( constant, numeric)’);
false
true
2. zip
map( f, x )
For example, if you have a list of integers, create a list of their absolute
values and of their squares by using the map command.
> L := [ -1, 2, -3, -4, 5 ];
> map(abs,L);
[1, 2, 3, 4, 5]
> map(x->x^2,L);
2
Exception: for an rtable, table or array, Maple applies the function to the entries
of the table or array, and not to the operands or indices.
192 • Chapter 4: The Maple Language
L := [1, x, x2 , x3 , x4 , x5 ]
[2, x2 + 1, x4 + 1, x6 + 1, x8 + 1, x10 + 1]
select( f, x )
remove( f, x )
selectremove( f, x )
X := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
> select(isprime,X);
4.5 Useful Looping Constructs • 193
[2, 3, 5, 7]
> remove(isprime,X);
[1, 4, 6, 8, 9, 10]
> selectremove(isprime,X);
{sin(1), e(2 x) }
X := 2 x y 2 − 3 y 4 z + 3 z w + 2 y 3 − z 2 w y
−3 y 4 z + 3 z w − z 2 w y
194 • Chapter 4: The Maple Language
2 x y2 + 3 z w + 2 y3
zip(f, u, v )
zip(f, u, v, d )
[a1 , b2 , c3 , d4 , e5 , f6 ]
If the lists or vectors are not the same length, the length of the result
depends on whether you provide d. If you do not specify d, the length will
be the length of the smaller of u and v.
> zip( (x,y)->x+y, [a,b,c,d,e,f], [1,2,3] );
[a + 1, b + 2, c + 3]
If you do specify d, the length of the result of the zip command will be
the length of the longer list (or vector) and Maple uses d for the missing
value(s).
> zip( (x,y)->x+y, [a,b,c,d,e,f], [1,2,3], xi );
[a + 1, b + 2, c + 3, d + ξ, e + ξ, f + ξ]
Note that Maple does not pass the extra argument, xi, to the function
f as it does with the map command.
4.5 Useful Looping Constructs • 195
seq(f, i = a ..b )
add(f, i = a ..b )
mul(f, i = a ..b )
1, 4, 9, 16
> mul(i^2,i=1..4);
576
x1 + x2 + x3 + x4
4.123, 5.123
You can also use the commands seq, add, and mul with the following
syntax.
196 • Chapter 4: The Maple Language
seq(f, i = X )
add(f, i = X )
mul(f, i = X )
a := x3 + 3 x2 + 3 x + 1
3, 2, 1, 0
> a := [23,-42,11,-3];
> mul(abs(i),i=a);
31878
> add(i^2,i=a);
2423
4.6 Substitution • 197
seq, add, and mul Versus $, sum, and product Note that the dollar
operator, $, and the sum and product commands are very similar to the
seq, mul, and add commands. However, they differ in an important way.
The index variable i and the end points a and b do not need to be integers.
For example
> x[k] $ k=1..n;
xk $ (k = 1..n)
4.6 Substitution
The subs command does a syntactic substitution. It replaces subex-
pressions in an expression with a new value; the subexpressions must be
operands in the sense of the op command.
> expr := x^3 + 3*x + 1;
expr := x3 + 3 x + 1
y3 + 3 y + 1
15
198 • Chapter 4: The Maple Language
subs( s, expr )
f := x y 2
y z2
Thus, subs substitutes according to the given equations from left to right.
Notice the difference between the previous example and the following one.
> subs( y=z, x=y, z=w, f );
y w2
sin(0)
4.6 Substitution • 199
abc
dc
You cannot always do this, and you may find that it does not always
produce the results you expect. The algsubs routine provides a more
powerful substitution facility.
> algsubs(a*b=d, a*b*c);
dc
Note also that operands of a rational power xn/d are x and n/d.
Although in the following example
> subs( x^(1/2)=y, a/x^(1/2) );
a
√
x
√
it looks as though the output has a x in it, the operands of this ex-
pression are a and x−1/2 . Think of the division as a negative power in
200 • Chapter 4: The Maple Language
a product, that is, a × x−1/2 . Because the operands of x−1/2 are x and
−1/2, subs does not see x1/2 in x−1/2 . The solution is to substitute for
the negative power x−1/2 .
> subs( x^(-1/2)=1/y, a/x^(1/2) );
a
y
The reader should refer to the online help information under ?algsubs
for more details. Note that the algsubs command, as powerful as it is, is
also much more computationally expensive than the subs command.
4.7 Conclusion
This chapter discusses the elements of Maple’s language. Maple breaks
your input into its smallest meaningful parts, called tokens. Its language
statements include assignments, conditional, looping, and reading from
and saving to files. Many types of expressions exist within Maple, and
using its expression trees tells you of the type and operands in an expres-
sion. You have seen the efficient looping constructs map, zip, and seq,
and how to make substitutions.
5 Procedures
proc( P )
local L ;
global G ;
options O ;
description D ;
B
end proc
201
202 • Chapter 5: Procedures
> proc(x,y)
> x^2 + y^2
> end proc;
F ( A )
13
Mapping Notation
You can also define simple one-line procedures by using an alternate syn-
tax borrowed from algebra.
( P ) -> B
F := (x, y) → x2 + y 2
If your procedure involves only one parameter, then you may omit the
parentheses around the formal parameter.
5.1 Procedure Definitions • 203
G := proc(n)
option operator , arrow ;
if n < 0 then 0 else 1 end if
end proc
> G(9), G(-2);
1, 0
The intended use for the arrow notation is solely for simple one-line
function definitions. It does not provide a mechanism for specifying local
or global variables, or options.
x → x2
t2
u2 + v 2
[1, 4, 9, 16]
You can add procedures together, or, if appropriate, you can process
them by using commands, such as the differential operator , D.
204 • Chapter 5: Procedures
x → 2x
1
F := exp + 2 (a → )
a
Procedure Simplification
When you create a procedure, Maple does not evaluate the procedure but
it does simplify the body of the procedure.
> proc(x) local t;
> t := x*x*x + 0*2;
> if true then sqrt(t); else t^2 end if;
> end proc;
3
4
F ( ArgumentSequence )
proc( FormalParameters )
body
end proc
Maple then executes the statements in the body of the procedure, sub-
stituting the actual parameters for the formal parameters.
Consider the following example.
> s := a,b: t := c:
> F := proc(x,y,z) x + y + z end proc:
> F(s,t);
a+b+c
> f(1,2);
Error, (in f) f uses a 3rd argument, z, which is
missing
> f(2,1);
2
206 • Chapter 5: Procedures
Declared Parameters
You may write procedures that only work for certain types of input. Use
declared formal parameters so that when you use the procedure with the
wrong types of input Maple issues an informative standard error message.
A type declaration has the following syntax.
parameter :: type
You can also use declared parameters with the operator option.
> G := (n::even) -> n! * (n/2)!;
1
G := n::even → n! ( n)!
2
> G(6);
4320
> G(5);
Error, invalid input: G expects its 1st argument, n, to
be of type even, but received 5
If you do not declare the type of a parameter, it can have any type.
Thus, proc(x ) is equivalent to proc(x ::anything). If that is what you
intend, you should use the latter form to inform other users that you
intend your procedure to work for any input.
5.2 Parameter Passing • 207
> f(a,b,c);
[a, b, c]
> f(c);
[c]
> f();
[]
The ith argument is simply args[i]. Thus, the following two pro-
cedures are equivalent, provided you call them with at least two actual
parameters of type numeric.
> MAX := proc(x::numeric,y::numeric)
> if x > y then x else y end if;
> end proc;
MAX := proc()
if args2 < args1 then args1 else args2 end if
end proc
208 • Chapter 5: Procedures
2
3
> cat(C,N);
> end proc:
> N := 0;
N := 0
C1 sin(x) + C2 cos(x)
f := x + y
> x := z^2/ y;
z2
x :=
y
> z := y^3 + 3;
z := y 3 + 3
(y 3 + 3)2
+y
y
You can control the actual level of evaluation by using eval . Using
the following sequence of commands, you can evaluate to one level, two
levels, and three levels.
> eval(f,1);
x+y
> eval(f,2);
z2
+y
y
> eval(f,3);
(y 3 + 3)2
+y
y
1
Such a concept of evaluation does not occur in traditional programming languages.
However, here, you may assign to a variable a formula involving other variables, which
in turn you may assign values and so on.
212 • Chapter 5: Procedures
1 1√ 1 √ p √
[1, − + 5 + I 2 5 + 5,
4 4 4
1 1√ 1 √ p √ 1 1√ 1 √ p √
− − 5 + I 2 5 − 5, − − 5 − I 2 5 − 5,
4 4 4 4 4 4
1 1√ 1 √ p √
− + 5 − I 2 5 + 5]
4 4 4
You may use any symbol as an option but the following options have
special meanings.
The remember and system Options When you invoke a procedure with
the remember option, Maple stores the result of the invocation in the
remember table associated with the procedure. Whenever you invoke
the procedure, Maple checks whether you have previously called the pro-
cedure with the same parameters. If so, Maple retrieves the previously
calculated result from the remember table rather than executing the pro-
cedure again.
> fib := proc(n::nonnegint)
> option remember;
> fib(n-1) + fib(n-2);
> end proc;
5.4 Procedure Options and the Description Field • 213
fib := proc(n::nonnegint )
option remember ;
fib(n − 1) + fib(n − 2)
end proc
You may place entries in the remember table of a procedure by direct
assignment; this method also works for procedures without the remember
option.
> fib(0) := 0;
fib(0) := 0
> fib(1) := 1;
fib(1) := 1
table([0 = 0, 1 = 1])
Since fib has the remember option, invoking it places new values in
its remember table.
> fib(9);
34
table([0 = 0, 1 = 1, 2 = 1, 3 = 2, 4 = 3, 5 = 5, 6 = 8, 7 = 13,
8 = 21,
9 = 34
])
The use of remember tables can drastically improve the efficiency of
recursively defined procedures.
The system option allows Maple to remove entries from a proce-
dure’s remember table. Such selective amnesia occurs during garbage
collection, an important part of Maple’s memory management scheme.
See section 2.5 for more details and examples of remember tables.
214 • Chapter 5: Procedures
The operator and arrow Options The operator option allows Maple
to make additional simplifications to the procedure, and the arrow option
indicates that the pretty-printer should display the procedure by using the
arrow notation.
> proc(x)
> option operator, arrow;
> x^2;
> end proc;
x → x2
The Copyright Option Maple considers any option that begins with
the word Copyright to be a Copyright option. Maple does not print
the body of a procedure with a Copyright option unless the interface
variable verboseproc is at least 2.
> f := proc(expr::anything, x::name)
> option ‘Copyright (c) 1684 by G. W. Leibnitz. All rights reserved‘;
> Diff(expr, x);
> end proc;
The builtin Option Maple has two main classes of procedures: those
which are part of the Maple kernel, and those which the Maple language
itself defines. The builtin option indicates the kernel procedures. You
can see this when you fully evaluate a built-in procedure.
> eval(type);
description symbol ;
proc(x)
description ‘computes the square of x ‘;
x2
end proc
Also, Maple prints the description field even if it does not print the
body of a procedure due to a Copyright option.
> f := proc(x)
> option ‘Copyrighted ?‘;
> description ‘computes the square of x‘;
> x^2; # compute x^2
> end proc:
> print(f);
proc(x)
description ‘computes the square of x ‘
...
end proc
false
If you call MEMBER with three arguments, then the type declaration
p::evaln ensures that Maple evaluates the third actual parameter to a
name2 rather than by using full evaluation.
> q := 78;
q := 78
true
> q;
Maple evaluates parameters only once. This means that you cannot
use formal parameters freely like local variables within a procedure body.
Once you have made an assignment to a parameter you should not
2
If the third parameter has not been declared as evaln, then you should enclose the
name q in single quotes (’q’) to ensure that the name and not the value of q is passed
to the procedure.
5.5 The Value Returned by a Procedure • 217
refer to that parameter again. The only legitimate purpose for assigning
to a parameter is so that on return from the procedure the corresponding
actual parameter has an assigned value. The following procedure assigns
the value −13 to its parameter, then returns the name of that parameter.
> f := proc(x::evaln)
> x := -13;
> x;
> end proc:
> f(q);
−13
−m < 0
m+1
m+2
true
> m;
Explicit Returns
An explicit return occurs when you invoke the return statement, which
has the following syntax.
5.5 The Value Returned by a Procedure • 219
return sequence
0, 0, 0
> GCD(12,8);
4, 3, 2
Error Returns
An error return occurs when you raise an exception by invoking the
error statement, which has the following syntax:
220 • Chapter 5: Procedures
error msgString
error msgString, msgParams
• The name of the procedure in which the exception was raised, or the
constant 0 if the exception was raised at the top-level.
• The msgString.
Trapping Exceptions
You can trap exceptions using the try statement. The syntax for the try
statement is:
222 • Chapter 5: Procedures
try tryStatSeq
catch catchStrings : catchStatSeq
end try
try tryStatSeq
catch catchStrings : catchStatSeq
finally finalStatSeq
end try
• Neither the exception object nor the catchStrings are evaluated (the
exception object will already have been evaluated by the error state-
ment that produced it).
• The "result" of a try statement (i.e., the value that % would return
if evaluated immediately after execution of the try statement) is the
result of the last statement executed within the try statement.
A given catchString (or a catch clause without one) can appear only
once in a try...end construct.
A very useful application of the try and error statements is to abort
an expensive computation as quickly and cleanly as possible. For example,
suppose you are trying to compute an integral by using one of several
methods, and in the middle of the first method, you determine that it
will not succeed. You would like to abort that method and go on to try
224 • Chapter 5: Procedures
another method. The code that tries the different methods might look
like this:
> try
> result := MethodA(f,x)
> catch "FAIL":
> result := MethodB(f,x)
> end try;
MethodA can abort its computation at any time by executing the state-
ment error "FAIL". The catch clause will catch that exception, and pro-
ceed to try MethodB. If any other error occurs during the execution of
MethodA, or if an error occurs during the execution of MethodB, it will not
be caught.
Another useful application of the try statement is to make sure certain
resources are freed when you are done with them, regardless of whether
or not anything went wrong while you were using them. For example, you
may wish to use the facilities of the I/O library (see Chapter 1) to read
the lines of a file and process them in some way:
> f := fopen("myfile",TEXT,READ):
> try
> line := readline(f);
> while line < 0 do
> ProcessContentsOfLine(line);
> line := readline(f)
> end do
> finally
> fclose(f)
> end try;
Returning Unevaluated
Maple often uses a particular form of return as a fail return, in the sense
that it cannot carry out the computation and so returns the unevaluated
function invocation as the result. The procedure MAX, below, calculates
the maximum of two numbers, x and y.
> MAX := proc(x,y) if x>y then x else y end if end proc:
3.2
The error occurs because Maple evaluates MAX(x, 1/x) before invok-
ing the plot command.
The solution is to make MAX return unevaluated when its parame-
ters, x and y, are not numeric. That is, in such cases MAX should return
’MAX’(x,y).
> MAX := proc(x, y)
> if type(x, numeric) and type(y, numeric) then
> if x>y then x else y end if;
> else
> ’MAX’(x,y);
> end if;
> end proc:
226 • Chapter 5: Procedures
The new version of MAX handles both numeric and non-numeric input.
> MAX(3.2, 2);
3.2
MAX(x, 2 y)
1.8
1.6
1.4
1.2
1
0.6 0.8 1 1.2 1.4 1.6 1.8 2
x
You can improve MAX so that it can find the maximum of any number
of arguments. Inside a procedure, args is the sequence of actual parame-
ters, nargs is the number of actual parameters, and procname is the name
of the procedure.
> MAX := proc()
> local m, i;
> m := -infinity;
> for i in (args) do
> if not type(i, numeric) then
> return ’procname’(args);
> end if;
> if i>m then m := i end if;
> end do;
> m;
> end proc:
> MAX(3,1,4);
> MAX(3,x,1,4);
5.6 The Procedure Object • 227
MAX(3, x, 1, 4)
The sin function and the int integration command follow the same
model as the MAX procedure above. If Maple can compute the result, it
returns it; otherwise, sin and int return unevaluated.
Exercise
1. Improve the MAX procedure above so that MAX(3,x,1,4) returns
MAX(x,4); that is, the procedure returns the maximum numerical
value along with all non-numerical values.
f := g
> g := h;
g := h
> h := x^2;
h := x2
Now f evaluates to x2 .
> f;
x2
228 • Chapter 5: Procedures
F := G
> G := H;
G := H
true
> type(F,procedure);
5.6 The Procedure Object • 229
true
> type(F,name(procedure));
true
> type(eval(F),procedure);
true
Thus, you can use the following test to ensure that F is the name of
a procedure.
> if type(F, name(procedure)) then ... end if
f(t, 3) := 12
230 • Chapter 5: Procedures
proc(x::name, n::posint )
description “a summation”
...
end proc
The formal parameters:
> op(1, eval(f));
x::name, n::posint
The options:
> op(3, eval(f));
Copyright
table([(t, 3) = 12])
The description:
> op(5, eval(f));
5.6 The Procedure Object • 231
“a summation”
Some Maple users prefer to write Maple procedures with their favorite
text editor. You can also use the read command to read in data from such
files. Maple executes each line in the file as if you had typed it directly
into your session.
If you make a number of related procedures, you may want to save
them as a Maple package. Making a package allows you to load the pro-
cedures using the with command. See section 6.3.
232 • Chapter 5: Procedures
5.7 Explorations
The purpose of the exercises in this section is to deepen your understand-
ing of how Maple procedures work. In some cases you may wish to study
the on-line help pages for the various Maple commands that you will need.
Exercises √
1. Implement the function f (x) = ( 1 − x2 )3 − 1, first as a procedure,
then by using the mapping notation. Compute f (1/2) and f (0.5) and
comment on the different results. Use the D operator to compute f 0 ,
and then compute f 0 (0).
What is wrong with this procedure? You may want to use the Maple
debugger to isolate the error. See chapter 7.
4. ab/g gives the least common multiple of two integers, a and b, where
g is the greatest common divisor of a and b. For example, the least
common multiple of 4 and 6 is 12. Write a Maple procedure, LCM,
which takes as input n > 0 integers a1 , a2 , . . . , an and computes their
least common multiple. By convention, the least common multiple of
zero and any other number is zero.
The following procedure computes Tn (x) in a loop for any given inte-
ger n.
5.8 Conclusion • 233
> 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:
The procedure has several errors. Which variables should have been
declared local? What happens if n is zero or negative? Identify and
correct all errors, using the Maple debugger where appropriate. Mod-
ify the procedure so that it returns unevaluated if n is a symbolic
value.
5.8 Conclusion
In this chapter, you have seen the details of the proc command. You
have learned the finer points of the options at your disposal when defin-
ing procedures. You have learned about functional operators, unnamed
procedures, and procedure simplification.
In addition, you have reviewed Maple’s evaluation rules which chap-
ter 2 introduced. For example, Maple generally evaluates local variables
to one level and global variables fully. Maple evaluates the arguments to a
procedure at the time you invoke it. How they are evaluated depends upon
the environment in which the call occurs, and in some cases, the types
specified within the procedure definition. Once evaluated, Maple substi-
tutes the values into the procedure and then executes it. Maple does no
further evaluation on the values which it substituted, unless you specifi-
cally use a command such as eval. This rule makes it impractical to use
parameters to store temporary results, as you would use local variables.
This chapter extended the discussion of type declarations, which were
introduced in chapters 1 and 2. Type declarations are particularly useful
as a means of stating the intended purpose of your procedures and as a
convenient means of supplying error messages to any user who might call
them with inappropriate values.
This chapter concludes the formal review of the Maple language which
began in chapter 4. The remaining chapters deal with specific areas of
Maple programming. For example, chapter 7 discusses the Maple debug-
ger, chapter 8 introduces you to the details of numerical programming
234 • Chapter 5: Procedures
1. Encapsulation
2. Packages
3. Modeling Objects
4. Generic Programming
235
236 • Chapter 6: Programming with Modules
TempGenerator := module()
local count ;
export gentemp;
description “generator for temporary symbols”;
end module
The module definition that appears above resembles a procedure defi-
nition. The main differences visible here are the use of the keyword mod-
ule instead of proc (and the corresponding terminator), and the export
declaration following the description string.
We could do something quite similar using only procedures.
> TempGeneratorProc := proc()
> description "generator for temporary symbols";
> local count, gentemp;
• 237
> count := 0;
> gentemp := proc()
> count := 1 + count;
> ‘tools/gensym‘( T || count )
> end proc;
> eval( gentemp, 1 )
> end proc:
We can assign the procedure returned by TempGeneratorProc, and then
use it to generate temporary symbols.
> f := TempGeneratorProc();
f := proc()
count := 1 + count ; ‘tools/gensym‘(T ||count )
end proc
> f();
T1
> f();
T2
T1
238 • Chapter 6: Programming with Modules
T2
T3
T4
module()
local L ;
export E ;
global G ;
options O ;
description D ;
B
end module
Module Parameters
Module definitions begin with the Maple keyword module, followed by an
(empty) pair of parentheses. This is similar to the parentheses that follow
the proc keyword in a procedure definition. Unlike procedures, however,
module definitions do not have explicit parameters. This is because, unlike
procedures (which result from the evaluation of procedure definitions),
modules are not “called” (or “invoked”) with arguments.
Instead, every module definition has an implicit parameter called
thismodule. Within the body of a module definition, this special name
evaluates to the module in which it occurs. This allows you to refer to
a module within its own definition (before the result of evaluating it has
been assigned to any name).
You have seen implicit parameters before; all procedure definitions
may reference the implicit parameters procname, args, and nargs. The
args and nargs currently have no meaning for modules, and the difference
between thismodule and procname is that procname evaluates to a
name, while thismodule evaluates to the module expression itself. This
is because the “invocation” phase of evaluating a module definition is
part of its normal evaluation, and it occurs immediately. Procedures, on
the other hand, are not invoked until called with arguments. Normally,
at least one name for a procedure is known by the time it is called; this
is not the case for modules.
Named Modules
An optional symbol may appear after the module keyword in a module
definition. Modules created with this variant on the syntax are called
named modules. Semantically, named modules are nearly identical to
normal modules, but the exported members of named modules are printed
differently, in a way that allows the module from which it was exported
to be identified visually.
> NormalModule := module() export e; end;
> NormalModule:-e;
> NamedModule:-e;
NamedModule : −e
It is also important (if you expect sensible results) that you never assign
a named module to another variable.
> SomeName := eval( NamedModule );
SomeName :=
module NamedModule () export e; end module
> SomeName:-e;
NamedModule : −e
This differs from the way error reporting works with procedures.
You cannot report the name of a normal module (where, by “the name”,
we mean the name of the variable to which the module is assigned), be-
cause the evaluation of the right side of an assignment occurs before the
assignment to the name takes place. So the error occurs before any asso-
ciation between a variable and the module has occurred.
Declarations
The declarations section of the module must appear immediately after
the parentheses. All statements in the declarations section are optional,
but at most one of each kind may appear. Most module declarations are
the same as those for procedures.
module()
export say ;
description “my first module”;
end module
The export declaration that appears in this example is explained
later in this chapter.
6.1 Syntax and Semantics • 243
message
> Hello:-say();
“HELLO WORLD!”
> message;
“HELLO WORLD!”
Local Variables You can refer to variables that are local to the module
definition by using the local declaration. Its format is exactly the same
as for procedures. Here is a variant on our Hello module above which
makes (gratuitous) use of a local variable.
> Hello := module()
> local loc;
> export say;
> loc := "HELLO WORLD!";
> say := proc()
> print( loc )
> end proc;
> end module:
Local variables are not visible outside the definition of the module
in which they occur. They are “private” to the module, and are exactly
analogous to local variables of procedures.
A local variable foo in a module (or procedure) is a distinct object
from a global variable with the same name foo. Local variables are nor-
mally “short-lived” variables; the normal lifetime of a local variable is
244 • Chapter 6: Programming with Modules
modexpr :- membername
> g := gen();
g := p
> g( 3 );
The local variable s of gen persists after gen has returned. It is “cap-
tured” in the closure of the procedure p, whose name is returned by gen.
Thus, both local variables p and s of gen “escape”, but in different ways.
The local name p is accessible because it is the assigned value of the global
variable g. However, there is no way to refer to s once gen has returned.
No Maple syntax exists for that purpose. What the member selection op-
erator :- does is provide a syntax for referencing certain local variables
of modules–those declared as exports.
Our most recent Hello example has one export, named say. In this
case, say is assigned a procedure. To call it, you can type
> Hello:-say();
“HELLO WORLD!”
Note, however, that this is a local name e, not the global instance of
the name.
> evalb( e = m:-e );
false
The first e in the expression above refers to the global e, while the
expression m:-e evaluates to the e that is local to the module m. This
means that you can have a special version of sin, for instance, that is
private to your module, and that assigning to it will not affect the value
of the global name sin.
The Procedure exports You can determine the names of the exports
of a module by using the procedure exports.
> exports( Hello );
say
> evalb( % = e );
6.1 Syntax and Semantics • 247
true
You can also obtain the local instances of those names by passing the
option instance.
> exports( m, ’instance’ );
> evalb( % = e );
false
true
For this reason, you cannot have the same name declared both as a
local and an export.
> module() export e; local e; end;
Error, export and local ‘e‘ have the same name
The Procedure member You have already seen the built-in procedure
member that is used to test for membership in a set or list.
> member( 4, { 1, 2, 3 } );
false
true
false
248 • Chapter 6: Programming with Modules
true
The name pos is now assigned the value 2 because b occurs at the
second position of the list [ a, b, c].
> pos;
When used with modules, the third argument is assigned the local
instance of the name whose membership is being tested, provided that
the return value is true.
> member( say, Hello, ’which’ );
true
> which;
say
If the return value from member is false, then the name remains
unassigned (or maintains its previously assigned value).
> unassign( ’which’ ):
> member( cry, Hello, ’which’ );
false
which
Module Options
As with procedures, a module definition may declare options. The op-
tions that Maple recognizes as being meaningful for modules are differ-
ent from those for procedures. Only the options trace, package, and
‘Copyright...‘ are meaningful (and have the same meaning) for both
procedures and modules.
A special module option load takes the form load = thunk, where
thunk is the name of an exported or local module member. thunk must
be a procedure that is invoked when the (instantiated) module is first
created or read from a Maple repository. This option may be used for any
per-session initialization required.
> z6:-mul( 2, 3 );
> z7:-add( 3, 4 );
true
true
was evaluated before being passed to type, so the expression that was
tested was not the definition, but the module to which it evaluates. We
must use unevaluation quotes (’) to delay the evaluation of a module
definition.
> type( ’module() end’, ’moduledefinition’ );
true
Other important type tests satisfied by modules are the types atomic
and last_name_eval.
> type( module() end, ’atomic’ );
true
The procedure map has no effect on modules; they pass through un-
changed.
252 • Chapter 6: Programming with Modules
true
true
true
−1 + 2 x + 27 x8
differentiate(sin(x), x)
differentiate(a, x) x2 + 2 a x + differentiate(b, x) x + b
+ differentiate(c, x)
cos(x) − sin(ex ) ex
2 cos(x2 ) x − 2 sin(x2 ) x
2ax + b
2 sin(x) cos(x)
> end if
> elif type( expr, ’‘*‘’ ) then
> a, b := op( 1, expr ), subsop( 1 = 1, expr );
> procname( a, var ) * b + a * procname( b, var )
> elif type( expr, ’function’ ) and nops( expr ) = 1 then
> # functions of a single variable; chain rule
> b := op( 0, expr ); # the name of the function
> a := op( 1, expr ); # the argument
> if assigned( functab[ b ] ) then
> # This is a ‘‘known’’ function
> functab[ b ]( a ) * procname( a, var )
> else
> # This function is not known; return unevaluated
> ’procname( args )’
> end if
> else
> ’procname( args )’
> end if
> end proc:
This not only simplifies the code used for the function case, but also
makes it very easy to add new functions.
Unfortunately, this implementation suffers from a serious drawback.
It is not extensible. The known functions are hardcoded as part of the
procedure definition for differentiate.
New functions cannot be added without editing this source code.
A second problem relates to performance. A complete implementation
would require a table of dozens or hundreds of functions. That large table
would have to be created and initialized each time differentiate was
invoked.
to add the derivative of the sin function. The export addFunc of the
DiffImpl module is a procedure that requires two arguments. The first
is the name of a function whose derivative is to be made known to the
differentiator. The second is a Maple procedure of one argument that
expresses the derivative of the function being added.
With this strategy in mind, we will create a module DiffImpl, with
principal export differentiate. At the same time, we will also take the
opportunity to make the basic differentiation rules extensible.
Here is the complete source code for the differentiator with these im-
provements.
> DiffImpl := module()
> description "a symbolic differentiator";
> local functab, ruletab, diffPower;
> export differentiate, addFunc, addRule, rule;
>
> addFunc := proc( fname::symbol, impl )
> functab[ fname ] := impl
> end proc;
>
> addRule := proc( T, impl )
> if type( T, ’{ set, list }’ ) then
> map( procname, args )
> elif type( T, ’And( name, type )’ ) then
> ruletab[ T ] := impl
> else
> error "expecting a type name, but got %1", T
> end if
> end proc;
>
> rule := proc( T )
> if type( T, ’And( name, type )’ ) then
> if assigned( ruletab[ T ] ) then
> eval( ruletab[ T ], 1 )
> else
> error "no rule for expressions of type %1", T
> end if
> else
> error "expecting a type symbol, but got %1", T
> end if
> end proc;
>
> differentiate := proc( expr, var )
> local a, b, e;
> if not has( expr, var ) then
> 0
> elif expr = var then
> 1
> elif type( expr, ’function’ ) and nops( expr ) = 1 then
> e := op( 0, expr );
6.1 Syntax and Semantics • 259
> whattype( a / b );
> whattype( [ f( x ), g( x ) ] );
list
in the module body. Now, the advantage of using this scheme is that,
not only can the author of the differentiator extend the system, but so
can users of the system. Having instantiated the module DiffImpl, any
user can add rules or new functions, simply by issuing appropriate calls
to addRule and addFunc.
The differentiator cannot handle the procedure tan
> differentiate( tan( x )/exp( x ), x );
tan(x) differentiate(tan(x), x)
− +
ex ex
x → 1 + tan(x)2
tan(x) 1 + tan(x)2
− +
ex ex
Similarly, there is not yet any rule for handling equations and other
relations.
> differentiate( y( x ) = sin( x^2 ) - cos( x^3 ), x );
x → sin(x) + cos(x)
x → e(2 x)
> differentiate( F( x ), x );
sin(x) + cos(x)
e(2 x)
> diff( F( x ), x );
sin(x) + cos(x)
diff /F := x → e(2 x)
e(2 x)
e(4 x)
6.2 Records
The simplest way in which to use modules is as Pascal-style records (or
“structures”, as in C and C++). A record is a data structure that has
some number of named “slots” or “fields”. In Maple, these slots can be
assigned arbitrary values.
rec :=
module() export a, b, c; option record ; end module
The name rec is now assigned a record with slots named a, b, and c.
These are the slot names for the record rec. You can access, and assign,
these slots by using the expressions rec:-a, rec:-b, and rec:-c.
> rec:-a := 2;
264 • Chapter 6: Programming with Modules
a := 2
> rec:-a;
If not assigned, the record slot evaluates to the local instance of the
slot name.
> rec:-b;
> evalb( % = b );
false
The usefulness of this is that the entire record may be passed around
as an aggregate data structure.
The record constructor accepts initializers for record slots. That is,
you may specify an initial value for any among the slots of a newly created
record by passing an equation with the slot name on the left side and the
initial value on the right.
> r := Record( ’a’ = 2, ’b’ = sqrt( 3 ) );
> r:-b;
√
3
true
This is a structured type that works the same way that type ‘module‘
does but recognizes records specifically.
6.2 Records • 265
z := module()
export re, i, j, k;
option record ;
end module
√
In this example, z represents the quaternion 2 + 3i + 2j + 5k (where
i, j, and k are the nonreal quaternion basis units). The quaternion records
can now be manipulated as single quantities. The following procedure ac-
cepts a quaternion record as its sole argument and computes the Euclidean
length of the quaternion that the record represents.
> qnorm := proc( q )
> use re = q:-re, i = q:-i, j = q:-j, k = q:-k in
> sqrt( re * re + i * i + j * j + k * k )
> end use
> end proc:
> qnorm( z );
√
22
true
266 • Chapter 6: Programming with Modules
6.3 Packages
Modules are ideal for writing Maple packages. They provide facilities that
are better equipped for large software projects than are the older table-
and procedure-based methods.
What Is a Package?
A package is a collection of procedures, and other data, that can be treated
as a whole. Packages typically gather a number of procedures that enable
you to perform computations in some well-defined problem domain. Pack-
ages may contain data other than procedures, and may even contain other
packages (“subpackages”).
Use Modules for New Packages Modules are the new implementa-
tion vehicle for packages. A module represents a package by its exported
names. The exported names can be assigned arbitrary Maple expressions,
typically procedures, and these names form the package.
max(|x − 1| + |x − 3| , |x − 2| + |x − 4|)
1
kx
1−
1 (k + 1) x
1+
2 1 (k − 1) x
1−
6 1 (k + 2) x
1+
6 1 + ...
two slots. When used to implement linked lists, the first slot holds the
data for the list entry, and the second slot stores a pointer to the next
pair in the list.
The LinkedList package implements an abstract data definition for
the pair data structure, and adds some higher level operations on pairs
to effect the list abstraction. A linked list is effectively represented by its
first pair.
The “pair” abstract data structure is very simple. It consists of a
constructor “pair”, and two accessors called “head” and “tail” that satisfy
the algebraic specification
p = pair(head(p), tail(p))
Note that linked lists are quite different from Maple’s builtin list struc-
tures, which are really immutable arrays. Linked lists are best suited for
applications in which you want to incrementally build up the list from its
members.1
1
Lisp programmers will recognise the pair, head and tail operations as the more
traditional operations known as “consÔ, “carÔ and “cdrÔ.
270 • Chapter 6: Programming with Modules
> setup,
> cleanup,
> map1,
> reverse1,
> _PAIR;
> option
> package,
> load = setup,
> unload = cleanup;
>
> setup := proc()
> global ‘type/Pair‘, ‘type/LinkedList‘;
> ‘type/Pair‘ := ’{ _PAIR( anything, anything ),
> identical( nil ) }’;
> ‘type/LinkedList‘ := proc( expr )
> if expr = nil then
> true
> elif type( expr, Pair ) then
> type( tail( expr ), ’LinkedList’ )
> else
> false
> end if
> end proc;
> userinfo( 1, ’LinkedList’,
> "new types ‘Pair’ and ‘LinkedList’ defined" );
> NULL
> end proc;
>
> cleanup := proc()
> global ‘type/Pair‘, ‘type/LinkedList‘;
> userinfo( 1, ’LinkedList’,
> "cleaning up global types" );
> ‘type/Pair‘ := evaln( ‘type/Pair‘ );
> ‘type/LinkedList‘ := evaln( ‘type/LinkedList‘ );
> NULL
> end proc;
>
> pair := ( a, b )
> -> setattribute( ’_PAIR’( a, b ), ’inert’ );
> head := ( c::Pair )
> -> ‘if‘( c = nil, nil, op( 1, c ) );
> tail := ( c::Pair )
> -> ‘if‘( c = nil, nil, op( 2, c ) );
> nullp := ( pair )
> -> evalb( pair = nil );
>
> list := proc()
> local a, L;
> L := nil;
> for a in args do
> L := pair( a, L )
> end do
> end proc;
>
6.3 Packages • 271
savelib procedure using the name of the module as its sole argument:
> savelib( ’LinkedList’ );
Evaluating the savelib call saves the module to the first repository
found in the global variable libname, or the repository named with the
global variable savelibname, if it is defined. (At least one of these must
be defined.) You should always ensure that your standard Maple
library is write-protected to avoid saving your own expressions
in it. If you accidentally save something to the standard Maple library,
you may need to restore the original from the media on which you obtained
your copy of Maple.
The package exports are listed as the exports of the module. A few
local variables are used to implement the package. The local procedures
map1 and reverse1 are part of the package implementation that is not
available to users of the package. They are visible only within the module
definition. This allows the package author to make improvements to the
package without disturbing any code that uses it. If the local procedures
reverse1 and map1 were exported (thus, available to users), it would be
difficult for the author to replace these routines without breaking existing
code that relies upon them.
The package includes two special (local) procedures, setup and
cleanup. These are executed, respectively, when the module is first read
from a repository, and when the package is either garbage collected or
when Maple is about to exit.
(a, b)
(a, b)
6.3 Packages • 273
This form requires that the index (in this case, the symbol pair) be
protected from evaluation, and the notation does not extend to packages
with nested subpackages.
To access the package exports interactively, use the with command.
> with( LinkedList );
Warning, the protected names length, map and member
have been redefined and unprotected
true
true
L := (1, nil )
> length( L );
10
> member( 3, L );
true
false
> reverse( L );
(1, (2, (3, (4, (5, (6, (7, (8, (9, (10, nil ))))))))))
(100, (81, (64, (49, (36, (25, (16, (9, (4, (1, nil ))))))))))
true
6.3 Packages • 275
> L2 := list( a, b, c, d );
The Maple source code for this package is available in the ‘samples’
directory of your Maple installation as the file ‘samples/ch06/ll.mpl’.
The source code in the ‘samples’ directory may differ slightly from that
shown in this book.
Design When you have written some Maple code, you will want to
write tests that exercise each part of the program to ensure that it works
correctly, and that it continues to work when it, or other programs on
which it depends, change over time. It is important to be able to determine
whether each statement in a procedure is executed by some test case.
The traceproc option of the Maple command debugopts provides that
capability. It takes the name p of a procedure, using the syntax
276 • Chapter 6: Programming with Modules
debugopts( ’traceproc’ = p );
> showstat( p );
p := proc(x)
|Calls Seconds Words|
PROC | 1 0.000 12|
1 | 1 0.000 12| if x < 0 then
2 | 0 0.000 0| 2*x
else
3 | 1 0.000 0| 1+2*x
end if
end proc
The display shows that only one branch of the if statement that
forms the body of p was taken so far. This is because only a non-negative
argument has been supplied as an argument to p. To get complete cover-
age, a negative argument must also be supplied.
> p( -1 );
−2
> showstat( p );
6.3 Packages • 277
p := proc(x)
|Calls Seconds Words|
PROC | 2 0.000 24|
1 | 2 0.000 24| if x < 0 then
2 | 1 0.000 0| 2*x
else
3 | 1 0.000 0| 1+2*x
end if
end proc
Now the display shows that each statement in the body of p has
been reached.
The profiling information is stored in an rtable assigned to a name
that is formed by concatenating the name TRACE with the name of the
procedure (the one used in the call to debugopts), separated by a /
character.
> eval( ‘TRACE/p‘ );
2 0 24
2 0 24
1 0 0
1 0 0
The little package illustrated in this section helps to extend this func-
tionality to modules, and acts as a front end to the debugopts with the
traceproc option.
The coverage package has two exports: profile and covered. Two
private procedures, rprofile and traced, are used as subroutines. They
are stored in local variables of the underlying module of the package.
The Package Source Here is the source code for the package.
> coverage := module()
> description "a package of utilities for "
> "code coverage profiling";
> option package;
> export profile, covered;
> local rprofile, traced;
>
> # Instrument a procedure or module
> # for coverage profiling. Return the
> # number of procedures instrumented.
> profile := proc()
> local arg;
278 • Chapter 6: Programming with Modules
first converts the names to strings, then removes the "TRACE/" prefix
by forming the substring from the seventh position to the end of the string,
and finally calls parse on each string to convert it to the procedure for
which profiling data is stored.
Using the Package As with all packages, you can access the coverage
package interactively by using the with command.
> with( coverage );
Warning, the protected name profile has been redefined
and unprotected
280 • Chapter 6: Programming with Modules
[covered , profile]
[copy ]
copy := proc(A)
|Calls Seconds Words|
PROC | 2 0.000 640|
1 | 2 0.000 640| if type(A,rtable) then
2 | 0 0.000 0| return rtable(rtable_indfns(A),
rtable_dims(A),A,rtable_options(A),
readonly = false)
elif type(A,{array, table}) then
3 | 2 0.000 582| if type(A,name) then
4 | 0 0.000 0| return map(proc () args end proc,
eval(A))
else
5 | 2 0.000 574| return map(proc () args end proc,A)
end if
6.3 Packages • 281
else
6 | 0 0.000 0| return A
end if
end proc
copy := proc(A)
|Calls Seconds Words|
PROC | 3 0.000 828|
1 | 3 0.000 828| if type(A,rtable) then
2 | 1 0.000 176| return rtable(rtable_indfns(A),
rtable_dims(A),A,rtable_options(A),
readonly = false)
elif type(A,{array, table}) then
3 | 2 0.000 582| if type(A,name) then
4 | 0 0.000 0| return map(proc () args end proc,
eval(A))
else
5 | 2 0.000 574| return map(proc () args end proc,A)
end if
else
6 | 0 0.000 0| return A
end if
end proc
copy := proc(A)
|Calls Seconds Words|
PROC | 4 0.000 1127|
1 | 4 0.000 1127| if type(A,rtable) then
2 | 1 0.000 176| return rtable(rtable_indfns(A),
rtable_dims(A),A,rtable_options(A),
readonly = false)
elif type(A,{array, table}) then
3 | 3 0.000 873| if type(A,name) then
4 | 1 0.000 287| return map(proc () args end proc,
eval(A))
else
282 • Chapter 6: Programming with Modules
The only missing case now is the one in which the argument to
copy is something other than an rtable, array, or table.
> copy( 2 ):
> showstat( copy );
copy := proc(A)
|Calls Seconds Words|
PROC | 5 0.000 1153|
1 | 5 0.000 1153| if type(A,rtable) then
2 | 1 0.000 176| return rtable(rtable_indfns(A),
rtable_dims(A),A,rtable_options(A),
readonly = false)
elif type(A,{array, table}) then
3 | 3 0.000 873| if type(A,name) then
4 | 1 0.000 287| return map(proc () args end proc,
eval(A))
else
5 | 2 0.000 574| return map(proc () args end proc,A)
end if
else
6 | 1 0.000 10| return A
end if
end proc
The final output shows that every statement has been reached by
our test cases. This functionality is very useful for interactively developing
unit tests for Maple programs.
The source presented here for the coverage package has been simpli-
fied for presentation in printed form. The full source code is available in
the samples directory of your Maple installation.
shapes.mpl
point.mpl
segment.mpl
circle.mpl
square.mpl
triangle.mpl
shapes.mpl ShapesPackage
point.mpl
segment.mpl
circle.mpl
square.mpl
triangle.mpl
To define the module that implements this package, we use the Maple
preprocessor to include the remaining source files at the appropriate point
in the “master” source file shapes.mpl. A number of $include directives
are included in shapes.mpl, such as
$include "point.mpl"
$include "segment.mpl"
...
Splitting a large project into a number of source files makes it easier
to manage, and allows several developers to work on a project simultane-
ously. The source file is divided into shape-specific functionality. Most of
the functionality for points, for instance, is implemented by source code
stored in the file point.mpl.
Shapes
Shapes
point triangle
segment square
circle
ShapesPackageSubmoduleStructure
Shapes := module()
export make, area, circumference;
local Shapes, circum_table;
Shapes := module()
export point, segment, circle, square, triangle;
point := module() ... end;
segment := module() ... end;
.....
end module;
make := proc() ... end;
area := proc() ... end;
circum_table := table(); ...
circumference := proc() ... end;
end module:
The Package API The Shapes package exports the following routines:
1. make
2. area
3. circumference
The exported procedure make is a constructor for shapes. It is used
to create a shape expression from the input data. For example, points are
constructed from their x and y coordinates.
> org := make( ’point’, 0, 0 );
org := make(point , 0, 0)
In each case, the name of the shape is passed as the first argument,
to tell make what kind of shape to return.
To compute the area of a shape, call the exported procedure area
with the shape as its argument.
> area( circ );
6.3 Packages • 287
2. conditional dispatching
3. table-based dispatching
Minimal checking is done to ensure that the input has the right struc-
ture. If an entry is found in the table circum_table for the shape “tag”
(as with the area routine), then the corresponding procedure is called
290 • Chapter 6: Programming with Modules
with the given shape as argument. (The shape must be passed as an ar-
gument, so that the shape-specific subroutine can extract the instance
data from it.) Otherwise, a diagnostic is issued, and an exception raised.
Since the area and circumference of a point are both 0, these proce-
dures are trivial to implement. In addition to the “required” exports, the
point submodule also exports two utility routines, xcoord and ycoord
for retrieving the x and y coordinates of a point. Providing these makes
it possible for clients of this submodule to use it without knowing any-
thing about the concrete representation of points. This makes it easier to
change the representation later should that be required.
Within this submodule, the names make, area, and circumference
shadow the names with the same external representation at the top-level
Shapes module.
Syntax and Semantics The keyword use introduces the use state-
ment, which has the following syntax template:
use env in
body
end use;
sin(x)2 + cos(x)2
When nested use statements are encountered, the name bindings es-
tablished by the inner use statement take precedence over those of the
outer one.
> use a = 2, b = 3 in
> use a = 3 in a + b end
> end use;
In this example, the inner binding of the name a to the value 3 takes
precedence, so the value of the expression a + b (and hence of the entire
statement) is the number 6. The inner binding of a to 3 has an effect
only within the body of the inner use statement. Once the execution has
exited the inner use statement, the binding of a to 2 is restored.
> use a = 2, b = 3 in
> # here a is bound to 2 and b to 3
> use a = 3 in
> # here, b is still bound to 3, but a is bound to 3
> a + b
> end use;
> # binding of a to 2 is restored
> a + b
> end use;
loop, which actually involves the four stages: parsing (reading), automatic
simplification, evaluation and printing.)
To see how this works, consider an example in which the use statement
appears inside a procedure.
> f := proc( a, b )
> use x = a + b, y = a - b in
> x * y
> end use
> end proc;
Note that the body of the procedure f contains no use statement. Dur-
ing automatic simplification, the use statement that formed the body of f
was “expanded”, yielding the expression that involves only the parameters
a and b.
0 0 0 0
294 • Chapter 6: Programming with Modules
3
(26 − λ) (−1666 λ − 97 λ2 − λ3 )
Please note that a name that appears in a binding list for a use
statement that is intended to be a module must evaluate to a module at
the time the use statement is simplified. This is necessary because the
simplification of the use statement must be able to determine the exports
of the module. In particular, the following attempt to pass a module as a
parameter to a procedure does not work, and yields an error during the
simplification of the procedure.
> proc( m, a, b )
> use m in e( a, b ) end
> end proc;
Error, no bindings were specified or implied
Operator Rebinding
An additional feature of the use statement is that it allows most infix and
prefix operators in the Maple language to be rebound. This is not really
the same thing as the “operator overloading” found in some programming
6.4 The use Statement • 295
F(a, b)
> m := module()
> export ‘*‘, ‘+‘;
> ‘+‘ := ( a, b ) -> a + b - 1;
> ‘*‘ := ( a, b ) -> a / b;
> end module:
> s * ( s + t );
s (s + t)
z := module()
local real _part , imag _part ;
export re, im, abs, arg ;
description “a complex number”;
end module
The procedure MakeComplex is a constructor for complex number ob-
jects. The value returned by the procedure is the instantiation of the
module whose definition appears in the body of MakeComplex.
The local state of the complex number is represented by the local
variables of the module, real_part and imag_part. The behavior is rep-
resented by the exported procedures re, im, abs, and arg.
The exports of a module that represents an object are sometimes
viewed also as messages . Objects respond to these messages by exhibiting
the behavior that the messages elicit.
> z:-re(), z:-im();
1, 1
> z:-abs();
6.5 Modeling Objects • 299
√
2
> z:-arg();
1
π
4
> heap[ 1 ]
> end if
> end proc;
>
> # Delete the highest priority item from the
> # priority queue.
> delete := proc()
> local val;
> val := heap[ 1 ]; # val := top()
> # move bottom to the top
> heap[ 1 ] := heap[ nitems ];
> # allow expression to be collected
> heap[ nitems ] := evaln( heap[ nitems ] );
> # decrement the bottom of heap counter
> nitems := nitems - 1;
> # heapify the array
> bubbledown( 1 );
> # return the value
> val
> end proc;
>
> # Insert an item into the priority queue.
> insert := proc( v )
> if nargs > 1 then
> op( map( procname, [ args ] ) )
> else
> nitems := 1 + nitems;
> heap[ nitems ] := v;
> bubbleup( nitems )
> end if
> end proc;
>
> # Insert any intially specified items.
> if lnargs > 1 then
> insert( op( largs ) )
> end if
> end module
> end proc:
The constructor takes a Maple procedure priority as its argument. For
each expression that may be placed on the queue, this procedure should
return a numeric measure of its “priority”. Items on the queue are main-
tained in a prioritized order so that the highest priority items are removed
first.
In this sample computation with a priority queue, we use the Maple
built-in procedure length as the “priority” of an expression. Here, the
randomly generated expressions are all polynomials.
> pq := PriorityQueue( x -> length( x ) );
6.5 Modeling Objects • 303
pq := module()
local heap, nitems , bubbleup, bubbledown;
export empty , top, insert , size, delete, init ;
description “a priority queue”;
end module
> for i from 1 to 10 do
> pq:-insert( randpoly( x ) );
> end do:
> while not pq:-empty() do
> pq:-delete()
> end do;
−85 x5 − 55 x4 − 37 x3 − 35 x2 + 97 x + 50
−99 x5 − 85 x4 − 86 x3 + 30 x2 + 80 x + 72
−53 x5 + 85 x4 + 49 x3 + 78 x2 + 17 x + 72
79 x5 + 56 x4 + 49 x3 + 63 x2 + 57 x − 59
−86 x5 + 23 x4 − 84 x3 + 19 x2 − 50 x + 88
−50 x5 − 12 x4 − 18 x3 + 31 x2 − 26 x − 62
−58 x5 − 90 x4 + 53 x3 − x2 + 94 x + 83
77 x5 + 66 x4 + 54 x3 − 5 x2 + 99 x − 61
45 x5 − 8 x4 − 93 x3 + 92 x2 + 43 x − 62
x5 − 47 x4 − 91 x3 − 47 x2 − 61 x + 41
[7, 7, 15, 25, 27, 27, 28, 29, 42, 51, 52, 55, 62, 74, 82,
88, 94, 97, 97, 98]
The fully commented source code for the Priority Queue constructor
is available in the sample source code of your Maple installation.
The point Constructor Points are quite simple shapes, so the corre-
sponding constructor is similarly simple.
> point := proc( x, y )
> module()
> export area, circumference, xcoord, ycoord;
> xcoord := () -> x;
> ycoord := () -> y;
> area := () -> 0;
> circumference := () -> 0;
> end module
> end proc:
Again, the lexically scoped parameters ctr and rad encode the in-
stance data of the circle object.
The remainder of the object oriented version of the Shapes package
can be read in the sample source code file ShapeObj.mpl.
With this discussion, we are finally able to see the real meaning be-
hind the distinction between local and exported variables in a module.
A module’s exports are part of its “promise” to those who would use it.
Whatever is expressed through its local variables is the business only of
the module, and is not to be relied upon, or even known, by clients of the
module. (Client access is, in fact, the only technical difference between
module locals and exports.)
Before the introduction of the module system, Maple’s “design by
contract” was enforced only by convention. Maple routines whose names
had to be enclosed in name quotes (‘) were considered “private”, and not
for client use. But this was only a convention. Moreover, it was necessary
to use global variables to communicate information and state between the
various routines that made up a subsystem (such as solve or assume).
Now, using modules, it is possible to design software systems that enforce
their contracts by a mechanism embedded in the Maple language itself.
Interfaces
The “contracts” discussed above are represented formally in Maple by an
interface. An interface is a special kind of structured type. It has the
form
‘module‘( symseq );
These symbols are the ones that clients are “allowed” to access as
module exports.
A module is said to satisfy, or to implement, an interface if it is of
the type defined by the interface.
> z5 := module()
> description "the integers modulo 5";
> export ‘+‘, ‘*‘, ‘-‘, zero, one;
> ‘+‘ := (a,b) -> a+b mod 5;
> ‘*‘ := (a,b) -> a*b mod 5;
> ‘-‘ := s -> 5-s mod 5;
> zero := 0;
> one := 1;
308 • Chapter 6: Programming with Modules
true
true
abgroup
true
true
true
ring
true
true
true
true
that appears in the module definition instructs Maple that, when the
instantiated module is read from a repository, it is to call the procedure
setup. The procedure named must be a local or an exported local of the
module. The local procedure setup in this module simply ensures that
the global variable type/interface is assigned an appropriate value. This
assignment is also made in the body of the module so that the assignment
is also executed in the session in which the module is instantiated. This
was done for illustrative purposes. A better scheme would simply have
invoked setup at some point in the body of the module definition.
3. store, for each vertex of the graph, the set of all its neighbours.
(The adjacency matrix is a square matrix whose rows are columns are
indexed by the vertices of the graph; the (i, j)-entry is equal to 1 if there
is an edge from i to j, and is equal to 0 otherwise.) We would like to be
able to write software that can manipulate a graph regardless of which of
the above (or other) representations is chosen.
true
316 • Chapter 6: Programming with Modules
we can use the routine vdeg with the graph g1, since graphs produced
by Graph1 implement the Graph interface.
> vdeg( g1, a );
2, 0
1, 1
0, 2
true
Because of this, the generic procedure vdeg works equally well with
it.
> vdeg( g2, a );
2, 0
1, 1
0, 2
> AdjacencyMatrix( g2 );
0 1 1
0 0 1
0 0 0
k
σ
η F
ϕ
D
commutes. Because a nonzero ring homomorphism into a field must be
injective, this says that every field F that contains D as a subring must
also contain an isomorphic copy of k.
Concretely, the quotient field of an integral domain D can be thought
of as the set of “reduced fractions” n/d, with n, d ∈ D. A formal con-
struction can be produced by defining an equivalence relation on the set
D × (D \ {0}), according to which two pairs (n1, d1) and (n2, d2) are
equivalent only if,
n1 · d2 = n2 · d1.
A representative from each equivalence class is chosen to represent the
field element defined by that class. This understanding guides the com-
puter representation of the quotient field.
hS, +, ∗, 0, 1i
true
true
those too are trivial in a field. We do include two new methods: make for
constructing field elements from their numerators and denominators, and
embed, the natural embedding of the integral domain D into its field k of
fractions. Additionally, the two methods numer and denom allow the user
to extract the components of a fraction.
> ‘type/Field‘ := ’‘module‘(
> ‘+‘::procedure,
> ‘*‘::procedure,
> ‘-‘::procedure,
> ‘/‘::procedure,
> normal::procedure,
> iszero::procedure,
> isone::procedure,
> zero, one,
> make::procedure,
> embed::procedure
> )’:
Naturally, the ring Z of integers is not a field.
> type( MapleIntegers, ’Field’ );
false
Fields produced by the quotient field constructor will satisfy this in-
terface.
The Quotient Field Functor Here is the generic constructor for quo-
tient fields.
> QuotientField := proc( R::GcdRing )
> description "quotient field functor";
> module()
> description "a quotient field";
> export ‘+‘, ‘*‘, ‘-‘, ‘/‘,
> zero, one,
> iszero, isone,
> make,
> numer, denom,
> normal, embed;
> make := proc( n, d )
> local u, nd;
> if R:-iszero( d ) then
> error "division by zero"
> end if;
> u, nd := R:-unormal( d );
> ’FRACTION’( u*n, nd )
> end proc;
> embed := d -> make( d, R:-one );
> numer := f -> op( 1, f );
6.6 Interfaces and Implementations • 323
FF := module()
export‘ + ‘, ‘ ∗ ‘, ‘ − ‘, ‘/‘, zero, one, iszero, isone, make,
numer , denom, normal , embed ;
description “a quotient field”;
end module
> type( FF, ’Field’ );
true
> a := FF:-make( 2, 3 );
6.6 Interfaces and Implementations • 325
a := FRACTION(2, 3)
> b := FF:-make( 2, 4 );
b := FRACTION(2, 4)
> use FF in
> a + b;
> a * b;
> a / b
> end use;
FRACTION(7, 6)
FRACTION(1, 3)
FRACTION(4, 3)
RR := module()
export‘ + ‘, ‘ ∗ ‘, ‘ − ‘, ‘/‘, zero, one, iszero, isone, make,
numer , denom, normal , embed ;
description “a quotient field”;
end module
> type( RR, ’Field’ );
true
> use RR in
> a + b,
> a * b,
> a / b
> end use;
6.6 Interfaces and Implementations • 327
7781 5 401827 3
(−2790 T 6 − T − 1638 T 4 + T
4 124
¾
1943715 2 144452 87333
+ T − T+ ) (
124 31 124
91 4 1067 3 6 2 693 297
T5 − T − T + T − T− ), (
248 496 31 496 248
5780880 T 5 + 4440688 T 4 − 16127440 T 3 − 9252880 T 2
− 11301360 T + 31320912)/(
91 4 1067 3 6 2 693 297
T5 − T − T + T − T− ), (
248 496 31 496 248
5780880 T 4 − 1711080 T 3 − 28100520 T 2 + 13000680 T
+ 16133040)/(
251 5 7 4 113 3 241 2 93
T6 + T − T − T − T − )
360 45 120 120 40
Example: A Generic Group Implementation
In this section, we illustrate how to develop a moderately complex software
system based on the use of features of Maple’s module system. Generic
programming is at the heart of the design. Only a fraction of the complete
system from which the examples are taken is shown. The examples that
follow comprise a system for computing with finite groups. Recall that a
group is a set of objects together with an associative binary operation,
for which there is an unique two-sided identity element, and with respect
to which each member of the underlying set possesses an unique inverse.
Examples of groups include systems of numbers, using addition, closed
sets of invertible matrices (all of the same size, with a common ground
field) using multiplication (“linear groups”), closed sets of permutations
(bijective mappings on a set) using composition (“permutation groups”),
and groups of points on elliptic curves. We are concerned here only with
finite groups.
(inv(y)) . x . y
6.6 Interfaces and Implementations • 329
mul(inv(x), inv(y), x, y)
> local i;
> [ seq( b[ i ], i = a ) ]
> end proc;
> G:-mul := () -> foldl( G:-‘.‘, G:-id, args );
> G:-inv := proc( g )
> local i, a;
> a := array( 1 .. deg );
> for i from 1 to deg do
> a[ g[ i ] ] := i
> end do;
> [ seq( a[ i ], i = 1 .. deg ) ]
> end proc;
> G:-member := proc( g, S, pos::name )
> if nargs = 1 then
> type( g, ’list( posint )’ )
> and { op( g ) } = { $ 1 .. deg }
> else
> :-member( args )
> end if
> end proc;
> G:-eq := ( a, b ) -> evalb( a = b );
> G:-gens := gens;
> eval( G, 1 )
> end proc:
For example, to construct the group h(12), (123)i in the symmetric group
S4 , we use the PermutationGroup constructor as follows.
> G := PermutationGroup( 4, { [2,1,3,4], [2,3,1,4] } );
G := module()
export
id , ‘.‘, mul , inv , eq , member , gens , order , elements ;
option record ;
end module
We can now call upon the “services” provided by the methods ex-
ported by the instantiated group G to compute with its elements.
> use G in
> inv( [ 2,1,3,4 ] ) . [2,3,1,4];
> end use;
[3, 2, 1, 4]
D6
(12)
(123456)
Gk = hg1 , g2 , . . . , gk i
hg1 i = G1 ≤ G2 ≤ · · · ≤ Gk ≤ · · · ≤ Gn = G.
G := module()
export
id , ‘.‘, mul , inv , eq , member , gens , order , elements ;
option record ;
end module
> Dimino( G );
its members commutes with all the generators of the parent group). We
can ask that properties be specified by requiring a procedure that tests
for membership in the subgroup. Thus, subgroups can be described by
either the following interfaces.
parent the parent group
test a membership test (a procedure)
gens a set of generators
Only one of the methods test and gens need be present. A Maple im-
plementation of this interface is as follows.
> ‘type/SubGroup‘ := ’{
> ‘module‘( parent::Group, gens::set ),
> ‘module‘( parent::Group, test::procedure )
> }’:
The SubGroup constructor must dispatch on the type of its second argu-
ment to determine which kind of record to create to model the subgroup.
> SubGroup := proc( G::{Group,SubGroup}, how::{set,procedure} )
> description "subgroup constructor";
> local S;
> if type( how, ’procedure’ ) then
> S:= Record( ’parent’, ’test’ = eval( how, 1 ) )
> else
> S := Record( ’parent’, ’gens’ = how )
> end if;
> S:-parent := G;
> eval( S, 1 )
> end proc:
For example, the center of the symmetric group S3 can be defined as
follows.
> S3 := Symmetric( 3 ):
> Z := SubGroup( S3, proc( z )
> local g;
> use S3 in
> for g in gens do
> if not eq( mul( inv( g ), inv( z ), g ), z ) then
> return false
> end if
> end do;
> end use;
> true
> end proc );
Z := module()
export parent , test ;
option record ;
end module
6.6 Interfaces and Implementations • 337
false
false
true
G := module()
export
id , ‘.‘, mul , inv , eq , member , gens , order , elements ;
option record ;
end module
> SubGroupElements( Centralizer( G, [ 1, 3, 2, 4 ] ) );
G := module()
export
id , ‘.‘, mul , inv , eq , member , gens , order , elements ;
option record ;
end module
> C := Centralizer( G, [ 1, 3, 2, 4 ] );
C := module()
export parent , test ;
option record ;
end module
> GroupOrder( G );
24
6.6 Interfaces and Implementations • 341
> GroupOrder( C );
Note that, when the argument G is neither a group nor a subgroup, the
procedure GroupElements returns unevaluated. This allows us to extend
other Maple operations, such as expand, combine or simplify to be effec-
tive on algebraic expressions involving unevaluated calls to GroupOrder.
Matrix Groups So far, all our groups have been permutation groups
returned by one of the constructors presented above. If we are to have
any confidence in the genericity of the code we have developed, we must
test it on some other kinds of groups. A good source for examples of finite
groups are the finite groups of exact matrices.
false
true
1
θ := π
3
> a := Matrix( 2, 2, [[ 0, 1 ], [ 1, 0 ]] );
´ µ
0 1
a :=
1 0
> b := Matrix( 2, 2,
> [[cos(theta),sin(theta)],
> [-sin(theta),cos(theta)]] );
344 • Chapter 6: Programming with Modules
1 1√
3
2 2
b :=
1 √ 1
− 3
2 2
> B := MatrixGroup( 2, a, b );
B := module()
export
id , ‘.‘, mul , inv , gens , eq , member , order , elements ;
option record ;
end module
> GroupElements( B );
1 1√ 1√ 1 1√ 1
3 − 3 3
2 2 2 2 2 2
, , ,
1√ 1 1 1√ 1 1√
− −
3 3 3
2 2 2 2 2 2
−1 1√ 1 1√ 1√ −1 ´
3 − 3 − 3 µ
2 2 2 2 2 2 0 1
, √ , , ,
1√ −1 1 1 −1 1√ 1 0
− 3 3 3
2 2 2 2 2 2
1√ −1 −1 1√
´ µ 3 − 3 ´ µ ´ µ
1 0 2 2 2 2 −1 0 0 −1
, , √ , ,
0 1 −1 1√ 1 −1 0 −1 −1 0
− 3 3
2 2 2 2
Direct Products To enrich the supply of example groups that we can
work with, we’ll develop a constructor for the direct product of (two)
groups. (Extending the constructor to handle any finite number of groups
is straight-forward, but complicates the exposition unnecessarily.) Direct
products are very important in the study of finite groups because all
finitely generated abelian groups possess an unique “factorisation” as a
direct product of cyclic groups. (In the abelian theory, direct products are
often referred to as direct sums.)
The direct product of two groups A and B is the group G whose
elements are all pairs (a, b), with a ∈ A and b ∈ B. The group product in
G is defined by (a1 , b1 )·(a2 , b2 ) = (a1 ·a2 , b1 ·b2 ). The inverse of an element
(a, b) is the pair (a−1 , b−1 ). All the operations are defined component-wise.
6.6 Interfaces and Implementations • 345
72
72
346 • Chapter 6: Programming with Modules
> A := Symmetric( 4 ):
> B := MatrixGroup( 2, Matrix( [[0,1],[1,0]] ) ):
We can define a mapping from the generators of A to the group B by
inserting the images of the generators into a procedure’s remember table.
> h( [2,1,3,4] ) := Matrix( [[0,1],[1,0]] ):
> h( [2,3,4,1] ) := Matrix( [[1,0],[0,1]] ):
This defines a Maple procedure h that performs the indicated mapping
and returns unevaluated for any other arguments.
> eval( h );
hom := module()
export domain, codomain, genmap;
option record ;
end module
> type( hom, ’Homomorphism’ );
true
6.7 Conclusion
This chapter introduced the concept of Maple modules. It described the
structure and flexibility of modules.
Encapsulation and generic programming with modules allow you to
write code that can be reused, transported, and easily maintained. By
collecting procedures into a module called a package, you can organize
your procedures into distinct sets of related functions. You can also use
modules to implement objects in Maple.
The descriptions in this chapter are complemented by numerous ex-
amples to help you learn the syntax and semantics of modules and provide
you with modules that can be customized and used in your own work.
7 Debugging Maple
Programs
349
350 • Chapter 7: Debugging Maple Programs
of your program 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. Additional information about the com-
mands is provided in Section 7.2.
The following procedure, sieve, 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 n, inclusive. To debug the sieve procedure, we
use breakpoints and watchpoints, which cause Maple to stop the execution
of the 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+l
> end if;
> end do;
> count;
> end proc:
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
7.1 A Tutorial Example • 351
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 that the numbers preceeding each line differ from line numbers
that may display in a text editor. For example, keywords that end a state-
ment (such as end do and end if) are not considered separate commands
and are therefore not numbered.
[sieve]
1
If a procedure has a remember table, you may have to execute a restart command
before issuing a second or subsequent stopat command. For more information about
remember tables, see ?remember.
352 • Chapter 7: Debugging Maple Programs
The 0 in the first line of the output represents the result of the exe-
cuted statement—that is, the result of count := 0. A “*” does not appear
next to the statement number because there is no breakpoint set immedi-
ately before statement 2. The debugger does not show the body of the for
loop, which itself consists of statements with their own statement num-
bers, unless execution actually stops within its body. Maple represents
the body of compound statements by ellipses (...).
Executing the next command again results in the following output.
DBG> next
true
sieve:
4 for i from 2 to n do
...
end do;
is executed n-1 times. The debugger displays the last result computed in
the loop (the assignment of the value true to flags[10]).
If you want to step into a nested control structure (such as an if
statement or for loop) or a procedure call, use the step debugger com-
mand.
DBG> step
true
sieve:
5 if flags[i] then
...
end if
DBG> step
true
sieve:
6 twicei := 2*i;
If you use the step debugger command when the next statement to
execute is not a deeper structured statement, 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;
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
7.1 A Tutorial Example • 355
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
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> outfrom
l
sieve:
5 if flags[i] then
...
end if
7.1 A Tutorial Example • 357
9l
You can now see that the procedure does not give the expected output.
Although you may find the reason obvious from the previous debugger
command examples, in most cases it is not easy to find procedure errors.
Therefore, pretend not to recognize the problem, and continue to use the
debugger. First, use the unstopat command to remove the breakpoint
from sieve.
> unstopat(sieve);
[]
[[sieve, count ]]
count := 0
sieve:
2 for i from 2 to n do
...
end do;
Execution stops because Maple has modified count, and the debugger
displays the assignment statement count := 0. As in the case of break-
points, the debugger then displays the name of the procedure and the next
statement to be executed in the procedure. Note that execution stops af-
ter Maple has assigned a value to count.
This first assignment to count is correct. Use the cont debugger com-
mand to continue execution of the procedure.
DBG> cont
count := l
sieve:
5 if flags[i] then
...
end if
If you do not look carefully, this also looks correct. Assume that noth-
ing is wrong and continue execution.
DBG> cont
count := 2*l
sieve:
5 if flags[i] then
...
end if
[]
After correcting the source text for sieve, issue a restart command,
read the corrected version of sieve back into Maple, and execute the
procedure again.
> sieve(10);
9l
This result is still incorrect. There are four primes less than 10, namely
2, 3, 5, and 7. Therefore, invoke the debugger once more, stepping into the
innermost parts of the procedure to investigate. Since you do not want to
start at the beginning of the procedure, set the breakpoint at statement
6.
> stopat(sieve,6);
[sieve]
> sieve(10);
true
sieve:
6* twicei := 2*i;
DBG> step
360 • Chapter 7: Debugging Maple Programs
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
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 true = false was returned. An equation was used instead
of an assignment. Therefore, Maple did not set flags[k] to false.
Once again, exit the debugger and correct the source text.
DBG> quit
showstat( procedureName );
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;
362 • Chapter 7: Debugging Maple Programs
> print(-x)
> end proc:
f := proc(x)
...
2 print(x)
end if;
3 print(-x)
end proc
This condition argument can refer to any global variable, local vari-
able, or parameter of the procedure. These conditional breakpoints are
marked by a question mark (?) if showstat is used to display the proce-
dure.
Since the stopat command sets the breakpoint before the specified
statement, when Maple encounters a breakpoint, execution stops and
Maple engages the debugger before the statement. This means that
it is not possible to set a breakpoint after the last statement in a
statement sequence—that is, at the end of a loop body, an if state-
ment 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, then they do not share breakpoints. If
you created a procedure by assigning it to the body of another procedure,
then their breakpoints are shared.
7.2 Maple Debugger Commands • 363
[g, h]
> showstat();
g := proc(x)
1* x^2
end proc
h := proc(x)
1* x^2
end proc
DEBUG( argument );
2
The showstat command does not mark explicit breakpoints with a “*Ô or a “?Ô.
364 • Chapter 7: Debugging Maple Programs
> 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
> f(2,3);
7.2 Maple Debugger Commands • 365
9
f:
5 print(a)
DBG> quit
> f(2);
"This is my breakpoint. The current value of x is:"
2
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
[f, g, h]
> print(f);
stopwhen( globalVariableName );
stopwhen( [procedureName, variableName ] );
The first form specifies that the debugger should be invoked whenever
the global variable globalVariableName is changed. Maple environment
variables, such as Digits, can also be monitored by using this method.
> stopwhen(Digits);
7.2 Maple Debugger Commands • 367
[Digits ]
The second form invokes the debugger whenever the (local or global)
variable variableName is changed in the procedure procedureName.
When stopwhen is called in either form or with no arguments, Maple
returns a list of the watchpoints that are currently set.
Execution stops after Maple has already assigned a value to the
watched variable. The debugger displays an assignment statement in-
stead of the last computed result (which would be the right-hand side
of the assignment statement).
stoperror( "errorMessage" );
[]
> stoperror();
1
9
> g(0);
> f(0);
Error, numeric exception: division by zero
f:
1 1/x
DBG> cont
Error, (in f) numeric exception: division by zero
[]
[traperror ]
> f(0);
Error, (in f) numeric exception: division by zero
However, Maple invokes the debugger when the trapped error occurs.
> g(0);
Error, numeric exception: division by zero
f:
1 1/x
DBG> step
Error, numeric exception: division by zero
g:
3 infinity
DBG> step
[ ]
7.2 Maple Debugger Commands • 371
[f ]
> f(10);
f:
1* x^2
DBG> sin(3.0)
372 • Chapter 7: Debugging Maple Programs
.1411200081
f:
1* x^2
DBG> cont
100
The debugger evaluates any variable names that you use in the ex-
pression in the context of the stopped procedure. Names of parameters
or local variables evaluate to their current values within the procedure.
Names of global variables evaluate to their current values. Environment
variables, such as Digits, evaluate to their values in the stopped proce-
dure’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 ]
> f(3);
f:
2* i^2
DBG> step
1
f:
2* i^2
7.2 Maple Debugger Commands • 373
DBG> (step)
3
f:
2* i^2
DBG> quit
> 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
374 • Chapter 7: Debugging Maple Programs
DBG> cont
17
sumn:
3? sum := sum+i
62
The where debugger command shows you the stack of procedure ac-
tivations. 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
sumn := proc(n)
local i, sum;
1 sum := 0;
2 for i to n do
3? sum := sum+i
end do
end proc
DBG> cont
true
> 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);
7.2 Maple Debugger Commands • 377
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
If you are not interested in the entire history of the nested procedure
calls, then use the numLevels parameter in the call to where to print out
only a certain number of levels.
DBG> where 3
fact: x*fact(x-1)
[2]
fact: x*fact(x-1)
[1]
fact:
2* 1
DBG> quit
showstop();
[f, int ]
Breakpoints in:
f
int
Watched variables:
y in procedure f
Digits
Watched errors:
7.2 Maple Debugger Commands • 379
• For the stoperror command, the quotation marks ("") are not re-
quired.
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 de-
bugger 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 kernel
routines, such as diff and has. These routines are implemented in C
and compiled into the Maple kernel. Debugging information about these
routines is not accessible to Maple since the routines deal with objects at
a level lower than the debugger can access.
Finally, if a procedure contains two identical statements that are ex-
pressions, the debugger cannot determine with certainty the statement at
380 • Chapter 7: Debugging Maple Programs
which execution stopped. If this situation occurs, you can still use the de-
bugger and execution can continue. The debugger merely issues a warning
that the displayed statement number may be incorrect.3
Tracing a Procedure
The simplest tools available for error detection in Maple are the printlevel
global variable, and the trace and tracelast commands. These facili-
ties enable you 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 global variable is set by using the following syntax,
where n is the level to which Maple commands are evaluated.
printlevel := n ;
3
This problem occurs because Maple stores all identical expressions as a single
occurrence of the expression, and the debugger has no way to determine at which
invocation execution stopped.
7.3 Detecting Errors • 381
> f(3);
81
2
> printlevel := 5;
printlevel := 5
> f(3);
y := 9
81
2
printlevel := 10
382 • Chapter 7: Debugging Maple Programs
> f(3);
y := 9
z := 81
162
printlevel := 1
trace(arguments );
f, g
> f(3):
> f(3);
y := 9
z := 81
162
81
2
f, g
> f(3);
81
2
tracelast;
• The first line displays which procedure was called and what parameter
was used.
• 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.
4
You can use debug and undebug as alternate names for trace and untrace.
7.3 Detecting Errors • 385
f := proc(x)
local i, j, k;
i := x ; j = x2 ; seq(k, k = i..j)
end proc
> f(2,3);
Error, (in f) unable to execute seq
> tracelast;
Using Assertions
An assertion is a statement about a procedure that you “assert” to be
true. 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.
5
For more information about garbage collection in Maple, see ?gc.
386 • Chapter 7: Debugging Maple Programs
ASSERT(condition, message );
false
false
At any time during the Maple session, you can confirm whether as-
sertion checking is on by entering the following command.
> kernelopts(ASSERT);
true
6
For more information about kernelopts, see ?kernelopts.
7.3 Detecting Errors • 387
f := proc(x, y)
local i, j;
i := 0 ;
j := 0 ;
while i 6= x do
ASSERT(0 < i, ‘invalid index ‘) ; j := j + y ; i := i + 1
end do;
j
end proc
> f(2,3);
Error, (in f) assertion failed, invalid index
true
f := proc(x)
if x < 0 then WARNING(“the result is complex”) end if ;
sqrt(x)
end proc
> f(-2);
Warning, the result is complex
√
I 2
√
I 2
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 errors can
cause exceptions—for example, attempting to read from a file that doesn’t
exist. Maple has two mechanisms available when such situations arise:
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 ever printed as an error
message, the actual values are specified by the msgParams.
For example, the error message "f has a 2nd argument, x, which
is missing" is specified by the following error statement.
error "%1 has a %-2 argument, %3, which is missing", f, 2, x
A numbered parameter of the form %n displays the nth msgParam
in line-printed notation (i.e., 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 error statement
above is displayed as “2nd”. 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 ele-
ments.
• The name of the procedure in which the exception was raised, or the
constant 0 if the exception was raised at the top-level.
• The msgString.
In this case, msgText is the text of the error message (which is con-
structed from the msgString and optional msgParams of the error state-
ment), and procName is the procedure in which the error occurred. If
7
The actual arguments to the error statement are also assigned to lasterror for
compatibility with older versions of Maple. For more information, see ?traperror.
390 • Chapter 7: Debugging Maple Programs
try tryStatSeq
catch catchStrings : catchStatSeq
finally finalStatSeq
end try
• Neither the exception object nor the catchStrings are evaluated (the
exception object has already been evaluated by the error statement
that produced it).
> try
> result := MethodA(f,x)
> catch "FAIL":
> result := MethodB(f,x)
> end try:
MethodA can abort its computation at any time by executing the state-
ment error "FAIL". The catch clause will catch that exception, and pro-
ceed to try MethodB. If any other error occurs during the execution of
7.3 Detecting Errors • 393
If any exception occurs, it is caught with the catch clause that has no
catchString, and the exception object is written into the file. The excep-
tion is re-raised by executing the error statement with no msgString. In
all cases, the file is closed by executing fclose(f) in the finally clause.
Checking Syntax
Maple’s maplemint command generates a list of semantic errors for a spec-
ified procedure, if any. The semantic errors for which maplemint checks
include parameter name conflicts, local and global variable name conflicts,
394 • Chapter 7: Debugging Maple Programs
maplemint( procedureName );
> maplemint(f);
This code is unreachable:
print(test)
These global variables were declared, but never used:
c
These local variables were used before they were assigned
a value:
a
These variables were used as the same loop variable for
nested loops:
i
7.4 Conclusion
This chapter surveyed a variety of Maple commands that are available to
help you find errors in procedures. In particular, the Maple debugger was
presented as a tool that you can use to find and correct errors.
8 Numerical Programming
in Maple
395
396 • Chapter 8: Numerical Programming in Maple
the machine you are using. The latter is determined by the architecture
of your computer, but offers the advantage of exceptional speed.
3.141592654
You may alter the number of digits either by changing the value of
Digits, or by specifying the number as an index to evalf. Note that
when you specify the number of digits as an index to evalf, the default,
Digits, remains unchanged.
> Digits := 20:
> evalf(Pi);
3.1415926535897932385
> evalf[200](Pi);
3.1415926535897932384626433832795028841\
97169399375105820974944592307816406286\
20899862803482534211706798214808651328\
23066470938446095505822317253594081284\
81117450284102701938521105559644622948\
9549303820
> evalf(sqrt(2));
1.4142135623730950488
8.1 The Basics of evalf • 397
> value(r);
Z 1
3
e(x ) dx
0
> evalf(r);
1.341904418
398 • Chapter 8: Numerical Programming in Maple
In other cases, Maple can find an exact solution, but the form of the
exact solution is almost incomprehensible. The function Beta below is one
of the special functions that appear in mathematical literature.
> q := Int( x^99 * (1-x)^199 / Beta(100, 200), x=0..1/5 );
1/5 99
− x)199
Z
x (1
q := dx
0 B(100, 200)
> value(q);
278522905457805211792552486504343059984\
03849800909690342170417622052715523897\
76190682816696442051841690247452471818\
79720294596176638677971757463413490644\
25727501861101435750157352018112989492\
.
972548449 217741280910371516468873\
84971552115934384961767251671031013243\
12241148610308262514475552524051323083\
13238717840332750249360603782630341376\
82537367383346083183346165228661133571\
76260162148352832620593365691185012466\
14718189600663973041983050027165652595\
68426426994847133755683898925781250000\
1
0
B(100, 200)
> evalf(q);
.3546007367 10−7
Note that the two examples above use the Int command rather than
int for the integration. If you use int, Maple first tries to integrate your
expression symbolically. Thus, when evaluating the commands below,
Maple spends time finding a symbolic answer and then converts it to
a floating-point approximation, rather than performing straight numeri-
cal integration.
8.2 Hardware Floating-Point Numbers • 399
.3546007367 10−7
.333333333333333314
> evalhf( Pi );
3.14159265358979312
d := 15.
e2 − 1
expr := ln(2 )
π
1.40300383684168617
1.40300383684169
Digits := 4658
1.40300383684168617
> evalhf(Digits);
15.
Digits := 10
1.341904418
Newton Iterations
You can use Newton’s method to find numerical solutions to equations.
As section 3.1 describes, if xn is an approximate solution to the equation
f (x) = 0, then xn+1 , given by the following formula, is typically a better
approximation.
f (xn )
xn+1 = xn − 0
f (xn )
This section illustrates how to take advantage of hardware floating-point
arithmetic to calculate Newton iterations.
The iterate procedure below takes a function, f, its derivative, df,
and an initial approximate solution, x0, as input to the equation f (x) = 0.
iteration calculates at most N successive Newton iterations until the
difference between the new approximation and the previous one is small.
The iterate procedure prints out the sequence of approximations so you
can follow the workings of the procedure.
> iterate := proc( f::procedure, df::procedure,
> x0::numeric, N::posint )
> local xold, xnew;
> xold := x0;
> xnew := evalf( xold - f(xold)/df(xold) );
> to N-1 while abs(xnew-xold) > 10^(1-Digits) do
> xold := xnew;
> print(xold);
> xnew := evalf( xold - f(xold)/df(xold) );
> end do;
> xnew;
> end proc:
The procedure below calculates the derivative of f and passes all the
necessary information to iterate.
> Newton := proc( f::procedure, x0::numeric, N::posint )
> local df;
> df := D(f);
> print(x0);
> iterate(f, df, x0, N);
> end proc:
f := x → x2 − 2
1.5
1.416666667
1.414215686
1.414213562
1.414213562
1.5
1.41666666666666674
1.41421568627450988
1.41421356237468987
1.41421356237309514
1.414213562
You may find it surprising that Newton must use software floating-
point arithmetic to find a root of the Bessel function below.
> F := z -> BesselJ(1, z);
F := z → BesselJ(1, z)
3.826493523
3.831702467
3.831705970
3.831705970
The reason is that evalhf does not know about BesselJ and the
symbolic code for BesselJ uses the type command and remember tables,
which evalhf does not allow.
> evalhf( BesselJ(1, 4) );
Error, remember tables are not supported in evalhf
> p(x);
2 + 5 x + 4 x2
155.439999999999997
> det(a);
2 1
a2, 2 −
3 3
If you call det from inside a call to evalhf, Maple uses the value 0
for the undefined entry, a[2,2].
> evalhf( det(a) );
−.333333333333333314
a2, 2
−.333333333333333314
[.666666666666666629 , .750000000000000000]
[.444444444444444420 , 0.]
The evalhf command always returns a single floating-point number,
but the var construct allows you to calculate a whole array of numbers
with one call to evalhf. Section 9.7 illustrates the use of var to calculate
a grid of function values that you can use for plotting.
You can also create arrays of hardware floating-point values directly
with the Array command. Proper use of this command can save significant
8.3 Floating-Point Models in Maple • 407
m × 10e .
Software Floats
Maple’s software floating-point computations are carried out in base 10.
The precision of a computation is determined by the setting of Digits.
The maximum exponent, minimum exponent, and maximum value for
Digits are machine wordsize dependent. You can obtain the values for
these limits from the Maple_floats command.
This software floating-point system is designed as a natural extension
of the industry standard for hardware floating-point computation, known
as IEEE 754. Thus, there are representations for infinity and undefined
(what IEEE 754 calls a "NaN", meaning "Not a Number"). Complex num-
bers are represented by using the standard x + I*y format.
One important feature of this system is that the floating-point rep-
resentation of zero, 0., retains its arithmetic sign in computations. That
is, Maple distinguishes between +0. and -0. when necessary. In most
situations, this difference is irrelevant, but when dealing with functions
such as ln(x), which have a discontinuity across the negative real axis,
preserving the sign of the imaginary part of a number on the negative
real axis is important.
For more intricate applications, Maple implements extensions of the
IEEE 754 notion of a numeric event, and provides facilities for mon-
itoring events and their associated status flags. The "Maple Numerics
Overview" help page is a good starting place to learn more about this
system. See ?numerics.
Roundoff Error
When you perform floating-point arithmetic, whether using software or
hardware floats, you are using approximate numbers rather than pre-
cise real numbers or expressions. Maple can work with exact (symbolic)
expressions. The difference between an exact real number and its floating-
point approximation is called the roundoff error . For example, suppose
you request a floating-point representation of π.
> pi := evalf(Pi);
π := 3.141592654
−.41021 10−9
Roundoff errors arise not only from the representation of input data,
but also as a result of performing arithmetic operations. Each time
you perform an arithmetic operation on two floating-point numbers, the
infinitely-precise result usually will not be representable in the floating-
point number system and therefore the computed result will also have an
associated roundoff error.
For example, suppose you multiply two ten-digit numbers with Digits
= 10. The result can easily have nineteen or twenty digits, but Maple will
only store the first ten digits.
> 1234567890 * 1937128552;
2391516709101395280
.2391516709 1019
Whenever you apply one of the four basic arithmetic operations (ad-
dition, subtraction, multiplication, or division) to two floating-point num-
bers, the result is the correctly rounded representation of the infinitely
precise result, unless overflow or underflow occurs. Of course, Maple may
need to compute an extra digit or two behind the scenes to ensure that
the answer is correct.
Even so, sometimes a surprising amount of error can accumulate, par-
ticularly when subtracting two numbers which are of similar magnitude. In
the calculation below, the accurate sum of x, y, and z is y = 3.141592654.
> x := evalf(987654321);
x := .987654321 109
> y := evalf(Pi);
y := 3.141592654
> z := -x;
z := −.987654321 109
410 • Chapter 8: Numerical Programming in Maple
> x + y + z;
3.1
Digits := 20
> x + y + z;
3.141592654
Suppose you want the name MyConst to represent the following infinite
series:
∞
X (−1)i π i
M yConst = .
2i i!
i=1
You can calculate approximations to the above series in many ways; the
procedure below is one implementation. Note that if ai is the ith term in
the sum, then ai+1 = −ai (π/2)/i gives the next term. You can calculate
an approximation to the series by adding terms until Maple’s model for
software floating-point numbers cannot distinguish the new partial sum
from the previous one. Using numerical analysis, you can prove that this
algorithm calculates Digits accurate digits of MyConst if you use two ex-
tra digits inside the algorithm. Therefore, the procedure below increments
Digits by two and uses evalf to round the result to the proper number
of digits before returning. The procedure does not have to reset the value
of Digits because Digits is an environment variable.
> ‘evalf/constant/MyConst‘ := proc()
> local i, term, halfpi, s, old_s;
> Digits := Digits + 2;
> halfpi := evalf(Pi/2);
> old_s := 1;
> term := 1.0;
> s := 0;
> for i from 1 while s <> old_s do
> term := -term * halfpi / i;
> old_s := s;
> s := s + term;
> end do;
> evalf[Digits-2](s);
> end proc:
−.7921204237
> evalf[40](MyConst);
−.7921204236492380914530443801650212299661
You can express the particular constant, MyConst, in closed form and,
in this case, you can use the closed-form formula to calculate approxima-
tions to MyConst more efficiently.
412 • Chapter 8: Numerical Programming in Maple
> value(%);
e(−1/2 π) (1 − e(1/2 π) )
> expand(%);
1
√ −1
eπ
> evalf(%);
−.7921204237
x − sin(x)
MyFcn := x →
x3
1
MyFcn(0) :=
6
8.4 Extending the evalf Command • 413
v := MyFcn(.000195)
> evalf(v);
.1618368482
.16666666634973617222
1 1 2 1 1
− x + x4 − x6 + O(x8 )
6 120 5040 362880
x2i
ai = (−1)i , i ≥ 0.
(2i + 3)!
Note that ai = −ai−1 x2 /((2i + 2)(2i + 3)). For small values of x, you
can then calculate an approximation to MyFcn(x) by adding terms un-
til Maple’s model for software floating-point numbers cannot distinguish
the new partial sum from the previous one. For larger values of x, catas-
trophic cancellation is not a problem, so you can use evalf to evaluate
the expression. Using numerical analysis, you can prove that this algo-
rithm calculates Digits accurate digits of the function value if you use
three extra digits inside the algorithm. Therefore, the procedure below
increments Digits by three and uses evalf to round the result to the
proper number of digits before returning.
414 • Chapter 8: Numerical Programming in Maple
.1666666663498
You should now recode the symbolic version of MyFcn so that it takes
advantage of ‘evalf/MyFcn‘ if the argument is a floating-point number.
> MyFcn := proc(x::algebraic)
> if type(x, float) then
> evalf(’MyFcn’(x));
> else
> (x - sin(x)) / x^3;
> end if;
> end proc:
1
MyFcn(0) :=
6
Now you can properly evaluate MyFcn with numeric as well as symbolic
arguments.
8.5 Using the Matlab Package • 415
> MyFcn(x);
x − sin(x)
x3
> MyFcn(0.099999999);
.1665833531735
> MyFcn(0.1);
.1665833531700
• det: determinant
• lu: LU decomposition
8.6 Conclusion
The various techniques described in this chapter afford an important ex-
tension to Maple’s programming language and its ability to perform sym-
bolic manipulations. With numerical techniques at your disposal, you can
solve equations which are otherwise unsolvable, investigate the properties
of complicated solutions, and quickly obtain numerical estimates.
Symbolic calculations give precise representations, but in some cases
can be expensive to compute even with such a powerful tool as Maple.
At the other extreme, hardware floating-point arithmetic allows you fast
computation directly from Maple. This involves, however, limited accu-
racy. Software floating-point offers a balance. As well as sometimes being
much faster than symbolic calculations, you also have the option to con-
trol the precision of your calculations, thus exerting control over errors.
Software floating-point calculations and representations mimic the
IEEE 754 standard representation closely, except for the great advan-
tage of arbitrary precision. Because of the similarity with this popular
standard, you can readily apply the knowledge of accumulation of error
and numerical analysis principles that numerous texts and papers contain.
When you need to know that your calculations are precise, this wealth of
information at your disposal should provide you with confidence in your
results.
9 Programming with Maple
Graphics
Maple has a wide range of commands for generating both two- and
three-dimensional plots. For mathematical expressions, you can use li-
brary procedures, such as plot and plot3d, or one of the many spe-
cialized graphics routines found in the plots and plottools packages,
the DEtools package (for working with differential equations), and the
stats package (for statistical data). The input to these commands is typ-
ically one or more Maple formulæ, operators, or functions, along with
information about domains and possibly ranges. In all cases, the graphic
commands allow for the setting of options, specifying such attributes as
coloring, shading, or axes style.
The purpose of this chapter is to reveal the structure of the procedures
that Maple uses to generate graphical output, and allow you to generate
your own graphics procedures. This chapter includes basic information
about argument conventions, setting defaults, and processing of plotting
options. A major part of the material describes the data structures that
Maple uses for plotting, along with various techniques to build such data
structures in order to produce graphics in Maple. In addition, you will see
how some of the existing functions in the plots and plottools packages
produce specific plotting data structures.
417
418 • Chapter 9: Programming with Maple Graphics
then you can plot the surface that p ∗ q determines in the following
manner.
> plot3d( p * q, 0..4*Pi, -2*Pi..2*Pi );
Both cases produce the same three-dimensional plot. In the first ex-
ample, you supply the information that the input is an expression in x
and y by giving the second and third arguments in the form x = range
and y = range , while in the second example, there are no variable names.
Working with formula expressions is simple, but in many cases, func-
tions provide a better mechanism for constructing mathematical func-
tions. The following constructs a mathematical function which, for a given
9.1 Basic Plot Functions • 419
1
0.5
0
–0.5
–1
–6 –6
–4 –4
–2 –2
y 0 2 0 x
2
4 4
6 6
Every plotting procedure allows for optional arguments. You give the
optional information in the form name =option . Some of these options
affect the amount of information concerning the function that you give
to the plotting procedures. The grid option that the Mandelbrot set ex-
ample uses is an instance of using an optional argument. You can use
other options for specifying visual information once you have determined
the graphical points. The type of axes, shading, surface style, line styles,
and coloring are but a few of the options available in this category. Ob-
tain information about all the allowable options for the two-dimensional
and three-dimensional cases using the help pages ?plot,options and
?plot3d,options.
Any graphics routine you create should allow users a similar set of op-
tions. When writing programs that call existing Maple graphics routines,
simply pass the potential optional arguments directly to these routines.
9.2 Programming with Plotting Library Functions • 421
Plotting a Loop
Consider the first problem of plotting a loop from a list of data.
> L1 := [ [5,29], [11,23], [11,36], [9,35] ];
36
34
32
30
28
26
24
5 6 7 8 9 10 11
You may want to write a procedure that also draws a line from the
last to the first point. All you need to do is append the first point in L1
to the end of L1.
> L2 := [ op(L1), L1[1] ];
L2 := [[5, 29], [11, 23], [11, 36], [9, 35], [5, 29]]
> plot( L2 );
422 • Chapter 9: Programming with Maple Graphics
36
34
32
30
28
26
24
5 6 7 8 9 10 11
36
34
32
30
28
26
24
5 6 7 8 9 10 11
Exercise
1. Improve loopplot so that it can handle the empty list as input.
q := (x, y) → cos(2 x)
The structure includes labels x and y for the plane but no label
for the z-axis.
The third example is again the graph of z = xy but this time in
cylindrical coordinates. The PLOT3D structure now contains a mesh of
points that make up the surface, along with the information that the
plotting device should display the surface in a point style.
9.3 Maple’s Plotting Data Structures • 427
0.8
0.6
0.4
0.2
0 0.5 1 1.5 2
and passes it to the Maple interface which determines that this is a plot
data structure. The Maple interface then dismantles the contents and
passes the information to a plot driver which then determines the graph-
ical information that it will render onto the plotting device. In the latest
example, the result is a single line from the origin to the point (2, 1). The
CURVES data structure consists of one or more lists of points each generat-
ing a curve, along with some optional arguments (for example, line style
or line thickness information). Thus, the expression
> n := 200:
> points := [ seq( [2*cos(i*Pi/n), sin(i*Pi/n) ], i=0..n) ]:
> PLOT( CURVES( evalf(points) ) );
428 • Chapter 9: Programming with Maple Graphics
0.8
0.6
0.4
0.2
–2 –1 0 1 2
false
1 1 1 1 1 1
[[ , 0], [ , sin( )], [ , sin( )], [ , 0]]
10 10 10 5 5 5
0.5
0 1 2 3 4 5 6
–0.5
–1
A Sum Plot
You can create procedures that directly build PLOT data structures. For
example, given an unevaluated sum you can compute the partial sums,
and place the values in a CURVES structure.
430 • Chapter 9: Programming with Maple Graphics
You can use the typematch command to pick the unevaluated sum
apart into its components.
> typematch( s, ’Sum’( term::algebraic,
> n::name=a::integer..b::integer ) );
true
The typematch command assigns the parts of the sum to the given
names.
> term, n, a, b;
1
, k, 1, 10
k2
49
36
1.55
1.5
1.45
1.4
1.35
1.3
1.25
2 4 6 8 10
–0.5
–0.55
–0.6
–0.65
–0.7
–0.75
–0.8
5 10 15 20 25
1
0.8
0.6
0.4
0.2
0
0 0
0.5 0.5
1 1
1.5 1.5
2 2
2.5 2.5
3 3
The following procedure creates the sides of a box and colors them
yellow.
> yellowsides := proc(x, y, z, u)
> # (x,y,0) = coordinates of a corner.
> # z = height of box
> # u = side length of box
> POLYGONS(
> [ [x,y,0], [x+u,y,0], [x+u,y,z], [x,y,z] ],
> [ [x,y,0], [x,y+u,0], [x,y+u,z], [x,y,z] ],
> [ [x+u, y,0], [x+u,y+u,0], [x+u,y+u,z], [x+u,y,z] ],
> [ [x+u, y+u,0], [x,y+u,0], [x,y+u,z], [x+u,y+u,z] ],
> COLOR(RGB,1,1,0) );
> end proc:
You can now put the sides and the top inside a PLOT3D structure to
display them.
> PLOT3D( yellowsides(1, 2, 3, 0.5),
> redtop(1, 2, 3, 0.5),
> STYLE(PATCH) );
434 • Chapter 9: Programming with Maple Graphics
Histograms look nice when you enclose them in a box of axes. Axes
are generated using AXESSTYLE.
> PLOT3D( sides, tops, STYLE(PATCH), AXESSTYLE(BOXED) );
0.25
0.2
0.15
0.1
0.05
0
0 0
1 1
2 2
3 3
4 4
3
2.5
2
z
1.5
1
0.5
0
3 1
2.5 1.2
1.4
x2 1.6 y
1.5 1.8
1 2
1
An n × m × 3 hfarray is also allowed as input to MESH.
436 • Chapter 9: Programming with Maple Graphics
1
0.8
0.6
z
0.4
0.2
0 3.5
1 3
0.8 2.5
0.6 2
y 0.4 1.5x
1
0.2 0.5
0 0
All the options available for PLOT are also available for PLOT3D. In ad-
dition, you can also use the GRIDSTYLE, LIGHTMODEL, and AMBIENTLIGHT
options. See ?plot3d,structure for details on the various options to the
PLOT3D structure.
The disk procedure below is similar to line except that you can spec-
ify the number of points that disk should use to generate the disk. There-
fore disk must handle that option, numpoints, separately. The hasoption
command determines whether a certain option is present.
> disk := proc(x::list, r::algebraic)
> # draw a disk of radius r centered at x in 2-D.
> local i, n, opts, vertices;
> opts := [ args[3..nargs] ] ;
> if not hasoption( opts, numpoints, n, ’opts’ )
> then n := 50;
> end if;
> opts := convert(opts, PLOToptions);
> vertices := seq( evalf( [ x[1] + r*cos(2*Pi*i/n),
> x[2] + r*sin(2*Pi*i/n) ] ),
> i = 0..n );
> POLYGONS( [vertices], op(opts) );
> end proc:
0.4
0.2
–1.5 –1 –0.5 0.5 1 1.5
–0.2
–0.4
Note how the options to the individual objects apply only to those
objects.
Plotting Gears
This example shows how you can manipulate plotting data structures to
embed two-dimensional plots into a three-dimensional setting. The pro-
cedure below creates a little piece of the boundary of a two-dimensional
graph of a gear-like structure.
> outside := proc(a, r, n)
> local p1, p2;
> p1 := evalf( [ cos(a*Pi/n), sin(a*Pi/n) ] );
> p2 := evalf( [ cos((a+1)*Pi/n), sin((a+1)*Pi/n) ] );
> if r = 1 then p1, p2;
> else p1, r*p1, r*p2, p2;
> end if
> end proc:
For example
> outside( Pi/4, 1.1, 16 );
0.35
0.3
0.25
0.2
0.15
0.93918 1.01306 1.08695
When you put the pieces together, you get a gear. SCALING(CONSTRAINED),
which corresponds to the option scaling=constrained, is used to ensure
that the gear appears round.
> points := [ seq( outside(2*a, 1.1, 16), a=0..16 ) ]:
> PLOT( CURVES(points), AXESSTYLE(NONE), SCALING(CONSTRAINED) );
You can fill this object using the POLYGONS object. However, you must
be careful, as Maple assumes that the polygons are convex. Hence, you
should draw each wedge-shaped section of the gear as a triangular poly-
gon.
> a := seq( [ [0, 0], outside(2*j, 1.1, 16) ], j=0..15 ):
> b := seq( [ [0, 0], outside(2*j+1, 1, 16) ], j=0..15 ):
> PLOT( POLYGONS(a,b), AXESSTYLE(NONE), SCALING(CONSTRAINED) );
440 • Chapter 9: Programming with Maple Graphics
which input two lists of vertices and join the corresponding vertices from
each list into vertices that make up quadrilaterals. You can create the top
and bottom vertices of the gear embedded into three-dimensional space
as follows.
> faces :=
> seq( double(p,1/2),
> p=[ seq( [ outside(2*a+1, 1.1, 16), [0,0] ],
> a=0..16 ),
> seq( [ outside(2*a, 1,16), [0,0] ], a=0..16 )
> ] ):
Now faces is a sequence of doubled outside values.
> PLOT3D( POLYGONS( faces ) );
9.4 Programming with Plot Data Structures • 441
If you double these points, you get vertices of the polygons making
up the border of the three-dimensional gear.
> bord := border( double( [ seq( outside(2*a+1, 1.1, 16),
> a=0..15 ) ], 1/2) ):
> PLOT3D( seq( POLYGONS(b), b=bord ) );
442 • Chapter 9: Programming with Maple Graphics
To display the gear you need to put these together in a single PLOT3D
structure. Use STYLE(PATCHNOGRID) as a local option to the top and
bottom of the gear so that they do not appear as several triangles.
> PLOT3D( POLYGONS(faces, STYLE(PATCHNOGRID) ),
> seq( POLYGONS(b), b=bord ),
> STYLE(PATCH), SCALING(CONSTRAINED) );
Polygon Meshes
Section 9.3 describes the MESH data structure which you generate when
you use plot3d to draw a parametrized surface. This simple matter in-
volves converting a mesh of points into a set of vertices for correspond-
ing polygons. Using polygons rather than a MESH structure allows you to
modify the individual polygons. The procedure polygongrid creates the
vertices of a quadrangle at the (i, j)th grid value.
> polygongrid := proc(gridlist, i, j)
> gridlist[j][i], gridlist[j][i+1],
> gridlist[j+1][i+1], gridlist[j+1][i];
> end proc:
L := [[[0, 0], [1, 0], [2, 0]], [[0, 1], [1, 1], [2, 1]],
[[0, 2], [1, 2], [2, 2]], [[0, 3], [1, 3], [2, 3]]]
The makePolygongrid procedure creates the POLYGONS structure cor-
responding to L.
> grid1 := makePolygongrid( L );
2.5
1.5
0.5
0 0.5 1 1.5 2
0
–3 –3
–2 –2
–1 –1
0 0
1 1
2 2
3 3
and rotate it at various angles via the functions in the plottools package.
> rotate( %, Pi/4, -Pi/4, Pi/4 );
9.5 Programming with the plottools Package • 445
2
1
0
–1
–2
–3
–2 –2
–1 –1
0 0
1 1
2 2
3
A Pie Chart
You can write a plotting procedure to build a pie chart of a list of inte-
ger data. The piechart procedure below uses the following partialsum
procedure which calculates the partial sums of a list of numbers up to a
given term.
> partialsum := proc(d, i)
> local j;
> evalf( Sum( d[j], j=1..i ) )
> end proc:
For example
> partialsum( [1, 2, 3, -6], 3 );
6.
10
15
8
10
16
12
The AXESSTYLE(NONE) option ensures that Maple does not draw any
axes with the pie chart.
A Dropshadow Procedure
You can use the existing procedures to create other types of plots that
are not part of the available Maple graphics library. For example, the
following procedure computes the three-dimensional plot of a surface,
z = f (x, y), that has a dropshadow projection onto a plane located below
the surface. The procedure makes use of the commands contourplot,
contourplot3d, display from the plots package, and transform from
the plottools package.
> dropshadowplot := proc(F::algebraic, r1::name=range,
> r2::name=range, r3::name=range)
> local minz, p2, p3, coption, opts, f, g, x, y;
>
> # set the number of contours (default 8)
> opts := [args[5..nargs]];
> if not hasoption( opts, ’contours’, coption, ’opts’ )
> then coption := 8;
> end if;
>
> # determine the base of the plot axes
> # from the third argument
> minz := lhs(‘if‘(r3::range, r3, rhs(r3)));
> minz := evalf(minz);
9.5 Programming with the plottools Package • 447
>
>
> # create 2d and 3d contour plots for F.
> p3 := plots[contourplot3d]( F, r1, r2,
> ’contours’=coption, op(opts) );
> p2 := plots[contourplot]( F, r1, r2,
> ’contours’=coption, op(opts) );
>
> # embed contour plot into R^3 via plottools[transform]
> g := unapply( [x,y,minz], x, y );
> f := plottools[transform]( g );
> plots[display]([ f(p2), p3 ]);
> end proc:
2
1
0
–1
–2
–3
–4
–3 –3
–2 –2
–1 –1
y0 0x
1 1
2 2
3 3
Creating a Tiling
The plottools package provides a convenient environment for program-
ming graphical procedures. For example, you can draw circular arcs in a
unit square.
> with(plots): with(plottools):
Warning, the name changecoords has been redefined
Warning, the name arrow has been redefined
You can tile the plane with a and b type rectangles. The following
procedure creates such a m × n tiling using a function, g, to determine
when to use an a-tile and when to use a b-tile. The function g should
return either 0, to use an a-tile, or 1, to use a b-tile.
> tiling := proc(g, m, n)
> local i, j, r, h, boundary, tiles;
>
> # define an a-tile
> r[0] := plottools[arc]( [0,0], 0.5, 0..Pi/2 ),
> plottools[arc]( [1,1], 0.5, Pi..3*Pi/2 );
> # define a b-tile
> r[1] := plottools[arc]( [0,1], 0.5, -Pi/2..0 ),
> plottools[arc]( [1,0], 0.5, Pi/2..Pi );
> boundary := plottools[curve]( [ [0,0], [0,n],
> [m,n], [m,0], [0,0]] );
> tiles := seq( seq( seq( plottools[translate](h, i, j),
> h=r[g(i, j)] ), i=0..m-1 ), j=0..n-1 );
> plots[display]( tiles, boundary, args[4..nargs] );
> end proc:
When you use the same procedure again, the random tiling is differ-
ent.
> tiling( oddeven, 20, 10, scaling=constrained, axes=none);
A Smith Chart
The commands in the plottools package allow for easy creation of such
useful graphs as a Smith Chart, used in microwave circuit analysis.
> smithChart := proc(r)
> local i, a, b, c ;
> a := PLOT( seq( plottools[arc]( [-i*r/4,0],
> i*r/4, 0..Pi ),
> i = 1..4 ),
> plottools[arc]( [0,r/2], r/2,
> Pi-arcsin(3/5)..3*Pi/2 ),
> plottools[arc]( [0,r], r, Pi..Pi+arcsin(15/17) ),
> plottools[arc]( [0,2*r], 2*r,
> Pi+arcsin(3/5)..Pi+arcsin(63/65) ),
> plottools[arc]( [0,4*r], 4*r,
> Pi+arcsin(15/17)..Pi+arcsin(63/65) )
> );
> b := plottools[transform]( (x, y) -> [x,-y] )(a);
> c := plottools[line]( [ 0, 0], [ -2*r, 0] ):
> plots[display]( a, b, c, axes = none,
> scaling = constrained,
9.5 Programming with the plottools Package • 451
> args[2..nargs] );
> end proc:
Exercise
1. Make a Smith Chart by building appropriate circular arcs above the
axes, creating a copy reflected on the axis (using the transform pro-
cedure), and then adding a final horizontal line. The parameter r
denotes the radius of the largest circle. Modifying the smithChart
procedure to add text to mark appropriate grid markers is a simple
operation.
1 5 3 5
POLYGONS([[ , ], [0, 2], [2, 2], [ , ]],
2 3 2 3
3 5 2 2 1 5
[[ , ], [2, 2], [1, 0], [1, ]], [[1, ], [1, 0], [0, 2], [ , ]],
2 3 3 3 2 3
STYLE(PATCHNOGRID )), CURVES(
1 5 3 5 2 1 5
[[0, 2], [2, 2], [1, 0], [0, 2]], [[ , ], [ , ], [1, ], [ , ]])
2 3 2 3 3 2 3
Use the display command from the plots package to show the tri-
angle.
> plots[display]( %, color=red );
1.5
0.5
0 0.5 1 1.5 2
9.5 Programming with the plottools Package • 453
As a second example, you can take a polygon and raise or lower its
barycenter.
> stellateFace := proc( vlist::list, aspectRatio::numeric )
> local apex, i, n;
>
> n := nops(vlist);
> apex := add( i, i = vlist ) * aspectRatio / n;
> POLYGONS( seq( [ apex, vlist[i],
> vlist[modp(i, n) + 1] ],
> i=1..n) );
> end proc:
The stellateFace procedure creates three polygons, one for each side
of the triangle.
> stellateFace( triangle, 1 );
454 • Chapter 9: Programming with Maple Graphics
1 1 1
POLYGONS([[ , , ], [1, 0, 0], [0, 1, 0]],
3 3 3
1 1 1 1 1 1
[[ , , ], [0, 1, 0], [0, 0, 1]], [[ , , ], [0, 0, 1], [1, 0, 0]])
3 3 3 3 3 3
Since these POLYGONS belong in three-dimensional space, you must
put them inside a PLOT3D structure to display them.
> PLOT3D( % );
You can then use the commands for manipulation of polygons to alter
the view of the Klein bottle.
> display( seq( cutout(k, 3/4), k=kleinpoints() ),
> scaling=constrained );
456 • Chapter 9: Programming with Maple Graphics
The input, F, is a list of size two, giving the functions that make up the
horizontal and vertical components of the vector field. The arguments r1
and r2 describe the domain grid of the vectors. The three arguments F,
r1, and r2 are similar in form to the input you need to use for plot3d.
Similarly, the optional information includes any sensible specification that
plot or plot3d allows. Thus, options of the form grid = [m,n ], style
= patch, and color = colorfunction are valid options.
The first problem is to draw a vector. Let [x, y] represent a point, the
starting point of the arrow, and [a, b], the components of the vector. You
can determine the shape of an arrow by three independent parameters,
t1, t2, and t3. Here t1 denotes the thickness of the arrow, t2 the thickness
of the arrow head, and t3 the ratio of the length of the arrow head in
comparison to the length of the arrow itself.
The procedure arrow below from the plottools package constructs
seven vertices of an arrow. It then builds the arrow by constructing two
polygons: a triangle (spanned by v5 , v6 , and v7 ) for the head of the arrow
and a rectangle (spanned by v1 , v2 , v3 , and v4 ) for the tail; it then removes
9.6 Example: Vector Field Plots • 457
boundary lines by setting the style option inside the polygon structure.
It also constructs the boundary of the entire arrow via a closed curve
through the vertices.
> myarrow := proc( point::list, vect::list, t1, t2, t3)
> local a, b, i, x, y, L, Cos, Sin, v, locopts;
>
> a := vect[1]; b := vect[2];
> if has( vect, ’undefined’) or (a=0 and b=0) then
> RETURN( POLYGONS( [ ] ) );
> end if;
> x := point[1]; y := point[2];
> # L = length of arrow
> L := evalf( sqrt(a^2 + b^2) );
> Cos := evalf( a / L );
> Sin := evalf( b / L);
> v[1] := [x + t1*Sin/2, y - t1*Cos/2];
> v[2] := [x - t1*Sin/2, y + t1*Cos/2];
> v[3] := [x - t1*Sin/2 - t3*Cos*L + a,
> y + t1*Cos/2 - t3*Sin*L + b];
> v[4] := [x + t1*Sin/2 - t3*Cos*L + a,
> y - t1*Cos/2 - t3*Sin*L + b];
> v[5] := [x - t2*Sin/2 - t3*Cos*L + a,
> y + t2*Cos/2 - t3*Sin*L + b];
> v[6] := [x + a, y + b];
> v[7] := [x + t2*Sin/2 - t3*Cos*L + a,
> y - t2*Cos/2 - t3*Sin*L + b];
> v := seq( evalf(v[i]), i= 1..7 );
>
> # convert optional arguments to PLOT data structure form
> locopts := convert( [style=patchnogrid,
> args[ 6..nargs ] ],
> PLOToptions );
> POLYGONS( [v[1], v[2], v[3], v[4]],
> [v[5], v[6], v[7]], op(locopts) ),
> CURVES( [v[1], v[2], v[3], v[5], v[6],
> v[7], v[4], v[1]] );
> end proc:
Note that you must build the polygon structure for the arrow in two
parts, because each polygon must be convex. In the special case where the
vector has both components equal to zero or an undefined component,
such as a value resulting from a non-numeric value (for example, a complex
value or a singularity point), the myarrow procedure returns a trivial
polygon. Here are four arrows.
> arrow1 := PLOT(myarrow( [0,0], [1,1], 0.2, 0.4, 1/3,
> color=red) ):
> arrow2 := PLOT(myarrow( [0,0], [1,1], 0.1, 0.2, 1/3,
> color=yellow) ):
> arrow3 := PLOT(myarrow( [0,0], [1,1], 0.2, 0.3, 1/2,
> color=blue) ):
458 • Chapter 9: Programming with Maple Graphics
1. 1.
0.0. 1. 0.0. 1.
1. 1.
0.0. 1. 0.0. 1.
For the conversion to a grid of numerical points, you can take advan-
tage of the extendibility of Maple’s convert command. The procedure
‘convert/grid‘ below takes a function f as input and evaluates it over
the grid which r1, r2, m, and n specify.
> ‘convert/grid‘ := proc(f, r1, r2, m, n)
> local a, b, i, j, dx, dy;
> # obtain information about domain
> a,b,dx,dy := domaininfo( r1, r2, m, n );
> # output grid of function values
> [ seq( [ seq( evalf( f( a + i*dx, b + j*dy ) ),
> i=0..(m-1) ) ], j=0..(n-1) ) ];
> end proc:
Now you can evaluate the undefined function, f , on a grid as follows.
> convert( f, grid, 1..2, 4..6, 3, 2 );
With these utility functions in place, you are ready to make the first
vectorfieldplot command by putting them all together.
> vectorfieldplot := proc(F, r1, r2, m, n)
> local vect1, vect2, a, b, dx, dy;
>
> # Generate each component over the grid of points.
> vect1 := convert( F[1], grid, r1, r2 ,m, n );
> vect2 := convert( F[2], grid, r1, r2 ,m, n );
>
> # Obtain the domain grid information from r1 and r2.
> a,b,dx,dy := domaininfo(r1, r2, m, n);
>
> # Generate the final plot structure.
> generateplot(vect1, vect2, m, n, a, b, dx, dy)
> end proc:
3
2.5
2
1.5
1
0.5
>
> n := nops( F ); m := nops( F[1] );
> # Generate the 1st and 2nd components of F.
> vect1 := map( u -> map( v -> evalf(v[1]) , u) , F);
> vect2 := map( u -> map( v -> evalf(v[2]) , u) , F);
>
> # Generate the final plot structure.
> generateplot(vect1, vect2, m, n, 1, 1, m-1, n-1)
> end proc:
2.2
2
1.8
1.6
1.4
1.2
1
1 2 3 4 5
[0...3.14159265358979, 0...3.14159265358979, [
[0., 0., 0.],
[0., −.975367972083633571, −.430301217000074065]]]
When xy > 0 and ln(−xy) is complex, the grid contains the value
undefined.
> convert( (x,y) -> log(-x*y), ’gridpoints’,
> 1..2, -2..1, grid=[2,3] );
3
2.5
2
1.5
1
0.5
3
2.5
2
1.5
1
0.5
>
> a,b,dx,dy := domaininfo(v[1], v[2], m, n);
>
> # Determine the function used for coloring the arrows.
> opts := [ args[ 4..nargs] ];
> if not hasoption( opts, color, colorinfo, ’opts’ ) then
> # Default coloring will be via
> # the scaled magnitude of the vectors.
> L := max( seq( seq( v1[j][i]^2 + v2[j][i]^2,
> i=1..m ), j=1..n ) );
> colorinfo := ( F[1]^2 + F[2]^2 )/L;
> end if;
>
> # Generate the information needed to color the arrows.
> colorinfo := convert( colorinfo, ’colorgrid’,
> r1, r2, op(opts) );
>
> # Get all the norms of the vectors using zip.
> norms := zip( (x,y) -> zip( (u,v)->
> if u=0 and v=0 then 1 else sqrt(u^2 + v^2) end if,
> x, y), v1, v2);
> # Normalize v1 and v2 (again using zip ).
> v1 := zip( (x,y) -> zip( (u,v)-> u/v, x, y),
> v1, norms );
>
> v2 := zip( (x,y) -> zip( (u,v)-> u/v, x, y),
> v2, norms );
>
> # Generate scaling information and plot data structure.
> xscale := dx/2.0; yscale := dy/2.0;
> mscale := max(xscale, yscale);
>
> PLOT( seq( seq( myarrow(
> [ a + (i-1)*dx - v1[j][i]*xscale/2,
> b + (j-1)*dy - v2[j][i]*yscale/2 ],
> [ v1[j][i]*xscale, v2[j][i]*yscale ],
> mscale/4, mscale/2, 1/3,
> ’color’=colorinfo[j][i]
> ), i=1..m ), j=1..n ) );
> end proc:
With this new version you can obtain the following plots.
> vectorfieldplot( [y*cos(x*y), x*sin(x*y)],
> x=0..Pi, y=0..Pi,grid=[15,20] );
466 • Chapter 9: Programming with Maple Graphics
3
2.5
2
1.5
1
0.5
3
2.5
2
1.5
1
0.5
Other vector field routines can be derived from the routines above.
For example, you can also write a complex vector field plot that takes
complex number locations and complex number descriptions of vectors
as input. You simply need to generate the grid of points in an alternate
manner.
In the plottools package, there is the arrow function, which gener-
ates arrows and vectors. This function is more versatile than the proce-
dures described in this section.
obvious task. You must deal with efficiency, error conditions, and non-
numeric output. You can handle the case where the input is a formula
in two variables in the same way as in the ribbonplot procedure from
section 9.2. Thus, for simplicity of presentation, this section avoids this
particular case.
The goal is to compute an array of values for f at each point on a
m × n rectangular grid. That is, at the locations
1
f := (x, y) →
sin(x y)
fnum := proc(_X , _Y )
local err ;
err := traperror(evalhf(f(_X , _Y ))) ;
if type([err ], [numeric]) then err
else
err := traperror(evalf(f(_X , _Y ))) ;
if type([err ], [numeric]) then err else undefined end if
end if
end proc
The above procedure, which is the result of this conversion, attempts
to calculate the numerical values as efficiently as possible. Hardware
floating-point arithmetic, although of limited precision, is more efficient
than software floating-point and is frequently sufficient for plotting. Thus,
fnum tries evalhf first. If evalhf is successful, it returns a numeric result;
468 • Chapter 9: Programming with Maple Graphics
1.18839510577812123
However, if you instead try to evaluate this same function at (0, 0),
Maple informs you that the function is undefined at these coordinates.
> fnum(0,0);
undefined
This evalgrid procedure is purely symbolic and does not handle error
conditions.
> A := array(1..2, 1..2):
> evalgrid( f, ’A’, 1, 2, 1, 2, 2, 2 ):
> eval(A);
1 1
sin(1) 3
sin( )
2
1 1
3 9
sin( ) sin( )
2 4
> a, b, c, d, m, n );
> end if;
> eval(z);
> end proc:
[0. , 0. , 0.]
[0. , .997494986604054445 , .141120008059867213]
If you ask for more digits than hardware floating-point arithmetic
can provide, then gridpoints must always use software floating-point
operations.
> Digits := 22:
> gridpoints( (x,y) -> sin(x*y) , 0..3, 0..3, 2, 3 );
[0. , 0. , 0.]
[0. , .9974949866040544309417 ,
.1411200080598672221007]
> Digits := 10:
9.8 Animation • 471
9.8 Animation
Maple has the ability to generate animations in either two or three dimen-
sions. As with all of Maple’s plotting facilities, such animations produce
user-accessible data structures. Data structures of the following type rep-
resent animations.
or
[1.998172852, .06977773357]
1.2
1
0.8
0.6
0.4
0.2
0 0.5 1 1.5 2
You can now make an animation. Make each frame consist of the
polygon spanned by the origin, (0, 0), and the sequence of points on the
curve.
> frame := n -> [ POLYGONS([ [ 0, 0 ],
> seq( points(t), t = 0..60*n) ],
> COLOR(RGB, 1.0/n, 1.0/n, 1.0/n) ) ]:
The animation consists of six frames.
> PLOT( ANIMATE( seq( frame(n), n = 1..6 ) ) );
The display command from the plots package can show an anima-
tion in static form.
> with(plots):
Warning, the name changecoords has been redefined
1.00 1. 1.
.50 0.0. 0.0.
2. 2.
0.0. 2. –1. –1.
The Maple library provides three methods for creating animations: the
animate and animate3d commands in the plots package, or the display
command with the insequence = true option set. For example, you can
show how a Fourier series approximates a function, f , on an interval
[a, b] by visualizing the function and successive approximations as the
number of terms increase with each frame. You can derive the nth partial
n
X 2π
sum of the Fourier series by using fn (x) = c0 /2 + ck cos( kx) +
b−a
k=1
2π
sk sin( kx), where
b−a
Z b ² ³
2 2π
ck = f (x) cos kx dx
b−a a b−a
and Z b ² ³
2 2π
sk = f (x) sin kx dx.
b−a a b−a
The fourierPicture procedure below first calculates and plots the kth
Fourier approximation for k up to n. Then fourierPicture generates an
animation of these plots, and finally it adds a plot of the function itself
as a backdrop.
> fourierPicture :=
> proc( func, xrange::name=range, n::posint)
> local x, a, b, l, k, j, p, q, partsum;
>
> a := lhs( rhs(xrange) );
> b := rhs( rhs(xrange) );
> l := b - a;
> x := 2 * Pi * lhs(xrange) / l;
>
> partsum := 1/l * evalf( Int( func, xrange) );
> for k from 1 to n do
> # Generate the terms of the Fourier series of func.
9.8 Animation • 475
You can now use fourierPicture to see, for example, the first six
Fourier approximations of ex .
> fourierPicture( exp(x), x=0..10, 6 ):
2. 2.
1. 2.
1. 1.
–2. 0.
–1.0.1.x2.3. 0.0.1.x2.3.
–1. –2. 0.0.1.x2.3. –2.
–1. –1.
–1. –1.
–2. –2.
–2.
2. 2. 2.
1. 1. 1.
0.0.1.x2.3. –2. 0.0.1.x2.3. –2. 0.0.1.x2.3.
–1.
–2.
–1. –1.
–1. –1. –1.
–2. –2. –2.
You can also create similar animations with other series approxima-
tions, such as Taylor, Padé, and Chebyshev–Padé, with the generalized
series structures that Maple uses.
Animation sequences exist in both two and three dimensions. The
procedure below ties a trefoil knot by using the tubeplot function in the
plots package.
> TieKnot := proc( n:: posint )
> local i, t, curve, picts;
> curve := [ -10*cos(t) - 2*cos(5*t) + 15*sin(2*t),
> -15*cos(2*t) + 10*sin(t) - 2*sin(5*t),
> 10*cos(3*t) ]:
> picts := [ seq( plots[tubeplot]( curve,
> t=0..2*Pi*i/n, radius=3),
> i=1..n ) ];
> plots[display]( picts, insequence=true, style=patch);
> end proc:
You can combine the graphical objects from the plottools pack-
age with the display in-sequence option to animate physical objects in
motion. The springPlot procedure below creates a spring from a three-
dimensional plot of a helix. springPlot also creates a box and a copy of
this box and moves one of the boxes to various locations depending on a
value of u. For every u, you can locate these boxes above and below the
spring. Finally springPlot makes a sphere and translates it to locations
above the top of the top box with the height again varying with a param-
eter. Finally, it produces the entire animation by organizing a sequence
of positions and showing them in sequence by using display.
> springPlot := proc( n )
> local u, curve, springs, box, tops, bottoms,
> helix, ball, balls;
> curve := (u,v) -> spacecurve(
> [cos(t), sin(t), 8*sin(u/v*Pi)*t/200],
> t=0..20*Pi,
> color=black, numpoints=200, thickness=3 ):
> springs := display( [ seq(curve(u,n), u=1..n) ],
> insequence=true ):
> box := cuboid( [-1,-1,0], [1,1,1], color=red ):
> ball := sphere( [0,0,2], grid=[15, 15], color=blue ):
> tops := display( [ seq(
> translate( box, 0, 0, sin(u/n*Pi)*4*Pi/5 ),
> u=1..n ) ], insequence=true ):
> bottoms := display( [ seq( translate(box, 0, 0, -1),
> u=1..n ) ], insequence=true ):
> balls := display( [ seq( translate( ball, 0, 0,
> 4*sin( (u-1)/(n-1)*Pi ) + 8*sin(u/n*Pi)*Pi/10 ),
> u=1..n ) ], insequence=true ):
> display( springs, tops, bottoms, balls,
> style=patch, orientation=[45,76],
> scaling=constrained );
> end proc:
The code above uses the short names of the commands from the plots
and plottools packages in order to improve readability. You must either
use long names or remember to load these two packages before using
springPlot.
> with(plots): with(plottools):
> springPlot(6);
> display( springPlot(6) );
478 • Chapter 9: Programming with Maple Graphics
Although usually less convenient, you may also specify the color at-
tributes at the lower level of graphics primitives. At the lowest level, you
can accomplish a coloring of a graphical object by including a COLOUR
function as one of the options inside the object.
> PLOT( POLYGONS( [ [0,0], [1,0], [1,1] ],
> [ [1,0], [1,1], [2,1], [2,0] ],
> COLOUR(RGB, 1/2, 1/3, 1/4 ) ) );
9.9 Programming with Color • 479
0.8
0.6
0.4
0.2
0 0.5 1 1.5 2
You can use different colors for each polygon via either
or
Thus, the following two PLOT structures represent the same picture of
a red and a green triangle.
> PLOT( POLYGONS( [ [0,0], [1,1], [2,0] ],
> COLOUR( RGB, 1, 0, 0 ) ),
> POLYGONS( [ [0,0], [1,1], [0,1] ],
> COLOUR( RGB, 0, 1, 0 ) ) );
0.8
0.6
0.4
0.2
0 0.5 1 1.5 2
Blue=0
1
0.8
0.6
0.4
0.2
You can use animation to vary the blue component as well. The
colormaps procedure below uses animation to generate an m × n × f
color table.
> colormaps := proc(m, n, f)
> local t;
> PLOT( ANIMATE( seq( [ colormap(m, n, t/(f-1)) ],
> t=0..f-1 ) ),
> AXESLABELS("Red", "Green") );
> end proc:
You can visualize the color scale for HUE coloring as follows.
> points := evalf( seq( [ [i/50, 0], [i/50, 1],
> [(i+1)/50, 1], [(i+1)/50, 0] ],
> i=0..49)):
For example
> c := CURVES( [ [0,0], [1,1], [2,2], [3,3] ],
> [ [2,0], [2,1], [3,1] ] );
CURVES([[0, 0], [1, 1]], [[1, 1], [2, 2]], [[2, 2], [3, 3]],
1 2
COLOUR(HUE , 0, , )), CURVES([[2, 0], [2, 1]],
3 3
[[2, 1], [3, 1]], COLOUR(HUE , 0, 1))
You can then map such a procedure over all CURVES structures of an
existing plot structure to provide the desired coloring for each curve.
> addcolor := proc( aplot )
> local recolor;
> recolor := x -> if op(0,x)=CURVES then
484 • Chapter 9: Programming with Maple Graphics
> addCurvecolor(x)
> else x end if;
> map( recolor, aplot );
> end proc:
0.5
0 1 2 3 4 5 6
x
–0.5
–1
If you add color to two curves simultaneously, the two colorings are
independent.
> q := plot( cos(2*x) + sin(x), x=0..2*Pi ):
> addcolor( plots[display](p, q) );
1
0.5
1 2 3x 4 5 6
0
–0.5
–1
–1.5
–2
You can easily alter the coloring of an existing plot by using coloring
functions. Such coloring functions should either be of the form CHue : R2 →
[0, 1] (for Hue coloring) or of the form CRGB : R2 → [0, 1] × [0, 1] × [0, 1].
The example above uses the color function CHue (x, y) = y/ max(yi ).
1
0.5
0
–0.5
–1
–3 –3
–2 –2
–1 –1
y0 0x
1 1
2 2
3 3
9.10 Conclusion
In this chapter, you have seen how you can make graphics procedures
based on the commands plot and plot3d, as well as the commands found
in the plots and plottools packages. However, for ultimate control, you
must create PLOT and PLOT3D data structures directly; these are the prim-
itive specifications of all Maple plots. Inside the PLOT and PLOT3D data
structures you can specify points, curves, and polygons, as well as grids
of values and meshes of points. You have also seen how to handle plot op-
tions, create numerical plotting procedures, work with grids and meshes,
manipulate plots and animations, and apply non-standard coloring to
your graphics.
488 • Chapter 9: Programming with Maple Graphics
10 Input and Output
In a real application, this list would have been generated by a Maple com-
mand you executed or by a Maple procedure you wrote. In this example,
the list was simply typed in as you see it above.
If you want to use some other program (like a presentation graphics
program, or perhaps a custom C program) to process data that Maple
489
490 • Chapter 10: Input and Output
has generated, then you often need to save the data to a file in a format
that the other program recognizes. Using the I/O library, you will find it
easy to write such data to a file.
> for xy in A do fprintf("myfile", "%d %e\n", xy[1], xy[2]) end do:
> fclose("myfile");
If you print the file myfile, or view it with a text editor, it looks like this:
0 0.000000e-01
1 8.427008e-01
2 9.953223e-01
3 9.999779e-01
4 1.000000e+00
5 1.000000e+00
The fprintf command wrote each pair of numbers to the file. This com-
mand takes two or more arguments, the first of which specifies the file
that Maple is to write, and the second of which specifies the format for
the data items. The remaining arguments are the actual data items that
Maple is to write.
In the example above, the file name is myfile. The first time a given
file name appears as an argument to fprintf (or any of the other output
commands described later), the command creates the file if it does not
already exist, and prepares (opens) it for writing. If the file exists, the
new version overwrites the old one. You can override this behavior (for
example, if you want to append to an already existing file) by using the
fopen command, described later.
The format string, "%d %e\n", specifies that Maple should write the
first data item as a decimal integer (%d), and the second data item in
Fortran-like scientific notation (%e). A single space should separate the
first and second data items, and a line break (\n) should follow the second
data item (to write each pair of numbers on a new line). By default, as
in our example, Maple rounds floating-point numbers to six significant
digits for output. You can specify more or fewer digits by using options
to the %e format. The section on fprintf describes these options in more
detail.
When you are finished writing to a file, you must close it. Until you
close a file, the data may or may not actually be in the file, because output
is buffered under most operating systems. The fclose command closes a
file. If you forget to close a file, Maple automatically closes it when you
exit.
10.1 A Tutorial Example • 491
For a simple case like the one presented here, writing the data to a
file by using the writedata command is easier.
> writedata("myfile2", A, [integer,float]):
The writedata command performs all the operations of opening the file,
writing the data in the specified format, an integer and a floating-point
number, and closing the file. However, writedata does not provide the
precise formatting control that you may need in some cases. For this, use
fprintf directly.
In some applications, you may want to read data from a file. For
example, some data acquisition software may supply data that you may
want to analyze. Reading data from a file is almost as easy as writing to
it.
> A := [];
A := []
> do
> xy := fscanf("myfile2", "%d %e");
> if xy = 0 then break end if;
> A := [op(A),xy];
> end do;
492 • Chapter 10: Input and Output
xy := [0, 0.]
A := [[0, 0.]]
xy := [1, .8427007929]
A := [[0, 0.], [1, .8427007929]]
xy := [2, .995322265]
A := [[0, 0.], [1, .8427007929], [2, .995322265]]
xy := [3, .9999779095]
xy := [4, .9999999846]
xy := [5, 1.000000000]
xy := []
> fclose("myfile2");
The first time you call fscanf with a given file name, Maple prepares
(opens) the file for reading. If it does not exist, Maple generates an error.
The second line of the loop checks if fscanf returned 0 to indicate
the end of the file, and breaks out of the loop if it has. Otherwise, Maple
appends the pair of numbers to the list of pairs in A. (The syntax A :=
[op(A),xy] tells Maple to assign to A a list consisting of the existing
elements of A, and the new element xy.)
As when you wrote to a file, you can read from a file more easily by
using the readdata command.
> A := readdata("myfile2", [integer,float]);
them, but buffered files are usually faster. In buffered files, Maple collects
characters in a buffer and writes them to a file all at once when the buffer
is full or the file is closed. Raw files are useful when you wish to explicitly
take advantage of knowledge about the underlying operating system, such
as the block size on the disk. For general use, you should use buffered files,
and they are used by default by most of the I/O library commands.
Commands that provide information about I/O status use the identi-
fiers STREAM and RAW to indicate buffered and unbuffered files, respectively.
The fileName specifies the name of the file to open. This name is specified
as a string, and follows the conventions that the underlying operating
system uses. The accessMode must be one of READ, WRITE, or APPEND,
indicating whether you should initially open the file for reading, writing,
or appending. The optional fileType is either TEXT or BINARY.
If you try to open the file for reading and it does not exist, fopen
generates an error.
10.4 File Manipulation Commands • 497
If you try to open the file for writing and it does not exist, Maple
first creates it. If it does exist and you specify WRITE, Maple truncates the
file to zero length; if you specified APPEND, subsequent calls to commands
that write to the file append to it.
Call the open command as follows.
The arguments to open are the same as those to fopen, except that you
cannot specify a fileType (TEXT or BINARY). Maple opens an unbuffered
file with type BINARY.
Both fopen and open return a file descriptor. Use this descriptor to
refer to the file for subsequent operations. You can still use the file’s name,
if you desire.
When you have finished with a file, you should tell Maple to close
it. This ensures that Maple actually writes all information to the disk. It
also frees up resources of the underlying operating system, which often
imposes a limit on the number of files that you can open simultaneously.
Close files by using the fclose or close commands. These two com-
mands are equivalent, and you can call them as follows.
fclose( fileIdentifier )
close( fileIdentifier )
The fileIdentifier is the name or descriptor of the file you wish to close.
Once you close a file, any descriptors referring to the file are no longer
valid.
> f := fopen("testFile.txt",WRITE):
> fclose(f);
36
feof( fileIdentifier )
The fileIdentifier is the name or descriptor of the file that you wish to
query. If you give a file name, and that file is not yet open, Maple opens
it in READ mode with type BINARY.
The feof command returns true if and only if you have reached the
end of the file during the most recent readline, readbytes, or fscanf
operation. Otherwise, feof returns false. This means that if 20 bytes
remain in a file and you use readbytes to read these 20 bytes, then feof
10.4 File Manipulation Commands • 499
still returns false. You only encounter the end-of-file when you attempt
another read.
iostatus()
The iostatus command returns a list. The list contains the following
elements:
iostatus()[1] The number of files that the Maple I/O library is cur-
rently using.
iostatus()[2] The number of active nested read commands (when read
reads a file which itself contains a read statement).
iostatus()[3] The upper bound on iostatus()[1] + iostatus()[2]
that the underlying operating system imposes.
iostatus()[n ] for n > 3. A list giving information about a file currently
in use by the Maple I/O library.
When n > 3, the lists that iostatus()[n ] return each contain the
following elements:
Removing Files
Many files are solely for temporary use. Often, you no longer need such
files when you complete your Maple session and thus, you should remove
them. Use the fremove command to do this.
500 • Chapter 10: Input and Output
fremove( fileIdentifier )
The fileIdentifier is the name or descriptor of the file you wish to remove.
If the file is currently open, Maple closes it before removing it. If the file
does not exist, Maple generates an error.
To remove a file without knowing whether it exists or not, use a
try/catch statement to trap the error that fremove might create.
> try fremove("myfile.txt") catch: end try:
readline( fileIdentifier )
The fileIdentifier is the name or descriptor of the file that you wish to
read. For compatibility with earlier versions of Maple, you can omit the
fileIdentifier, in which case Maple uses default. Thus readline() and
readline(default) are equivalent.
If you use -1 as the fileIdentifier, Maple also takes input from the
default stream, except that Maple’s command line preprocessor runs
on all input lines. This means that lines beginning with “!” pass to the
operating system instead of returning through readline, and that lines
beginning with “?” translate to calls to the help command.
If you call readline with a file name, and that file is not yet open,
Maple opens it in READ mode as a TEXT file. If readline returns 0 (indi-
cating the end of the file) when called with a file name, it automatically
closes the file.
The following example defines a Maple procedure which reads a text
file and displays it on the default output stream.
10.5 Input Commands • 501
Formatted Input
The fscanf and scanf commands read from a file, parsing numbers and
substrings according to a specified format. The commands return a list of
these parsed objects. If no more characters remain in the file when you
call fscanf or scanf, they return 0 instead of a list, indicating that it
has reached the end of the file.
Call the fscanf and scanf commands as follows.
The fileIdentifier is the name or descriptor of the file you wish to read.
A call to scanf is equivalent to a call to fscanf with default as the
fileIdentifier.
If you call fscanf with a file name, and that file is not yet open, Maple
opens it in READ mode as a TEXT file. If fscanf returns 0 (indicating the
end of the file) when you call it with a file name, Maple automatically
closes the file.
The format specifies how Maple is to parse the input. The format is
a Maple string made up of a sequence of conversion specifications, that
may be separated by other characters. Each conversion specification has
the following format, where the brackets indicate optional components.
The “%” symbol begins the conversion specification. The optional “*”
indicates that Maple is to scan the object, but not return it as part of the
result. It is discarded.
The optional width indicates the maximum number of characters to
scan for this object. You can use this to scan one larger object as two
smaller objects.
The optional modifiers are used to indicate the type of the value to
be returned:
The z format scans the real part, followed by a the character specified
by c, followed by the imaginary part. The Z format scans the real
part, followed by a “+” or “-” sign, followed by the imaginary part,
followed by a string of character corresponding to the current setting
of interface(imaginaryunit).
The z and Z options can result in one of the few conditions in which
scanf will raise an exception. If scanf is part way through scanning a
complex value (for example, the real part has already been successfully
scanned), and is unable to finish scanning the remainder (for exam-
ple, there is no imaginary part after the real part), scanf will raise
an exception of the form " ‘%1‘ expected in input for complex
format ", where %1 will be replaced by the expected character (for
example, a comma).
The code indicates the type of object you wish to scan. It determines
the type of object that Maple returns in the resulting list. The code can
be one of the following:
scanf is looking for the first digit of a number, it assumes that one
of these special values has been found, and proceeds to look for the
subsequent nf or aN. If the rest of the special value is not found, an
exception is raised.
he, hf, or hg These are special formats for reading one or two-dimensional
numeric arrays. In general, such arrays should be read by using the
more sophisticated functionality provided by the {} format, but the
he, hf, and hg formats are provided for backward compatibility with
hfarrays, and provide some intelligence in automatically dealing with
a variety of textual layouts of such arrays.
The following input must make up a one or two-dimensional array
of floating-point (or integer) values. Characters encountered during
scanning are categorized into three classes: numeric, separator, and
terminator. All the characters that can appear within a number (the
digits, decimal point, signs, E, e, D, and d) are numeric. Any white
space, commas, or square brackets are separators. A square bracket
not immediately followed by a comma, and any other character, are
terminators. If a backslash is encountered, it and the following char-
acter are ignored completely.
The dimensions of the array are determined by the number of lines
read, and the number of values in the first line. If either of these is 1,
or if the number of rows multiplied by the number of columns does
not equal the total number of values read, a one-dimensional array is
produced.
The definition of “the first line” is “everything read up to the first
line break that does not immediately follow a comma or a backslash,
or up to the first closing square bracket that is immediately followed
by a comma”.
The kinds of things that can be read this way include anything that
was written by the corresponding printf, “typical” tables of numbers,
and lprinted or saved (in text form) Maple lists and lists of lists.
The result is returned as an hfarray of one or two dimensions.
a Maple collects and parses the next non-blank characters, up to but not
including the following blank characters (or the end of the string). An
unevaluated Maple expression is returned.
[. . .] The characters between “[” and “]” become a list of characters that
are acceptable as a character string. Maple scans characters from the
input until it encounters one that is not in the list. The scanned
characters are then returned as a Maple string.
If the list begins with a “^” character, the list represents all those
characters not in the list.
If a “]” is to appear in the list, it must immediately follow the opening
“[” or the “^” if one exists.
You can use a “-” in the list to represent a range of characters. For
example, “A-Z” represents any capital letter. If a “-” is to appear as
a character instead of representing a range, it must appear either at
the beginning or the end of the list.
{. . .}wft The characters between the left brace, "{", and the right brace,
"}", are options for scanning Arrays, Matrices, or Vectors (i.e., the
various classes of rtable). The optional w is an integer specifying the
width to scan for each element (any width specified before the opening
"{" would apply to the entire rtable being scanned, but is ignored).
The character f specifies the format code, and can be any format code
supported by scanf except [...] or {...}. The character t, which
must be one of a, m, c, or r, specifies the type of object to be created
(Array, Matrix, Vector[column], or Vector[row] respectively).
Details on rtable formatting options are described in the help page
?rtable_scanf.
506 • Chapter 10: Input and Output
Maple skips non-blank characters in the format but not within a con-
version specification (where they must match the corresponding charac-
ters in the input). It ignores white space in the format, except that a space
immediately preceding a “%c” specification causes the “%c” specification
to skip any blanks in the input.
If it does not successfully scan any objects, Maple returns an empty
list.
The fscanf and scanf commands use the underlying implementation
that the hardware vendor provides for the “%o” and “%x” formats. As a
result, input of octal and hexadecimal integers is subject to the restrictions
of the machine architecture.
The following example defines a Maple procedure that reads a file
containing a table of numbers, in which each row can have a different
width. The first number in each row is an integer specifying how many
real numbers follow it in that row, and commas separate all the numbers
in each row.
> ReadRows := proc( fileName::string )
> local A, count, row, num;
> A := [];
> do
> # Determine how many numbers are in this row.
> count := fscanf(fileName,"%d");
> if count = 0 then break end if;
> if count = [] then
> error "integer expected in file"
> end if;
> count := count[1];
>
> # Read the numbers in the row.
> row := [];
> while count > 0 do
> num := fscanf(fileName,",%e");
> if num = 0 then
> error "unexpected end of file"
> end if;
> if num = [] then
> error "number expected in file"
10.5 Input Commands • 507
The prompt argument specifies the prompt that readstat is to use. If you
omit the prompt argument, Maple uses a blank prompt. You can either
supply or omit all of the three arguments ditto3, ditto2, and ditto1. If you
supply them, they specify the values which Maple uses for %%%, %%, and
% in the statement that readstat reads. Specify each of these arguments
as a Maple list containing the actual value for substitution. This allows
for values that are expression sequences. For example, if % is to have the
value 2*n+3 and %% is to have the value a,b, then use [2*n+3] for ditto1
and [a,b] for ditto2.
The response to readstat must be a single Maple expression. The
expression may span more than one input line, but readstat does not
permit multiple expressions on one line. If the input contains a syntax
error, readstat returns an error describing the nature of the error, and
its position in the input.
The following example shows a trivial use of readstat within a pro-
cedure.
> InteractiveDiff := proc( )
> local a, b;
> a := readstat("Please enter an expression: ");
> b := readstat("Differentiate with respect to: ");
> printf("The derivative of %a with respect to %a is %a\n",
> a,b,diff(a,b))
> end proc:
508 • Chapter 10: Input and Output
The fileIdentifier is the name or descriptor of the file from which readdata
reads the data. The dataType must be one of integer or float, or you
can omit it, in which case readdata assumes float. If readdata needs
to read more than one column, you can specify the type of each column
by using a list of data types.
The numColumns argument indicates how many columns of data are
to be read from the file. If you omit numColumns, readdata reads the
number of columns specified by the number of data types that you spec-
ified (one column if you did not specify any dataType).
If Maple reads only one column, readdata returns a list of the values
read. If Maple reads more than one column, readdata returns a list of
lists, each sublist of which contains the data read from one line of the file.
If you call readdata with a file name, and that file is not yet open,
Maple opens it in READ mode as a TEXT file. Furthermore, if you call
readdata with a file name, it automatically closes the file when readdata
returns.
The following two examples are equivalent uses of readdata to read
a table of (x, y, z)-triples of real numbers from a file.
> A1 := readdata("my_xyz_file.text",3);
> A2 := readdata("my_xyz_file.text",[float,float,float]);
The variable argument specifies which parameter you wish to change, and
the expression argument specifies the value that the parameter is to have.
See the following sections or ?interface for which parameters you can
set. You may set multiple parameters by giving several arguments of the
form variable = expression , with commas separating them.
To query the setting of a parameter, use the following syntax.
interface( variable )
lprint( expressionSequence )
> interface(screenwidth=30);
> lprint(expand((x+y)^5));
x^5+5*x^4*y+10*x^3*y^2+10*x^2
*y^3+5*x*y^4+y^5
print( expressionSequence )
They include:
indentamount This specifies the number of spaces that Maple uses to in-
dent the continuation of expressions that are too large to fit on a single
line. This parameter takes effect only when you set prettyprint (see
above) to 1, and/or when Maple is printing procedures. The default
setting of indentamount is 4.
x6 + 6 x5 y + 15 x4 y 2 + 20 x3 y 3 + 15 x2 y 4 + 6 x y 5 + y 6
> interface(prettyprint=1);
> print(expand((x+y)^6));
6 5 4 2 3 3 2 4 5
x + 6 x y + 15 x y + 20 x y + 15 x y + 6 x y
6
+ y
> interface(screenwidth=35);
> print(expand((x+y)^6));
6 5 4 2 3 3
x + 6 x y + 15 x y + 20 x y
2 4 5 6
+ 15 x y + 6 x y + y
> interface(indentamount=1);
> print(expand((x+y)^6));
6 5 4 2 3 3
x + 6 x y + 15 x y + 20 x y
2 4 5 6
+ 15 x y + 6 x y + y
10.6 Output Commands • 513
> interface(prettyprint=0);
> print(expand((x+y)^6));
x^6+6*x^5*y+15*x^4*y^2+20*x^3*y^3+
15*x^2*y^4+6*x*y^5+y^6
The fileIdentifier is the name or description of the file to which you want
to write, and stringSequence is the sequence of strings that writeline
should write. If you omit the stringSequence, then writeline writes a
blank line to the file.
Formatted Output
The fprintf and printf commands write objects to a file, using a spec-
ified format.
Call the fprintf and printf commands as follows.
The “%” symbol begins the format specification. One or more of the fol-
lowing flags can optionally follow the “%” symbol:
0 The output is padded on the left (between the sign and the first digit)
with zeroes. If you also specify a “-”, the “0” is ignored.
The optional precision specifies the number of digits that appear after
the decimal point for floating-point formats, or the maximum field width
for string formats.
You may specify both width and/or precision as “*”, in which case
Maple takes the width and/or precision from the argument list. The width
and/or precision arguments must appear, in that order, before the argu-
ment that is being output. A negative width argument is equivalent to
the appearance of the “-” flag.
The optional modifiers are used to indicate the type of the value to
be printed:
The code indicates the type of object that Maple is to write. The code
can be one of the following.
g or G Formats the object using “d”, “e” (or “E” if you specified “G”), or
“f” format, depending on its value. If the formatted value does not
contain a decimal point, Maple uses “d” format. If the value is less
than 10−4 or greater than 10precision , Maple uses “e” (or “E”) format.
Otherwise, Maple uses “f” format.
If the value being formatted is infinity, -infinity, or undefined, the
output is "Inf", "-Inf", or "NaN" respectively.
q or Q These are similar to "%a" or "%A", except that "%q" or "%Q" will
consume all remaining arguments and print them as an expression
sequence, with each element formatted in "%a" or "%A" format re-
spectively. No additional format specifiers can appear after "%q" or
"%Q", since there will be no arguments left to format.
10.6 Output Commands • 517
m The object, which can be any Maple object, is output in Maple’s “.m”
file format. Maple outputs at least width characters (if specified),
and at most precision characters (if specified). Note: truncating a
Maple “.m” format expression by specifying a precision can result in
an incomplete or incorrect Maple expression in the output.
Maple outputs characters that are in format but not within a format
specification verbatim.
All of the formats apply to Arrays (type Array), Matrices (type
Matrix), Vectors (type Vector), and hfarrays (type hfarray), all of which
are objects of type rtable.
If no rtable-specific formatting options are specified (via the {...} op-
tion, see ?rtable_printf), the %a, %A, %m, and %M format codes will print
a representation of the rtable structure itself. For example, %a would print
a Matrix, Vector, or Array call.
If no additional rtable-specific formatting options are specified for a
format code other than %a, %A, %m, and %M, or if an empty rtable option
sequence (i.e., just {}) is specified for any format code, the following
default formatting is applied:
One-dimensional objects are formatted as one long line, with the ele-
ments separated by at least one space.
Objects of N dimensions, where N > 1, are formatted as a sequence of
(N −1)-dimensional objects separated by N −2 blank lines. Therefore, two-
dimensional objects are formatted in the obvious way, three-dimensional
objects are formatted as a series of two-dimensional objects separated by
blank lines, and so on.
Any of the floating-point formats can accept integer, rational, or
floating-point objects; Maple converts the objects to floating-point val-
ues and outputs them appropriately.
The fprintf and printf commands do not automatically start a
new line at the end of the output. If you require a new line, the format
string must contain a new line character, “\n”. Output from fprintf
and printf is not subject to line wrapping at interface(screenwidth)
characters.
The “%o”, “%x”, and “%X” formats use the underlying implementation
that the hardware vendor provides. As a result, output of octal and hex-
adecimal values is subject to the restrictions of the machine architecture.
518 • Chapter 10: Input and Output
1
For information about how to read and write rtable-based Matrices and Vectors,
see the help pages ?ImportMatrix and ?ImportVector.
10.6 Output Commands • 519
fflush( fileIdentifier )
The fileIdentifier is the name or descriptor of the file whose buffer Maple
is to flush. When you call fflush, Maple writes all information that is in
the buffer but not yet in the physical file to the file. Typically, a program
would call fflush whenever something significant is written (for example,
a complete intermediate result or a few lines of output).
Note that you do not need to use fflush; anything you write to a
file will physically be written no later than when you close the file. The
fflush command simply forces Maple to write data on demand, so that
you can monitor the progress of a file.
520 • Chapter 10: Input and Output
writeto( fileName )
appendto( fileName )
The fileName argument specifies the name of the file to which Maple is
to redirect the output. If you call writeto, Maple truncates the file if it
already exists, and writes subsequent output to the file. The appendto
command appends to the end of the file if the file already exists. If the
file you specify is already open (for example, it is in use by other file I/O
operations), Maple generates an error.
The special fileName terminal (specified as a name, not a string)
causes Maple to send subsequent default output to the original default
output stream (the one that was in effect when you started Maple). The
calls writeto(terminal) and appendto(terminal) are equivalent.
Issuing a writeto or appendto call directly from the Maple prompt
is not the best choice of action. When writeto or appendto are in effect,
Maple also writes any error messages that may result from subsequent
operations to the file. Therefore, you cannot see what is happening. You
should generally use the writeto and appendto commands within proce-
dures or files of Maple commands that the read command is reading.
√
1√ x2 + x 2 + 1 1√ √
f := x → 2 ln( √ )+ 2 arctan(x 2 + 1)
8 x2 − x 2 + 1 4
1 √ √
+ 2 arctan(x 2 − 1)
4
The fortran command generates a Fortran routine.
> codegen[fortran](f, optimized);
c The options were : operatorarrow
doubleprecision function f(x)
doubleprecision x
doubleprecision t1
doubleprecision t12
doubleprecision t16
doubleprecision t2
doubleprecision t3
doubleprecision t8
t1 = sqrt(2.D0)
t2 = x**2
t3 = x*t1
t8 = log((t2+t3+1)/(t2-t3+1))
t12 = atan(t3+1)
t16 = atan(t3-1)
f = t1*t8/8+t1*t12/4+t1*t16/4
return
end
}
}
LATEX Generation
Maple supports conversion of Maple expressions to the LATEX typesetting
language. Conversion to typesetting languages is useful when you need to
insert a result in a scientific paper.
You can perform conversion to LATEX by using the latex command.
Call the latex command as follows.
√
1√ x2 + x 2 + 1
Z
1
dx = 2 ln( √ )
x4 + 1 8 x2 − x 2 + 1
1√ √ 1√ √
+ 2 arctan(x 2 + 1) + 2 arctan(x 2 − 1)
4 4
524 • Chapter 10: Input and Output
> latex(%);
\int \! \left( {x}^{4}+1 \right) ^{-1}{dx}=1/8
\,\sqrt {2}\ln \left( {\frac {{x}^{2}+x\sqrt
{2}+1}{{x}^{2}-x\sqrt {2}+1}} \right) +1/4\,
\sqrt {2}\arctan \left( x\sqrt {2}+1 \right) +
1/4\,\sqrt {2}\arctan \left( x\sqrt {2}-1
\right)
[84, 101, 115, 116, 32, 83, 116, 114, 105, 110, 103]
> convert([84,101,115,116,0,83,116,114,105,110,103],bytes);
“Test”
10.7 Conversion Commands • 525
The string argument is the string that needs parsing. It must describe a
Maple expression (or statement, see below) by using the Maple language
syntax.
You may supply one or more options to the parse command:
The errorDescription describes the nature of the error (for example, ‘+‘
unexpected, or unexpected end of input). The errorLocation gives the
approximate character position within the string at which Maple detected
the error.
When you call parse from the Maple prompt, Maple displays the
parsed result depending on whether the call to parse ends in a semicolon
or a colon. Whether the string passed to parse ends in a semicolon or a
colon does not matter.
> parse("a+2+b+3");
526 • Chapter 10: Input and Output
a+5+b
> parse("sin(3.0)"):
> %;
.1411200081
The format specifies how Maple is to format the elements of the expres-
sionSequence. This Maple string is made up of a sequence of formatting
specifications, possibly separated by other characters. See 10.6.
The sprintf command returns a string containing the formatted re-
sult.
Call the sscanf command as follows.
The sourceString provides the input for scanning. The format specifies
how Maple is to parse the input. A sequence of conversion specifications
(and possibly other anticipated characters) make up this Maple string.
See 10.5. The sscanf command returns a list of the scanned objects, just
as fscanf and scanf do.
The following example illustrates sprintf and sscanf by converting a
floating-point number and two algebraic expressions into a floating-point
format, Maple syntax, and Maple .m format, respectively. This string is
then parsed back into the corresponding objects using sscanf.
> s := sprintf("%4.2f %a %m",evalf(Pi),sin(3),cos(3));
Before you can write the actual Fortran output to the file, you must close
the file. Otherwise, the fortran command attempts to open the file in
APPEND mode, which results in an error if the file is already open.
> fclose(file):
Now you can write the actual Fortran statements to the file.
528 • Chapter 10: Input and Output
10.10 Conclusion
This chapter has revealed the details of importing and exporting data and
code into and out of Maple. Most commands discussed in this chapter are
more primitive than those commands which you are likely to use, such
as save and writeto. The aforementioned Maple commands ensure that
you are properly equipped to write specialized exporting and importing
procedures. Their basis is similar to the commands found in the popular
C programming language, although they have been extended to allow easy
printing of algebraic expressions.
Overall, this book provides an essential framework for understanding
Maple’s programming language. Each chapter is designed to teach you to
use a particular area of Maple effectively. However, a complete discussion
of Maple can not fit into a single book. The Maple help system is an
excellent resource and complements this volume. While this book teaches
fundamental concepts and provides a pedagogical introduction to topics,
the help system provides the details on each command and feature. It
explains such things as the options and syntax of Maple commands and
serves as a resource for use of the Maple interface.
Also, numerous authors have published many books about Maple.
These include not only books, such as this one, on the general use of
Maple, but also books directed toward the use of Maple in a particular
field or application. Should you wish to consult books that parallel your
own area of interest, this book will still serve as a handy reference and
guide to Maple programming.
530 • Chapter 10: Input and Output
11 Using Compiled Code in
Maple
531
532 • Chapter 11: Using Compiled Code in Maple
and easy to use. This method of directly calling the external code allows
the use of an external library without modification.
> a := 33:
> b := 22:
> myAdd(a,b);
55
r := 44
External Definition
The define_external function constructs and returns another function
which can be used to make the actual call. The define_external function
is called as follows.
Type Specification
Step two of the introductory example indicated how to specify types using
Maple notation. Maple uses its own notation to provide a generic well-
defined interface for calling compiled code in any language.
The format of each arg parameter is as follows.
argumentIdentifier :: dataDescriptor
Character String Data Formats Strings are similar to both scalar and
array data. A string in C is an array of characters, but it is often manip-
ulated as if it were an object. A string in Maple is an atomic object, but
it can be manipulated as if it were an array of characters.
Parameter n in string[n] indicates that the called function is expecting
a fixed size string. Otherwise, a pointer to a character buffer (char*) will
be used.
Strings are implicitly passed by reference (only a pointer to the string
is passed), but any changes made to the string are not copied back to
Maple unless the string is declared with a size.
11.1 Method 1: Calling External Functions • 537
order=... This may be left unspecified for vectors since Fortran and C
representation is the same. Otherwise, this will default to Fortran_order
when calling a Fortran library and C_order when calling a C library.
indfn=(..., ...) This specifies the indexing functions that the Array,
Matrix, or Vector must have.
Enumerated Types
Maple’s external calling mechanism does not directly support enumerated
types (such as enum in C). Instead, use the integer[n] type with n of an
appropriate size to match the size of the enumerated type of the compiler
with which the external function was compiled (usually this is the same
size as the int type).
Call by Reference
Unless modified as described below, each argument is passed by value.
The REF modifier can be used to override this.
11.2 Method 2: Wrapper Generation • 541
Array Options
If an ARRAY argument is declared as CALL_ONLY and an Array, Matrix,
or Vector with proper settings is passed to the external function (so
that no copying is required), then CALL_ONLY will have no effect and thus
will not prevent the called function from overwriting the original array.
To prevent this from occurring, the option COPY can be included in the
ARRAY descriptor.
The ARRAY descriptor accepts extra options when used with wrapper
generation. These options can be specified as follows.
The dim1 through dimN parameters are each integer ranges, specify-
ing the range of each dimension of the array. Any of the upper or lower
bounds may be the name of another argument, in which case the value of
that argument will specifiy the corresponding array bound at run-time.
The options are used to specify how an array should be passed. The
following are valid options.
542 • Chapter 11: Using Compiled Code in Maple
COPY Do not operate in-place on the given array. That is, make a copy
first, and use the copy for passing to and from the external function.
NO_COPY This ensures that a copy of the data is never made. Usually,
when using a wrapper generated external call, if the Array, Matrix,
or Vector is of the wrong type, (say the order is wrong), then a copy
is made with the correct properties before passing it to the external
function. Also, the “returned” array will have the properties of the
copy. If NO_COPY is specified, and an Array, Matrix, or Vector with
incorrect options is passed, an exception is raised. Arrays are always
passed by reference. If no options are given (via a REF descriptor),
they are passed by using the CALL_ONLY behavior of REF with the
noted exception described at the beginning of this section.
Non-Passed Arguments
Sometimes it will be necessary to pass additional arguments to the Maple
wrapper that should not be passed on to the external function. For ex-
ample, consider the following hypothetical C function:
int sum( int *v1, int *v2 )
This function takes two integer vectors, v1 and v2, and adds the ele-
ments of v2 to v1, stopping when it finds an entry that is zero. It might
be nice for the generated wrapper to make sure the vectors are the same
size. The Maple definition for this function is as follows.
> Sum := define_external( ‘sum‘,
> v1 :: ARRAY(1..size,integer[4]),
> v2 :: ARRAY(1..size,integer[4]),
> size :: NO_PASS(integer[4]),
> RETURN :: integer[4],
> LIB="libsum.dll");
The NO_PASS modifier indicates that the size argument should not be
passed to the external function. The Sum function could then be called by
the following statement,
> Sum(v1,v2,op(1,v1));
where v1 and v2 are vectors. Maple will pass the vector data, or a copy
of the vector data, and pass it to the external sum function. It will not
pass the size element to the external function, but size will be used for
argument checking (since the NO_CHECK option was not specified).
Note that this option can only be used for top-level arguments. That
is, it is invalid to declare a callback procedure’s arguments as NO_PASS.
11.2 Method 2: Wrapper Generation • 543
Conversions
When the procedure returned by define_external is actually called,
the Maple arguments that are passed are converted to the corresponding
arguments of the external function. Likewise, the value returned from the
external function is converted back to the corresponding Maple type.
The following table describes each of the external types and the Maple
types that can be converted into that type. The first listed Maple type
is the one that a result of the corresponding external type would be con-
verted into.
External Type Allowed Maple Type(s)
boolean[n] boolean
integer[n] integer
float[n] float, rational, integer, numeric
complex[n] complex, numeric, float, rational, integer
char[n] one-character string
string[n] string, symbol, 0
ARRAY() Array, Vector, Matrix, name, 0
STRUCT() list, table
UNION() table
PROC() procedure
For STRUCTs, either lists or tables are valid for a particular declaration.
Once declared, only one of the types–a list or a table–will be acceptable.
They cannot be used interchangeably unless the wrapper is regenerated.
For UNIONs, only tables are permitted, and the table must contain ex-
actly one entry when passed (corresponding to one of the members of the
544 • Chapter 11: Using Compiled Code in Maple
union).
If an argument of an incompatible type is passed, an error occurs,
and the external function will not be called. Likewise, if a value is passed
that would be out of range for the specified type (e.g., integer too large),
an error occurs. When passing floating-point values, precision in excess of
that supported by the external type is discarded, provided the magnitude
of the value is within the range of the external type.
Arguments that were declared as REFerences may be passed either
a name, a zero, or the declared kind of Maple expression. If a name is
passed, it is evaluated, and the value is passed by reference to the ex-
ternal function. After the external function returns, the revised value is
converted back to the type specified for the argument and assigned back
to the name. If the name passed has no value, then either NULL is passed,
or a pointer to newly allocated space for the structure is passed. This
behavior is determined by the presence or absence of ALLOC in the REF
declaration. If a zero is passed, NULL is passed to the external function.
If any other Maple expression is passed, its value is passed by reference,
and the revised value is discarded.
Compiler Options
To compile the wrapper library, Maple requires the use of a C compiler
installed on the same machine that is running Maple. Maple will generate
a system command to call the compiler. The compiler needs to be well
known to the system. It should be in the system PATH and all associated
environment variables need to be set.
The compile and link commands are completely customizable pro-
vided that your compiler has a command-line interface available. Default
configurations are provided, which should make most cases work “out of
the box.” Maple is preprogrammed to use the vendor-supplied C compiler
to compile wrappers on most platforms.1
All default compile and link options are stored in a module that can be
obtained by using the command define_external(‘COMPILE_OPTIONS‘).
When the module returned by this command is modified, the modifica-
tion will affect all wrapper generation commands via define_external
for the remainder of the session. All of the names exported by the compile
options module can also be specified as a parameter to define_external.
When specified as a parameter, the effect lasts only for the duration of
that call.
The compile and link commands are assembled by calling the COMPILE_COMMAND
1
Under Microsoft Windows, Maple uses the Microsoft C Compiler.
11.2 Method 2: Wrapper Generation • 545
COBJ_FLAG This is the flag used by the compiler to specify the ob-
ject filename. The compiler command uses COBJ_FLAG || FILE ||
OBJ_EXT to name the object file. On most platforms it is “-o”.
LOBJ_FLAG This is the flag used by the linker to specify the tar-
get library name. The link command uses LOBJ_FLAG || FILE ||
DLL_EXT to name the shared library.
FILE This is the base name of the file to be compiled. The file extension
should not be included in this name. For example, if you want to
compile “foo.c”, set FILE="foo" and FILE_EXT=".c". When FILE is
set to NULL the system generates a file name based on the function
name.
2
If using the Microsoft C compiler, the LINK_COMMAND is set to NULL since the
COMPILE_COMMAND does both the compiling and linking.
546 • Chapter 11: Using Compiled Code in Maple
LIB This names the library which contains the external function you
want to call. This option must be specified in every call to define_external.
LIBS This specifies other libraries that need to be linked with the wrap-
per library to resolve all external symbols. Use an expression sequence
to specify more than one library. For example, LIBS=("/usr/local/
maple/extern/lib/libtest.so","/users/jdoe/libdoe.so").
The tricky part in the above example is that gcc likes to have a space
between -o and the object name. Modifying the COBJ_FLAG allows this to
be easily done. All other option default values were acceptable.
The best way see what commands are actually being executed is to
set the infolevel for define_external to 3 or higher. Repeating the
above example you might see the following.
> p := define_external(‘COMPILE_OPTIONS‘):
> p:-COMPILER := "gcc";
> p:-COBJ_FLAG := "-o ":
> infolevel[define_external] := 3:
> define_external(‘mat_mult‘,‘WRAPPER‘,‘LIB‘="libcexttest.so"):
"COMPILE_COMMAND"
"gcc -g -c -I/user/local/maple/extern/include -o \
mwrap_mat_mult.o mwrap_mat_mult.c"
"LINK_COMMAND"
"ld -znodefs -G -dy -Bdynamic
-L/user/local/maple/bin/bin.SUN_SPARC_SOLARIS \
-omwrap_mat_mult.so mwrap_mat_mult.o -lc -lmaplec"
An alternate way to see the compile and link commands is to call the
command-builder procedures directly. Make sure to set or unassign the
variables that will be filled in, otherwise they will be left blank.
548 • Chapter 11: Using Compiled Code in Maple
> p := define_external(‘COMPILE_OPTIONS‘):
> p:-COMPILER := "gcc";
> p:-COBJ_FLAG := "-o ":
> p:-COMPILE_COMMAND();
> unassign(’p:-FILE’);
> p:-COMPILE_COMMAND();
8.80000019073486328
> restart;
> quadruple_it := define_external(’quadruple_it’,
> WRAPPER,FILE="quad",
> x::float[4],
> RETURN::float[4],
> WRAPLIB="quad.dll",
> LIB="test.dll"):
> quadruple_it(2.2);
8.80000019073486328
When DLLs are created and compiled at runtime it is important not
to duplicate the name of a previously generated DLL without restarting
Maple (either by closing Maple down or issuing the restart command).
Maple will maintain an open connection with the first DLL opened
with any given name. Attempting to create a new DLL of the same
name without restarting may lead to unexpected results. The Maple
command dlclose can be used to avoid restarting, but subsequently
calling any external function in that closed DLL without reissuing the
define_external command will likely crash Maple.
11.2 Method 2: Wrapper Generation • 549
Evaluation Rules
External functions follow normal Maple evaluation rules in that the argu-
ments are evaluated during a function call. It therefore may be necessary
to quote assigned names when passing by-reference. For example, consider
the following function that multiplies a number by two in-place.
*i *= 2;
}
The solution is to name the value you want to pass out. The name
needs to be quoted in order to prevent evaluation and thus have only the
value passed out.
> double_it(n); # n is evaluated so 3 gets passed
> n;
> double_it("NULL");
>
> concat := define_external(’concat’,
> RETURN::string, a::string, b::string,
> LIB="libtest.dll"):
> concat("NULL","x");
"NULLx"
> concat(0,0);
In the concat example above, the C code might look like the following.
Note that this function does not clean up memory as it should.
r = (char*)malloc((strlen(a)+strlen(b)+1)*sizeof(char));
strcpy(r,a);
strcat(r,b);
return( r );
}
only two basic steps (DLL creation and function invocation as described
on pages 533-534) in addition to wrapper generation. Wrappers were in-
troduced in Section 11.2.
ALGEB MWRAP_quadruple_it(
MKernelVector kv,
FLOAT32 (*fn) ( FLOAT32 a1 ),
ALGEB fn_args
);
The prototype above was taken from the wrapper quad.c described in
the previous section. The first argument kv is a handle to the Maple kernel
function vector. The second argument fn is a function pointer assigned
the symbol looked up in the external DLL. In this case, fn will be assigned
the quadruple_it external function. The last argument is a Maple ex-
pression sequence data structure containing all the arguments passed to
the function during any given call to the Maple procedure generated by
the define_external command.
The above entry point is the format used when wrappers are auto-
matically generated, and when WRAPLIB is specified. An alternate external
entry point that excludes the function pointer is available when the pa-
rameter MAPLE is specified instead of WRAPPER or WRAPLIB.
ALGEB MWRAP_quadruple_it(
MKernelVector kv,
ALGEB fn_args
);
Maple uses directed acyclic graphs (dags) to represent all objects such
as integers, floating point numbers, sums, modules, procedures, etc. (See
Appendix A for more details about Maple’s internal representation of
objects.) These dags have the type ALGEB in C wrappers, and INTEGER
or INTEGER*8 in Fortran wrappers. Fortran 77 has no user type defi-
nition semantics so ALGEB pointers must be “faked” by using machine
word-sized integers. If the machine word size is 64-bit (e.g., as on a
DEC Alpha), then the header maplefortran64bit.hf must be used and
INTEGER*8 must be used as the dag datatype. Execute the Maple com-
mand kernelopts(wordsize) to see if you should be using 32-bit or
64-bit integer-dag types in Fortran. When working with C, the datatype
is ALGEB regardless of the machine word size.
For the most part, treat these dags as black boxes. In other words,
you do not have to know the internal details of dags to manipulate and
work with them. The only exception is the argument sequence passed to
the wrapper entry point. This is an expression seqence (EXPSEQ) dag, and
can be treated as an array of dags starting at index 1 (not 0). Thus,
fn_args[1] is the first parameter passed to the external function. Use
MapleNumArgs to determine the number of arguments passed. Note that
the Fortran API uses a slightly different naming convention. The equiva-
lent Fortran call is maple_num_args. The C API names will be used for
the remainder of this chapter. Refer to the API listing to find equivalent
Fortran names.
The easiest way to start writing custom wrappers is to inspect auto-
matically generated wrappers. Consider the add function that was intro-
duced at the beginning of this chapter. Use the WRAPPER option to tell
define_external to generate a wrapper. Also use the NO_COMPILE op-
tion to tell define_external not to compile the generated wrapper. The
name of the generated file will be returned.
> myAdd := define_external(
> ’add’,
> ’WRAPPER’,
> ’NO_COMPILE’,
> ’num1’::integer[4],
> ’num2’::integer[4],
> ’RETURN’::integer[4]
> );
myAdd := "mwrap_add.c"
/* MWRAP_add Wrapper
11.3 Method 3: Customizing Wrappers • 553
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mplshlib.h>
#include <maplec.h>
MKernelVector mapleKernelVec;
typedef void *MaplePointer;
ALGEB *args;
/* main - MWRAP_add */
ALGEB MWRAP_add( MKernelVector kv,
INTEGER32 (*fn) ( INTEGER32 a1, INTEGER32 a2 ),
ALGEB fn_args )
{
INTEGER32 a1;
INTEGER32 a2;
INTEGER32 r;
ALGEB mr;
int i;
mapleKernelVec = kv;
args = (ALGEB*) fn_args;
if( MapleNumArgs(mapleKernelVec,(ALGEB)args) != 2 )
MapleRaiseError(mapleKernelVec,"Incorrect number
of arguments");
/* integer[4] */
a1 = MapleToInteger32(mapleKernelVec,args[1]);
/* integer[4] */
a2 = MapleToInteger32(mapleKernelVec,args[2]);
r = (*fn)(a1, a2);
mr = ToMapleInteger(mapleKernelVec,(long) r);
return( mr );
}
554 • Chapter 11: Using Compiled Code in Maple
#include <stdio.h>
#include <stdlib.h>
#include <maplec.h>
if( MapleNumArgs(kv,fn_args) != 2 )
MapleRaiseError(kv,"Incorrect number of arguments");
r = a1 + a2;
return( ToMapleInteger(kv,(long) r) );
}
This program first checks to make sure there were exactly two ar-
guments passed in the Maple function call. It then converts the two
11.3 Method 3: Customizing Wrappers • 555
> myAdd(2.2,1);
> myAdd(2^80,2^70);
INCLUDE "maplefortran.hf"
INTEGER kv
INTEGER args
INTEGER arg
556 • Chapter 11: Using Compiled Code in Maple
ERRMSGLEN = 20
r = a1 + a2
END
Once compiled into a DLL, the same syntax can be used in Maple
to access the function. The only difference is the additional keyword
’FORTRAN’ in the define_external call.
> myAdd := define_external(’myAdd’,’MAPLE’,’FORTRAN’,’LIB’=
> "myAdd.dll"):
> myAdd(2,3);
External API
An external API is provided for users who want to augment existing
wrappers or write their own custom wrappers. This section describes the
functions available when linking with the Maple API library (see Ta-
ble 11.4) and including either maplec.h or maplefortran.hf.
11.3 Method 3: Customizing Wrappers • 557
The following functions indicate the type of the given Maple object.
These functions all return TRUE (1) when the Maple dag s fits the
description given by the function name. If s is not of the correct type,
FALSE (0) is returned. Maple’s NULL is not the same as a C Pointer-
NULL. The former case is the empty expression sequence in the Maple
language. The latter case is a pointer variable set to the address zero. Since
there is no concept of real pointers in the Maple Language, the idea of
Pointer-NULL in this context means the Maple integer zero, or an unas-
signed Maple name. The IsMaple...Numeric routines use the Maple type
numeric definition. All other checks use the dag type definition. For ex-
ample, type(t[1],name) returns true in Maple, but IsMapleName checks
for a NAME dag and will return FALSE since t[1] is internally represented
as a TABLEREF dag. Integer query routines with the bit size specified in the
name will check to make sure the given Maple object s is a Maple integer
and also that it could fit into the specified number of bits if converted to
a hardware integer.
The following are the equivalent Fortran routines. Note that com-
plex and string conversion are done by reference. That is, the third
argument passed to the function will be set to the converted value
rather than the function returning the value. Equivalent functions for
MapleToComplexFloatDAG and MapleToPointer are not available.
ALGEB im );
ALGEB ToMapleExpressionSequence( MKernelVector kv, int
nargs, /* ALGEB arg1, ALGEB arg2, */ ... );
ALGEB ToMapleInteger( MKernelVector kv, long i );
ALGEB ToMapleInteger64( MKernelVector kv, INTEGER64 i );
ALGEB ToMapleFloat( MKernelVector kv, double f );
ALGEB ToMapleName( MKernelVector kv, char *n, M_BOOL
is_global );
ALGEB ToMapleNULL( MKernelVector kv );
ALGEB ToMapleNULLPointer( MKernelVector kv );
ALGEB ToMaplePointer( MKernelVector kv, void *v );
ALGEB ToMapleRelation( MKernelVector kv, const char *rel,
ALGEB lhs, ALGEB rhs );
ALGEB ToMapleString( MKernelVector kv, char *s );
ALGEB ToMapleUneval( MKernelVector kv, ALGEB s );
to_maple_boolean( kv, b )
to_maple_char( kv, c )
to_maple_complex( kv, re, im )
to_maple_complex_float( kv, re, im )
to_maple_integer( kv, i )
to_maple_integer64( kv, i )
to_maple_float( kv, f )
to_maple_name( kv, s, s_len )
to_maple_null( kv )
to_maple_string( kv, s, s_len )
ALGEB rt );
ALGEB RTableCopyImPart( MKernelVector kv, RTableSettings
*s, ALGEB rt );
ALGEB RTableCopyRealPart( MKernelVector kv, RTableSettings
*s, ALGEB rt );
ALGEB RTableZipReIm( MKernelVector kv, RTableSettings *s,
ALGEB rt_re, ALGEB rt_im );
RTableAppendIndFn(kv,&settings,ToMapleName(kv,"symmetric",
TRUE));
RTableAppendIndFn(kv,&settings,EvalMapleStatement(kv,
"triangular[upper]"));
data_type = RTABLE_DAG
maple_type = ’anything’ (Maple name ’anything’)
subtype = RTABLE_ARRAY
storage = RTABLE_RECT
p1 = -1, p2 = -1
order = RTABLE_FORTRAN
read_only = FALSE
foreign = FALSE
num_dimensions = -1
index_functions = ’NULL’ (Maple NULL)
attributes = ’NULL’ (Maple NULL)
transpose = FALSE
fill = 0
3
For more information, see ?rtable.
11.3 Method 3: Customizing Wrappers • 565
RTableData val;
M_INT *index;
index[0] = 2;
index[1] = 1;
val.float64 = 3.14;
RTableAssign(kv,rt,index,val);
List Manipulation To work with Maple lists, the following API functions
can be used. These functions are only available using the C API.
Table Manipulation To work with Maple tables, the following API func-
tions can be used. These functions are only available using the C API.
ALGEB MapleTableAlloc( MKernelVector kv );
void MapleTableAssign( MKernelVector kv, ALGEB table,
ALGEB ind, ALGEB val );
ALGEB MapleTableSelect( MKernelVector kv, ALGEB table,
ALGEB ind );
void MapleTableDelete( MKernelVector kv, ALGEB table,
ALGEB ind );
M_BOOL MapleTableHasEntry( MKernelVector kv, ALGEB table,
ALGEB ind );
MapleTableAlloc creates a TABLE dag. The table is initially empty.
MapleTableAssign sets the ind element of the given table to the
value val. That is, table[ind] := val, where ind can be a NAME or an
expression sequence of numbers, or any other valid index into a Maple
table.
MapleTableSelect returns the ind element of the given table.
MapleTableDelete removes the ind element from the table.
MapleTableHasEntry queries the table to see if it contains an el-
ement at index ind. If it does, TRUE is returned; otherwise, FALSE is
returned.
Data Selection The following functions are available when using the
C API only and deal with selecting from various kinds of Maple data
structures.
ALGEB MapleSelectImaginaryPart( MKernelVector kv, ALGEB s );
ALGEB MapleSelectRealPart( MKernelVector kv, ALGEB s );
ALGEB MapleSelectIndexed( MKernelVector kv, ALGEB s, M_INT
dim, M_INT *ind );
MapleSelectImaginaryPart and MapleSelectRealPart return the
imaginary and real parts of a complex number dag, respectively.
568 • Chapter 11: Using Compiled Code in Maple
ALGEB val;
M_INT ind[3];
ind[0] = 1;
ind[1] = 2;
ind[2] = 3;
These functions display the message msg, stop execution, and return
to Maple’s input loop. A call to MapleRaiseError does not return.
11.3 Method 3: Customizing Wrappers • 569
The character string msg may contain wildcards of the form %N, where
N is a non-zero integer. These wildcards will be replaced by the extra ar-
gument, arg1 or arg2, before displaying the message. If %-N is specified,
then the optional argument will be displayed with st, nd, rd, or th ap-
pended to it. For example:
This, if invoked, will raise the error, "the 4th argument, ’foo’, is not
valid", assuming i=4, and args[i] is set to the Maple name foo.4
The only option not allowed is %0 since the function cannot know how
many optional arguments are left to parse.
The C API also provides a mechanism for trapping errors raised by
Maple.
typedef struct {
MKernelVector k;
ALGEB fn, arg1, arg2;
} CallbackArgs;
4
For more information, see ?error.
570 • Chapter 11: Using Compiled Code in Maple
a.k = k;
a.fn = fn;
a.arg1 = ToMapleFloat(k,3.14);
a.arg2 = ToMapleInteger(k,44);
result = (ALGEB)MapleTrapError(k,tryCallback,&a,&errorflag);
if( errorflag ) {
/* do something */
}
else {
/* do something else */
}
}
Hardware Float Evaluation The following procedures evaluate a Maple
Procedure or statement using hardware floats.
double MapleEvalhf( MKernelVector kv, ALGEB s );
double EvalhfMapleProc( MKernelVector kv, ALGEB fn,
int nargs, double *args );
The equivalent Fortran functions are as follows.
DOUBLEPRECISION maple_evalhf( kv, s)
DOUBLEPRECISION evalhf_maple_proc( kv, fn, nargs, args )
MapleEvalhf applies evalhf to the given dag s. Then evalhf will
either evaluate an expression using hardware floats to produce a hardware
float result, or it will return the handle to an evalhfable rtable that can
be used as a parameter to EvalhfMapleProc.
EvalhfMapleProc calls the evalhf computation engine directly to
evaluate the given procedure fn without converting the hardware float
parameters to software floats. The procedure fn is a valid Maple PROC
dag, nargs is the number of parameters to pass to fn, and args is the list
of parameters. Note that args starts at 1; args[1] is the first parameter,
args[nargs] is the last, and args[0] is not used.
11.3 Method 3: Customizing Wrappers • 571
#include "maplec.h"
In Maple, the external routine would be accessed just like any other,
except an error will be raised if the given procedure is not able to use
evalhf.
> f := proc(n,x,y) y[1] := n*sin(x); end:
> y := Vector([1,2],datatype=float[8]):
572 • Chapter 11: Using Compiled Code in Maple
> p := define_external(’test’,MAPLE,LIB="libtest.so"):
> p(y,f):
a1 = ToMapleFloat(kv,3.14);
MapleResult = EvalMapleProc(kv,args[1],1,a1);
CResult = MapleToFloat64(kv,MapleResult);
EvalMapleStatement enables you to enter a single parsable Maple
statement and have it evaluated. For example, the following call will eval-
uate the integral x3 in the range x = 0..1.
ALGEB MapleResult;
double CResult;
MapleResult = EvalMapleStatement(kv,"int(x^3,x=0..1)");
CResult = mapleToFloat64(kv,MapleResult);
MapleEval evaluates a Maple expression. It is especially useful for
getting at the value of an assigned name.
MapleAssign sets the value dag rhs to the name dag lhs. This is
equivalent to the Maple statement
> lhs := rhs;
ALGEB rhs;
M_INT ind[3];
ind[0] = 1;
ind[1] = 2;
ind[3] = 3;
rhs = ToMapleFloat(kv,3.14);
MapleAssignIndexed(kv,arg1,3,ind,rhs);
ALGEB arg1;
INTEGER32 *i;
i = MapleAlloc(kv,sizeof(INTEGER32));
*i = MapleToInteger32(kv,arg1);
deals directly with Maple data structures can corrupt Maple by misusing
the data structure manipulation facilities.
Therefore, external calling is a feature to use at your own risk.
Whether an external routine is one that you have written, or is one
supplied by a third party to which you have declared an interface (via
define_external), Maple must rely on the integrity of the external rou-
tine when it is called.
11.5 Conclusion
This chapter outlined the three methods for using compiled C or Fortran
routines in Maple. You can extend the power of Maple by using your own
or third party libraries.
0
#
,-
6 #
' 4
'
#
#
* 4
4
01H!
@ $$ @4 3334
.@
@) 3334 >2
> $$ >4 " .>
>)
1>,>!
@ $$ @4 3334
.@
@) 3334 >2
> $$ >4 " .>
>)
H!
@ $$ @4 3334 . !/" !.@
@4 3334 >2
> $$ >4 .>4
> ")
1> $$ "
¯
4
' /" 1. 8 &
8
4
5
0 .
4 9
18'8E 0'*<
!
'
4
1 8
.
4
10'*<
!
:#G*,3
9 '0<' .
4 9
18'8E 0'*<
!
,-
1
*
.4 9
18'8E 0'*<
!
5
' # ,- .///
'
¯
578 • Chapter 11: Using Compiled Code in Maple
A Internal Representation
and Manipulation
Each of these structures, along with the constraints on its length and
contents, is described in the following sections.
579
580 • Appendix A: Internal Representation and Manipulation
The flow of control need not remain internal to the Maple kernel. In
many cases, where appropriate, a decision is made to call functions written
in Maple and residing in the library. For example, many uses of the expand
function will be handled in the kernel. However, if an expansion of a sum
to a large power is required, then the internal expand will call the external
Maple library function ‘expand/bigpow‘ to resolve it. Functions such as
diff, evalf, series, and type make extensive use of this feature.
Thus, for example, the basic function diff does not know how to dif-
ferentiate any function. All of that knowledge resides in the Maple library
in procedures named ‘diff/functionName ‘. This is a fundamental fea-
ture of Maple since it permits flexibility (changing the library), personal
tailoring (by defining your own handling functions), readability (much of
582 • Appendix A: Internal Representation and Manipulation
Maple’s knowledge is visible at the user level), and it allows the kernel to
remain small by unloading non-essential functions to the library.
The header field, stored in one or more machine words, encodes the
length of the structure and its type. Additional bits are used to record
simplification status, garbage collection information, persistent store sta-
tus, and various information about specific data structures (e.g., whether
or not a for loop contains a break or next).
The length is encoded in 26 bits on 32-bit architectures, resulting in
a maximum single object size of 67, 108, 863 words (268, 435, 452 bytes,
or 256 megabytes). On 64-bit architectures, the length is stored in 32
bits, for a maximum object size of 4, 294, 967, 295 words (34, 359, 738, 360
bytes, or 32 gigabytes).
Every structure is created with its own length, and that length will not
change during the existence of the structure. Furthermore, the contents
of most data structures are never changed during execution, because it is
unpredictable how many other data structures may be referring to it, and
relying on it not to change. The normal procedure to modify a structure
is to copy it, and then to modify the copy. Structures that are no longer
used will eventually be reclaimed by the garbage collector.
The following figures describe each of the 58 structures currently im-
plemented in Maple, along with the constraints on their length and con-
tents. The 6-bit numeric value identifying the type of structure is of little
interest, so symbolic names will be used.
Logical AND
Assignment Statement
Binary Object
The BINARY structure can hold any arbitrary data. It is not used
directly as a Maple object, but is used as storage for large blocks of data
inside other Maple objects (currently only RTABLEs). It is also sometimes
used as temporary storage space during various kernel operations.
Break Statement
BREAK
Name Concatenation
Complex Value
COMPLEX ∧ re ∧ im
COMPLEX ∧ im
Debug
Length: 2 or more
Error Statement
ERROR ∧ expr
This represents the Maple error statement. The expr is either a single
expression (if only a message was specified in the error statement), or an
expression sequence (if arguments were also specified). The actual internal
tag used for the ERROR structure is MERROR, to prevent collision with a
macro defined by some C compilers.
Expression Sequence
Floating-Point Number
FLOAT ∧ integer1 ∧ integer2 ∧ attrib − expr
Maple syntax: 1.2, 1.2e3, Float(12,34), Float(infinity)
Length: 2 (or 3 with attributes)
The name follows the same rules as in ASSIGN, except that it can
also be the empty expression sequence (NULL), indicating that there is no
controlling variable for the loop.
A.2 Internal Representations of Data Types • 587
Foreign Data
FOREIGN ...
Function Call
FUNCTION ∧ name ∧ expr − seq ∧ attrib − expr
Garbage
GARBAGE ...
Hardware Float
HFLOAT floatword
If Statement
IF ∧ cond− ∧ stat− ∧ cond− ∧ stat− ... ... ∧ stat−
Maple syntax:
if condExpr then
statSeq
elif condExpr then
statSeq
...
else statSeq
end if
Length: 3 or more
Length: 3
Negative Integer
Positive Integer
Less Than
LESSTHAN ∧ expr ∧ expr
Like the LESSEQ structure above, this structure has two interpreta-
tions, depending on the context. It can be interpreted as a relation (i.e.,
an inequation), or as a comparison (e.g., in the condition of an if state-
ment, or the argument to a call to evalb).
Maple does not have a greater-than structure. Any input of that form
is turned around into a LESS structure.
LEXICAL integer
List
The elements of the expr-seq are the elements of the list. The list can
optionally have attributes.
LOCAL integer
Member
Module Definition
MODDEF param- local- option- export- stat- desc-
...
seq seq seq seq seq seq
module modName ( )
description descSeq;
local localSeq;
export exportSeq;
global globalSeq;
option optionSeq;
statSeq
end module
Length: 10
Module Instance
MODULE ∧ export − seq ∧ mod − def ∧ local − seq
points back to the original module definition. The local-seq field points
to an expression sequence of names of the instantiated local variables of
the module.
Identifier
NAME ∧ assigned− ∧ attrib− characters characters ...
expr expr
Next Statement
NEXT
Logical NOT
NOT ∧ expr
Logical OR
OR ∧ expr ∧ expr
PARAM integer
PARAM 0
This represents the Maple symbol nargs, the number of arguments passed
when the procedure was called.
PARAM −1
This represents the Maple symbol args, the entire sequence of arguments
passed when the procedure was called.
PARAM −2
Power
Procedure Definition
PROC ∧ param− ∧ local− ∧ option− ∧ rem− ∧ stat− ∧ desc−
...
seq seq seq table seq seq
∧ global− ∧ lexical−
seq seq
Maple syntax:
proc ( paramSeq )
description descSeq;
local localSeq;
export exportSeq;
global globalSeq;
option optionSeq;
statSeq
end proc
Length: 9
integer pointed to by its stat-seq. The integer gives the built-in function
number.
The desc-seq field points to an expression sequence of NAMEs or
STRINGs. These are meant to provide a brief description of what the pro-
cedure does, and are displayed even when interface(verboseproc) is
less than 2.
The global-seq field points to a list of the explicitly declared global
variables in the procedure (those that appeared in the global statement).
This information is never used at run-time, but it is used when simplifying
nested procedures to determine the binding of lexically scoped identifiers.
For example, an identifier on the left-hand side of an assignment in a
nested procedure can be global if it appears in the global statement of a
surrounding procedure. This information is also used at procedure print-
ing time, so that the global statement will contain exactly the same
global identifiers that were declared in the first place.
The lexical-seq field points to an expression sequence of links to iden-
tifiers in the surrounding scope, if any. The sequence consists of pairs of
pointers. The first pointer of each pair is to the globally unique NAME of
the identifier; this is needed at simplification and printing time. The sec-
ond pointer is a pointer to a LOCAL, PARAM, or LEXICAL structure which is
understood to be relative to the surrounding scope. When a procedure is
evaluated (not necessarily called), the lexical-seq is updated by replacing
each of the second pointers with a pointer to the actual object repre-
sented. The name pointers are not touched, so that the actual identifier
names are still available. The lexical-seq for a procedure contains entries
for any surrounding-scope identifiers used by that procedure or by any
procedures contained within it.
Range
Rational
RATIONAL ∧ integer ∧ pos − integer
This structure is one of the basic numeric objects in Maple. Note that
this is not a division operation, but only a representation for rational
numbers. Both fields must be integers (INTPOS, INTNEG or an immediate
integer) and the second must be positive.
Read Statement
READ ∧ expr
Return Statement
RETURN ∧ expr − seq
Rectangular Table
RTABLE ∧ data ∧ maple− ∧ ind− ∧ attrib flags num-
...
type fn elems
A.2 Internal Representations of Data Types • 599
L1 U1 ... ... LN UN P1 P2
The data field points to either a block of memory (for dense and NAG-
sparse RTABLEs), or to a HASHTAB structure (for Maple-sparse RTABLEs).
The data block is either an object of type BINARY, or memory allocated
directly from the operating system’s storage manager when the block
would be too large to be allocated as a Maple data structure. If the data
block is a BINARY object, the data pointer points to the first data word,
not to the object header.
The maple-type field points to a Maple structure specifying the data
type of the elements of an RTABLE of Maple objects. If the RTABLE con-
tains hardware objects, the maple-type field points to the Maple NAME
anything.
The ind-fn pointer points to either an empty expression sequence
(NULL), or an expression sequence containing at least one indexing func-
tion and a pointer to a copy of the RTABLE structure. The copy of the
RTABLE is identical to the original, except that its ind-fn field refers to
one less indexing function (either NULL, or another expression sequence
containing at least one indexing function and a pointer to another copy
of the RTABLE with one less indexing function again).
The attrib pointer points to an expression sequence of zero or more
arbitrary attributes, which can be set by the setattribute function, and
queried by attributes.
The flags field is a bit field containing the following sub-fields:
• storage - 4 bits - describes the storage layout (e.g. sparse, upper tri-
angular, etc.)
• foreign - 1 bit - indicates that the space pointed to by the data field
does not belong to Maple, so Maple should not garbage collect it.
• number of dimensions - 6 bits - the number of dimensions of the
RTABLE, from 0 to 63.
The num-elems field indicates the total number of elements of stor-
age allocated for the data. For a Maple-sparse RTABLE, num-elems is
not used. For a NAG-sparse RTABLE, num-elems specifies the number of
elements currently allocated, some of which might not be in use.
The remaining fields specify the upper and lower bounds of each di-
mension, and are stored directly as signed machine integers. The limits
on bounds are −2, 147, 483, 648 to 2, 147, 483, 647 for 32-bit architectures
and −9, 223, 372, 036, 854, 775, 808 to 9, 223, 372, 036, 854, 775, 807 for 64-
bit architectures. The total number of elements cannot exceed the upper
limit numbers either.
Save Statement
SAVE ∧ expr − seq
Maple syntax: save expr, expr, ...
Length: 2
Series
SERIES ∧ expr ∧ expr integer ∧ expr integer ... ...
Maple syntax: none
Length: 2n + 2
Set
Statement Sequence
Stop Maple
STOP
String
Sum, Difference
Table
TABLE ∧ index − f unc ∧ array − bounds ∧ hash − tab
This is a general table type, as created by the table and array func-
tions in Maple. The index-func will point to either a NAME or a PROC.
For general tables, the array-bounds field points to the empty expression
sequence (NULL). For arrays (not to be confused with Arrays, which are
implemented as RTABLEs), the array-bounds field refers to an expression
sequence of RANGEs of integers. The hash-tab field points to a HASHTAB
structure containing the elements.
Table Reference
TABLEREF ∧ name ∧ expr − seq ∧ attrib − expr
Try Statement
TRY ∧ try− ∧ catch− ∧ catch− ... ... ∧ f inal−
Unevaluated Expression
UNEVAL ∧ expr
Use Statement
USE ∧ bindings ∧ statseq
Maple Syntax:
604 • Appendix A: Internal Representation and Manipulation
use bindings in
statseq
end use
Length: 3
Hash Table
Hash Chain
• computing its signature and searching for this signature in the table.
verify that it is the same expression. If the expression is found, the one
in the table is used and the searched one is discarded. A full comparison
of expressions has to be performed only when there is a "collision" of
signatures.
Since simplified expressions are guaranteed to have a unique occur-
rence, it is possible to test for equality of simplified expressions using a
single pointer comparison. Unique representation of identical expressions
is a crucial ingredient to the efficiency of tables, hence also the remember
option. Also, since the relative order of objects is preserved during garbage
collection, this means that sequences of objects can be ordered by machine
address. For example, sets in Maple are represented this way. The set op-
erations union, intersection, etc. can be done in linear time by merging
sorted sequences. Sorting by machine address is also available to the user
with the sort command.
Remember Tables
A remember table is a hash table in which the argument(s) to a procedure
call are stored as the table index, and the result of the procedure call is
stored as the table value. Because a simplified expression in Maple has a
unique instance in memory, the address of the arguments can be used as
the hash function. Hence, searching a remember table is very fast.
There are eight kernel functions which use remember tables: evalf,
series, divide, normal, expand, diff, readlib, and frontend. The
internal handling of the latter five is straightforward. There are some
exceptions with the first three, namely:
• evalf and series need to store some additional environment infor-
mation (’Digits’ for evalf and ’Order’ for series). Consequently, the
608 • Appendix A: Internal Representation and Manipulation
entries for these are extended with the precision information. If a re-
sult is requested with the same or less precision than what is stored in
the table, it is retrieved anyway and "rounded". If a result is produced
with more precision than what is stored, it is replaced in the table.
3. an indexing function.
A.4 Portability
The Maple kernel and the textual user interface are not tied to any one
operating system or hardware architecture. The Maple kernel was de-
signed to be portable to any system which supports a C compiler, a flat
address space, and a 32-bit or 64-bit word size. Some platforms on which
Maple is supported are (refer to the installation instructions for currently
supported OS versions):
The majority of the source code comprising the kernel is the same
across all platforms. Extensive use of macros and conditional compila-
tion take care of platform dependencies, such as word size, byte ordering,
storage alignment requirements, differences in hardware floating point
support, and sometimes, C compiler bugs.
The Maple library is interpreted by the Maple kernel. Therefore, other
than issues such as maximum object size, it is completely independent of
610 • Appendix A: Internal Representation and Manipulation
611
612 • Index
zip, 194
ZPPOLY, 604
630 • Index