Maple 9 Advanced Programming Guide PDF
Maple 9 Advanced Programming Guide PDF
Maple 9 Advanced Programming Guide PDF
Advanced Programming
Guide
M. B. Monagan K. O. Geddes K. M. Heal
G. Labahn S. M. Vorkoetter J. McCarron
P. DeMarco
c Maplesoft, a division of Waterloo Maple Inc. 2003.
ii •
c Maplesoft, a division of Waterloo Maple Inc. 2003. All rights re-
served.
Printed in Canada
ISBN 1-894511-44-1
Contents
Preface 1
Audience . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Worksheet Graphical Interface . . . . . . . . . . . . . . . . . . 2
Manual Set . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Conventions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Customer Feedback . . . . . . . . . . . . . . . . . . . . . . . . . 3
iii
iv • Contents
Power . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 414
Procedure Definition . . . . . . . . . . . . . . . . . . . . . 414
Product, Quotient, Power . . . . . . . . . . . . . . . . . . 416
Range . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 416
Rational . . . . . . . . . . . . . . . . . . . . . . . . . . . . 416
Read Statement . . . . . . . . . . . . . . . . . . . . . . . . 417
Return Statement . . . . . . . . . . . . . . . . . . . . . . 417
Rectangular Table . . . . . . . . . . . . . . . . . . . . . . 417
Save Statement . . . . . . . . . . . . . . . . . . . . . . . . 419
Series . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 419
Set . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 419
Statement Sequence . . . . . . . . . . . . . . . . . . . . . 420
Stop Maple . . . . . . . . . . . . . . . . . . . . . . . . . . 420
String . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420
Sum, Difference . . . . . . . . . . . . . . . . . . . . . . . . 421
Table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 421
Table Reference . . . . . . . . . . . . . . . . . . . . . . . . 421
Try Statement . . . . . . . . . . . . . . . . . . . . . . . . 422
Unevaluated Expression . . . . . . . . . . . . . . . . . . . 422
Use Statement . . . . . . . . . . . . . . . . . . . . . . . . 422
Logical XOR . . . . . . . . . . . . . . . . . . . . . . . . . 423
Polynomials with Integer Coefficients modulo n . . . . . . 423
A.3 The Use of Hashing in Maple . . . . . . . . . . . . . . . . 424
Basic Hash Tables . . . . . . . . . . . . . . . . . . . . . . 424
Dynamic Hash Tables . . . . . . . . . . . . . . . . . . . . 425
The Simplification Table . . . . . . . . . . . . . . . . . . . 426
The Name Table . . . . . . . . . . . . . . . . . . . . . . . 427
Remember Tables . . . . . . . . . . . . . . . . . . . . . . . 427
Maple Language Arrays and Tables . . . . . . . . . . . . . 428
Maple Language Rectangular Tables . . . . . . . . . . . . 429
A.4 Portability . . . . . . . . . . . . . . . . . . . . . . . . . . . 429
Index 431
Preface
• Numerical programming
Audience
This manual provides information for experienced Maple programmers.
You should be familiar with the following.
• Example worksheets
1
2 • Preface
Manual Set
There are three other manuals available for Maple users, the Maple Get-
ting Started Guide, the Maple Learning Guide, and the Maple Intro-
ductory Programming Guide.2
• The Maple Learning Guide explains how Maple and the Maple lan-
guage work. It describes the most important commands and uses them
to solve technical problems. User hints for Maplet applications are also
described in this guide.
The Maple software also has an online help system. The Maple help sys-
tem allows you to search in many ways and is always available. There are
also examples that you can copy, paste, and execute immediately.
1
The command-line version provides optimum performance. However, the worksheet
interface is easier to use and renders typeset, editable math output and higher quality
plots.
2
The Student Edition does not include the Maple Introductory Programming Guide
and the Maple Advanced Programming Guide. These programming guides can be pur-
chased from school and specialty bookstores or directly from Maplesoft.
Conventions • 3
Conventions
This manual uses the following typographical conventions.
Customer Feedback
Maplesoft welcomes your feedback. For suggestions and comments related
to this and other manuals, email doc@maplesoft.com.
4 • Preface
1 Procedures, Variables,
and Extending Maple
Prerequisite Knowledge
Before reading this chapter, you must have an understanding of Maple
evaluation rules for variables and parameters as described in chapter 6 of
the Introductory Programming Guide.
In This Chapter
Nested Procedures You can define a Maple procedure within another
Maple procedure.
Local Variables Local variables can exist after the procedure which cre-
ated them has exited. This feature allows a procedure to return a proce-
dure. The new procedure requires a unique place to store information.
5
6 • Chapter 1: Procedures, Variables, and Extending Maple
1 1
[1, , , 2]
2 4
Example This new procedure divides each element of a list by the first
element of that list.
> nest := proc(x::list)
> local v;
> v := x[1];
> map( y -> y/v, x );
> end proc:
> nest(lst);
1 1
[1, , , 2]
2 4
Scoping Rules
This section explains Maple scoping rules. You will learn how Maple de-
termines which variables are local to a procedure and which are global.
You must have a basic understanding of Maple evaluation rules for pa-
rameters, and for local and global variables. For more information, refer
to chapter 6 of the Introductory Programming Guide.
8 4 2 16
[ , , , ]
v v v v
If, using the above rule, Maple cannot determine whether a variable
is global or local, the following default decisions are made.
Example
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 shows which variables Maple recognizes as local and which
are 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
1.1 Nested Procedures • 9
a := [2, 4, 1, 5, 3]
[2, 1, 3, 5, 4]
[2, 1, 3, 5, 4]
a := [2, 4, 1, 5, 3]
1.1 Nested Procedures • 11
[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
6.648630719
0.828316845400000, −0.328875163100000,
0.790988967100000, 0.624953401700000,
0.362773633800000, 0.679519822000000,
−0.0465278542000000, −0.291055180800000
The proper design choice here is that U should depend only on the
value of Digits when you invoke U. The following version of uniform
accomplishes this by placing the entire 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:
0.476383408581006, 0.554836962987261,
0.147655743361511, 0.273247304736175,
0.148172828708797, −0.258115633420094,
0.558246581434993, 0.518084711267009
Note: The interface variable displayprecision controls the number of
decimal places to be displayed. The default value is −1, representing full
precision as determined by the Digits environment variable. This sim-
plifies display without introducing round-off error. For more information,
refer to ?interface.
14 • Chapter 1: Procedures, Variables, and Extending Maple
Conveying Values
The following example demonstrates how locating the roots of a function
by using Newton’s method can be implemented in a procedure.
1. Choose a point on the x-axis that you think might be close to a root.
3. Draw the tangent to the curve at that point and observe where the
tangent intersects the x-axis. For most functions, this second point is
closer to the real root than your initial guess. To find the root, use
the new point as a new guess and keep drawing tangents and finding
new points.
1.2 Procedures That Return Procedures • 15
2
1.5
1
0.5
x0 x1
0 1 2 3 4 5 6 7 8
–0.5 x
–1
1. Newton’s method
f (xk )
xk+1 = xk −
f 0 (xk )
Example 1
The following procedure takes a function and creates a new procedure,
which takes an initial guess and, for that particular function, generates
the next guess. The new procedure does not work for other functions. To
find the roots of a new function, use MakeIteration to generate a new
guess-generating procedure. The unapply command turns an expression
into a procedure.
> MakeIteration := proc( expr::algebraic, x::name )
> local iteration;
> iteration := x - expr/diff(expr, x);
> unapply(iteration, x);
> end proc:
x0 := 2.0
x0 := 4.828427124
x0 := 4.032533198
x0 := 4.000065353
x0 := 4.000000000
Example 2
The MakeIteration procedure requires its first argument to be an al-
gebraic expression. You can also write a version of MakeIteration that
works on functions. Since the following MakeIteration procedure recog-
nizes the parameter f as a procedure, you must use the eval command
to evaluate it fully.
> MakeIteration := proc( f::procedure )
> (x->x) - eval(f) / D(eval(f));
> end proc:
> g := x -> x - cos(x);
g := x → x − cos(x)
x → x − cos(x)
SirIsaac := (x → x) −
x → 1 + sin(x)
1.2 Procedures That Return Procedures • 17
x0 := 1.0
x0 := 0.7503638679
x0 := 0.7391128909
x0 := 0.7390851334
x0 := 0.7390851332
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) ):
x → sin(x + 1)
Maple lexical scoping rules declare the f within the inner procedure
to be the same f as the parameter within the outer procedure. Therefore,
the shift command works as written.
The previous example of shift 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);
18 • Chapter 1: Procedures, Variables, and Extending Maple
hh := x → h(x + 1)
> hh(x,y);
> hh := shift(h);
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
1.3 Local Variables and Invoking Procedures • 19
Example 1
The following procedure creates a new local variable, a, and then returns
this new variable.
> make_a := proc()
> local a;
> a;
> end proc;
By using local variables, you can produce displays that Maple would
otherwise simplify. For example, in Maple, a set contains unique elements.
The following demonstrates that each variable a that make_a returns is
unique.
> test := { a, a, a };
test := {a}
test := {a, a}
test := {a, a, a, a, a, a, a}
20 • Chapter 1: Procedures, Variables, and Extending Maple
Example 2
You can display expressions that Maple would ordinarily simplify au-
tomatically. For example, Maple automatically simplifies the expression
a + a to 2a. It is difficult to display the equation a + a = 2a. To display
such an equation, use the procedure make_a from Example 1.
> a + make_a() = 2*a;
a + a = 2a
eqn := a + a = 2 a
another _a := a
You can then assign the global name a to this extracted variable and
verify the equation.
1.3 Local Variables and Invoking Procedures • 21
2a = 2a
> evalb(%);
true
Assume Facility For complicated expressions, you must use the assume
command to extract the desired variable. You may have encountered this
situation before without realizing it, when you were using the assume
facility to remove an assumption. The assume facility attaches various
definitions to the variable you specify, with one result being that the
name subsequently appears as a local name with an appended tilde. No
relationship exists between the local variable b with an assumption, which
is displayed as b~, and the global variable name containing a tilde b~.
> 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
Example 3
Creating the Cartesian Product of a Sequence of Sets When you pass
a sequence of sets to the procedure, it constructs a new procedure. The
new procedure returns the next term in the Cartesian product each time
you invoke it. Local variables from the outer procedure are used to keep
track of which term to return next.
The Cartesian product of a sequence of sets is the set of all lists
in which the ith entry is an element of the ith set. Thus, the Cartesian
product of {α, β, γ} and {x, y} is
{α, β, γ} × {x, y} = {[α, x], [β, x], [γ, x], [α, y], [β, y], [γ, y]}.
The number of elements in the Cartesian product of a sequence of sets
grows very rapidly as the number of sets or size of the sets increases. It
therefore requires a large amount of memory to store all the elements of
the Cartesian product.
Solution You must write a procedure that returns a new element of the
Cartesian product each time you call it. By calling such a procedure re-
peatedly, you can process every element in the Cartesian product without
storing all its elements at once.
The following procedure returns the next element of the Cartesian
product of the list of sets s. It uses an array, c, of counters to determine
the next element. For example, c[1]=3 and c[2]=1 correspond to the
third element of the first set and the first element of the second set.
> s := [ {alpha, beta, gamma}, {x, y} ];
c := [3, 1]
1.3 Local Variables and Invoking Procedures • 23
[β, x]
Before you call the element procedure you must initialize all the coun-
ters to 1, except the first one, which must be 0.
> c := array( [0, 1] );
c := [0, 1]
[γ, x]
[α, x]
[β, x]
[γ, y]
[α, y]
[β, y]
24 • Chapter 1: Procedures, Variables, and Extending Maple
FAIL
[γ, x]
Example 4
Instead of writing a new procedure for each Cartesian product you study,
you can write a procedure, CartesianProduct, that returns such a pro-
cedure. CartesianProduct creates a list, s, of its arguments, which must
be sets, and then initializes the array, c, of counters and defines the sub-
procedure element. Finally, the element subprocedure is invoked inside
a proc structure.
> CartesianProduct := proc()
> local s, c, element;
> s := [args];
> if not type(s, list(set)) then
> error "expected a sequence of sets, but received",
> args ;
> end if;
> c := array( [0, 1$(nops(s)-1)] );
>
> element := proc(s::list(set), c::array(1, nonnegint))
> local i, j;
> for i to nops(s) do
> c[i] := c[i] + 1;
> if c[i] <= nops( s[i] ) then
> return [ seq(s[j][c[j]], j=1..nops(s)) ] ;
> end if;
> c[i] := 1;
> end do;
> c[1] := 0;
> FAIL;
> end proc;
>
> proc()
> element(s, c);
> end proc;
> end proc:
Again, you can find all six elements of {α, β, γ} × {x, y}.
> f := CartesianProduct( {alpha, beta, gamma}, {x,y} );
1.3 Local Variables and Invoking Procedures • 25
[γ, 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]
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 )
Maplesoft
s := “Maplesoft”
28 • Chapter 1: Procedures, Variables, and Extending Maple
Example 1
The following application prompts the user for an answer to a question.
> DetermineSign := proc(a::algebraic) local s;
> printf("Is the sign of %a positive? Answer yes or no: ",a);
> s := readline(terminal);
> evalb( s="yes" or s = "y" );
> end proc:
> DetermineSign(u-1);
true
readstat( prompt )
n−1
The user input for a readstat command must have a terminating semi-
colon or colon, or an error is raised.
Advantages Unlike the readline command, which only reads one line,
the readstat allows you to break a large expression across multiple lines.
Another advantage of using the readstat command is that if there is
an error in the input, the readstat command automatically repeats the
prompt for user input.
1.4 Interactive Input • 29
390625
Example 2
The following is an application of the readstat command that imple-
ments an interface to the limit command. The procedure, given the
function f (x), assumes x is the variable if only one variable is present.
Otherwise, the user is asked for the variable and the limit point.
> GetLimitInput := proc(f::algebraic)
> local x, a, K;
> # choose all variables in f
> K := select(type, indets(f), name);
>
> if nops(K) = 1 then
> x := K[1];
> else
> x := readstat("Input limit variable: ");
> while not type(x, name) do
> printf("A variable is required: received %a\n", x);
> x := readstat("Please re-input limit variable: ");
> end do;
> end if;
> a := readstat("Input limit point: ");
> x = a;
> end proc:
x=0
30 • Chapter 1: Procedures, Variables, and Extending Maple
In the following output, the user first tries to use the number 1 as the
limit variable. Because 1 is not a name, GetLimitInput requests another
limit variable.
> GetLimitInput( exp(u*x) );
x=∞
s := “a*x^2 + 1”
> y := parse( s );
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
1.5 Extending Maple • 31
The parse command does not evaluate the expression it returns. You
must use eval to evaluate the expression explicitly. In the following out-
put, the variable a is not evaluted 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
Information: For more details about the parse command, see Parsing
Maple Expressions and Statements on page 218.
Summary The techniques in this section are very simple, but you can
use them to create useful applications such as Maple tutorials, procedures
that test students, or interactive lessons.
Writing the structure once reduces the risk of errors. When you have
defined the variable ‘type/name ‘, you can use name as a type.
> ‘type/Variables‘ := {name, list(name), set(name)}:
> type( x, Variables );
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
The Maple software recognizes many operators, for example +, *, ^, and,
not, and union. These operators have special meaning to Maple. The
operators can represent:
• Algebraic operations, such as addition or multiplication
• Logical operations
(7 &^ 8) &^ 9
false
false
7 &^ 8
Maple uses the infix notation, in which the operator is placed between
the operands, only if the neutral operator has exactly two arguments.
> &^(4), &^(5, 6), &^(7, 8, 9);
Example 1
You can define the actions of a neutral operator by assigning a proce-
dure to its name. The following example implements the Hamiltonians by
assigning a neutral operator to a procedure that multiplies two Hamilto-
nians.
type/Hamiltonian :=
{∗, +, realcons , name, 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 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
36 • Chapter 1: Procedures, Variables, and Extending Maple
You can place all the special multiplication rules for the symbols I,
J, and K in the remember table of ‘&^‘.
20 + 41 I + 20 J − 3 K
20 − 15 I − 4 J + 43 K
> 56 &^ I;
1.5 Extending Maple • 37
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
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(%);
Extending Commands
If you introduce custom data structures, there are no manipulation rules
for them. In most cases, you write special-purpose procedures that manip-
ulate new data structures. However, sometimes extending the capabilities
of one or more of the Maple built-in commands is easier than develop-
ing new data structures and special-purpose procedures. You can extend
several Maple commands, among them expand, simplify, diff, series,
and evalf.
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)
(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 following procedure uses the typematch command to determine
if its argument is of the form (a&^b)&^c and, if so, it selects the a, b,
and c.
> s := x &^ y &^ z;
s := (x &^ y) &^ z
true
> a, b, c;
x, y, z
1.5 Extending Maple • 41
The userinfo Command You can give the user details about procedure
simplifications using the userinfo command. The ‘simplify/&^‘ pro-
cedure prints an informative message if you set infolevel[simplify] or
infolevel[all] to greater than or equal to least 2.
> ‘simplify/&^‘ := proc( x )
> local a, b, c;
> if typematch( x,
> ’‘&^‘’( ’‘&^‘’( a::anything, b::anything ),
> c::anything ) ) then
> userinfo(2, simplify, "applying the associative law");
> a &^ ( b &^ c );
> else
> x;
> end if;
> end proc:
> simplify(%);
−x
infolevel simplify := 5
> simplify(%);
1.6 Conclusion
Procedures which return procedures and local variables are fundamental
to advanced programming. Interactive input and extending Maple are also
important topics in advanced programming.
2 Programming with
Modules
Modules
This chapter describes Maple modules. Modules are a type of Maple ex-
pression (like numbers, equations, and procedures), that enable you to
write generic algorithms, create packages, or use Pascal-style records in
programs.
The use of modules satifies four important software engineering concepts.
• Encapsulation
• Packages
• Object Modeling
• Generic Programming
43
44 • Chapter 2: Programming with Modules
Examples
For better understanding, it is helpful to examine a small module.
TempGenerator := module()
local count ;
export gentemp;
description “generator for temporary symbols”;
end module
Example Summary The module definition resembles a procedure defi-
nition. The main differences are the use of the keyword module instead
of proc (and the corresponding terminator) and the export declaration
following the description string.
f := proc()
count := 1 + count ; ‘tools/gensym‘(T ||count )
end proc
> f();
T1
> f();
T2
T1
46 • Chapter 2: Programming with Modules
T2
T3
T4
In This Chapter
This chapter provides many example modules. Some examples are very
simple, designed to illustrate a specific point. Others are more substantial.
Many of the nontrivial examples are available as Maple source code in the
samples directory of the Maple installation. You can load them into the
private Maple library and experiment with them. You can modify, extend,
and improve these code samples, and use them in custom programs.
The following topics are covered in this chapter.
• Modeling Objects
module()
local L ;
export E ;
global G ;
options O ;
description D ;
B
end module
This module definition does not have: exported variables, locals, ref-
erences, global variables, or a body of statements. The module to which
this evaluates is not very useful.
• Zero or more Maple statements. The body is executed when the mod-
ule definition is evaluated, producing a 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 because modules are
not called (or invoked) with arguments.
Named Modules
An optional symbol may appear after the module keyword in a mod-
ule definition. Modules created with this variant on the syntax are called
2.1 Syntax and Semantics • 49
> NormalModule:-e;
Here, the symbol (the name of the module) after the module keyword
is NamedModule.
> module NamedModule() export e; end module;
> NamedModule:-e;
NamedModule : −e
SomeName :=
module NamedModule () export e; end module
> SomeName:-e;
NamedModule : −e
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 is explained later in this chapter.
message
> Hello:-say();
“HELLO WORLD!”
> message;
“HELLO WORLD!”
52 • Chapter 2: Programming with Modules
Local Variables You can refer to variables that are local to the module
definition by using the local declaration. Its format is the same as for
procedures. Here is a variant on the previous Hello module which uses 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 in a module (or procedure) is a distinct object from
a global variable with the same name. Local variables are normally short-
lived variables; the normal lifetime of a local variable is the execution time
of the body of code (a module or procedure body) to which it is local.
(Local variables may persist once execution of the scope in which they oc-
cur has completed, but they are normally inaccessable and will eventually
be recycled by the Maple automatic storage management system.)
modexpr :- membername
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. The member selection
operator :- provides a syntax for referencing certain local variables of
modules–those declared as exports.
The most recent Hello example has one export, named say. In this
case, say is assigned a procedure. To call it, enter
> Hello:-say();
“HELLO WORLD!”
> noSuchModule:-e;
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 previous expression refers to the global e, while the
expression m:-e evaluates to the e that is local to the module m. This
distinction between a global and export of the same name is useful. For
example, you can create a module with an export sin. Assigning a value
to the export sin does not affect the protected global name sin.
2.1 Syntax and Semantics • 55
The exports Procedure You can determine the names of the exports
of a module by using the exports procedure.
> exports( Hello );
say
> evalb( % = e );
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;
The member Procedure 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
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’ );
2.1 Syntax and Semantics • 57
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 contain options. The op-
tions available for modules are different from those for procedures. Only
the options trace, and ‘Copyright...‘ are common to procedures and
modules. The following four options have a predefined meaning for mod-
ules: load, unload, package, and record.
of that procedure. In that case, the module is not garbage collected. When
it eventually is garbage collected or Maple exits, the unload= procedure
is not executed again. The load= and unload= procedures are called
with no arguments.
The package Option Modules with the option package represent Maple
packages. The exports of a module created with the package option are
automatically protected.
modules) and procedure parameters are the same as the rules for nested
procedures.
You can write a generic module for arithmetic modulo any positive
integer n, and then specialize it for any integer that you need. This is
possible as a result of the standard lexical scoping rules. You must write
a constructor procedure for the module that accepts the value of n as
an argument. Here is a generic version of the z6 example.
> MakeZn := proc( n::posint )
> module()
> export add, mul;
> add := ( a, b ) -> a + b mod n;
> mul := ( a, b ) -> a * b mod n;
> end module
> end proc:
To generate a module that does arithmetic modulo 7, call the constructor
MakeZn with the number 7 as its argument.
> z7 := MakeZn( 7 );
> z7:-add( 3, 4 );
0
60 • Chapter 2: Programming with Modules
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. You
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.
> map( print, module() export a, b, c; end );
Modules also follow last name evaluation rules. For more information
on last name evaluation rules, refer to ?last_name_eval.
2.1 Syntax and Semantics • 61
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)
Adding Missing Functionality To add the missing functionality, add a
case for expressions of type function.
> differentiate := proc( expr, var )
> local a, b;
>
> if not has( expr, var ) then
> 0
> elif expr = var then
> 1
> elif type( expr, ’‘+‘’ ) then
> map( procname, args )
> elif type( expr, ’‘^‘’ ) then
> a, b := op( expr );
> if not has( b, var ) then
> b * a ^ ( b - 1 ) * procname( a, var )
> else
> ’procname( args )’
64 • Chapter 2: Programming with Modules
> 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 b = ’sin’ then
> cos( a ) * procname( a, var )
> elif b = ’cos’ then
> -sin( a ) * procname( a, var )
> elif b = ’exp’ then
> exp( a ) * procname( a, var )
> elif b = ’ln’ then
> ( 1 / a ) * procname( a, var )
> else
> ’procname( args )’
> end if
> else
> ’procname( args )’
> end if
> end proc:
This uses the chain rule to compute the derivatives of calls to known
functions.
> differentiate( sin( x ) + cos( exp( x ) ), x );
cos(x) − sin(ex ) ex
2 cos(x2 ) x − 2 sin(x2 ) x
At the same time, this has also improved the handling of expressions
independent of the variable of differentiation.
> differentiate( a*x^2 + b*x + c, x );
2ax + b
2.1 Syntax and Semantics • 65
2 sin(x) cos(x)
> 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.
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.
2.1 Syntax and Semantics • 67
With this strategy in mind, you can create a module DiffImpl, with
principal export differentiate. At the same time, you can also 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 );
> a := op( expr );
> if assigned( functab[ e ] ) then
> functab[ e ]( a ) * procname( a, var )
> else
> ’procname( args )’
> end if
> else
> b := whattype( expr );
68 • Chapter 2: Programming with Modules
> whattype( a / b );
∗
2.1 Syntax and Semantics • 69
> whattype( [ f( x ), g( x ) ] );
list
in the module body. 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)
72 • Chapter 2: Programming with Modules
(You can use a different argument to diff to avoid recalling the an-
swer from its remember table.) The traditional mechanism fails because
it relies on the external representation of names, and not upon their
bindings, so each attempt to define an extension to diff in fact adds a
definition for the derivative of all functions whose names are spelled "F".
2.2 Records
The simplest way 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. Although the underlying data structure of a Maple record is cur-
rently a module, records and modules represent distinct abstractions. A
record is simply an aggregate data structure in which the members have
fixed names. Modules provide additional facilities such as computation at
initialization and access control.
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;
a := 2
> rec:-a;
2
2.2 Records • 73
If not assigned, the record slot evaluates to the local instance of the
slot name.
> rec:-b;
> evalb( % = b );
false
> r:-b;
√
3
r := module()
export a::integer , b::numeric;
option record ;
end module
> r:-b := "a string";
If the initializer for a record slot is a procedure, you can use the
reserved name self to refer to the record you are constructing. This
allows records to be self-referential. For example, you can write a complex
number constructor as follows.
> MyComplex := ( r, i ) ->
> Record( ’re’ = r, ’im’ = i, ’abs’ = (() -> sqrt(
> self:-re^2 + self:-im^2 )) ):
> c := MyComplex( 2, 3 ):
> c:-re, c:-im, c:-abs();
√
2, 3, 13
true
This is a structured type that works the same way as the ‘module‘
type, but recognizes records specifically.
> r := Record( a = 2, b = "foo" ):
> type( r, ’record( a::integer, b::string )’ );
true
2.2 Records • 75
Note: In a record type, the slot types are used to test against the values
assigned to the slots (if any), and are not related to type assertions on
the slot names (if any).
> r := Record( a::integer = 2, b::{symbol,string} = "foo" ):
> type( r, ’record( a::numeric, b::string )’ );
true
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
2, 3
> r := Record[p]( c = 4 );
r :=
module() export a, b, c; option record ; end module
> r:-a, r:-b, r:-c;
2, 3, 4
In this example, the record p is the prototype, and the second record
r inherits the slots a and b, and their values, from the prototype p. It also
augments the slots obtained from p with a new slot c. The prototype p is
not changed.
> r:-a := 9;
a := 9
2.2 Records • 77
> p:-a;
true
2.3 Packages
Modules are ideal for writing Maple packages. They provide facilities for
large software projects that are better than 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|)
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 + ...
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 con-
structor 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 the Maple built-in list
structures, 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 recognize the pair, head, and tail operations as the more
traditional operations known as “consÔ, “carÔ and “cdrÔ.
82 • Chapter 2: Programming with Modules
> 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;
>
> length := proc( lst )
> if nullp( lst ) then
2.3 Packages • 83
> 0
> else
> 1 + length( tail( lst ) )
> end if
> end proc;
>
> member := proc( item, lst )
> if nullp( lst ) then
> false
> elif item = head( lst ) then
> true
> else
> procname( item, tail( lst ) )
> end if
> end proc;
>
> map := proc( p, lst )
> if nullp( lst ) then
> nil
> else
> pair( p( head( lst ) ),
> procname( p, tail( lst ) ) )
> end if
> end proc;
>
> append := proc( lst1, lst2 )
> if nullp( lst1 ) then
> lst2
> else
> pair( head( lst1 ),
> procname( tail( lst1 ), lst2 ) )
> end if
> end proc;
>
> reverse1 := proc( sofar, todo )
> if nullp( todo ) then
> sofar
> else
> procname( pair( head( todo ), sofar ),
> tail( todo ) )
> end if
> end proc;
>
> reverse := lst -> reverse1( nil, lst );
>
> setup();
>
> end module:
Normally, a package definition like this would be entered into a Maple
source file using a text editor, or in a worksheet using the Maple graphical
user interface. In either case, the definition would then be followed by a
call to the savelib procedure using the name of the module as its sole
84 • Chapter 2: Programming with Modules
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.)
(a, b)
(a, b)
2.3 Packages • 85
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 );
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
2.3 Packages • 87
> L2 := list( a, b, c, d );
Design You can write tests that exercise each part of the program to
ensure that the program:
• Works correctly
2
The Maple CodeTools package provides tools for profiling code and testing code
coverage. For more information, refer to ?CodeTools.
88 • Chapter 2: 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 argu-
ment has been supplied as an argument to p. To get complete coverage,
a negative argument must also be supplied.
> p( -1 );
−2
> showstat( p );
2.3 Packages • 89
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
The display shows that each statement in the body of p has been
reached.
To display the profiling information, use the debugopts command
with the traceproctable=procedure_name equation argument.
> debugopts( traceproctable=p );
2 0 24
2 0 24
1 0 0
1 0 0
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, userprocs;
>
> # Instrument a procedure or module
> # for coverage profiling. Return the
> # number of procedures instrumented.
> profile := proc()
> local arg;
> add( rprofile( arg ), arg = [ args ] )
> end proc;
90 • Chapter 2: Programming with Modules
>
> rprofile := proc( s::name )
> local e;
> if type( s, ’procedure’ ) then
> debugopts( ’traceproc’ = s );
> 1
> elif type( s, ’‘module‘’ ) then
> add( procname( e ),
> e = select( type,
> [ exports( s, ’instance’ ) ],
> ’{ ‘module‘, procedure }’ ) )
> else
> error "only procedures and modules can be profiled"
> end if
> end proc;
>
> # Subroutine to recognize non-builtin procedures
> userprocs := proc( s)
> type( ’s’, procedure) and not( type( ’s’, builtin ) )
> end proc;
>
> # Subroutine to recognize profiled procedures
> traced := proc( s )
> debugopts( ’istraceproced’ = ’s’ )
> end proc;
>
> # Determine which procedures have
> # coverage information.
> covered := proc()
> local S;
> S := [ anames( ) ];
> S := select( userprocs, S );
> S := select( traced, S );
> if nargs > 0 and args[ 1 ] = ’nonzero’ then
> S := select( s -> evalb( s[1,1] <> 0 ), S )
> elif nargs > 0 then
> error "optional argument is the name nonzero"
> end if;
> map( parse, map( convert, S, ’string’ ) )
> end proc;
> end module:
first converts the names to strings, and then 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 );
[covered , profile]
[p, copy ]
copy := proc(A)
|Calls Seconds Words|
PROC | 2 0.000 664|
1 | 2 0.000 50| 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 24| if type(A,name) then
4 | 0 0.000 0| return map(proc () args end proc,
eval(A))
else
5 | 2 0.000 590| return map(proc () args end proc,A)
end if
else
6 | 0 0.000 0| return A
end if
2.3 Packages • 93
end proc
it appears that the rtable case (statement 2) has not been called.
Add a test for the rtable case.
> copy( rtable() ):
> showstat( copy );
copy := proc(A)
|Calls Seconds Words|
PROC | 3 0.000 832|
1 | 3 0.000 62| if type(A,rtable) then
2 | 1 0.000 156| return rtable(rtable_indfns(A),
rtable_dims(A),A,rtable_options(A),
readonly = false)
elif type(A,{array, table}) then
3 | 2 0.000 24| if type(A,name) then
4 | 0 0.000 0| return map(proc () args end proc,
eval(A))
else
5 | 2 0.000 590| 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 1185|
1 | 4 0.000 96| if type(A,rtable) then
2 | 1 0.000 156| return rtable(rtable_indfns(A),
rtable_dims(A),A,rtable_options(A),
readonly = false)
elif type(A,{array, table}) then
3 | 3 0.000 28| if type(A,name) then
4 | 1 0.000 315| return map(proc () args end proc,
eval(A))
else
5 | 2 0.000 590| return map(proc () args end proc,A)
end if
else
94 • Chapter 2: Programming with Modules
6 | 0 0.000 0| return A
end if
end proc
The only case that has not been called 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 1221|
1 | 5 0.000 122| if type(A,rtable) then
2 | 1 0.000 156| return rtable(rtable_indfns(A),
rtable_dims(A),A,rtable_options(A),
readonly = false)
elif type(A,{array, table}) then
3 | 3 0.000 28| if type(A,name) then
4 | 1 0.000 315| return map(proc () args end proc,
eval(A))
else
5 | 2 0.000 590| 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
the test cases. This functionality is very useful for interactively developing
unit tests for Maple programs.
Note: The source presented here for the coverage package has been
simplified for presentation in printed form. The full source code is available
in the samples/AdvPG directory of the Maple installation.
Note: Only portions of the source code for this package are shown here.
The fully commented source code can be found in the samples/AdvPG/shapes
directory of the 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, use the Maple pre-
processor 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:
98 • Chapter 2: Programming with Modules
The Package API The Shapes package exports the following routines:
1. make
2. area
3. circumference
org := make(point , 0, 0)
In each case, the name of the shape is passed as the first argument,
to specify to make the kind of shape to return.
The area Procedure To compute the area of a shape, call the exported
procedure area with the shape as its argument.
> area( circ );
2. Conditional dispatching
3. Table-based dispatching
Summary The first argument to make is a symbol that denotes the kind
of shape to construct (point, circle, triangle). This symbol is used
as an index in the Shapes:-Shapes submodule. The first statement uses
member to test whether the symbol passed in the parameter what is ex-
ported by the submodule Shapes:-Shapes. If it is not found, an appro-
priate diagnostic is issued, and an exception raised. If member returns
the value true, then its third argument, the local variable theShape, is
assigned the export found in the submodule.
For example, if what is the symbol circle, then the local variable
theShape is assigned the submodule Shapes:-Shapes:-circle that im-
plements operations on circles. The same idea is used to select the shape-
specific constructor; it is the value assigned to the local variable ctor
upon a true return value from the second call to member. Any remaining
arguments are used as data to construct the shape. These are passed to the
make export in a shape-specific submodule, if found, and are not checked
further at this level. This design localizes the shapes to the corresponding
submodule.
> end if
> end proc:
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
with the given shape as an argument. (The shape must be passed as an
argument, 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, if 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 statement,
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.
104 • Chapter 2: Programming with Modules
> use a = 2, b = 3 in
> use a = 3 in a + b end
> end use;
In this example, the inner binding of the value 3 to the name a takes
precedence, so the value of the expression a + b (and hence of the entire
statement) is the number 6. The inner binding of 3 to a has an effect
only within the body of the inner use statement. Once the execution has
exited the inner use statement, the binding of 2 to a 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;
of f was expanded, yielding the expression that involves only the param-
eters a and b.
0 0 0 0
3
λ + 71 λ − 856 λ2 − 43316 λ
4 3
Note that a name that appears in a binding list for a use statement,
which 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.
106 • Chapter 2: Programming with Modules
> proc( m, a, b )
> use m in e( a, b ) end
> end proc;
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
the operator overloading found in some programming languages (such as
C++), because the rebinding occurs during automatic simplification in
Maple.
If an operator name appears on the left side of a binding equation
for a use statement (consequently, if it is an exported name of a module
that is bound via use), then the corresponding operator expressions in
the body of the use statement are transformed into function calls. For
example:
> use ‘+‘ = F in a + b end;
F(a, b)
> m := module()
> export ‘*‘, ‘+‘;
> ‘+‘ := ( a, b ) -> a + b - 1;
> ‘*‘ := ( a, b ) -> a / b;
> end module:
> s * ( s + t );
2.4 The use Statement • 107
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.
110 • Chapter 2: Programming with Modules
1, 1
> z:-abs();
√
2
> z:-arg();
1
π
4
Priority Queues
A useful data structure that can be implemented in an object-oriented
way with modules is the priority queue. A priority queue is a container
data structure that admits the following operations:
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;
50 − 85 x5 − 55 x4 − 37 x3 − 35 x2 + 97 x
72 − 99 x5 − 85 x4 − 86 x3 + 30 x2 + 80 x
72 − 53 x5 + 85 x4 + 49 x3 + 78 x2 + 17 x
−59 + 79 x5 + 56 x4 + 49 x3 + 63 x2 + 57 x
88 − 86 x5 + 23 x4 − 84 x3 + 19 x2 − 50 x
−62 − 50 x5 − 12 x4 − 18 x3 + 31 x2 − 26 x
83 − 58 x5 − 90 x4 + 53 x3 − x2 + 94 x
−61 + 77 x5 + 66 x4 + 54 x3 − 5 x2 + 99 x
−62 + 45 x5 − 8 x4 − 93 x3 + 92 x2 + 43 x
41 + x5 − 47 x4 − 91 x3 − 47 x2 − 61 x
[7, 7, 15, 25, 27, 27, 28, 29, 42, 51, 52, 55, 62, 74, 82,
88, 94, 97, 97, 98]
Note: The fully commented source code for the PriorityQueue con-
structor is available in the samples/advPG/PriorityQueue directory of
the Maple installation.
The segment Constructor Segments are represented using the start and
end points of the segment. These are the points returned by the point
constructor.
> segment := proc( pt1, pt2 )
> module()
> export area,
> circumference,
> length,
> start_point,
> end_point;
> local mymidpoint;
>
> start_point := () -> pt1;
> end_point := () -> pt2;
> area := () -> 0;
> circumference := () -> 0;
> length := proc()
> local x, y;
> x := pt1:-xcoord() - pt2:-xcoord();
> y := pt1:-ycoord() - pt2:-ycoord();
> sqrt( x^2 + y^2 )
> end proc;
> midpoint := proc()
> local x, y;
> if assigned( mymidpoint ) then
> mymidpoint
> else
> y := (pt1:-ycoord() + pt2:-ycoord())/2;
> x := (pt1:-xcoord() + pt2:-xcoord())/2;
> point( x, y )
> end if
> end proc;
> end module
> end proc:
Again, the lexically scoped parameters ctr and rad encode the in-
stance data of the circle object.
Note: The remainder of the object oriented version of the Shapes pack-
age can be read in ShapeObj.mpl file in the samples/AdvPG directory of
your Maple installation.
Interfaces
The contracts discussed previously in this section 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 to which clients have 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.
2.6 Interfaces and Implementations • 119
> 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;
> end module:
> type( z5, ’ring’ );
true
true
Interfaces are an abstraction that form part of the Maple type system.
They provide a form of constrained polymorphism. Not every Maple type
is an interface; only those that have the form described are. You can define
a Maple type (that, as it happens, is not itself an interface) to describe
interfaces.
> ‘type/interface‘ := ’specfunc( {symbol,symbol::type},
> ‘module‘ )’:
This is a structured type. It describes expressions that are themselves
structured types. They have the form of an unevaluated function call
with the operator symbol ‘module‘ and all arguments of type symbol, or
of type symbol::type. In the two previous examples in this section, the
types type/ring and type/abgroup are the interface expressions, and the
names ring and abgroup are the respective names of those interfaces.
abgroup
true
true
2.6 Interfaces and Implementations • 123
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
is done for illustrative purposes. A better scheme simply invokes setup
in the body of the module definition.
124 • Chapter 2: Programming with Modules
a
b
e
c d
f
This diagram represents a graph with vertex set V = {a, b, c, d, e, f }, and
edge set E = {(a, b), (a, c), (b, d), (c, f ), (f, d), (b, e), (d, e), (c, b), (c, d)}.
3. Store, for each vertex of the graph, the set of all its neighbours.
(The adjacency matrix is a square matrix whose rows and 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.) You can write software
that manipulates a graph independent of representation.
true
2.6 Interfaces and Implementations • 127
you 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
Therefore, the generic procedure vdeg works equally well with it.
> vdeg( g2, a );
2, 0
1, 1
0, 2
Note: The full source code for these procedures is available in the
samples/AdvPG/graph directory of the Maple installation.
> AdjacencyMatrix( g2 );
0 1 1
0 0 1
0 0 0
Quotient Fields
As an example of generic programming, a generic quotient field (or field
of fractions) construction algorithm is discussed.
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.
U (R) × R −→ R : (u, r) → u · r
Designing the Ring Interfaces The first step in representing these ideas
in software is to devise an interface that describes the rings. Suppose that
the rings are equipped with the basic ring operations, as well as several
methods that implement desired computations.
> ‘type/Ring‘ := ’‘module‘(
> ‘+‘::procedure,
> ‘*‘::procedure,
> ‘-‘::procedure,
> iszero::procedure,
> isone::procedure,
> zero, one
> )’:
This interface corresponds naturally with a formal mathematical charac-
terization of the ring as a tuple
hS, +, ∗, 0, 1i
true
true
• make for constructing field elements from their numerators and de-
nominators
2.6 Interfaces and Implementations • 133
• 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,
> numer::procedure,
> denom::procedure
> )’:
Naturally, the ring Z of integers is not a field.
> type( MapleIntegers, ’Field’ );
false
The Quotient Field Functor Here is the generic constructor for quotient
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 );
> denom := f -> op( 2, f );
134 • Chapter 2: Programming with Modules
FF := module()
export‘ + ‘, ‘ ∗ ‘, ‘ − ‘, ‘/‘, zero, one, iszero, isone, make,
numer , denom, normal , embed ;
description “a quotient field”;
end module
> type( FF, ’Field’ );
136 • Chapter 2: Programming with Modules
true
> a := FF:-make( 2, 3 );
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;
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
• Closed sets of invertible matrices (all of the same size, with a common
ground field), using multiplication (linear groups)
An Interface for Finite Groups First, you must decide how to represent
the generic group interface. This is determined by the proposed use of the
group objects. Once again, the design takes a group to be a repository of
data and computational services that you can query or invoke.
The Group signature used in the examples describes a computational
model of abstract groups that supports the methods in Table 2.3.
> ‘type/Group‘ := ’‘module‘(
> id, ‘.‘, mul, inv,
> eq, member,
> gens,
> order, elements
> )’:
A corresponding constructor for groups is easily written using the Record
constructor introduced earlier. For the examples in this section, no default
methods are introduced.
> Group := proc()
> Record( op( ‘type/Group‘ ) );
> end proc:
This constructor does very little work on its own. It relies on more spe-
cialized constructors to establish useful values or defaults for the methods
exported.
You can write generic algorithms using this interface immediately.
A few simple examples are these routines for computing conjugates and
commutators of group elements. The conjugate of a group member a by
a group member b is b−1 ab. This routine computes the conjugate of an
element a by an element b in a group G.
> Conjugate := proc( G, a, b )
> description "compute the conjugate of a "
> "group element by another";
140 • Chapter 2: Programming with Modules
(inv(y)) . x . y
Similarly, you can compute the commutator [a, b] = a(−1) b(−1) ab,
generically, as follows.
> Commutator := proc( G, a, b )
> description "compute the commutator of "
> "two group elements";
> use ‘/‘ = G:-inv, mul = G:-mul in
> mul( inv( a ), inv( b ), a, b )
> end use
> end proc:
Again, this computation is done symbolically, so the group operations
return unevaluated.
> Commutator( Group(), ’x’, ’y’ );
mul(inv(x), inv(y), x, y)
(12).) The constructor takes a positive integer as its first argument, indi-
cating the degree of the permutation group. The remaining arguments are
expected to be permutations (represented as lists) of the stated degree.
These are used to form the generating set of the group returned by the
constructor.
> PermutationGroup := proc( deg::posint )
> description "permutation group constructor";
> local G, gens;
> gens := { args[ 2 .. -1 ] };
> G := Group();
> G:-id := [ $ 1 .. deg ];
> G:-‘.‘ := proc( a, b )
> 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 , 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
142 • Chapter 2: Programming with Modules
To compute with its elements, use the methods exported by the in-
stantiated group G.
> use G in
> inv( [ 2,1,3,4 ] ) . [2,3,1,4];
> end use;
[3, 2, 1, 4]
D6
(12)
(123456)
2.6 Interfaces and Implementations • 143
Exercises
1. Use the fact that the alternating group An of degree n >= 3 is gen-
erated by the set {(123), (234), (345), . . . , (n − 2, n − 1, n)} of 3-cycles
to write a constructor Alternating for this class of groups.
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 );
Only one of the methods test and gens need be present. A Maple
implementation 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 );
148 • Chapter 2: Programming with Modules
Z := module()
export parent , test ;
option record ;
end module
> Z:-test( [2,1,3] );
false
false
true
> end if
> else
> ’procname’( args )
> end if
> end proc:
Several elements of the design allow you to take advantage of structural
knowledge to improve efficiency. This routine first checks whether the
export elements of its input group is of type set. If it is, then it is taken
to be a stored enumeration of the group elements and is simply returned.
Otherwise, if the export elements is a procedure, then it is taken to be a
(perhaps specialized) routine for computing the requested enumeration.
Finally, Dimino’s algorithm is used as a last resort if no better alternative
is provided. As a simple optimization, the result of Dimino’s algorithm
is stored as a new value for the elements export so that it need only be
computed once.
Providing the GroupElements interface shields the user from needing
to know what the available alternatives are and how to use them. An
additional benefit of the design is that it allows you to change the algo-
rithm selection criteria at any time (to correct software faults, or make
functional or performance improvements). Code using this interface need
not be modified, provided that the routine continues to honor its contract.
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 );
152 • Chapter 2: Programming with Modules
24
> GroupOrder( C );
Note that, when the argument G is neither a group nor a subgroup, the
procedure GroupElements returns unevaluated. This allows you 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 the groups have been permutation groups
returned by one of the constructors previously presented. You must test
the code on some other kinds of groups. A good source for examples of
finite groups are the finite groups of exact matrices.
> member( A, { B, C } );
false
To deal with this property of these data structures, you must imple-
ment a generic version of the Maple command member. The gmember
routine accepts arguments like those required by the member routine in
addition to the first argument, which specifies an equality test. This utility
is used in the following implementation of the matrix group constructor.
> gmember := proc( test, g::anything, S::{set,list}, pos::name )
> description "a generic membership predicate";
> local i;
> if type( test, ’procedure’ ) then
> for i from 1 to nops( S ) do
> if test( g, S[ i ] ) then
> if nargs > 3 then
> pos := i
> end if;
> return true
> end if
> end do;
> false
> elif test = ’‘=‘’ then
> # use the standard membership test
> :-member( args[ 2 .. -1 ] )
> else
> ’procname’( args )
> end if
> end proc:
The built-in procedure Equal in the LinearAlgebra package provides an
equality predicate that is suitable for use with matrices.
> gmember( LinearAlgebra:-Equal, A, { B, C } );
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)]] );
2.6 Interfaces and Implementations • 155
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
´ µ 3 µ − 3
0 1 2 2 1 0 2 2
, , , ,
1 0 1√ 1 0 1 1 1√
− 3 3
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
1√ −1 1√ −1 −1 1√ ´
− 3 3 − 3 µ
2 2 2 2 2 2 −1 0
, , , ,
1√ 1√
√
−1 −1 1 −1 0 −1
3 − 3 3
2 2 2 2 2 2
´ µ
0 −1
−1 0
Direct Products To enrich the supply of example groups that you can
use, develop a constructor for the direct product of (two) groups. (Ex-
tending 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 gen-
erated abelian groups possess an unique factorization as a direct product
of cyclic groups. (In the abelian theory, direct products are often referred
to as direct sums.)
156 • Chapter 2: Programming with Modules
72
72
> A := Symmetric( 4 ):
> B := MatrixGroup( 2, Matrix( [[0,1],[1,0]] ) ):
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
Exercises
Introduction to Searching
Prototypically, a document is a file of structured or unstructured text, but
this section treats documents as abstract data items. A document can be
160 • Chapter 2: Programming with Modules
104
Searching is simple, using this index. Search for the terms “mathe-
matical” and “beauty”.
> Search := proc( query::list(string) )
> global Index;
> local results, term;
> results := {};
> for term in query do
> if assigned( Index[ term ] ) then
> results := results union Index[ term ]
> end if
> end do;
> results
> end proc:
> Search( [ "mathematical", "beauty" ] );
There are several problems with this index. One problem is that the
index is quite large (relative to the size of the corpus). Many words that
occur in the corpus convey little or no information about the content of
the documents in which they occur. This can lead to irrelevant results,
especially for poorly chosen search terms.
> nops( Search( [ "the" ] ) );
83
{}
164 • Chapter 2: Programming with Modules
Because the stop word “the” has been removed from the index, it
returns no matches. This is also a user interface enhancement because
queries can contain stop words without affecting the results.
> Search( [ "mathematical", "beauty" ] );
The new process returns many more documents relevant to the query.
doc5Vector := [0, 0, 0, 1, 0, 0, 1, 0, 0, 0]
Compute the query term vector for the search term “mathematical
beauty”.
> queryVector := DocumentTermVector( "mathematical beauty" );
queryVector := [0, 0, 1, 0, 0, 0, 1, 0, 0, 0]
1
166 • Chapter 2: Programming with Modules
1
1
TermDocumentMatrix :=
1 0 0 0 0 1 0 0 0 0
0 0 0 1 0 0 1 0 0 0
0 1 1 0 1 0 0 1 1 1
> use LinearAlgebra in
> Scores := TermDocumentMatrix . Transpose( queryVector )
> end use;
0
Scores := 1
1
Consider the projection of the document term vectors onto the hyperplane
defined by the component vectors of the query vector, and then introduce
appropriate scaling, using vector norms. In this context, searching can be
viewed as a process of determining those vectors representing documents
in a corpus for which the angle between their projections and the query
vector is small.
Term Weighting
A document that contains many instances of a given term is generally
considered to be more relevant to queries containing that term than a
document with fewer instances. To improve rankings, therefore, record
not only the presence of a term in a document, but also its frequency.
This is a simple change to DocumentTermMatrix. The term vector of a
document is the vector of E n whose ith entry is equal to the number of
times the ith corpus term occurs in the document.
> DocumentTermVector := proc( doc )
> global SmallCorpusTerms;
> local terms;
> terms := DocumentTerms( doc );
> Vector[row]( 1 .. nops( SmallCorpusTerms ),
> i -> numboccur( terms, SmallCorpusTerms[ i ] ) )
> end proc:
This can lead to significantly improved results when searching a larger
corpus.
To improve this method, scale the number of instances by the size (the
total number of terms with multiplicities) of the document. For example,
a book about cats is not more relevant than a short paper on cats merely
because the term “cats” appears more often in the book than in the short
article.
> DocumentTermVector := proc( doc )
> global SmallCorpusTerms;
> local terms;
> terms := DocumentTerms( doc );
> Vector[row]( 1 .. nops( SmallCorpusTerms ),
> i -> evalf( numboccur( terms, SmallCorpusTerms[ i ] )
> / nops( terms ) ) )
> end proc:
With this change, recompute the term-document matrix and the matrix
of scores, which represents the search results. Also recompute the query
term vector.
168 • Chapter 2: Programming with Modules
9
172 • Chapter 2: Programming with Modules
> u := u[ 1 .. -1, 1 .. k ];
> v := v[ 1 .. k, 1 .. -1 ];
> s := DiagonalMatrix( s[ 1 .. k ] );
> # Replace the term-document matrix with its rank
> # k approximation
> term_document_matrix := MatrixMatrixMultiply( u,
> MatrixMatrixMultiply( s, v ) )
> end use;
> eval( thismodule, 1 )
> end proc;
>
> search := proc( query, numberOfResults::posint )
> local qt, qv, scores;
> if not type( term_document_matrix, ’Matrix’ ) then
> error "corpus not yet indexed"
> end if;
> qt := qfilter( query );
> qv := queryTermVector( qt, corpusTerms );
> use LinearAlgebra in
> scores := Transpose( MatrixVectorMultiply(
> term_document_matrix, qv ) );
> Tools:-permSort( scores )
> end use
> end proc;
> end module
> end proc;
>
> NumberOfDocuments := corpus -> corpus:-numberOfDocuments();
> GetDocumentIdByIndex := ( corpus, idx ) ->
> corpus:-getDocumentIdByIndex( idx );
> BuildIndex := ( corpus, k ) -> ‘if‘( nargs = 1,
> corpus:-buildIndex(), corpus:-buildIndex( k ) );
> Search := ( corpus, query ) -> corpus:-search( query );
>
> # Locals
> documentTermVector := proc( doc, corpusTerms::list )
> local terms, norm;
> terms := FilterDocument( doc );
> Vector[row]( 1 .. nops( corpusTerms ),
> i -> evalf( numboccur( terms, corpusTerms[ i ] ) /
> nops( corpusTerms ) ),
> ’datatype’ = ’float’[ 8 ],
> ’storage’ = ’sparse’ )
> end proc;
>
> queryTermVector := proc( queryTerms::list, corpusTerms::list )
> Vector[column]( 1 .. nops( corpusTerms ),
> i -> evalf( numboccur( queryTerms, corpusTerms[ i ] ) /
> nops( corpusTerms ) ),
> ’datatype’ = ’float’[ 8 ],
> ’storage’ = ’sparse’ )
> end proc;
>
> # The Tools submodule
2.7 Extended Example: A Search Engine • 177
> stopWords := {
> # The 48 most common English words
> "i", "a", "all", "an", "and", "are",
> "as", "at", "be", "been", "but", "by",
> "can", "do", "for", "from", "had", "has",
> "have", "he", "his", "if", "in", "is",
> "it", "not", "of", "on", "or", "she",
> "that", "the", "their", "there", "they", "this",
> "to", "was", "we", "were", "what", "which",
> "who", "will", "with", "would", "you",
> # a few others
> "thing", "true", "most", "does", "don’t",
> NULL};
>
> Text := proc( text::string )
> description "compute the terms in a text string";
> local words;
> use StringTools in
> words := map( LowerCase, Words( text ) );
> words := remove( type, words, stopWords );
> map( Stem, words )
> end use
> end proc;
>
> XML := proc( xml )
> description "compute the terms in an XML document";
> local t, count, rec;
>
> rec := proc( xml, t::table )
> local cm, texts, text, others;
> use XMLTools in
> if IsElement( xml ) then
> cm := ContentModel( xml );
> texts, others := selectremove( IsText, cm );
> for text in texts do
> count := 1 + count;
> t[ count ] := text
> end do;
> map( procname, others, t )
> end if
> end use
> end proc;
> count := 0;
> t := rec( xml, t )
> [seq]( t[ i ], i = 1 .. count )
> end proc;
>
> Maple := proc( expr )
> description "compute the terms in a Maple expression";
> local fns, terms, nocc;
> fns := [op]( map2( op, 0, indets( expr, ’function’ ) ) );
> nocc := map2( numboccur, expr, fns );
> terms := [seq]( [ seq( fns[ i ], j = 1 .. nocc[ i ] ) ],
> i = 1 .. nops( fns ) );
2.7 Extended Example: A Search Engine • 179
The revised package contains several new document filters. To use doc-
ument formats that are not directly supported, compose these filters with
180 • Chapter 2: Programming with Modules
custom code. Rather than providing a vector of raw scores, the Search
command in the package now returns a permutation of the document
list indicating document rankings. This can be used directly with the
GetDocumentIdByIndex routine.
ranking := [1, 3, 5, 6, 2, 7, 9, 8, 4]
132
Use each formula as both the document and its ID. The Maple filter
in the Filters subpackage extracts the terms from each document.
> corpus2 := Corpus( Formulae, id -> id, Filters:-Maple,
> query -> [op]( {op}( query ) ) ):
> BuildIndex( corpus2 ):
It is possible to locate formulae relevant to, for example, the sin and cos
functions.
> ranking := Search( corpus2, [ ’sin’, ’cos’ ] );
182 • Chapter 2: Programming with Modules
ranking := [120, 19, 103, 127, 29, 104, 126, 119, 59, 81,
131, 97, 125, 102, 101, 124, 49, 76, 107, 4, 9, 96, 132,
128, 83, 6, 82, 108, 22, 16, 114, 91, 116, 113, 92, 94,
118, 24, 86, 112, 90, 105, 42, 65, 33, 95, 25, 117, 20,
32, 23, 14, 17, 2, 12, 10, 7, 3, 5, 18, 80, 110, 111, 109,
21, 30, 89, 87, 88, 115, 44, 39, 64, 38, 85, 68, 61, 69,
93, 40, 36, 35, 62, 67, 1, 43, 37, 66, 34, 41, 63, 31, 13,
11, 60, 130, 122, 129, 121, 123, 48, 47, 26, 27, 53, 50,
57, 84, 106, 99, 100, 98, 77, 75, 56, 74, 55, 54, 45, 28,
72, 78, 52, 51, 58, 15, 79, 71, 70, 46, 8, 73]
> map2( GetDocumentIdByIndex, corpus2, ranking[ 1 .. 3 ] );
1
tan( z) 1
tan(z) = 2
2 ,
1 2 csc(arccsch(x) I + arccsch(y) I)4
1 − tan( z)
2
1 1 2
+ (− 2 + 2 )
x y
−y 2 − x2 − 2
−2 = 0,
csc(arccsch(x) I + arccsch(y) I)2 x2 y 2
1 2
1 1 − tan( z)
cot(z) = 2
2 1
tan( z)
2
Construct a similar corpus using a different choice for the document
IDs and the fetcher routine passed to the constructor. Instead of using
formulae for both the document and its ID, use the position of the formula
in the global list Formulae as the document ID, and pass a suitable fetcher
routine.
> corpus3 := Corpus( [$1..nops(Formulae)], id -> Formulae[
> id ], Filters:-Maple, query -> [op]( {op}( query ) ) ):
> ranking := Search( corpus2, [ ’sin’, ’cos’ ] );
2.7 Extended Example: A Search Engine • 183
ranking := [120, 19, 103, 127, 29, 104, 126, 119, 59, 81,
131, 97, 125, 102, 101, 124, 49, 76, 107, 4, 9, 96, 132,
128, 83, 6, 82, 108, 22, 16, 114, 91, 116, 113, 92, 94,
118, 24, 86, 112, 90, 105, 42, 65, 33, 95, 25, 117, 20,
32, 23, 14, 17, 2, 12, 10, 7, 3, 5, 18, 80, 110, 111, 109,
21, 30, 89, 87, 88, 115, 44, 39, 64, 38, 85, 68, 61, 69,
93, 40, 36, 35, 62, 67, 1, 43, 37, 66, 34, 41, 63, 31, 13,
11, 60, 130, 122, 129, 121, 123, 48, 47, 26, 27, 53, 50,
57, 84, 106, 99, 100, 98, 77, 75, 56, 74, 55, 54, 45, 28,
72, 78, 52, 51, 58, 15, 79, 71, 70, 46, 8, 73]
The common and practical case, in which a corpus represents a col-
lection of files to be indexed, can be handled by using a constructor call
such as the following.
> corpus := Corpus(
> remove( type, listdir( "MyDocuments" ), { ".", ".." } ),
> fname -> readbytes( fname, ’TEXT’, infinity ),
> Filters:-Text ):
2.8 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
procedures into distinct sets of related functions. You can also use modules
to implement objects in Maple.
3 Input and Output
In This Chapter
• Tutorial Example
• Input Commands
• Output Commands
• Conversion Commands
• Notes to C Programmers
185
186 • Chapter 3: Input and Output
The file myfile is saved in the current directory. To determine the current
directory, use the currentdir() command. 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 writes each pair of numbers to the file. This com-
mand takes two or more arguments. The first argument specifies the file
that Maple is to write, and the second argument specifies the format for
the data items. The remaining arguments are the actual data items that
Maple is to write.
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. For more information on the fopen
command, see 3.4 Opening and Closing Files.
Format String The format string, "%d %e\n", specifies that Maple write
the data items as follows:
• First data item as a decimal integer (%d)
• Second data item in Fortran-like scientific notation (%e)
• A single space separates the first and second data items
• A line break (\n) follows the second data item (to write each pair of
numbers on a new line)
By default, as in the 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.
Closing a File When you are finished writing to a file, you must close
it. Until you close a file, the data may not 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.
One Command for Opening, Writing, and Closing a File 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 then closing the file. However, writedata does not provide
the precise formatting control that you may need in some cases. For this,
use fprintf.
Reading Data From a File In some applications, you need to read data
from a file. For example, some data acquisition software supplies data
that you need to analyze. Reading data from a file is almost as easy as
writing to it.
188 • Chapter 3: Input and Output
> A := [];
A := []
> do
> xy := fscanf("myfile2", "%d %e");
> if xy = 0 then break end if;
> A := [op(A),xy];
> end do;
xy := [0, 0.]
A := [[0, 0.]]
xy := [1, 0.8427007929]
A := [[0, 0.], [1, 0.8427007929]]
xy := [2, 0.995322265]
A := [[0, 0.], [1, 0.8427007929], [2, 0.995322265]]
xy := [3, 0.9999779095]
xy := [4, 0.9999999846]
xy := [5, 1.000000000]
xy := []
3.1 A Tutorial Example • 189
A := [[0, 0.], [1, 0.8427007929], [2, 0.995322265],
[3, 0.9999779095], [4, 0.9999999846],
[5, 1.000000000], []]
xy := 0
> fclose("myfile2");
One Command for Opening, Reading, and Closing a File As when you
write to a file, you can read from a file more easily by using the readdata
command.
> A := readdata("myfile2", [integer,float]);
Buffered Files:
• When buffering a lot of I/O, buffered file operations are usually faster.
• In general, you should use buffered files. They are used by default
with most I/O library commands.
Raw Files:
• Raw files are useful when examining the properties of the underlying
operating system, such as the block size on the disk.
representation of this character is the byte whose value is 10, the ASCII
linefeed character. Many operating systems, however, represent the con-
cept of newline within a file using a different character, or a sequence of
two characters. For example, DOS/Windows represents a newline with
two consecutive bytes whose values are 13 and 10 (carriage return and
line feed). The Macintosh represents a newline with the single byte with
value 13 (carriage return).
The Maple I/O library can use text files or binary files. When Maple
writes to a text file, any newline characters that it writes to the file are
translated into the appropriate character or character sequence that the
underlying operating system uses. When Maple reads this character or
character sequence from a file, it translates it to the single newline char-
acter. When Maple writes to a binary file, no translation takes place; it
reads newline characters and writes them as the single byte with value
10.
When running Maple under the UNIX r
operating system or one of
its many variants, Maple makes no distinction between text and binary
files. It treats both in the same way, and no translation takes place.
• You cannot write to a file that is open only for reading. If you attempt,
using the Maple I/O library, to write to a file which is open for reading,
Maple closes and reopens the file for writing. If the user does not have
the necessary permissions to write to the file (if the file is read-only,
or resides on a read-only file system), errors occur.
• You can write to and read from a file that is open for writing.
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.
If you try to open the file for writing and it does not exist, Maple
creates it. If it does exist and you specify WRITE, Maple truncates the file
to zero length; if you specify 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.
194 • Chapter 3: Input and Output
Both fopen and open return a file descriptor. Use this descriptor to
refer to the file for subsequent operations. You can still use the filename.
When you have finished with a file, instruct Maple to close it. This en-
sures that Maple writes all information to the disk. It also frees 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. You can call them as follows.
fclose( fileIdentifier )
close( fileIdentifier )
The fileIdentifier is the name or descriptor of the file to close. Once you
close a file, any descriptors referring to the file are no longer valid.
> f := fopen("testFile.txt",WRITE):
36
feof( fileIdentifier )
The fileIdentifier is the name or descriptor of the file to query. If you give
a filename, 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
still returns false. You encounter the end-of-file only after 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.
196 • Chapter 3: Input and Output
When n > 3, the lists that iostatus()[n ] return each contain the
following elements:
iostatus()[n ][4] The file pointer or file descriptor that the underlying
operating system uses. The pointer is in the form FP=integer or
FD=integer .
Removing Files
Many files are solely for temporary use. Because you do not need these
files in future Maple sessions, remove them. Use the fremove command
to do this.
fremove( fileIdentifier )
readline( fileIdentifier )
The fileIdentifier is the name or descriptor of the file to read. The length,
which you may omit, specifies how many bytes Maple needs to read. If
you omit length, Maple reads one byte. The optional parameter TEXT
indicates that the result is to be returned as a string rather than a list of
integers.
You can specify the length as infinity, in which case Maple reads
the remainder of the file.
If you specify TEXT when a byte with value 0 resides among the bytes
being read, the resulting string contains only those characters preceding
the 0 byte.
If you call readbytes with a filename, and that file is not open, Maple
opens it in READ mode. If you specify TEXT, Maple opens it as a TEXT file;
otherwise, Maple opens it as a BINARY file. If readbytes returns 0 (indi-
cating the end of the file) when you call it with a filename, it automatically
closes the file.
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.
If you call fscanf with a filename, and that file is not 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 filename, Maple automatically
closes the file.
Format String The format specifies how Maple is to parse the input.
The format is a Maple string consists of a sequence of conversion spec-
ifications, that may be separated by other characters. Each conversion
specification has the following format, where the brackets indicate op-
tional components.
• The optional “*” indicates that Maple is to scan the object, but not
return it as part of the result. It is discarded.
The optional modifiers are used to indicate the type of the value to
be returned:
The code indicates the type of object to scan. It determines the type
of object that Maple returns in the resulting list. The code can be one of
the following.
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.
3.5 Input Commands • 201
[. . .] 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 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 (that is, 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
"{" applies 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).
For more information on rtable formatting options, refer to
?rtable_scanf.
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 can 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 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
3.5 Input Commands • 205
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 filename, and that file is not open, Maple
opens it in READ mode as a TEXT file. Furthermore, if you call readdata
with a filename, it automatically closes the file when readdata returns.
> A1 := readdata("my_xyz_file.text",3);
> A2 := readdata("my_xyz_file.text",[float,float,float]);
Note: Data elements in the file are white space delimited. Newlines sepa-
rate rows, and white space separates columns. The numColumns argument
defines how many columns to read and those columns are read from all
rows. For the file:
1234
5678
readdata(...,2) returns [[1,2],5,6]] and readdata(...,3) returns
[[1,2,3],[5,6,7]].
206 • Chapter 3: Input and Output
The variable argument specifies which parameter to change, and the ex-
pression argument specifies the value that the parameter is to have. For
a list of parameters you can use, see the following sections or refer to
?interface. You can set multiple parameters by giving several argu-
ments of the form variable = expression , with commas separating
them.
To query the setting of a parameter, use the following syntax.
interface( variable )
The variable argument specifies the parameter to query. The interface
command returns the current setting of the parameter. You can query
only one parameter at a time.
lprint( expressionSequence )
x^5+5*x^4*y+10*x^3*y^2+10*x^2*y^3+5*x*y^4+y^5
> 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:
labelwidth This indicates the size that a subexpression must have for
Maple to consider it for labeling (if labelling is true). The size is
the approximate width, in characters, of the expression when printed
with print and prettyprint = 1.
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));
210 • Chapter 3: Input and Output
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(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
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.
212 • Chapter 3: Input and Output
q or Q These are similar to "%a" or "%A", except that "%q" or "%Q" ignores
all remaining arguments and print them as an expression sequence,
with each element formatted in "%a" or "%A" format respectively. No
additional format specifiers can appear after "%q" or "%Q", since there
are no arguments to format.
m The object, which can be any Maple object, is output in the Maple
.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 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.
the format string must contain a new line character, “\n”. Out-
put from fprintf and printf is not subject to line wrapping at
interface(screenwidth) characters.
• The “%o”, “%x”, and “%X” formats use the underlying implementa-
tion that the hardware vendor provides. As a result, output of octal
and hexadecimal values is subject to the restrictions of the operating
system.
• When writing matrices or lists of lists, you can specify the dataType
as a list of data types, one corresponding to each column in the output.
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 is physically written before 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.
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
exists, and writes subsequent output to the file. The appendto command
appends to the end of the file if the file exists. If the file you specify is 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.
[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”
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 semi-
colon or a colon. The string passed to parse does not require a trailing
semicolon or a colon. If included, it is ignored.
> parse("a+2+b+3");
a+5+b
> parse("sin(3.0)"):
> %;
0.1411200081
220 • Chapter 3: Input and Output
The format specifies how Maple is to format the elements of the ex-
pressionSequence. This Maple string consists of a sequence of formatting
specifications, possibly separated by other characters. For more informa-
tion, see 3.6 Formatted Output on page 211.
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) consist of this Maple string.
For information on the format, see Formatted Input on page 198. 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 into the corresponding objects using sscanf.
> s := sprintf("%4.2f %a %m",evalf(Pi),sin(3),cos(3));
3.9 Conclusion
This chapter presented the details of importing and exporting data and
code to and from Maple. While this book teaches fundamental concepts
and provides a pedagogical introduction to topics, the Maple 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. For more information on a command, enter
?command_name at the Maple prompt.
222 • Chapter 3: Input and Output
4 Numerical Programming
in Maple
Floating-Point Calculations
The focus of this chapter is on how to perform floating-point calculations
in Maple. You can select from the following.
In This Chapter
• Basics of the evalf Command
223
224 • Chapter 4: Numerical Programming in Maple
3.141592654
You can 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);
4.1 The Basics of evalf • 225
3.1415926535897932384626433832795028841\
97169399375105820974944592307816406286\
20899862803482534211706798214808651328\
23066470938446095505822317253594081284\
81117450284102701938521105559644622948\
9549303820
> evalf(sqrt(2));
1.4142135623730950488
> value(r);
Z 1
3
e(x ) dx
0
> evalf(r);
1.341904418
In other cases, Maple can find an exact solution, but the form of the
exact solution is almost incomprehensible. The following function Beta is
a special function that appears 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);
4.2 Hardware Floating-Point Numbers • 227
0.3546007367 10−7
The two previous examples use the Int command rather than int for
the integration. If you use int, Maple first tries to integrate the expres-
sion symbolically. Thus, when evaluating the following commands, Maple
determines a symbolic answer and then converts it to a floating-point
approximation, rather than performing direct numerical integration.
> evalf( int(x^99 * (1-x)^199 / Beta(100, 200), x=0..1/5) );
0.3546007367 10−7
0.333333333333333314
> evalhf( Pi );
3.14159265358979312
228 • Chapter 4: Numerical Programming in Maple
d := 15.
e2 − 1
expr := ln(2 )
π
1.40300383684168617
1.40300383684169
Digits := 4658
1.40300383684168617
4.2 Hardware Floating-Point Numbers • 229
> evalhf(Digits);
15.
Digits := 10
The evalhf command evaluates many Maple functions, but not all.
For example, you cannot evaluate an integral using hardware floating-
point arithmetic.
> evaluate( Int(exp(x^3), x=0..1) );
Newton’s Method
This section illustrates how to take advantage of hardware floating-point
arithmetic to calculate successive approximations using Newton’s method.
You can use Newton’s method to find numerical solutions to equations.
As section 1.2 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 )
Example The iterate procedure takes a function, f, its derivative, df,
and an initial approximate solution, x0, as input to the equation f (x) = 0.
The iteration procedure calculates at most N successive Newton itera-
tions until the difference between the new approximation and the previous
one is small. The iterate procedure prints the sequence of approxima-
tions to show successive approximations.
> 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:
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
F := z → BesselJ(1, z)
> p(x);
2 + 5 x + 4 x2
If you intend to enclose p in a call to evalhf, you cannot define the lo-
cal array a using array(1..3, [2,5,4]) because lists are not supported
in evalhf. You can, however, enclose p in a call to evalhf if the parameter
x is a number.
> evalhf(p(5.6));
155.439999999999997
> det(a);
234 • Chapter 4: Numerical Programming in Maple
2 1
a2, 2 −
3 3
If you call det from inside a call to evalhf, Maple uses the value 0
for the unassigned entry, a[2,2].
> evalhf( det(a) );
−0.333333333333333314
a2, 2
−0.333333333333333314
[0.666666666666666629 , 0.750000000000000000]
[0.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. 5.7 Generating Grids of Points illustrates
the use of var to calculate a grid of function values 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
amounts of time, especially in plotting routines, which rely heavily on
4.3 Floating-Point Models in Maple • 235
m × 10e .
Software Floats
Maple software floating-point computations are performed in base 10.
The precision of a computation is determined by the setting of Digits.
The maximum exponent, minimum exponent, and maximum value for
236 • Chapter 4: Numerical Programming in Maple
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 moni-
toring events and their associated status flags. To learn more about this
system, refer to ?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
−0.41021 10−9
2391516709101395280
0.2391516709 1019
Whenever you apply one of the four basic arithmetic operations (addi-
tion, subtraction, multiplication, division, or square root) to two floating-
point numbers, 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,
particularly when subtracting two numbers which are of similar mag-
nitude. In the following calculation, the accurate sum of x, y, and z is
y = 3.141592654.
> x := evalf(987654321);
x := 0.987654321 109
> y := evalf(Pi);
y := 3.141592654
> z := -x;
z := −0.987654321 109
> x + y + z;
3.1
238 • Chapter 4: Numerical Programming in Maple
Digits := 20
> x + y + z;
3.141592654
Example Let the name MyConst represent the following infinite series:
∞
X (−1)i π i
M yConst =
2i i!
i=1
4.4 Extending the evalf Command • 239
You can calculate approximations to the series in many ways; the following
procedure 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 the Maple model for software floating-point numbers cannot dis-
tinguish successive partial sums.
• Using numerical analysis, you can prove that this algorithm calcu-
lates an approximation of MyConst accurate to the number of digits
specified by Digits, if you use two extra digits inside the algorithm.
The following procedure 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, which is automatically reset after executing the
procedure.
> ‘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:
−0.7921204237
> evalf[40](MyConst);
−0.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.
240 • Chapter 4: Numerical Programming in Maple
> value(%);
1 − e(1/2 π)
e(1/2 π)
> expand(%);
1
√ −1
eπ
> evalf(%);
−0.7921204237
x − sin(x)
MyFcn := x →
x3
1
MyFcn(0) :=
6
4.4 Extending the evalf Command • 241
v := MyFcn(0.000195)
> evalf(v);
0.1618368482
0.16666666634973617222
1 1 2 1 1
− x + x4 − x6 + O(x8 )
6 120 5040 362880
x2i
ai = (−1)i , i ≥ 0.
(2i + 3)!
• Using numerical analysis, you can prove that this algorithm calculates
an approximation of the function value accurate to the number of
digits specified by Digits, if you use three extra digits inside the
algorithm.
242 • Chapter 4: Numerical Programming in Maple
0.1666666663498
1
MyFcn(0) :=
6
4.5 Using the Matlab Package • 243
Now you can properly evaluate MyFcn with numeric as well as symbolic
arguments.
> MyFcn(x);
x − sin(x)
x3
> MyFcn(0.099999999);
0.1665833531735
> MyFcn(0.1);
0.1665833531700
• det: determinant
• lu: LU decomposition
4.6 Conclusion
With numerical techniques, you can solve equations which are other-
wise 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 a tool as powerful as Maple. At
the other extreme, hardware floating-point arithmetic allows you fast com-
putation directly from Maple. This involves, however, limited accuracy.
Software floating-point offers a balance. As well as sometimes being much
faster than symbolic calculations, you also have the option to control 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. The similarity with this popular standard
allows you to readily apply accumulation of error and numerical analysis
principles contained in numerous texts and papers.
5 Programming with Maple
Graphics
Maple Plots
Maple has a wide range of packages and procedures for generating 2-D
and 3-D plots. These include:
• The plot and plot3d procedures for generating basic 2-D and 3-D
plots, respectively
• The plots package for generating specialized plots, such as vector
field plots or contour plots
• The plottools package for generating graphical objects for inclusion
in plots
In addition to those listed above, many other Maple packages, such as
DEtools, Student, and stats, include specialized procedures for plotting
in specific contexts.
These procedures require as arguments information that allows the nu-
merical plot data values to be computed. They also accept options that
set attributes such as colour, shading, and axes style.
In This Chapter
• Basic Plotting Procedures
245
246 • Chapter 5: Programming with Maple Graphics
• Animation
You can plot the same surface by first defining two procedures that
each take two arguments:
> p := (x, y) -> sin(x): q := (x, y) -> sin(y):
and then providing p∗q as the first argument to the plot3d command:
In the first example, the plot3d procedure recognizes that the first
argument is an expression in x and y because the second and third argu-
ments have the forms x=range and y=range . In the second example, the
second and third arguments are simply ranges and contain no variable
names.
Working with expressions in independent variables is simple, but in
many cases, procedures and operators provide a better mechanism for
defining plots. We use the term operator form to refer to the form of the
calling sequence that accepts procedures and operators.
Altering a Plot
After you issue a plotting command, the result is displayed on the default
plotting device (in the worksheet, generally, the current window). You can
use the tools available in the worksheet interface to interactively alter the
plot characteristics, such as drawing style, axes style, and orientation. You
can also specify this information using optional arguments to plot3d.
> plot3d(sin(x)*sin(y), x=-2*Pi..2*Pi, y=-2*Pi..2*Pi,
> style=patchnogrid, axes=frame);
1
0.5
0
–0.5
–1
–6 –6
–4 –4
–2 –2
y 0 2 0 x
2
4 4
6 6
Plotting a Loop
Consider the 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
To draw a line from the last point to the first point, 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);
36
34
32
30
28
26
24
5 6 7 8 9 10 11
• It should verify that the input, L, is a list of points, where each point
is a list of two constants. That is, it should verify that L is of type
list([constant, constant]).
5.2 Programming with Plotting Library Procedures • 251
36
34
32
30
28
26
24
5 6 7 8 9 10 11
Exercise
1. Improve the loopplot procedure so that it accepts the empty list as
input.
this extension is that you must create two-argument procedures from one-
argument procedures, which was not required in ribbonplot in Example
3. To do this, create an auxiliary procedure, extend, that uses the unapply
command.
> extend := proc(f)
> local x,y;
> unapply(f(x), x, y);
> end proc:
q := (x, y) → cos(2 x)
3. Maple then passes these objects to the chosen device driver for display.
5.3 Maple Plot Data Structures • 255
You can assign the plot data structures to variables, transform them
into other structures, save them, or print them.
The structure includes labels x and y for the x-axis and y-axis but no
label for the z-axis.
Because the plot is not in Cartesian coordinates and there are no de-
fault labels, the PLOT3D structure does not contain an AXESLABELS struc-
ture.
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 determines the graphical
5.3 Maple Plot Data Structures • 257
0.8
0.6
0.4
0.2
–2 –1 0 1 2
false
(Objects, Options )
> p(2);
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.
> s := Sum(1/k^2, k=1..10);
10
X 1
s :=
k2
k=1
true
The typematch command assigns the parts of the sum to the given
names.
> term, n, a, b;
260 • Chapter 5: Programming with Maple Graphics
1
, k, 1, 10
k2
49
36
1+m
À !
X 1
psum := evalf @ m →
k2
k=1
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
> 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:
To display the sides and the top, place them in a PLOT3D structure.
> PLOT3D(yellowsides(1, 2, 3, 0.5),
> redtop(1, 2, 3, 0.5),
> STYLE(PATCH));
Using yellowsides and redtop, you can create a 3-D histogram plot.
The following is the histogram corresponding to z = 1/(x + y + 4), for
0 ≤ x ≤ 4 and 0 ≤ y ≤ 4.
> sides := seq(seq(yellowsides(i, j, 1/(i+j+4), 0.75),
> j=0..4), i=0..4):
> tops := seq(seq(redtop( i, j, 1/(i+j+4), 0.75),
> j=0..4), i=0..4):
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
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
266 • Chapter 5: Programming with Maple Graphics
0.4
0.2
–1.5 –1 –0.5 0.5 1 1.5
–0.2
–0.4
268 • Chapter 5: Programming with Maple Graphics
Plotting Gears
Example 3 shows how you can manipulate plotting data structures to
embed 2-D plots into a 3-D setting.
For example:
> outside(Pi/4, 1.1, 16);
[0.9881327882, 0.1536020604],
[1.086946067, 0.1689622664],
[1.033097800, 0.3777683623],
[0.9391798182, 0.3434257839]
0.35
0.3
0.25
0.2
0.15
0.93918 1.01306 1.08695
5.4 Programming with Plot Data Structures • 269
When you display the pieces together, you produce a gear. The op-
tion structure SCALING(CONSTRAINED), which corresponds to the option
scaling=constrained, ensures that the gear appears circular.
> points := [seq(outside(2*a, 1.1, 16), a=0..16)]:
> PLOT(CURVES(points), AXESSTYLE(NONE), SCALING(CONSTRAINED));
Fill this object using the POLYGONS object. Because Maple assumes
that the polygons are convex, you must draw each wedge-shaped section
of the gear as a triangular polygon.
> 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));
which takes a list of vertices and creates two copies in 3-D space, the first
at height 0 and the second at height t, and border:
> border := proc(L1, L2)
> local i, n;
> n := nops(L1);
> seq([L1[i], L2[i], L2[i+1], L1[i+1]], i = 1..n-1),
> [L1[n], L2[n], L2[1], L1[1]];
> end proc:
which accepts two lists of vertices and joins the corresponding vertices
from each list into vertices that comprise quadrilaterals. You can create
the top and bottom vertices of the gear embedded into 3-D 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)
> ]):
The faces structure is a sequence of doubled outside values.
> PLOT3D( POLYGONS(faces));
To create vertices of the polygons that comprise the border of the 3-D
gear, double these points.
> bord := border(double([seq(outside(2*a+1, 1.1, 16),
> a=0..15)], 1/2)):
> PLOT3D(seq(POLYGONS(b), b=bord));
To display the gear, combine these two PLOT3D structures into a single
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));
272 • Chapter 5: Programming with Maple Graphics
Polygon Meshes
MESH data structures, described on page 265, are generated when you use
plot3d to draw parameterized surfaces. Example 4 converts a mesh of
points to the set of vertices for the corresponding polygon. By using poly-
gons instead of a MESH structure, you can modify the individual polygons.
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
2
1
0
–1
–2
–3
–2 –2
–1 –1
0 0
1 1
2 2
3
5.5 Programming with the plottools Package • 275
A Pie Chart
You can write a plotting procedure to build a pie chart of a list of integer
data. The piechart procedure 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:
6.
• Computes the relative weights of the data along with the centers of
each pie slice
• Varies the colors of each slice by first defining a color procedure based
on hue coloring
> piechart := proc(data::list(integer))
> local b, c, i, n, x, y, total;
>
> n := nops(data);
> total := partialsum(data, n);
> b := 0, seq(evalf(2*Pi*partialsum(data, i)/total),
> i=1..n);
> x := seq((cos(b[i])+cos(b[i+1]))/3, i=1..n):
> y := seq((sin(b[i])+sin(b[i+1]))/3, i=1..n):
> c := (i, n) -> COLOR(HUE, i/(n + 1)):
> PLOT(seq(plottools[pieslice]([0, 0], 1,
> b[i]..b[i+1], color=c(i, n)),
> i=1..n),
> seq(TEXT([x[i], y[i]], convert(data[i], name)),
> i = 1..n),
> AXESSTYLE(NONE), SCALING(CONSTRAINED));
> end proc:
276 • Chapter 5: Programming with Maple Graphics
The AXESSTYLE(NONE) option ensures that Maple does not draw any
axes with the pie chart.
The following is a piechart with six slices.
> piechart([ 8, 10, 15, 10, 12, 16 ]);
10
15
8
10
16
12
A Dropshadow Procedure
You can use the existing procedures to create other types of plots that
are not part of the Maple plotting library. For example, the following
procedure computes the 3-D plot of a surface, z = f (x, y), that has a
dropshadow projection onto a plane located below the surface.
2
1
0
–1
–2
–3
–4
–3 –3
–2 –2
–1 –1
y0 0x
1 1
2 2
3 3
Summary
• The first section of the dropshadow procedure determines if there is
a contours option in the optional arguments (those after the fourth
argument) by calling the hasoption procedure.
• The next section of dropshadowplot determines the z value of the
base. Note that it checks for input in operator form.
• The remaining sections create the correct plotting objects which rep-
resent the two types of contour plots.
The dropshadowplot procedure embeds the 2-D contour plot into 3-D
space using the transformation
(x, y) 7→ [x, y, minz]
from R2 → R3 . Finally, it displays the two plots in one 3-D plotting
object.
278 • Chapter 5: Programming with Maple Graphics
Note: You can provide an alternate number of levels or specify the pre-
cise contour locations by using the contours option. Thus,
> dropshadowplot(expr, x=-3..3, y=-3..3, z=-4..3,
> filled=true, contours=[-2,-1,0,1,2]);
produces a plot similar to the one just generated, except that it pro-
duces 5 contours at levels −2, −1, 0, 1, and 2.
Creating a Tiling
The plottools package provides a convenient environment for writing
plotting programs. For example, you can draw circular arcs in a unit
square.
> with(plots): with(plottools):
Example 3 You can tile the plane with a and b type rectangles. The
following procedure creates a m×n tiling using a procedure g to determine
when to use an a tile and when to use a b tile. The function g returns
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
You can use the commands in the plottools package to create graphs,
such as a Smith Chart, which is 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, args[2..nargs]);
> end proc:
5.5 Programming with the plottools Package • 281
Exercise
1. Make a Smith Chart by building appropriate circular arcs above the
axes.
3. Add a final horizontal line. The parameter r denotes the radius of the
largest circle.
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 plots[display] command to show the triangle.
> plots[display](%, color=red);
1.5
0.5
0 0.5 1 1.5 2
The stellateFace procedure creates three polygons, one for each side
of the triangle.
> stellateFace(triangle, 1);
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
Because these polygons are 3-D objects, to display them, place the
POLYGONS structure inside a PLOT3D structure.
284 • Chapter 5: Programming with Maple Graphics
> PLOT3D(%);
Example 7 This example uses a version of the Klein bottle created from
POLYGONS structures.
> kleinpoints := proc()
> local bottom, middle, handle, top, p, q;
>
5.5 Programming with the plottools Package • 285
To alter the view of the Klein bottle, use polygon manipulation com-
mands.
> display(seq( cutout(k, 3/4), k=kleinpoints()),
> scaling=constrained);
286 • Chapter 5: Programming with Maple Graphics
• The three arguments F, r1, and r2 are similar in form to the arguments
required for the plot3d command.
Drawing a Vector
The first step is to draw a vector. Let [x, y] represent the starting point
of the arrow and [a, b] represent the components of the vector. Specify
the shape of an arrow with three independent parameters, t1, t2, and t3,
where 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 to the length of the
arrow.
Note: Because each polygon must be convex, you must build the poly-
gon structure for the arrow in two parts.
In the special case that 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. The following 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)):
> arrow4 := PLOT(myarrow([0,0], [1,1], 0.1, 0.5, 1/4,
> color=green)):
The display command from the plots package can show an array of
plots.
> with(plots):
1. 1.
0.0. 1. 0.0. 1.
1. 1.
0.0. 1. 0.0. 1.
Example 5 With these utility functions, you can write the vectorfieldplot
command.
> 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
2.2
2
1.8
1.6
1.4
1.2
1
1 2 3 4 5
• The procedure only works with operator form. You can solve this
problem by converting expressions to procedures, and then recursively
calling the vectorfieldplot procedure with the converted input (as
in the ribbonplot procedure in section 5.2).
[0...3.14159265358979, 0...3.14159265358979, [
[0., 0., 0.],
[0., −0.975367972083633571, −0.430301217000074065]]]
When xy > 0, then ln(−xy) is complex, so 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
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 preceding routines.
For example, you can also write a complex vector field plot that takes
complex number locations and complex number descriptions of vectors as
input. Simply generate the grid of points in an alternate manner.
The arrow procedure, in the plots package, generates arrows and vec-
tors. The arrow procedure is more versatile than the procedures described
in this section.
1
f := (x, y) →
sin(x y)
fnum := proc(x, y)
local r;
try r := evalhf(f(x, y)) catch : end try ;
if not type(r, ’numeric’) then
try r := evalf(f(x, y)) catch : end try
end if;
‘if‘(type(r, ’numeric’), r, ’undefined ’)
end proc
The fnum 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 soft-
ware floating-point and is frequently sufficient for plotting. Thus, the fnum
procedure tries the evalhf function first. If evalhf generates an error or
returns a non-numeric result, the fnum procedure attempts the calculation
again by using software floating-point arithmetic and then calling evalf.
Even this calculation is not always possible. For example, the function f
is undefined whenever x = 0 or y = 0. In such cases, the procedure fnum
returns the name undefined. The Maple plot display routines recognize
this special name.
At the point (1, 1), the function f has the value 1/ sin(1) and so fnum
returns a numerical estimate.
> fnum(1,1);
1.18839510577812123
However, if you try to evaluate f at (0, 0), Maple returns that the
function is undefined at these coordinates.
298 • Chapter 5: Programming with Maple Graphics
> fnum(0,0);
undefined
Summary Creating such a procedure is the first step in creating the grid
of values. For efficiency, when possible, compute the function values and
the grid points by using hardware floating-point arithmetic. In addition,
it is recommended that you perform as much computation as possible in a
single call to evalhf. For hardware floating-point arithmetic, Maple must
first convert the expression to a series of commands involving hardware
floating-point numbers, and then convert the results back to the Maple
format for numbers. For more information on numerical calculations, see
chapter 4.
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):
> A;
1 1
sin(1) 3
sin( )
2
1 1
3 9
sin( ) sin( )
2 4
[0. , 0. , 0.]
[0. , 0.997494986604054445 , 0.141120008059867213]
If you ask for more digits than hardware floating-point arithmetic can
provide, then gridpoints uses software floating-point operations.
> Digits := 22:
> gridpoints((x,y) -> sin(x*y), 0..3, 0..3, 2, 3);
[0. , 0. , 0.]
[0. , 0.9974949866040544309417 ,
0.1411200080598672221007]
> Digits := 10:
When hardware floating-point calculations are possible, the data is re-
turned in an Array with datatype=float[8], in which case you can dis-
play it by enclosing it in a GRID structure in a PLOT3D structure.
> PLOT3D(GRID(0..3, 0..3, gridpoints((x,y) -> sin(x*y), 0..3,
> 0..3, 10, 10)), AXES(BOXED));
1
0.5
0
–0.5
–1
0 0
0.5 0.5
1 1
1.5 1.5
2 2
2.5 2.5
3 3
5.8 Animation
Maple can generate animations in two or three dimensions. As with all
Maple plotting facilities, such animations produce user-accessible data
structures. Data structures of the following type represent animations.
or
[1.998172852, 0.06977773357]
1.2
1
0.8
0.6
0.4
0.2
0 0.5 1 1.5 2
302 • Chapter 5: Programming with Maple Graphics
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 (not shown) consists of six frames.
> PLOT( ANIMATE( seq( frame(n), n = 1..6 ) ) );
1.0 1. 1.
.5 0.0. 0.0.
2. 2.
0.0. 2. –1. –1.
and Z b ² ³
2 2π
sk = f (x) sin kx dx.
b−a a b−a
304 • Chapter 5: Programming with Maple Graphics
You can now use fourierPicture to plot, for example, the first six
Fourier approximations of ex .
> fourierPicture( exp(x), x=0..10, 6 ):
The following are the first six Fourier approximations of x -> signum(x-1).
Because the signum function is discontinuous, the discont=true option
is required.
> fourierPicture( 2*signum(x-1), x=-2..3, 6,
> discont=true );
2. 2.
2.
x
–2. 0.0. 2. x –2. 0.0. 2. x
–2. 0.0. 2.
–2. –2.
–2.
2. 2. 2.
x x x
–2. 0.0. 2. –2. 0.0. 2. –2. 0.0. 2.
–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.
You can tie the knot in, for example, six stages.
> TieKnot(6);
The code in the springPlot procedure uses the short names of com-
mands from the plots and plottools packages to improve readability. In
general, it is recommended that you use long names. To use this version of
the springPlot procedure, you must first load the plots and plottools
packages.
> with(plots): with(plottools):
> springPlot(6);
> display( springPlot(6) );
Although usually less convenient, you can also specify the color at-
tributes at the lower level of graphics primitives. At the lowest level, you
can color 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 ) ) );
0.8
0.6
0.4
0.2
0 0.5 1 1.5 2
or
5.9 Programming with Color • 309
For example, 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
Using Animation
You can use animation to vary the blue component of the previous table.
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))
To apply the new coloring to a plot, map the addCurvecolor proce-
dure over all the CURVES objects of an existing plot structure.
> addcolor := proc( aplot )
> local recolor;
> recolor := x -> if op(0,x)=CURVES then
> 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 specify color for two curves in the same display, 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 alter the coloring of an existing plot by using coloring func-
tions. Coloring functions must be of the form CHue : R2 → [0, 1] (for Hue
coloring) or of the form CRGB : R2 → [0, 1] × [0, 1] × [0, 1] (for RGB color-
ing).
Example 3 uses the color function CHue (x, y) = y/ max(yi ).
Example 4 You must first convert the grid or mesh to polygonal form.
The remainder of the procedure assigns either a red or white color to a
polygon, depending on which grid area it represents.
> chessplot3d := proc(f, r1, r2)
> local m, n, i, j, plotgrid, p, opts, coloring, size;
>
> # obtain grid size
> # and generate the plotting data structure
> if hasoption( [ args[4..nargs] ], grid, size) then
> m := size[1];
> n := size[2];
> else # defaults
> m := 25;
> n := 25;
> end if;
>
> p := plot3d( f, r1, r2, args[4..nargs] );
>
> # convert grid data (first operand of p)
> # into polygon data
> plotgrid := op( convert( op(1, p), POLYGONS ) );
> # make coloring function - alternating red and white
> coloring := (i, j) -> if modp(i-j, 2)=0 then
> convert(red, colorRGB)
> else
> convert(white, colorRGB)
> end if;
> # op(2..-1, p) is all the operands of p but the first
> PLOT3D( seq( seq( POLYGONS( plotgrid[j + (i-1)*(n-1)],
> coloring(i, j) ),
> i=1..m-1 ), j=1..n-1 ),
> op(2..-1, p) );
> end proc:
316 • Chapter 5: Programming with Maple Graphics
1
0.5
0
–0.5
–1
–3 –3
–2 –2
–1 –1
y0 0x
1 1
2 2
3 3
Note: The chessplot3d procedure works when the plot structure from
plot3d is of GRID or MESH output type. The MESH output type is used for
parametric surfaces or surfaces that use alternate coordinate systems, for
example,
> chessplot3d( (4/3)^x*sin(y), x=-1..2*Pi, y=0..Pi,
> coords=spherical, style=patch,
> lightmodel=light4 );
5.10 Conclusion
In this chapter, you have learned how to make graphics procedures based
on the plot and plot3d commands, as well as the commands found in the
plots and plottools packages. However, for greater control, you must
create PLOT and PLOT3D data structures directly; these are the primitive
5.10 Conclusion • 317
specifications of all Maple plots. Inside PLOT data structures, you can
specify, for example, point, curve, and polygon objects. Inside PLOT3D
data structures, you can specify the objects used in PLOT structures as
well as, for example, grids of values and meshes of points. You have also
learned how to handle plot options, create numerical plotting procedures,
work with grids and meshes, manipulate plots and animations, and apply
non-standard coloring to graphics.
318 • Chapter 5: Programming with Maple Graphics
6 Advanced Connectivity
In This Chapter
• Code Generation
• External Calling: Using Compiled Code in Maple
• OpenMaple: Using Maple in Compiled Code
Code Generation
The CodeGeneration package provides utilities for translating Maple code
into other programming languages such as C, Fortran, Java, MATLAB,
and Visual Basicr
. The resulting code can be compiled and used inde-
pendent of Maple.
319
320 • Chapter 6: Advanced Connectivity
You can use many options with the CodeGeneration functions. For
detailed information, refer to the ?CodeGenerationOptions help page.
Some of the more commonly used options follow.
Translation Process
The CodeGeneration functions recognize only a subset of the Maple
types. These are listed on the ?CodeGenerationDetails help page. The
Maple types are translated to appropriate types in the target language.
Compatibility of types is checked before operations are translated, and
type coercions are performed if necessary. The CodeGeneration func-
tions attempt to deduce the type of any untyped variable. You can
exercise greater control over type analysis and deduction by using the
322 • Chapter 6: Advanced Connectivity
import java.lang.Math;
class CodeGenerationClass {
public static void f (double x)
{
double y;
y = Math.log(x) * Math.exp(-x);
System.out.print("The result is " + y);
}
}
and the output option specifies that a string is returned. In this case, the
output is assigned to the variable s.
> g := proc(x, y, z)
> return x*y-y*z+x*z;
> end proc:
> s := CodeGeneration[‘C‘](g, defaulttype=integer, output=string);
the intermediate form of a Maple expression input. In general you can de-
termine the intermediate form with the CodeGeneration:-IntermediateCode
translator. However, because some aspects of the intermediate code are
specific to the language to which you are translating, it may help to see
the intermediate code for a specific translator. This can be done by setting
infolevel[CodeGeneration] to a value greater than 3 and performing
a translation.
Example 4 shows the intermediate code for the expression 2x2 − 1.
The first argument of the Scope structure is the name of a type table
used internally during the translation process.
Scope( nametab,
StatementSequence(
Assignment(GeneratedName("cg"), Sum(Product(Integer(2),
Power(Name("x"), Integer(2))), Negation(Integer(1))))
)
)
Printer := module()
exportPrintTarget , GetFunctionSignature, AddLibrary ,
AddOperator , GetOperator , AddType, GetType,
AddPrintHandler , GetPrintHandler ,
SetLanguageAttribute, ApplyLanguageAttribute,
GetLanguageAttribute, AddFunction, AddFunctions,
GetFunction, SetPrecedence , GetPrecedence ,
GetIncludes , GetExpressionType, GetScopeName,
GetScopeStructure, Indent , PopIndentation,
PushIndentation, Endline, Linebreak , Print ,
PrintBinary , PrintParentheses , PrintStatementBlock ,
PrintDelimitedList , PrintUnary ;
...
end module
> Printer:-AddOperator( Addition = "+" );
“+”
[“sine”, {}]
6.1 Code Generation • 327
“+”
[“sine”, {}]
Note: When creating your language definition module, you must delay
the evaluation of the module by using single quotes before adding it using
LanguageDefinition:-Add. That is, the language definition module must
be added as a module definition, not as a module.
330 • Chapter 6: Advanced Connectivity
Using a New Translator After adding the definition of the language us-
ing either the LanguageDefinition:-Define or LanguageDefinition:-Add
commands, translate to the new language using the CodeGeneration:-Translate
command.
doubleprecision function p1 ()
p1 = sin(x + y * z) + dble(int(aint(x)))
return
end
DOUBLEPRECISION FUNCTION P1 ()
P1 = DSIN(X + Y * Z) + DBLE(INT(DINT(X)))
RETURN
END
• Generating Wrappers
• Customizing Wrappers
Step 1: DLL Creation First, this function must be compiled into a Dy-
namic Link Library (Windows XXX.DLL), or Shared Library (UNIX
libXXX.so or Macintosh XXX.dylib). For the rest of this chapter, the
compiled library is referred to as a DLL. If the sources are downloaded
from the internet or purchased, a DLL may already have been built. Oth-
erwise, consult the compiler’s documentation for help on how to build a
DLL. When building the DLL, ensure that you export the function that
Maple is intended to be able to call. In this case, the function name is
add.
This is the only step that requires the user to have knowledge of
a specific compiler. For the remaining steps, it does not matter if the
function was written in C, Fortran, or Java.
For Maple, the external library functions must be compiled by using
the _stdcall calling convention, which is the default under UNIX but
must be specified when using most Windows compilers.
to compile the DLL. The example uses the C type int. In Maple, this is
specified as integer[4]. The 4 in the square brackets denotes the number
of bytes used to represent the integer. Most C compilers use 4-byte ints,
but some older compilers may use 2-byte ints. The Maple type specifica-
tion supports both types of compiler integer sizes. For a map of the most
common type relations, see Table 6.2 on page 336.
Since num1 and num2 are both ints, they can be specified as the fol-
lowing in Maple.
num1::integer[4]
num2::integer[4]
The return type does not have a name so the keyword RETURN is used.
RETURN::integer[4]
Using all of this information, the complete function can be defined by
calling the Maple function define_external.
> myAdd := define_external(
> ’add’,
> ’num1’::integer[4],
> ’num2’::integer[4],
> ’RETURN’::integer[4],
> ’LIB’="mylib.dll"
> );
Important: Specify the function exactly, and ensure that the arguments
are in the correct order. Failure to do this may result in strange behavior
or program crashes when executing Step 3.
> a := 33:
> b := 22:
334 • Chapter 6: Advanced Connectivity
> 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.
external function when used in the language for which it was writ-
ten. The only exception is that one argument may be given the name
RETURN. This specifies the type returned by the function rather than
a parameter passed to the function. For more information about how
each argi is specified, see the following Type Specification subsec-
tion.
• If instead of the arg parameters, the single word MAPLE is specified, the
external function is assumed to accept the raw Maple data structures
passed without conversion. This assumes that the wrapper has been
manually generated and compiled into a DLL. Various support func-
tions for writing such external functions are described in Method
3: Customizing Wrappers on page 350. Using MAPLE instead of
specifying arguments is the basis of method 3.
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*) is
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. Declaring a size on a
string to be passed to a Java method has no effect. The string size will
not be limited, and modifications are not copied back.
called the wrapper, and the library generated by this code is called the
wrapper library.
TABLE Tables are used as the corresponding Maple type. Using tables
is the default behavior, and they are easier to use than lists. When
tables are used, the member names correspond to table indices.
LIST Lists are used as the corresponding Maple type. Lists are primarily
used in a read-only basis. Lists cannot be modified in-place, so making
updates to a list structure in external code requires a copy to be made.
When structures must be passed back to Maple, or if they contain
pointer types, it is better to use tables.
Enumerated Types
The Maple external calling mechanism does not directly support enumer-
ated 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).
340 • Chapter 6: Advanced Connectivity
Call by Reference
Unless overridden, each argument is passed by value. The REF modifier
can be used to override this.
ANYTHING This option must be first in the list of options. Use this
option to declare the equivalent of a C void* parameter. The wrapper
code attempts to convert passed arguments to simple types, (4-byte
integer, 8-byte float, complex, or string), when encountered. If no
conversion to one of these types is possible, NULL is passed to the
external function.
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
6.2 External Calling: Using Compiled Code in Maple • 341
copying is required), CALL_ONLY has no effect and thus does not prevent
the called function from overwriting the original array. To prevent this
from occurring, include the option COPY 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 integer ranges, specifying 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 specifies the corresponding array bound at run time.
The options are used to specify how an array is passed. The following
are valid options.
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), a copy is made
with the correct properties before passing it to the external function.
Using NO_COPY prevents this. Also, the returned array has the prop-
erties of the copy. If NO_COPY is specified, and an Array, Matrix, or
Vector with incorrect options is passed, an exception is raised. Ar-
rays 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 is necessary to pass additional arguments to the Maple wrap-
per that should not be passed on to the external function. For example,
consider the following hypothetical C function:
This function takes two integer vectors, v1 and v2, and adds the el-
ements of v2 to v1, stopping when it finds an entry that is zero. The
generated wrapper can be made to verify whether the vectors are the
same size. The Maple definition for this function is as follows.
342 • Chapter 6: Advanced Connectivity
The NO_PASS modifier specifies that the size argument should not be
passed to the external function. The Sum function is then called by the
following statement,
> Sum(v1,v2,op(1,v1));
where v1 and v2 are vectors. Maple passes the vector data, or a copy of
the vector data, to the external sum function. It does not pass the size
element to the external function, but size is used for argument checking
(because 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.
Conversions
When the procedure returned by define_external is 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 to the corresponding Maple type.
The following table describes the external types and the Maple types
that can be converted. The first listed Maple type is the one to which a
result of the corresponding external type is converted into.
6.2 External Calling: Using Compiled Code in Maple • 343
For STRUCTs, either lists or tables are valid for a particular declaration.
Once declared, only one of the types (a list or a table) is acceptable. They
cannot be used interchangeably unless the wrapper is regenerated. For
UNIONs, only tables are permitted, and the table must contain exactly one
entry when passed (corresponding to one of the members of the union).
If an argument of an incompatible type is passed, an error occurs,
and the external function is not called. Likewise, if a value is passed that
is out of range for the specified type (for example, 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, that is, passed by-
reference, can be passed either a name, a zero, or the declared kind of
Maple expression.
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 generates
a system command to call the compiler. The compiler must be recog-
nized by the system. It should be in the system PATH and all associated
environment variables must be set.
The compile and link commands are completely customizable provided
that your compiler has a command-line interface. 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 wrap-
pers 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 affects all wrapper generation commands via define_external for
the remainder of the session. Any 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 and LINK_COMMAND procedures defined in the compile
options module. These procedures make use of the definitions in the com-
pile options module to formulate a command string that is executed using
ssystem.2
To customize the compile and link commands, you can modify the
following options. All option values must be strings or NULL, except for
COMPILE_COMMAND and LINK_COMMAND, which must be procedures or NULL.
1
In Microsoft Windows, Maple uses the Microsoft Visual C Compiler.
2
If using the Microsoft C compiler, the LINK_COMMAND is set to NULL because the
COMPILE_COMMAND does both the compiling and linking.
6.2 External Calling: Using Compiled Code in Maple • 345
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
must not be included in this name. For example, 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.
FILE_EXT This is the program file extension. If you want to compile
“foo.c”, set FILE_EXT=".c", and FILE="foo".
OBJ_EXT This is the object file extension. Common extensions are
“.o” and “.obj”.
DLL_EXT This is the dynamic library extension. Common extensions
are “.dll” and “.so”.
INC_FLAG This precedes directories in the INC_PATH. On most plat-
forms it is “-I”.
INC_PATH This specifies the directories to search for header files.
Use an expression sequence to specify more than one directory,
for example, INC_PATH=("/usr/local/maple/extern/include",
"/users/jdoe/include").
COMPILE_COMMAND This is set to the procedure that generates
the compiler command. The procedure must return a string. In gen-
eral, it is not necessary to change the default.
LINKER This specifies the name of the linker executable.
LINK_FLAGS This specifies miscellaneous flags passed to the linker,
including those that cause the linker to build a dynamic (shared)
library.
LIB_FLAG This precedes directories in the LIB_PATH. On most plat-
forms it is “-L”.
LIB_PATH This specifies the directories to search for libraries. Use an
expression sequence to specify more than one directory, for example,
LIB_PATH=("/usr/local/maple/extern/lib","/users/jdoe/lib").
346 • Chapter 6: Advanced Connectivity
LIB This names the library which contains the external function 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").
SYS_LIBS This specifies system libraries to link with the wrapper li-
brary to resolve all external symbols. Use an expression sequence to
specify more than one library, for example, LIBS=("-lc","-lm").
Example The following example shows how to set up the GNU compiler
on a machine running Solaris.
> p := define_external(‘COMPILE_OPTIONS‘):
> p:-COMPILER := "gcc";
> p:-COBJ_FLAG := "-o ":
> define_external(‘mat_mult‘,‘WRAPPER‘,‘LIB‘="libcexttest.so"):
The gcc requires a space between -o and the object name. Modifying
the COBJ_FLAG allows this to be easily done. All other option default
values are acceptable.
6.2 External Calling: Using Compiled Code in Maple • 347
"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"
Another way to view the compile and link commands is to call the
command-builder procedures directly. Ensure to set or unassign the vari-
ables that will be assigned, otherwise they are blank.
> 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 exiting Maple or issuing the restart command). Maple
maintains an open connection with the first DLL opened with any given
name. Attempting to create a new DLL of the same name without restart-
ing can 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.
Evaluation Rules
External functions follow normal Maple evaluation rules in that the ar-
guments are evaluated during a function call. It therefore may be nec-
essary to enclose assigned names in right single quotes when passing-by-
reference. For example, consider the following function that multiplies a
number by two in-place.
void double_it( int *i )
{
if( i == NULL ) return;
*i *= 2;
}
In Maple, the wrapperless definition of this function might appear as
follows.
6.2 External Calling: Using Compiled Code in Maple • 349
When executing this function, the argument ’i’ is converted from the
Maple internal representation of an integer to a 4-byte hardware integer.
A pointer to the hardware integer is then passed to the external function,
’double_it’. Though ’i’ is declared as a pointer to an integer, it is
acceptable to call ’double_it’ with non-pointer input.
> double_it(3);
"NULLx"
350 • Chapter 6: Advanced Connectivity
> concat(0,0);
In the concat example, the C code might look like the following. Note
that this function does not clean memory as it should.
r = (char*)malloc((strlen(a)+strlen(b)+1)*sizeof(char));
strcpy(r,a);
strcat(r,b);
return( r );
}
ALGEB MWRAP_quadruple_it(
6.2 External Calling: Using Compiled Code in Maple • 351
MKernelVector kv,
FLOAT32 (*fn) ( FLOAT32 a1 ),
ALGEB fn_args
);
This prototype is 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 found in the external DLL. In this case, fn is assigned the
quadruple_it external function. The last argument is a Maple expres-
sion 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 entry point is the format used when wrappers are automatically
generated, and when WRAPLIB is specified. An alternate external entry
point that excludes the function pointer is available when the parameter
MAPLE is specified instead of WRAPPER or WRAPLIB.
ALGEB MWRAP_quadruple_it(
MKernelVector kv,
ALGEB fn_args
);
The API function prototypes for manipulating Maple data structures
are in $MAPLE/extern/include where $MAPLE is the path of the Maple
installation. The header file maplec.h should be included when writ-
ing custom C wrappers. One of the header files, maplefortran.hf or
maplefortran64bit.hf, should be included when writing custom For-
tran wrappers. Other header files, mplshlib.h, and mpltable.h contain
macros, types, and data structures that are needed for direct manipula-
tion of Maple data structures.
Maple uses directed acyclic graphs (dags) to represent all objects,
such as integers, floating point numbers, sums, modules, or procedures.
(For more information about Maple internal representation of objects,
see Appendix A.) These dags have the type ALGEB in C wrappers, and
INTEGER or INTEGER*8 in Fortran wrappers. Fortran 77 has no user type
definition semantics so ALGEB pointers must be “faked” by using ma-
chine word-sized integers. If the machine word size is 64-bit (for example,
as on a DEC Alpha), the header maplefortran64bit.hf must be used
and INTEGER*8 must be used as the dag datatype. Execute the Maple
command kernelopts(wordsize) to determine whether you need to use
32-bit or 64-bit integer-dag types in Fortran. When working with C, the
datatype is ALGEB regardless of the machine word size.
352 • Chapter 6: Advanced Connectivity
myAdd := "mwrap_add.c"
/* MWRAP_add Wrapper
Generated automatically by Maple
Do not edit this file. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mplshlib.h>
#include <maplec.h>
MKernelVector mapleKernelVec;
typedef void *MaplePointer;
6.2 External Calling: Using Compiled Code in Maple • 353
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 );
}
#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 verifies if the Maple function call passed exactly
two arguments. It then converts the two arguments to hardware integers
and adds them. The result is converted to a Maple integer and returned.
This program can be compiled into a DLL using your favorite C com-
piler. Ensure that you link with the Maple API shared library. The DLL
can be placed into the Maple bin.$SYSTEM directory, or somewhere else
in the PATH. When using DLLs outside of bin.$SYSTEM directory, you
may need to specify the full path to the DLL in the LIB argument to
define_external. UNIX developers may need to set their load-library-
path.
Table 6.5 on page 395 lists the Maple API Libraries for C and Fortran.
After compiling the DLL, the function can be used in Maple. No type
desciptors are needed in the define_external call because Maple does
6.2 External Calling: Using Compiled Code in Maple • 355
> myAdd(2.2,1);
> myAdd(2^80,2^70);
INCLUDE "maplefortran.hf"
INTEGER kv
INTEGER args
INTEGER arg
INTEGER a1, a2, r
CHARACTER ERRMSG*20
INTEGER ERRMSGLEN
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 Table 6.5
on page 395) and including either maplec.h or maplefortran.hf.
These functions all return TRUE (1) when the Maple dag s fits the de-
scription given by the function name. If s is not of the correct type, FALSE
(0) is returned. The Maple NULL is not the same as a C Pointer-NULL.
The former is the empty expression sequence in the Maple language. The
latter 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 unassigned Maple name. The
IsMaple...Numeric routines use the Maple type numeric definition. All
other checks use the dag type definition. For example, type(t[1],name)
returns true in Maple, but IsMapleName checks for a NAME dag and re-
turns FALSE because t[1] is internally represented as a TABLEREF dag.
Integer query routines with the bit size specified in the name check to
ensure 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 complex
and string conversion are done by reference. That is, the third argument
passed to the function is set to the converted value rather than the func-
tion returning the value. Equivalent functions for MapleToComplexFloatDAG
and MapleToPointer are not available.
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
(b1,b2). The user must know that b1 and b2 are always integers.
Conversely, c in constant[c] is always the same type as the rtable’s
datatype. Thus for float[8] rtables, to convert to a hardware type use
MapleToFloat64.
RtableSetAttribute sets all the attributes of the RtableSettings
structure s to the single NAME attribute, name.
RtableSetIndFn sets all the indexing functions of the RtableSettings
structure s and resets it to the single indexing function infn.
RtableSetType sets the data_type field in the given RtableSettings
structure s to id, and when id=RTABLE_DAG, sets the maple_type to name.
For example, to set the data type to float[8], RTableSetType(kv,&s,
RTABLE_FLOAT,NULL) is called. To set the type to numeric,
RTableSetType(kv,&s,RTABLE_DAG,"numeric") is called. Basic type ids
are defined in mpltable.h. To set compound types, the RtableSettings
data structure can be manipulated directly as follows.
settings.data_type = RTABLE_DAG;
settings.maple_type = EvalMapleStatement(kv,
"complex(numeric)");
RtableSelect returns the value rt[index], where rt is an RTABLE,
and index is an integer array.
RtableAssign assigns the value val to rt[index]. This function must
be used instead of assigning directly to the rtable data-block whenever
the given rtable has an indexing function or unusual storage format (for
example, sparse). The index is an integer array. For example, the fol-
lowing code assigns the value 3.14 to the [2,1] element of the given
datatype=float[8] rtable.
RTableData val;
M_INT *index;
index[0] = 2;
index[1] = 1;
val.float64 = 3.14;
RTableAssign(kv,rt,index,val);
RtableSparseCompact removes any zeros in the sparse rtable data
block. This should be called after an external routine that modifies the
sparse data block directly.
RtableSparseIndexRow returns the vector of indices for the i th di-
mension of rt. The rt must be a NAG sparse rtable.
366 • Chapter 6: Advanced Connectivity
List Manipulation To work with Maple lists, the following API functions
can be used. These functions are only available using the C API.
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 val;
M_INT ind[3];
ind[0] = 1;
ind[1] = 2;
ind[2] = 3;
368 • Chapter 6: Advanced Connectivity
These functions display the message msg, stop execution, and return
to the Maple input loop. A call to MapleRaiseError does not return.
The character string msg can contain wildcards of the form %N, where N
is a non-zero integer. These wildcards are replaced by the extra argument,
arg1 or arg2, before displaying the message. If %-N is specified, then the
optional argument is displayed with st, nd, rd, or th appended to it. For
example:
This, if invoked, raises 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 because the function cannot deter-
mine the number of unparsed optional arguments.
The C API also provides a mechanism for trapping errors raised by
Maple.
4
For more information, refer to ?error.
6.2 External Calling: Using Compiled Code in Maple • 369
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 {
370 • Chapter 6: Advanced Connectivity
/* do something else */
}
}
#include "maplec.h"
EvalhfMapleProc(kv,fn,3,hfparams);
}
> 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);
ALGEB MapleResult;
double CResult;
MapleResult = EvalMapleStatement(kv,"int(x^3,x=0..1)");
CResult = mapleToFloat64(kv,MapleResult);
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;
6.2 External Calling: Using Compiled Code in Maple • 373
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);
includes any external global or static ALGEB variables that will be referred
to in a later external call. Failure to protect such a persistent variable leads
to unexpected results if the Maple garbage collector removes it between
function calls.
MapleGcAllow allows the algebraic structure a to be collected by the
Maple garbage collector. Any algebraic structure that is not referenced
by another algebraic structure is automatically destroyed and its memory
reclaimed. Algebraics are protected from garbage collection if they are
used somewhere (that is, the value of a global name or part of an array’s
data). The normal state of an algebraic is to have garbage collection
enabled on it.
System Integrity
The Maple kernel has no control over the quality or reliability of external
functions. If an external function performs an illegal operation, such as
accessing memory outside of its address space, that operation can result
in a segmentation fault or system error. The external routine crashes,
causing Maple to crash too.
If an external routine accesses memory outside of its address space but
inside the Maple address space, the external routine will likely not crash,
but Maple will become corrupted, resulting in inexplicable behavior or a
crash later in the Maple session. Similarly, an external routine that deals
directly with Maple data structures can corrupt Maple by misusing the
data structure manipulation facilities.
Therefore, use external calling 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 routine when it is called.
Interface Overview
The programming interface (API) is built on the existing external call-
ing mechanism. Using OpenMaple provides direct access to many of the
Maple internal data types. This is similar to the access define_external
provides to the author of an external wrapper. For more information on
external calling, see 6.2 External Calling: Using Compiled Code in
Maple or refer to ?external_calling and ?CustomWrapper.
OpenMaple provides the ability to start the Maple kernel and control
output. By default, output is sent to stdout. Setting up call-back functions
allows you to direct output to, for example, a text box or a string. For
more information on call-back functions, see Call-back Functions on
page 380.
Data Types Maple defines a few low-level data types to improve porta-
bility between architectures. Some types are used as parameters or return
values for OpenMaple API functions.
M_BOOL An integer that can take one of two values, TRUE or FALSE. The
size of this type is the same as M_INT.
All API functions are declared with the modifier M_DECL and EXT_DECL.
When using Microsoft Visual C/C++ (MSVC), M_DECL is defined us-
ing #define as __stdcall and EXT_DECL is defined using #define as
__declspec(dllimport) (to specify calling conventions and dll symbol
exports). For other compilers, these are defined using #define as noth-
ing. When not using MSVC on Windows, ensure that you define them
appropriately.
376 • Chapter 6: Advanced Connectivity
Basic API Functions The OpenMaple API consists of all the standard
functions available in the external call API, plus StartMaple, StopMaple,
and RestartMaple functions. For more information on external calling,
see 6.2 External Calling: Using Compiled Code in Maple or refer
to ?external_calling.
The sequence for calling the basic API functions is as follows.
4. Call StopMaple. Once you stop the Maple kernel, you cannot restart
it. For more information on stopping Maple, see Terminating Maple
on page 379.
Options that are not supported include preprocessor directives (-D, -I,
-U) and filter modes (-F, -l, -P). Options specific to only the graphical
interface are also not supported.
The structure representing the final result is also returned and can be
manipulated by other functions that work with ALGEB types.
The EvalMapleStatement function is defined as follows.
• The arg parameters are one or more arbitrary Maple objects that
are substituted into numbered parameter locations in the msg string
when the exception is displayed. For information on using optional
arguments, refer to ?error.
Note: After calling the StopMaple function, you cannot call OpenMaple
API functions (including StartMaple).
380 • Chapter 6: Advanced Connectivity
Call-back Functions
Results are returned from a Maple computation using your call-back func-
tions. The output is passed to a call-back function. Otherwise, the results
go to standard output, stdout.
You must pass the call-back functions to the StartMaple function
in a structure of type MCallBackVector. The MCallBackVector type is
defined as follows.
typedef struct {
void (M_DECL *textCallBack)( void *data, int tag,
char *output );
void (M_DECL *errorCallBack)( void *data, M_INT
offset, char *msg );
void (M_DECL *statusCallBack)( void *data, long
kilobytesUsed, long kilobytesAlloc, double
cpuTime );
char * (M_DECL *readLineCallBack)( void *data,
M_BOOL debug );
M_BOOL (M_DECL *redirectCallBack)( void *data, char
*name, char *mode );
char * (M_DECL *streamCallBack)( void *data, char
*name, M_INT nargs, char **args );
M_BOOL (M_DECL *queryInterrupt)( void *data );
char * (M_DECL *callBackCallBack)( void *data, char
*output );
} MCallBackVector, *MCallBack;
Each function takes one or more parameters. All take a generic data
parameter. The data parameter is passed the value of the data parameter
of the StartMaple function.
If a call-back function needs the MKernelVector of the kernel from
which it was called, your application can use the data parameter to pass
this information. For example, the data parameter can pass a pointer to
an object or structure containing the MKernelVector.
Note: All the API functions, including the call-back functions, are de-
clared with the M_DECL modifier. The functions assigned to the call-back
vector must also be declared with the M_DECL modifier.
The call-back functions are defined in maplec.h and described in the
following subsections.
6.3 OpenMaple: Using Maple in Compiled Code • 381
• The tag parameter indicates the type of Maple output. The tag pa-
rameter can take one of the following values (as defined in moemapi.h).
• The debug parameter indicates that the call to readline was made
by the Maple debugger (the Maple debugger uses readline to get
debugger commands from the user).
• The name parameter specifies the name of the file to which output is
appended.
• The mode parameter specifies the file access mode to use: "wt" for
write or "at" for append.
The name and mode parameters are compatible with the C library
function fopen.
6
For more information on the readline function, refer to chapter 7 of the Introduc-
tory Programming Guide or ?readline.
7
For more information on the writeto and appendto functions, see Redirecting
the default Output Stream on page 217.
384 • Chapter 6: Advanced Connectivity
If the name parameter is NULL (in which case the parameters are not
compatible with fopen), Maple is signalling that redirection is terminated.
Subsequent output is sent to the main output stream. Again, this is user-
dependent.
If no redirectCallBack function is provided, any attempt to exe-
cute the Maple writeto function or appendto function produces an error
(reported using the errorCallBack function or textCallBack function).
Stream Call-back Function The Maple math engine and the Open-
Maple API communicate using logical streams. The Maple engine in-
terprets a stream as an unevaluated call to a function with a name that
begins with "INTERFACE_". Data is sent on a stream either implicitly by
various operations (for example, the Maple print function sends its data
on the INTERFACE_PRINT stream), or explicitly by a call to the Maple
streamcall function. There are several predefined streams. In Open-
Maple, most streams map to one of the call-back functions described
in this subsection.
Streams are usually used to output information (as is done by the
INTERFACE_PRINT stream), but some streams can be used to request in-
formation as well. For example, the INTERFACE_READLINE stream is used
to request user input during execution of a Maple procedure. In Open-
Maple, INTERFACE_READLINE is mapped to the readLineCallBack.
In addition to the predefined streams, a Maple user, or an OpenMaple
developer, can create streams by passing a Maple expression of the form,
INTERFACE_streamName(arguments)
• The name parameter specifies the name of the stream without the
"INTERFACE_" prefix.
• The args parameter points to an array of string pointers, one for each
argument passed. Each string is a line-printed (1-D) Maple expression
corresponding to that argument.
386 • Chapter 6: Advanced Connectivity
• The stream name is passed. You can use multiple streams in Maple
code, and quickly determine which operation to perform by examining
the stream name in the streamCallBack function.
Setting the Help Database Search Path The help system is accessed
using the MapleHelp function. As with stand-alone Maple, the help search
path is defined by the value of libname8 . The MapleLibName function
can be used to query or change this path. For more information on the
MapleLibName function, refer to maplec.h.
Retrieving a Help Page To retrieve and display a help page, use the
MapleHelp function.
The following figure illustrates text mode help output.
8
For more information on libname, refer to ?libname.
6.3 OpenMaple: Using Maple in Compiled Code • 387
• The kv parameter specifies the handle to the Maple kernel. This han-
dle is returned by StartMaple.
• The topic parameter specifies the help page. The topic is generally
a Maple keyword, function name, symbol, or a combination thereof
separated by commas. For example, to refer to a function in a package,
use "package_name, function_name".
388 • Chapter 6: Advanced Connectivity
Technical Issues
This subsection discusses technical issues related to using OpenMaple.
File Structure
Off the main Maple installation path, the following subdirectories contain
files related to OpenMaple.
AIX
setenv LIBPATH bin.IBM\_RISC\_UNIX
cc samples/OpenMaple/cmaple/omexample.c -o
bin.IBM\_RISC\_UNIX/omexample -Lbin.IBM\_RISC\_UNIX
-Iinclude -lmaplec
bin.IBM\_RISC\_UNIX/omexample
9
For a list of currently supported operating system versions, refer to the installation
instructions.
6.3 OpenMaple: Using Maple in Compiled Code • 391
HP-UX
setenv SHLIB\_PATH bin.HP\_RISC\_UNIX
cc samples/OpenMaple/cmaple/omexample.c -o
bin.HP\_RISC\_UNIX/omexample -Lbin.HP\_RISC\_UNIX
-Iinclude -lmaplec -lCsup
bin.HP\_RISC\_UNIX/omexample
IRIX
setenv LD\_LIBRARY\_PATH bin.SGI\_MIPS\_UNIX
cc samples/OpenMaple/cmaple/omexample.c -o
bin.SGI\_MIPS\_UNIX/omexample -Lbin.SGI\_MIPS\_UNIX
-Iinclude -lmaplec
bin.SGI\_MIPS\_UNIX/omexample
Linux
setenv LD\_LIBRARY\_PATH bin.IBM\_INTEL\_LINUX
gcc samples/OpenMaple/cmaple/omexample.c -o
bin.IBM\_INTEL\_LINUX/omexample
-Lbin.IBM\_INTEL\_LINUX -Iinclude -lmaplec
bin.IBM\_INTEL\_LINUX/omexample
Mac OS X
setenv DYLD\_LIBRARY\_PATH bin.APPLE\_PPC\_OSX
gcc samples/OpenMaple/cmaple/omexample.c -o
bin.APPLE\_PPC\_OSX/omexample -Lbin.APPLE\_PPC\_OSX
-Iinclude -lmaplec -lmaple
bin.APPLE\_PPC\_OSX/omexample
Solaris
setenv LD\_LIBRARY\_PATH bin.SUN\_SPARC\_SOLARIS
cc samples/OpenMaple/cmaple/omexample.c -o
bin.SUN\_SPARC\_SOLARIS/omexample
-Lbin.SUN\_SPARC\_SOLARIS -Iinclude -lmaplec
bin.SUN\_SPARC\_SOLARIS/omexample
392 • Chapter 6: Advanced Connectivity
6.4 Conclusion
This chapter outlined how the CodeGeneration package provides utilities
for translating Maple code to other programming languages. Additionally,
this chapter discussed how the Maple kernel can be extended by integrat-
ing compiled code using the ExternalCalling package and how Maple
can be used by compiled code using OpenMaple.
Table 6.3 Compound Types
Maple Data Descriptor C Type Fortran Type Java Type
ARRAY(datatype=typename, type *A type *A type[] A
order=..., etc. )
string[n] char x[n] CHARACTER*2 string
complex[4] struct COMPLEX
{ float r, i; } COMPLEX*8 NA
complex[8] struct DOUBLE NA
{ double r, i; } COMPLEX
COMPLEX*16
REF(typename) TYPENAME* NA NA
6.4 Conclusion
•
393
Table 6.4 Wrapper Compound Types
Maple Data Descriptor C Type Fortran Type Java Type
STRUCT( member1 :: descriptor1, ..., struct { type1 member1; ..., NA NA
memberN :: descriptorN, options ) typeN memberN; }
Chapter 6: Advanced Connectivity
A Internal Representation
and Manipulation
397
398 • Appendix A: Internal Representation and Manipulation
Components
Maple consists of three main components: a kernel, a library, and a user
interface. The kernel and library together are known as the math engine.
User Interface The user interface is the part of Maple that the user sees,
and is conceptually separate from the math engine. The same math engine
can be used with different user interfaces. Usually, Maple is provided with
a graphical user interface (GUI) and a command-line interface. The GUI is
more useful for interactive use, especially when working with plots or large
matrices. The command-line interface is practical for batch processing, or
solving large problems where you want to devote all the resources of your
computer to computation.
Maplet applications provide an alternate user interface, which is built
from a description generated in the Math Engine by a series of user com-
mands. For more information on the Maplets package, refer to the Intro-
ductory Programming Guide.
Internal Functions
The internal functions in Maple are divided into five distinct groups:
Flow of Control
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
400 • Appendix A: Internal Representation and Manipulation
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 (for example,
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 does
not change during the existence of the structure. Furthermore, the con-
tents of most data structures are never changed during execution, because
it is unpredictable how many other data structures may be referring to
A.2 Internal Representations of Data Types • 401
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
CONTROL ∧ integer
Debug
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
Maple syntax:
for name from fromExpr by byExpr to toExpr
while condExpr do
statSeq
end do
Maple syntax:
for name in inExpr
while condExpr do
statSeq
end do
Length: 7 or 5
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.
The from-expr, by-expr, to-expr, and cond-expr entries are general
expressions. All are optional in the syntax of for loops and can thus be
filled in with default values (1, 1, NULL, and true respectively) by the
parser.
The stat-seq entry can be a single Maple statement or expression, a
STATSEQ structure, or NULL indicating an empty loop body. An additional
bit in the FOR structure’s header is used to indicate whether the stat-seq
contains any break or next statements.
Foreign Data
FOREIGN ...
Function Call
Garbage
GARBAGE ...
Hardware Float
HFLOAT floatword
If Statement
IF ∧ cond− ∧ stat− ∧ cond− ∧ stat− ... ... ∧ stat−
Maple syntax:
if condExpr1 then
statSeq1
elif condExpr2 then
statSeq2
...
else statSeqN
end if
Length: 3 or more
Logical IMPLIES
Negative Integer
Positive Integer
INTPOS integer integer ...
Maple syntax: 123
Length: 2 or more
Less Than
LESSTHAN ∧ expr1 ∧ expr2
Like the LESSEQ structure above, this structure has two interpreta-
tions, depending on the context. It can be interpreted as a relation (that
is, an inequation), or as a comparison (for example, in the condition of
an if statement, or the argument to a call to evalb).
Maple does not have a greater-than structure. Any input of that form
is stored as a LESS structure.
List
LIST ∧ expr − seq ∧ attrib − expr
Maple syntax: [ expr, expr, ... ]
Length: 2 (or 3 with attributes)
The elements of the expr-seq are the elements of the list. The list can
optionally have attributes.
Member
MEMBER ∧ module ∧ name
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
printing time, so that the global statement contains exactly the 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 module def-
inition is evaluated, the lexical-seq is updated by replacing each of the
second pointers with a pointer to the actual object represented. The name
pointers are not touched, so that the actual identifier names are still avail-
able. The lexical-seq for a module contains entries for any surrounding-
scope identifiers used by that module or by any procedures or modules
contained within it.
The mod-name field points to the optional name of the module. If
a module name was specified when the module was declared, the name
will appear there. If no module name was specified, this field will contain
NULL.
Module Instance
Identifier
NAME ∧ assigned− ∧ attrib− characters characters ...
expr expr
Next Statement
NEXT
Logical NOT
NOT ∧ expr
Logical OR
OR ∧ expr1 ∧ expr2
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;
A.2 Internal Representations of Data Types • 415
local localSeq;
export exportSeq;
global globalSeq;
option optionSeq;
statSeq
end proc
Length: 9
Range
Rational
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
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
418 • Appendix A: Internal Representation and Manipulation
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 subfields.
• 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.
Save Statement
SAVE ∧ expr − seq
Series
SERIES ∧ expr1 ∧ expr2 integer ∧ expr3 integer ... ...
Set
SET ∧ expr − seq ∧ attrib − expr
420 • Appendix A: Internal Representation and Manipulation
Statement Sequence
Stop Maple
STOP
String
is followed by a zero-byte. Any unused bytes in the last machine word are
also zero.
The maximum length of a string is 268, 435, 447 characters on 32-bit
architectures and 34, 359, 738, 351 characters on 64-bit architectures.
Sum, Difference
Table
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
Try Statement
Maple syntax:
try tryStat
catch "catchStr": catchStat
...
finally finalStat;
end try
Length: 3 or more
Unevaluated Expression
UNEVAL ∧ expr
Use Statement
Maple Syntax:
A.2 Internal Representations of Data Types • 423
use bindings in
statseq
end use
Length: 3
Logical XOR
Hash Table
that is a child of the directory with 256 children have hash keys that are
identical in the first 18 bits.
When a hash chain with unused bits overflows, it is split into two.
This may require creating a subdirectory with two children or doubling
the size of the hash chain’s parent directory. In either case, another bit
from the hash key is introduced for indexing. This bit is used to divide
the elements of the old chain into the two new chains. If the hash chain
has no unused bits for indexing, the chain grows as needed. This growth
occurs only if many elements are inserted with identical hash keys.
• Computing its signature and searching for this signature in the table
in the table is used and the searched one is discarded. A full compari-
son 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 ma-
chine address. For example, sets in Maple are represented this way. The
set operations, such as union or intersection, can be done in linear time
by merging sorted sequences. Sorting by machine address is also available
to the user with the sort command.
3. Maple functions anames and unames which return all assigned and
unassigned global names, respectively
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 several kernel functions which use remember tables in-
cluding, evalf, series, divide, normal, expand, diff, readlib, and
frontend. The functions evalf, series, and divide are handled inter-
nally in a special way for the following reasons:
the entries for these are extended with the precision information. If a
result is requested with the same or less precision than what is stored
in the table, it is retrieved 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 command-line interface are not tied to any one
operating system or hardware architecture. The Maple kernel is designed
to be portable to any system which supports a C compiler, a flat address
space, and a 32-bit or 64-bit word size. Table A.2 lists some platforms on
which Maple is supported (refer to the installation instructions for a list
of currently supported operating system 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,
430 • Appendix A: Internal Representation and Manipulation
431
432 • Index
record, 74 version
type, 400 classic worksheet, 2
extending, 31 command-line, 2
typematch, 40 standard worksheet, 2
types
and modules, 60 worksheet
checking, 27, 32, 250 classic, 2
defining new, 31, 35 graphical interface, 2
enumerated, 339 standard, 2
matching, 259 versions, 2
structured, 32 wrapper, 331
custom, 331, 350
unapply, 15 Maple-generated, 331, 338
unbuffered files, 190 writebytes, 210
undefined, 236 writedata, 187, 215
UNEVAL internal data structure, writeline, 210
422 writeto, 217, 383, 384
unevaluated expressions, 229, 422
uniform distribution, 11 xor, 423
uniform random number genera- XOR internal data structure, 423
tor, 11
zero
unload, 57
floating-point representation,
use, 46, 103, 422
236
USE internal data structure, 422
sign of, 236
user input, 27, 30
ZPPOLY internal data structure,
user interface, 398
423
userinfo, 41
variables
exported vs. local, 118
global, 20, 21
identifying, 20
lexically scoped, 409
local, 19, 26, 410
scope of, 6
unassigning, 21
undeclared, 7
vector fields, 286
Vectors, 214, 215
read, 215
write, 215
444 • Index