Maple Programming Guide
Maple Programming Guide
L. Bernardin
P. Chin
P. DeMarco
K. O. Geddes
D. E. G. Hare
K. M. Heal
G. Labahn
J. P. May
J. McCarron
M. B. Monagan
D. Ohashi
S. M. Vorkoetter
Adobe and Acrobat are either registered trademarks or trademarks of Adobe Systems Incorporated in the United
States and/or other countries.
This document was produced using a special version of Maple and DocBook.
Printed in Canada
ISBN 978-1-926902-08-1
Contents
Preface ....................................................................................................... xxi
1 Introduction to Programming in Maple .............................................................. 1
1.1 In This Chapter ...................................................................................... 1
1.2 The Maple Software ................................................................................ 1
The User Interface .................................................................................. 1
The Computation Engine .......................................................................... 1
1.3 Maple Statements ................................................................................... 2
Getting Help .......................................................................................... 2
Displaying a Text String ........................................................................... 2
Performing an Arithmetic Operation ........................................................... 3
Assigning to a Name ............................................................................... 3
Using Maple Library Commands .............................................................. 3
1.4 Procedures ............................................................................................ 4
Defining a Simple Procedure ..................................................................... 4
Entering a Procedure Definition ................................................................. 4
Adding Comments to a Procedure .............................................................. 7
Calling a Procedure ................................................................................. 8
Maple Library Commands, Built-In Commands, and User-Defined Proced-
ures ....................................................................................................... 8
Full Evaluation and Last Name Evaluation .................................................. 9
Viewing Procedure Definitions and Maple Library Code ............................... 10
1.5 Interrupting Computations and Clearing the Internal Memory ........................ 11
Interrupting a Maple Computation ............................................................ 11
Clearing the Maple Internal Memory ........................................................ 12
1.6 Avoiding Common Problems ................................................................... 12
Unexpected End of Statement .................................................................. 12
Missing Operator .................................................................................. 13
Invalid, Wrong Number or Type of Arguments ........................................... 14
Unbalanced Parentheses ......................................................................... 14
Assignment Versus Equality .................................................................... 14
1.7 Exercises ............................................................................................. 16
2 Maple Language Elements ............................................................................ 17
2.1 In This Chapter ..................................................................................... 17
2.2 Character Set ....................................................................................... 17
2.3 Tokens ................................................................................................ 18
Reserved Words .................................................................................... 18
Programming-Language Operators ........................................................... 19
Names ................................................................................................ 22
2.4 Natural Integers .................................................................................... 23
2.5 Strings ................................................................................................ 24
Length of a String ................................................................................. 24
iii
iv Contents
Substrings ............................................................................................ 24
Searching a String ................................................................................. 25
String Concatenation ............................................................................. 25
Mutability of Strings .............................................................................. 26
Special Characters in Strings ................................................................... 27
Parsing Strings ..................................................................................... 27
Converting Expressions to Strings ............................................................ 28
2.6 Using Special Characters ........................................................................ 29
Token Separators .................................................................................. 29
Blank Spaces, New Lines, Comments, and Continuation ............................... 29
Punctuation Marks ................................................................................ 30
Escape Characters ................................................................................. 33
2.7 Types and Operands .............................................................................. 34
DAGs ................................................................................................. 34
Maple Types ........................................................................................ 35
Operands and op ................................................................................... 36
2.8 Avoiding Common Problems ................................................................... 40
Attempting to Assign to a Protected Name ................................................. 40
Invalid Left-Hand Assignment ................................................................. 41
Incorrect Syntax in Parse ........................................................................ 41
White Space Characters within a Token ..................................................... 41
Incorrect Use of Double and Single Quotes ................................................ 41
Avoid Using Maple Keywords as Names ................................................... 42
2.9 Exercises ............................................................................................. 43
3 Maple Expressions ....................................................................................... 45
3.1 In This Chapter ..................................................................................... 45
3.2 Introduction ......................................................................................... 45
Expressions and Statements .................................................................... 45
Automatic Simplification and Evaluation ................................................... 45
Syntax and Constructors ........................................................................ 45
3.3 Names ................................................................................................ 47
Creating Names: Lexical Conventions ....................................................... 48
3.4 Unevaluated Expressions ........................................................................ 52
Protecting Names and Options ................................................................. 53
Generic Expressions .............................................................................. 54
Pass by Reference ................................................................................. 54
Displaying the Original Command ............................................................ 55
Unassigning Names ............................................................................... 55
Evaluation and Automatic Simplification ................................................... 56
Example: Defining a Procedure that Is Returned Unevaluated ........................ 57
3.5 Numbers ............................................................................................. 59
Integers ............................................................................................... 59
Fractions ............................................................................................. 60
Contents v
Floats .................................................................................................. 61
Complex Numbers ................................................................................ 65
3.6 Indexed Expressions .............................................................................. 69
3.7 Member Selection ................................................................................. 74
3.8 Functions ............................................................................................ 75
Calls to Procedures ................................................................................ 77
3.9 Arithmetic Expressions .......................................................................... 77
Arithmetic Operators ............................................................................. 77
Noncommutative Multiplication ............................................................... 90
Factorials ............................................................................................. 92
Forming Sums and Products .................................................................... 93
3.10 Boolean and Relational Expressions ........................................................ 94
Boolean Constants ................................................................................. 94
Boolean Operators ................................................................................. 94
Relational Operators .............................................................................. 98
Efficient Boolean Iteration .................................................................... 102
3.11 Expressions for Data Structures ............................................................ 103
Sequences .......................................................................................... 103
Lists .................................................................................................. 105
Sets .................................................................................................. 107
Tables ............................................................................................... 107
Rectangular Tables .............................................................................. 108
3.12 Set-Theoretic Expressions ................................................................... 109
Membership ....................................................................................... 110
Set Inclusion ....................................................................................... 110
Other Binary Operators for Sets ............................................................. 111
3.13 Other Expressions .............................................................................. 113
Functional Operators ............................................................................ 113
Composition ....................................................................................... 114
Neutral Operators ................................................................................ 116
Ranges .............................................................................................. 116
The Concatenation Operator .................................................................. 119
The Double Colon Operator ................................................................... 121
Series ................................................................................................ 122
3.14 Attributes ......................................................................................... 124
3.15 Using Expressions ............................................................................. 126
Evaluating and Simplifying Expressions .................................................. 126
Substituting Subexpressions .................................................................. 127
Structured Types ................................................................................. 130
3.16 Exercises ......................................................................................... 134
4 Basic Data Structures ................................................................................. 135
4.1 In This Chapter ................................................................................... 135
4.2 Introduction ....................................................................................... 135
vi Contents
xvii
xviii List of Figures
List of Tables
Table 2.1: Special Characters ............................................................................ 17
Table 2.2: Reserved Keywords ......................................................................... 18
Table 2.3: Binary Operators ............................................................................. 19
Table 2.4: Unary Operators .............................................................................. 20
Table 2.5: Element-wise Operators .................................................................... 21
Table 2.6: Token Separators ............................................................................. 30
Table 2.7: Subtype ......................................................................................... 36
Table 3.1: Initially Known Names ..................................................................... 49
Table 5.1: Operators That Can Be Rebound ....................................................... 204
Table 6.1: Procedure Operands ........................................................................ 277
Table 7.1: Floating-Point Contagion Rules ........................................................ 297
Table 10.1: RandomnessTests ......................................................................... 395
Table 13.1: Basic Data Types .......................................................................... 524
Table 13.2: Compound Data Types .................................................................. 525
Table 13.3: Printer Commands ........................................................................ 540
Table 15.1: sieveTest.mpl ............................................................................... 631
Table 15.2: sieveTest2.mpl ............................................................................. 631
Table 15.3: Modified sieveTest2.mpl ................................................................ 632
Table A.1: Maple Structures ........................................................................... 635
xix
xx List of Tables
Preface
Technical computation forms the heart of problem solving in mathematics, engineering,
and science. To help you, Maple offers a vast repository of mathematical algorithms
covering a wide range of applications.
At the core of Maple, the symbolic computation engine is second to none in terms of
scalability and performance. Indeed, symbolics was the core focus when Maple was first
conceived at the University of Waterloo in 1980 and to this day Maple continues to be the
benchmark software for symbolic computing.
The Maple user interface allows you to harness all this computational power by using context-
sensitive menus, task templates, and interactive assistants. The first steps are intuitively
easy to use and quickly lead you into the captivating, creative, and dynamic world of Maple.
As you get more proficient, you will want to explore more deeply and directly access all of
the computational power available to you. You can accomplish this through the Maple
programming language. Combining elements from procedural languages (such as Pascal),
functional languages (such as Lisp) and object-oriented languages (such as Java ), Maple
provides you with an exceptionally simple yet powerful language to write your own programs.
High-level constructs such as map allow you to express in a single statement what would
take ten lines of code in a language like C.
Maple allows you to quickly focus and reliably solve problems with easy access to over
5000 algorithms and functions developed over 30 years of cutting-edge research and devel-
opment.
Maple's user community is now over two million people. Together we have built large col-
lections of Maple worksheets and Maple programs, much of which is freely available on
the web for you to reuse or learn from. The majority of the mathematical algorithms you
find in Maple today are written in the Maple Programming Language. As a Maple user, you
write programs using the same basic tools that the Maple developers themselves use.
Moreover you can easily view most of the code in the Maple library and you can even extend
the Maple system, tying your programs in with existing functionality.
xxi
xxii Preface
This guide will lead you from your first steps in Maple programming to writing sophisticated
routines and packages, allowing you to tackle problems in mathematics, engineering, and
science effectively and efficiently. You will quickly progress towards proficiency in Maple
programming, allowing you to harness the full power of Maple.
Have fun!
Audience
This guide provides information for users who are new to Maple programming, as well as
experienced Maple programmers. Before reading this guide, you should be familiar with
the following.
The Maple help system
How to use Maple interactively
The Maple User Manual
To display the standard interface, double-click your Maple desktop icon (Windows and
Macintosh) or run the xmaple command (UNIX).
used with a variety of languages including C, C++, Java, Fortran, Visual Basic, and
C#. For more information about OpenMaple, see OpenMaple (page 508).
The Maple command-line interface is a console-based application that can be used for
batch processing Maple command files. For more information, see The Maple Command-
line Interface (page 519).
Maplet applications are custom interfaces that are created using the Maple programming
language. For more information, see Programming Interactive Elements (page 489).
For more information about the Maple user interfaces, refer to the Maple User Manual or
the versions help page.
You can also include your code in an external text file to be read by a worksheet or document,
or batch processed. For more information, refer to the file help page.
In document mode, you enter mathematical expressions within document blocks; no Maple
input prompt (>) or execution group boundaries are displayed in the document. You can
use this mode to create professional reports that combine text and typeset math with plots,
images, and other interactive components.
In worksheet mode, you enter mathematical expressions at input prompts, which are displayed
at the start of each input line in a Maple document. When you type an expression and press
Enter, the expression is evaluated and a new input prompt is displayed in the next line. In
both modes, the default format for entering mathematical text is 2-D math notation.
Both modes are equally suitable for creating and running programs in Maple. Select the
mode that suits your preferences and tasks. For more information about both modes, refer
to the worksheet,help,documentsvsworksheets help page.
xxiv Preface
In 1-D math notation (or Maple input), mathematical text is displayed in a red fixed-width
font that is not typeset.
> int(sin(x),x):
1-D math notation can be used in external text files to write Maple code that can be read
by a worksheet or batch processed. You can enter individual statements in 1-D math notation
or configure Maple to display mathematical input in 1-D math by default in all future Maple
sessions.
Note: While 2-D math is the recommended format for mathematical text and equations
and can be used for short command sequences and procedures, it is generally not recom-
mended for long programs and package definitions.
Most input in this guide is shown in 1-D math notation. To clearly distinguish commands
and input, this guide uses a leading prompt character (>) and all input is entered in worksheet
mode.
For more information on starting Maple, toggling between 1-D and 2-D math notation, and
managing your files, refer to the Maple User Manual or enter ?managing at the Maple
prompt.
Web Resources
Maplesoft Application Center: The Application Center provides thousands of complete
applications that you can download and use in Maple. For more information, visit ht-
tp://www.maplesoft.com/applications.
MaplePrimes: MaplePrimes is an online forum where you can search for tips and
techniques, read blogs, and discuss your work in Maple with an active community. For
more information, visit http://www.mapleprimes.com.
Maplesoft Online Help: Documentation included with Maple is also posted online. The
web version offers the latest updates, Google-based searching, and an easy way to
provide feedback on help documentation. For more information, visit, ht-
tp://www.maplesoft.com/support/help.
Preface xxv
Teacher Resource Center: The Teacher Resource Center provides course content, lecture
notes, demonstrations, and other resources to help teachers incorporate Maple in their
classrooms. For more information, visit http://www.maplesoft.com/TeacherResource.
Student Resource Center: The Student Resource Center provides online forums, training
videos, and other resources to help students with their work in Maple. For more inform-
ation, visit http://www.maplesoft.com/studentcenter.
Conventions
This guide uses the following typographical conventions.
bold font - Maple command, package name, option name, dialog box, menu, or text field
italics - new or important concept
Note - additional information that is relevant to a concept or section
Important - information that must be read and followed
Customer Feedback
Maplesoft welcomes your feedback. For suggestions and comments related to this and other
manuals, contact [email protected].
xxvi Preface
1 Introduction to Programming in Maple
Maple provides an interactive problem-solving environment, complete with procedures for
performing symbolic, numeric, and graphical computations. At the core of the Maple com-
puter algebra system is a powerful programming language, upon which the Maple libraries
of mathematical commands are built.
For more information about the Maple user interface, refer to the Maple User Manual.
The kernel is the core of the Maple computation engine. It contains the essential facilities
required to run and interpret Maple programs, and manage data structures. In this guide,
the kernel commands are referred to as built-in commands.
The Maple kernel also consists of kernel extensions, which are collections of external
compiled libraries that are included in Maple to provide low-level programming functionality.
These libraries include Basic Linear Algebra Subprograms (BLAS), GNU Multiple Precision
(GMP), the NAG C Library, and the C Linear Algebra PACKage (CLAPACK).
The math library contains most of the Maple commands. It includes functionality for nu-
merous mathematical domains, including calculus, linear algebra, number theory, and
combinatorics. Also, it contains commands for numerous other tasks, including importing
1
2 1 Introduction to Programming in Maple
data into Maple, XML processing, graphics, and translating Maple code to other programming
languages.
All library commands are implemented in the high-level Maple programming language, so
they can be viewed and modified by users. By learning the Maple programming language,
you can create custom programs and packages, and extend the Maple library.
Statements in 1-D notation require a trailing semicolon (;) or colon (:). If you enter a state-
ment with a trailing semicolon, for most statements, the result is displayed. If you enter a
statement with a trailing colon, the result is computed but not displayed.
> 2 + 3;
(1.1)
> 2 + 3:
For more information about statements in Maple, see Maple Statements (page 181).
Getting Help
To view a help page for a particular topic, enter a question mark (?) followed by the corres-
ponding topic name. For example, ?procedure displays a help page that describes how to
write a Maple procedure.
For more information about getting help in Maple, refer to the help and HelpGuide help
pages.
This type of Maple statement does not have a trailing colon or semicolon.
(1.2)
Normally, you would create a string as part of another statement, such as an assignment or
an argument for a procedure.
1.3 Maple Statements 3
For more information about strings in Maple, see Maple Language Elements (page 17).
(1.3)
Assigning to a Name
By naming a calculated result or complicated expression, you can reference it. To assign
to a name, use the assignment operator, :=.
> a := 103993/33102;
(1.4)
> 2 * a;
(1.5)
For more information about names and assignment, see Maple Language Elements (page 17).
(1.6)
You can use the Maple library of commands, introduced in The Computation En-
gine (page 1), for many purposes. For example, you can find the derivative of an expression
by using the diff command.
4 1 Introduction to Programming in Maple
(1.7)
Note the difference between the names used in these two examples. In the first example, a
is a variable with an assigned value. In the second example, x is a symbol with no assigned
value. Maple can represent and compute with symbolic expressions.
For more information about the Maple library commands, refer to the Maple User Manual
or the help system.
1.4 Procedures
This section introduces the concept of procedures in Maple. For more information about
procedures, see Procedures (page 209).
(1.8)
You can also enter a procedure or any Maple statement on multiple lines. To move the
cursor to the next line as you are entering a multiline statement, hold the Shift key and press
Enter at the end of each line.
Note: This is necessary in the interactive worksheet environment only. If you enter code in
a code edit region, you can simply type the text and press Enter to move the cursor to next
line. For more information on code edit regions, refer to the CodeEditRegion help page.
For more information about using Shift+Enter, see Unexpected End of Statement (page 12).
You can indent lines in a procedure by using the spacebar. After you enter the last line, end
proc;, press Enter.
1.4 Procedures 5
(1.9)
To run this procedure, enter its name followed by a set of parentheses and a semicolon:
> hello();
(1.10)
(1.11)
This procedure requires one input, x. The procedure computes the approximation of the
value of x divided by 2. When a return statement is not specified, a Maple procedure returns
the result of the last statement that was run. Since evalf(x/2) is the last calculation performed
in the procedure half (in fact, it is the only calculation), the result of that calculation is re-
turned.
The procedure is named half by using the := notation in the same way that you would assign
any other object to a name. After you have named a procedure, you can use it as a command
in the current Maple session. The syntax to run your procedure is the same syntax used to
run a Maple library command: enter the procedure name followed by the input to the pro-
cedure enclosed in parentheses.
> half(2/3);
(1.12)
> half(a);
(1.13)
(1.14)
proc( P )
...
end proc
The letter P indicates the parameters. The body of the procedure is between the proc and
end proc keywords.
Consider the following two statements, which calculate the angle in a right triangle given
the lengths of two sides.
> theta := arcsin(opposite/hypotenuse);
(1.15)
> evalf(180/Pi*theta);
(1.16)
The following example shows a procedure that corresponds to these statements. The proced-
ure definition contains two input parameters for the length of two sides of a right triangle.
> GetAngle := proc( opposite, hypotenuse )
local theta;
theta := arcsin(opposite/hypotenuse);
evalf(180/Pi*theta);
end proc;
(1.17)
When you run the procedure definition, the output shown is the Maple interpretation of this
procedure definition. Examine it carefully and note the following characteristics.
The name of this procedure (program) is GetAngle. Note that Maple is case-sensitive,
so GetAngle is distinct from getangle.
The procedure definition starts with proc( opposite, hypotenuse ). The two names in
parentheses indicate the parameters, or inputs, of the procedure.
Semicolons or colons separate the individual commands of the procedure.
The local theta; statement declares theta as a local variable. A local variable has meaning
in the procedure definition only. Therefore, if you were to declare another variable called
1.4 Procedures 7
theta outside of the procedure, that variable would be different from the local variable
theta declared in the procedure and you could use theta as a variable name outside of
the procedure GetAngle without conflict.
For more information about local variables, see Variables in Procedures (page 230).
Pi is a predefined variable in Maple. Two predefined functions, evalf and arcsin, are
used in the calculation.
The end proc keywords and a colon or semicolon indicate the end of the procedure.
As you enter the procedure, the commands of the procedure do not display output. The
procedure definition is displayed as output only after you complete it with end proc and
a semicolon.
There is no explicit return statement, so the result of calling the procedure is the result
of the last calculation.
The procedure definition that displays in the output is equivalent to, but not identical to,
the procedure definition you enter. When Maple parses the statement, the commands of
the procedure may be simplified.
To hide the output resulting from a complicated procedure definition, use a colon instead
of a semicolon at the end of the definition.
You can include single line comments anywhere in the procedure. They begin with a pound
character (#). You can also enter multiline comments between (* and *) symbols as shown
in the example above.
Calling a Procedure
Running a procedure is referred to as an invocation or a procedure call. When you invoke
a procedure, Maple runs the statements that form the procedure body one at a time. The
result of the last computed statement within the procedure is returned as the value of the
procedure call.
For example, to run the procedure GetAngle--that is, to cause the statements that form the
procedure to be run in sequence--enter its name followed by parentheses enclosing the inputs,
in this case, two numbers delimited (separated) by commas (,). End the statement with a
semicolon.
> GetAngle(4,5);
(1.18)
Only the result of the last calculation performed within the procedure GetAngle is returned-
-the result of evalf(180/Pi*theta). The assignment theta:=arcsin(opposite/hypotenuse);
is performed, but the statement result is not displayed.
Maple commands are implemented in one of two formats: those written and compiled in
an external language such as C and those written in the Maple programming language.
The commands that are compiled as part of the Maple kernel are referred to as built-in
commands. These are widely used in computations, and are fundamental for implementing
other Maple commands.
For more information about built-in kernel commands, see The Computation En-
gine (page 1) and The builtin Option (page 224).
1.4 Procedures 9
The commands in the Maple library are written in the Maple programming language. These
commands exist as individual commands or as packages of commands. They are accessed
and interpreted by the Maple system as required. The code for the library commands and
the definitions of user-defined procedures can be viewed and modified. However, before
exploring library commands, it is important that you learn about evaluation rules to under-
stand the code.
(1.19)
> e;
(1.20)
This is called full evaluation--each name in the expression is fully evaluated to the last as-
signed expression in any chain of assignments. The following statements further illustrate
how full evaluation works.
> c := b;
(1.21)
> b := a;
(1.22)
> a := 1;
(1.23)
> c;
(1.24)
If you try this approach with a procedure, Maple displays only the name of the procedure
instead of its value (the procedure definition). For example, in the previous section, GetAngle
10 1 Introduction to Programming in Maple
is defined as a procedure. If you try to view the body of procedure GetAngle by referring
to it by name, the procedure definition is not displayed.
> GetAngle;
(1.25)
This model of evaluation is called last name evaluation and it hides the procedure details.
There are several reasons for this approach relating to advanced evaluation topics. The most
important concept to understand is that you will only see the name of a procedure when you
reference it by itself or when it is returned unevaluated; you will not see the full procedure
definition. To obtain the value of the name GetAngle, use the eval command, which forces
full evaluation.
Last name evaluation applies to procedures, tables, and modules in Maple. For more inform-
ation, refer to the last_name_eval help page.
> eval(GetAngle);
(1.26)
For example, to view the procedure definition for the Maple least common multiple command,
lcm, enter the following statements.
For more information about interface variables, refer to the interface help page.
> interface(verboseproc = 2):
1.5 Interrupting Computations and Clearing the Internal Memory 11
> print(lcm);
(1.27)
Because the built-in kernel commands are compiled in machine code, and not written in the
Maple language, you cannot view their definitions. If you print the definition of a built-in
procedure, you will see that the procedure has only an option builtin statement and no
visible body.
> print(add);
(1.28)
Note: Maple may not always respond immediately to an interrupt request if it is performing
a complex computation. You may need to wait a few seconds before the computation stops.
12 1 Introduction to Programming in Maple
Note: For more information on the toolbar icons, refer to the worksheet/reference/Work-
sheetToolbar help page.
Hold the Ctrl key and press the C key (in UNIX and Windows command-line versions).
Hold the Command key and press the period key (.) (in Macintosh command-line and
worksheet versions).
To perform a hard interrupt, which stops the computation and exits the Maple session, in
the Windows command-line interface, hold the Ctrl key and press the Break key.
For more information on clearing the Maple internal memory and the restart command,
refer to the restart help page. For more information on the toolbar icons, refer to the
worksheet/reference/WorksheetToolbar help page.
Maple tracks the use of permanent and temporary objects. Its internal garbage collection
facility places memory that is no longer in use on free lists so it can be used again efficiently
as needed. For more information on garbage collection and the gc command, see Garbage
Collection (page 628).
Tip: You can use the parse command to find errors in statements, and the Maple debugger
to find errors in programs. For more information on the debugger, see The Maple Debugger:
A Tutorial Example (page 583) or refer to the parse and debugger help pages.
If you press Enter to move the cursor to a new line when you are entering a procedure
definition on multiple lines, the following error is displayed.
> p:=proc()
Warning, premature end of input, use <Shift> + <Enter> to avoid this
message.
To prevent this error message from displaying as you enter a procedure definition, hold the
Shift key and press Enter at the end of each line, instead of pressing only Enter.
> p := proc()
"Hello World";
end proc;
(1.29)
In 1-D math notation, if you do not enter a trailing semicolon or colon, Maple inserts a
semicolon and displays the following warning message.
> 1 + 2
Warning, inserted missing semicolon at end of statement
(1.30)
(1.31)
Missing Operator
The most common error of this type is omitting the multiplication operator.
> 2 a + b;
Error, missing operator or `;`
You can avoid this error by entering an asterisk (*) to indicate multiplication.
14 1 Introduction to Programming in Maple
> 2*a + b;
(1.32)
Implicit multiplication, which can be used in 2-D math input, is not valid syntax in 1-D
math input.
If such an error occurs, check the appropriate help page for the correct syntax. Enter ?top-
ic_name at the Maple prompt.
The same type of error message is displayed if you call a user-defined procedure, such as
GetAngle, with the wrong number of the arguments.
Unbalanced Parentheses
In complicated expressions or nested commands, it is easy to omit a closing parenthesis.
> {[1,0], [0,1};
Error, `}` unexpected
(1.34)
The equal sign, =, is used in equality tests or to create equations. Creating an equation is a
valid Maple statement.
> x = y^2+3;
(1.35)
> solve(%,y);
(1.36)
> x;
(1.37)
In the example above, % is a special name that stores the value of the last statement. The
solve command is used to isolate y in the equation defined in the first statement. The first
statement is not an assignment; x remains a symbol with no assigned value.
You can use the assignment operator, :=, to assign x the value y^2+3. The assignment op-
erator assigns the value of the right-hand side to the left-hand side. After an assignment is
made, the left-hand side can be used in place of the value of the right-hand side. The left-
hand side cannot be a number; it must be a name, indexed name, function call, or sequence
of these values.
> x := y^2+3;
(1.38)
> solve(x,y);
(1.39)
> x;
(1.40)
For more information about equations and Boolean testing, see Boolean and Relational
Expressions (page 94) or refer to the evalb help page. For more information about names
and assignment, see Names (page 47) and Assignments (page 182).
16 1 Introduction to Programming in Maple
1.7 Exercises
1. Assign the integers 12321, 23432, and 34543 to the names a, b, and c. Use these names
to find the sum and difference of each pair of numbers.
2. Write two procedures. The first requires two inputs and finds their sum. The second re-
quires two inputs and finds their product. Use these procedures to add and multiply pairs
of numbers. How could you use these procedures to add and multiply three numbers?
3. Display your procedure definitions. Are they identical to the code you entered to write
them?
2 Maple Language Elements
Before programming in Maple, it is important to learn the properties and roles of the basic
elements of the Maple language. This chapter introduces some of the main concepts, which
will be described in more detail later in this guide.
There are also 33 special characters, which are listed in Table 2.1. These characters, or
combinations of these characters, have special meanings in the Maple language.
17
18 2 Maple Language Elements
These are the only characters used in the Maple language. However, all character types can
be used in names and strings, including international characters. For more information on
how to create names using international characters, see Names (page 22).
Note: When you manipulate a string or determine the length of a string, non-ASCII and
international characters may be counted as more than one byte.
(2.1)
(2.2)
2.3 Tokens
The Maple language combines characters into tokens. The set of tokens consists of reserved
words (also called keywords), programming-language operators, names, strings, and natural
integers.
Reserved Words
Maple keywords are reserved words that have special meanings. Therefore, you cannot
change them or use them as variables in procedures. The keywords are listed in Table 2.2.
You can find information about specific keywords in later chapters of this guide or the help
system.
For more information about reserved words in Maple, refer to the keyword help page.
Keywords Purpose
break, next loop control
2.3 Tokens 19
Keywords Purpose
if, then, elif, else if statement
for, from, in, by, to, while, do for and while loops
proc, local, global, option, error, return, options, descrip- procedures
tion
export, module, use modules
end ends structures
assuming assume facility
try, catch, finally exception handling
read, save read and save statements
quit, done, stop ending Maple
union, minus, intersect, subset set operators
and, or, not, xor Boolean operators
implies implication operator
mod modulus operator
Programming-Language Operators
There are two main types of Maple language operators: unary and binary. Simply put, a
unary operator acts on one operand, as in -a, where the operator - is applied to a. A binary
operator acts on two operands, as in a+b, where + is the operator and the operands are a
and b.
The Maple binary and unary operators, and their meanings, are listed in Table 2.3 and Table
2.4. For more information about these operators, refer to the operators,binary and operat-
ors,unary help topics.
Operator Meaning
+ unary plus (prefix)
- unary minus (prefix)
! factorial (postfix)
$ sequence operator (prefix)
not logical not (prefix)
&string neutral operator (prefix)
. decimal point (prefix or postfix)
Most of the unary and binary operators can also be used in element-wise form with objects
that have multiple elements. To perform an element-wise operation, add a trailing tilde (~)
after an operator that has an element-wise form. An element-wise operation allows you to
apply an operation to the elements of a list, set, table, Array, Matrix, or Vector. For example,
compare Matrix multiplication with element-wise multiplication of paired entries in a
Matrix.
> <1,2;3,4> . <2,2;2,2>;
(2.3)
(2.4)
The Maple element-wise operators are listed in Table 2.5. For more information about these
operators, refer to the operators,elementwise help page.
2.3 Tokens 21
Also, three special nullary operators (also called ditto operators) can be used in interactive
sessions. These are special Maple names that can be used to refer to previously computed
non-NULL expressions.
% last expression
%% second-last expression
%%% third-last expression
While they can be used for simple computations, the ditto operators should be avoided when
writing programs. For results that need to be used in subsequent expressions, assign values
to variables instead.
Note: In a worksheet, the ditto operators do not necessarily reference the results of the
lines located above the execution groups in which they are used. They reference the results
of the most recently performed computations in the Maple session, regardless of the exe-
cution group or document in which they are located. Also, in terms of evaluation, the
ditto operators are treated differently than local variables in a procedure. They are fully
evaluated, which may require more processing than one-level evaluation of local variables.
For more information about local variables, see Local Variables (page 231).
22 2 Maple Language Elements
For more information about the ditto operators, refer to the ditto help page.
Names
A name in Maple is a sequence of one or more characters that uniquely identifies a command,
file, variable, or other entity.
The simplest instance of a name consists of a letter followed by a sequence of letters, digits,
and underscores.
> My_Name_1;
(2.5)
If you need to create a name that includes blank spaces or international characters, use left
single quotes (`).
> `A quoted name`;
(2.6)
(2.7)
In general any name that can be formed without left single quotes is identical to the same
name with quotes. For example, x and `x` refer to the same name x. Left single quotes are
similar to double quotes in that double quotes are used to build strings while left single
quotes are used to build names.
Note that the reverse is not true, some names can be formed with left single quotes that are
not identical to expressions typed in without quotes. One example is the name `2`. By putting
quotes around the 2 here, a name is formed instead of a number. Another example is a
quoted keyword, like `module`. To test if an expression is of type module, check type(ex-
pr,`module`). Without the quotes, the Maple parser determines that this is the start of a
module definition and the parser will flag a syntax error.
Characters in Maple are case-sensitive. Therefore, for example, the name Apple is different
from the name apple.
> Apple := 4;
(2.8)
> apple := 5;
(2.9)
2.4 Natural Integers 23
(2.10)
For example, in the first statement below, y is a name that does not have a value. In the
second statement, the variable x has the value 3.
> 2*y - 1;
(2.11)
> x := 3; x^2 + 1;
(2.12)
You can create an empty name, which has no characters in its spelling.
> type( ``, 'name' );
(2.13)
Early versions of Maple did not have separate types for names and strings. As a result, many
commands for string processing will also accept names and process their characters the
same way. It is generally better to use strings for such processing as strings can never have
assigned values.
For more more information about names, see Names (page 47).
(2.14)
24 2 Maple Language Elements
For more information about integers, see Integers (page 59) and Numeric Types in
Maple (page 281).
2.5 Strings
A string is a sequence of characters that evaluates to itself. To create a string, enclose any
sequence of characters in double quotes.
> "This is a string";
(2.15)
In the following sections, strings and string operations are described. For information on
the StringTools package, refer to the StringTools help page.
Length of a String
Use the length command to determine the number of bytes in a string.
> length("What is the length of this string?");
(2.16)
All of the characters between, but excluding, the double quotes are counted. Each blank
space is counted as one character. Non-ASCII characters may be counted as more than one
byte.
The maximum string length is system-dependent and ranges from about 268 million bytes
on 32-bit systems to more than 34 billion bytes on 64-bit systems.
Substrings
You can extract a substring of a string by using a subscripted integer range (also called a
selection operation).
> S := "This is a string";
(2.17)
> S[6];
(2.18)
2.5 Strings 25
> S[6..9];
(2.19)
Negative numbers in the range count backwards from the end of the string. -2 is the second
last character in the string. Either range endpoint can also be left off to indicate from the
beginning, or to the end.
> S[-6..-1];
(2.20)
> S[11..];
(2.21)
Searching a String
To perform case-sensitive and case-insensitive string searching, use the SearchText and
searchtext commands, respectively.
SearchText( pattern, exprString, range );
searchtext( pattern, exprString, range );
The SearchText command searches for exact matches of pattern in exprString. The
searchtext command performs the same search, but it is case-insensitive. If pattern is
found, Maple returns an integer indicating the position of the first character in pattern in
exprString. If the pattern is not found in exprString, 0 is returned.
> SearchText("my s", "This is my string.");
(2.22)
(2.23)
The optional range restricts the search to the specified range. It is equivalent to performing
a search on a substring, and it is useful when the pattern occurs more than once in the string.
> SearchText("is", "This is my string.", 4..-1);
(2.24)
String Concatenation
Strings can be formed through concatenation by using the cat command.
26 2 Maple Language Elements
cat( sequence )
Here, the sequence parameter can contain any number of expressions that are separated by
commas.
The cat command is commonly used to concatenate strings with names and integers, and
the result returned has the type (name or string) of the first argument to cat.
> i := 5;
(2.25)
(2.26)
(2.27)
Mutability of Strings
Strings are not mutable objects in Maple. This means that appending text to a string is not
done in-place, but involves allocating new storage for the result and copying the original
text, plus the appended text, into that new space. This is typically not an issue unless you
are incrementally processing large amounts of text. In the latter case, the StringBuffer
command may be useful.
> with(StringTools):
> s := StringBuffer();
(2.28)
(2.29)
(2.30)
Similarly, to display a backslash character as one of the characters in a string, enter two
consecutive backslash characters, \\. You must escape the backslash in this manner because
backslash is itself a special character. For more information, see Blank Spaces, New Lines,
Comments, and Continuation (page 29).
> "a\\b";
(2.31)
The special backslash character mentioned above counts as only one character, as demon-
strated by using the length command.
> length((2.31));
(2.32)
Doubling up backslashes is most notable when entering full path names in Maple. For this
situation it is easier to use forward slash instead. Forward slash is recognized as a directory
separator on all platforms including Windows.
Parsing Strings
The parse command accepts any Maple string and parses the string as if it had been entered
or read from a file. This is especially useful when you want to interpret commands typed
into text-components inside your Maple document.
parse( exprString, option );
Without specifying extra options, the string should contain exactly one Maple expression.
The expression is parsed and returned unevaluated.
> parse("a+b");
(2.33)
> parse("a+b;");
(2.34)
28 2 Maple Language Elements
If the string is syntactically incorrect, the parse command displays an error message of the
form incorrect syntax in parse: ... (number).
The number indicates the offset in characters, counted from the beginning of the string, at
which the syntax error was detected.
> parse("a++b");
Error, incorrect syntax in parse: `+` unexpected (near 3rd character
of parsed string)
If the option statement is specified, the string is parsed and evaluated, and then the result
is returned.
> parse("sin(Pi)");
(2.35)
(2.36)
Maple can convert a variety of expressions. For more information about expressions, see
Maple Expressions (page 45). For more information about conversions in Maple, refer to
the convert help page.
> convert(a, 'string');
(2.37)
(2.38)
(2.39)
2.6 Using Special Characters 29
The white space characters separate tokens, but are not themselves tokens. White space
characters cannot normally be used within a token.
> a: = b;
Error, `=` unexpected
(2.40)
White space characters can be part of a token in a name or string formed by enclosing a
sequence of characters in left single quotes or double quotes respectively. For more inform-
ation, see White Space Characters within a Token (page 41).
Except in a string, all characters that follow a pound sign "#" on a line are part of a comment.
For information about adding comments in Maple procedures, see Adding Comments to a
Procedure (page 7).
> a := 1 + x + x^2; #This is a comment
(2.41)
Since white space and new line characters are functionally identical, you can continue
statements from line to line, as described in Entering a Procedure Definition (page 4).
> a:= 1 + x +
x^2;
(2.42)
30 2 Maple Language Elements
Note: Press Shift+Enter to continue typing on the next line without evaluating the ex-
pression.
To enter a long number or string on multiple lines, use the backslash character (\) as a line
continuation character.
You can use the backslash character to break up a long sequence of digits into groups of
smaller sequences to enhance readability.
> "The input should be either a list of \
variables or a set of variables";
(2.43)
(2.44)
You can also enter long strings by using a continuation character. Maple automatically
concatenates string constants that are on separate lines, so another way to enter a long string
is to close one set of double quotes at the end of a line and enter a new double quote at the
beginning of the next line.
> S:= "This is the start of a long string "
"and this is part of the same string with no line in between";
(2.45)
Punctuation Marks
The punctuation marks that act as token separators are listed in Table 2.6.
(2.46)
(2.47)
(2.48)
(2.49)
(2.50)
32 2 Maple Language Elements
Parentheses
The left and right parentheses group terms in an expression, arguments in a function call,
and parameters in a procedure definition.
> (a+b)*c; cos(Pi);
proc( x, y, z )
x+y+z;
end proc:
(2.51)
The left and right parentheses are also used to select components from certain data structures
(programmer indexing).
Square Brackets
Use the left and right square brackets to form indexed (subscripted) names and to select
components from data structures such as Arrays, tables, and lists. For more information on
selection, see Indexed Expressions (page 69). For more information on mathematical index-
ing and programmer indexing, see Basic Data Access (page 156).
> a[1]; L:=[2,3,5,7]; L[3];
(2.52)
(2.53)
2.6 Using Special Characters 33
Angle Brackets
The left and right angle brackets (<>) in conjunction with the the comma, semicolon, and/or
vertical bar (|) can be used to create Matrices and Vectors. For more information, refer to
the Matrix and MVshortcut help pages.
> <1,2,3; 4,5,6>;
(2.54)
(2.55)
Comma
Use the comma to form an expression sequence. Expression sequences are used to specify
the arguments of a function call or the elements of a list or set.
> sin(Pi), 0, limit(cos(xi)/xi, xi=infinity);
(2.56)
Escape Characters
An escape character indicates that the character that follows the escape character must be
handled in a special manner. The escape characters in Maple are ?, !, #, and \.
? - The question mark character, if it appears as the first non-blank character on a line,
opens the Maple help system. The words following ? on the same line determine the
terms used to display a help page. Use either "," or "/" to separate the words that follow
the question mark character. For more information, refer to the help help page.
! - The exclamation mark character, if it appears as the first non-blank character on a line,
passes the remainder of the line as a command to the host operating system. For more
information, refer to the system and escape help pages.
# and (*, *) - The pound sign character indicates that the characters that follow it on the
line are a comment. The multiline comment characters, (*, and *) indicate the beginning
and end of a comment. For more information, see Adding Comments to a Proced-
ure (page 7) or refer to the comment help page.
34 2 Maple Language Elements
\ - The backslash character is used to continue lines, to group characters in a token, and
introduce control characters. For more information, refer to the backslash help page.
DAGs
All data in Maple is stored as a directed acyclic graph (DAG). An identifying tag for each
DAG indicates what type of data it stores and how it is stored. Names, strings, lists, and
positive and negative integers are examples of some DAG types. For a list of DAG types
and how they are stored in memory, see Internal Representation (page 635). The op command
(short for operand) can often be used to determine the DAG type of the underlying data by
returning the zeroth operand.
> op(0, [1,2,3]);
(2.57)
(2.58)
(2.59)
(2.60)
However, the correspondence is not exact. A notable exception to this is function calls
where the zeroth operand is the function name.
> op(0, f(x));
(2.61)
(2.62)
When names are assigned to data, they act as pointers in other languages. However, for
most purposes, the Maple evaluation rules are such that you can think of them as variables.
If you want to manipulate an assigned name rather than the data assigned to it, you can use
2.7 Types and Operands 35
right single quotes (also called unevaluation quotes), which are described in more detail
in Unevaluated Expressions (page 52).
Assigned names that are pointers do not require type declarations as in low-level program-
ming languages. This means that a name a may be assigned an integer and then later assigned
a list without discretion. This system of weak typing means that, when writing robust code,
you must verify types since variables may be assigned any value.
Maple Types
The type facility in Maple is accessed by using the type command. It is a mathematical type
facility; however, some basic types such as integer or list map directly to the type of DAG.
Some types, such as numeric, encapsulate a group of many different kinds of structures
and structured types, such as name^integer, can match a very specific value.
Type checking is important in Maple to decide whether an expression is valid input for
procedure calls and Maple commands. You can use the the type command or the :: operator
for type checking. The operator form is primarily used to declare the type of a procedure
parameter (see Procedures (page 209)). The type command has the following syntax.
type( expression, typeName );
If the expression is of type typeName, the type command returns a value of true. Otherwise,
a value of false is returned.
> type([1,2,3], 'list');
(2.63)
(2.64)
(2.65)
(2.66)
The type of any integer is integer. The type command can also interpret many subtypes of
integers, some of which are listed in Table 2.7.
36 2 Maple Language Elements
Subtype Meaning
integer[8] 64-bit sized integer
integer[4] 32-bit sized integer
negint negative integer
posint positive integer
nonnegint non-negative integer
nonposint non-positive integer
even even integer
odd odd integer
prime prime number
For more information, refer to the type help page, which also contains a complete list of
types in Maple.
The type facility can also interpret compound or structured types such as list(integer) for
a list of integers or list({negint,prime}) for a list of negative or prime integers.
> type([-1, 2, 11], 'list({negint,prime})');
(2.67)
(2.68)
Operands and op
In addition to providing information about the underlying type, the op command can provide
information about the other operands or parts of a data structure. Most data structures in
Maple can be divided into components. For example, an equation of the form x=y+x can
be divided as follows.
the operator, =
the left-hand side, x
the right-hand side, y+x
To determine the operands and the number of operands in an expression, use the op and
nops commands respectively. These commands have the following basic syntax.
2.7 Types and Operands 37
op( i, expression );
nops( expression );
If the optional first argument i to the op command is a positive integer, the ith operand of
expression is returned.
> eq := x=y+x:
> nops(eq);
(2.69)
(2.70)
(2.71)
(2.72)
(2.73)
The op command can also determine the operands of an expression at various levels of a
structure with the following syntax.
op( [i1, i2, ...], expression );
This syntax is equivalent to and more efficient than a nested call to the op command.
> op([2,0], eq);
(2.74)
(2.75)
This hierarchical structure of expressions explains the name DAG. The internal representation
of looks like an infix expression tree. See Figure 2.1
38 2 Maple Language Elements
For efficiency, Maple does not store multiple copies of identical objects, so the two x nodes
in the tree can be identified (Figure 2.2).
2.7 Types and Operands 39
The term directed acyclic graph simply refers to this variation of a tree where nodes may
have multiple parents.
The tree form of an expression can be displayed using the dismantle command.
> dismantle(eq);
EQUATION(3)
NAME(4): x
SUM(5)
NAME(4): y
INTPOS(2): 1
NAME(4): x
INTPOS(2): 1
The astute observer will notice that the output does exactly match the DAG above. This is
because Maple uses a more sophisticated internal representation for sums as described in
Internal Representation (page 635). The real structure of the DAG shown in Figure 2.3.
40 2 Maple Language Elements
The next three chapters introduce many of the other types in Maple and describe how to
create and use them in programs.
For more information about protected names, see Protected Names (page 49) or refer to
the protect help page.
> int := 10;
Error, attempting to assign to `int` which is protected
2.8 Avoiding Common Problems 41
For more information about strings, see Strings (page 24) or refer to the string help page.
> "my string" := 10;
Error, invalid left hand side of assignment
The error message indicates the character number (counted from the left double quote)
where the error was detected. In this case, the 6th character (the second minus sign) caused
the error.
The less-than-or-equal operator <= is a token in Maple. Therefore, it cannot contain a space.
> evalb(2 <= 3);
(2.76)
For more information about using quotes, see Punctuation Marks (page 30) or refer to the
quotes help page.
42 2 Maple Language Elements
(2.77)
> type((2.77),'string');
(2.78)
To form a name, enclose the expression in left single quotes. Unlike a string, which is dis-
played with double quotes around it, names are usually printed without quotes. The name
in this example only looks like a polynomial.
> `2 + 3`;
(2.79)
> type((2.79),'name');
(2.80)
(2.81)
> eval(f);
(2.82)
To resolve this issue, select a variable name that is not a Maple keyword.
Note: It is possible to use a Maple keyword as a name by enclosing it in left single quotes.
For example,
> `end` := 2;
(2.83)
2.9 Exercises 43
> 1 + `end`;
(2.84)
However, this approach is not recommended. When possible, avoid using keywords as
names.
2.9 Exercises
1. Using Maple operators, do the following:
a. Compute the sum of 5434 and 6342.
b. Compute the product of 92 and 310.
c. Compute the quotient of the result from a) divided by the result from b).
d. Create a list containing the numbers from a), b), and c).
e. Square each element of the list from d).
2. Create variables named "my quotient" and "my remainder". Use these variables and the
irem command to find the integer quotient and remainder of 12345 divided by 234. Tip:
You will need both left single quotes and right single quotes around one of the variables.
3. Compute 3^(3^98) modulo 7.
5. Concatenate the three strings "int", "(x^2,", and "x)". Parse the resulting string. Evaluate
the parsed string.
6. Determine a random integer between 40 and 100 using the command rand(40..100).
Concatenate this number with the string, "The student's grade is ". Extract the student's
grade from the resulting string.
7. Assign the expressions x^2 and x*x to the names a and b. Find the three operands of a
and b. Compare the results with those returned by using the dismantle command, that
is, dismantle(a) and dismantle(b). The dismantle command displays the internal data
structure used.
44 2 Maple Language Elements
3 Maple Expressions
This chapter introduces Maple expressions associated with scalar data structures.
3.2 Introduction
Expressions and Statements
Maple language elements can be classified as either expressions or statements. An expression
is a first-class data element in the Maple language. In other words, expressions can be stored
in data structures, passed as arguments to procedures, and manipulated in various ways;
they are often used to represent mathematical objects. Statements, on the other hand, are
not first-class data elements; they generally describe non-mathematical programming con-
structs and are used to affect the state of Maple.
This chapter describes expressions associated with scalar data structures. For information
about non-scalar data structures, see Basic Data Structures (page 135).
For more information about Maple statements, see Maple Statements (page 181).
45
46 3 Maple Expressions
For example, a sum that would normally be entered using the syntax for addition
> a + b + c + d;
(3.1)
(3.2)
With some exceptions (for example, series, lists, sets, and procedures), the name of the
constructor for an expression can be displayed by using the op command with its first argu-
ment equal to 0.
> op( 0, a + b + c + d );
(3.3)
The example above shows that the constructor for the expression a + b + c + d is the com-
mand assigned to the name `+`.
In cases where the constructor for an expression is the zeroth operand, the expression may
be reconstructed from its other operands by passing them to the constructor. Therefore,
most expressions satisfy the following general constructor equation.
expr = op( 0, expr )( op( expr ) )
For example,
> expr := 2 / 3;
(3.4)
(3.5)
(3.6)
3.3 Names 47
(3.7)
> expr := a + b + c + d;
(3.8)
(3.9)
(3.10)
(3.11)
3.3 Names
Names, also called symbols, have several purposes in Maple. They can be used to reference
algebraic indeterminates, symbols, and variables in your code.
Names (page 22) provided a basic introduction to Maple names. The following section
describes concepts related to names in more detail.
A Maple name can be either global or local, depending on its scope. In this chapter, only
global names are used. A global name is created either by referring to it at the top level of
your program or by declaring it to be global in either a procedure or module definition. For
more information about scope, see Variables in Procedures (page 230).
Two names are the same if they have the same spelling and scope. Maple keeps only one
copy of any name in memory, so in a large expression that includes an indeterminate x,
only one copy of the name x is kept in memory. Each occurrence of x in the expression
refers to the same name x.
The polynomial
> x^3 - 3*x^2 + 3*x - 1;
(3.12)
contains three occurrences of the name x, but all three point to the same location in memory.
48 3 Maple Expressions
Maple is unique in that names can represent themselves. As a result, you can use names as
algebraic indeterminates, for example, to construct polynomials or other algebraic expres-
sions.
Names can also be used to represent variables in your code. When a name is assigned to a
value, that name is associated with another expression and evaluating the name results in
its assigned value. When a name is unassigned, evaluating the name results in the name itself.
(3.13)
Before using a name on the left side of an assignment, the name has no assigned value.
> b;
(3.14)
When a value is assigned to a name, subsequent evaluation of the name results in its assigned
value.
> a;
(3.15)
For more information about assigning values, see Assignments (page 182).
Environment Variables
Names beginning with an underscore character (_) are reserved for use by the Maple library.
You should not create names that begin with an underscore.
As a special case, any name beginning with the four character sequence "_Env" is treated
as an environment variable.
Environment variables are a special kind of variable in that an assignment to one within a
procedure is automatically unassigned when the procedure has finished running. Therefore,
environment variables only affect subprocedures called from that procedure, unless they
are superseded locally.
3.3 Names 49
The following predefined environment variables do not begin with _Env: Testzero, Use-
HardwareFloats, Rounding, %, %%, %%%, Digits, _ans, index/newtable, mod, Order,
printlevel, Normalizer NumericEventHandlers.
For more information about environment variables, refer to the envvar help page. For more
information about procedures, see Procedures (page 209) or refer to the procedure help
page.
Constants
In addition to keywords, as described in Reserved Words (page 18), Maple has several
predefined constants.
You can display a sequence of all the names that represent symbolic constants in Maple by
using the global variable constants.
> constants;
(3.16)
(3.17)
Maple also has several other special constants. Table 3.1 lists some of them. For more in-
formation, refer to the initialconstants help page.
For more information about constants in Maple, refer to the constant help page.
Protected Names
A protected name has a predefined meaning; you cannot directly assign a value to a protected
name. For example, the names of built-in commands such as sin; utility operations such as
50 3 Maple Expressions
degree; commands such as diff; and type names such as integer and list, are protected
names. An error occurs if you attempt to assign a value to any of these names.
> list := [1,2];
Error, attempting to assign to `list` which is protected
The Maple system prevents these names from re-assignment. However, even though it is
not recommended, it is possible to reassign values to these names by first unprotecting them
as illustrated by the following statements.
Note: You can unassign values to Maple system names by entering a restart command or
by ending your Maple session. In general, using the unprotect command to modify Maple
system names is not recommended.
> unprotect(sin);
> sin := "a sin indeed";
(3.18)
As a result, Maple components that rely on the sine function may not work as expected.
3.3 Names 51
(3.20)
(3.21)
To prevent values from being assigned to a name, use the protect command.
> mysqr := x -> x^2;
(3.22)
(3.23)
52 3 Maple Expressions
where expr is an arbitrary expression. All of the expressions contained within the right
single quotes are not evaluated.
For example, the sin command normally performs the following computations.
> sin( 0.5 );
(3.24)
> sin( Pi / 2 );
(3.25)
To prevent the evaluation of these computations, you can enclose the expressions in right
single quotes (also called unevaluation quotes) as follows.
> 'sin( 0.5 )';
(3.26)
(3.27)
(3.28)
Also, you can enclose subexpressions in unevaluation quotes to prevent certain parts of an
expression from evaluating.
3.4 Unevaluated Expressions 53
(3.29)
The sections below describe cases in which you may want to delay evaluation.
(3.30)
> a;
(3.31)
> 'a';
(3.32)
This is important when you want to use a variable as a name, regardless of whether it has
an assigned value.
Also, unevaluation quotes can be used to protect options. Names are often used as options
to control the behavior of a command. If the name of that option has been used as a variable,
the command that has been called uses the value of the variable and not the option name as
expected. Unevaluation quotes can be used around option names to protect against this.
> periodic := 4;
(3.33)
> numtheory:-cfrac(3^(1/2),'periodic');
(3.34)
In the next example, an exception is raised because the name of a command option is not
enclosed in unevaluation quotes.
> output := 2:
54 3 Maple Expressions
In this example, the best way to use the output option is to quote the name, thus preventing
its evaluation in case the name output has an assigned value.
> CodeGeneration:-C( x^2, 'output' = 'string' );
(3.35)
Tip: It is also recommended that you also use unevaluation quotes for the names of types
and conversions. For more information, see Structured Types (page 130).
For more information on types and conversions, refer to the type and convert help pages.
Generic Expressions
Expressions sometimes describe the operation to take place in a generic sense. For example,
B[i] can be used in certain contexts with unevaluation quotes to denote a generic index into
B. If unevaluation quotes are not used, Maple will try to look up the specific ith element of
B.
> B := <1,2,3,4>;
(3.36)
(3.37)
Pass by Reference
Some commands accept a name as an argument, with the intent that it will be used to store
a result. Unevaluation quotes ensure that the variable name (and not the value assigned to
the variable) is used in the procedure.
3.4 Unevaluated Expressions 55
(3.38)
(3.39)
If quotient is not enclosed in unevaluation quotes, the second call in the above example
raises an exception because 15, the value of quotient, is not a valid third argument to the
irem command.
(3.40)
> v;
(3.41)
Unassigning Names
To reset the value of a name, assign the unevaluated name (its initial value) to the name.
For example,
> x := 2+3;
(3.42)
> x := 'x';
(3.43)
(3.44)
In this example, Maple first simplifies the unevaluated sum '2 + 3' to the expression '5'.
During the evaluation process, Maple "removes" the right single quotes and produces the
numeric result 5.
All unevaluated expressions are of the type uneval. You can use the type command to check
whether an expression is an unevaluated expression.
> type( ''x'', 'uneval' );
(3.45)
In the example above, the first argument to the call to the type command is the name x,
which is enclosed in two sets of unevaluation quotes. The result of evaluating the first argu-
ment is the unevaluated expression 'x' because the evaluation process removes one set of
unevaluation quotes. The resulting expression is therefore of type uneval.
On the other hand, if you enclose the first argument to type in only one set of unevaluation
quotes, the evaluation process removes the only set of unevaluation quotes, leaving the
result as the name x, which is not an unevaluated expression.
> type( 'x', 'uneval' );
(3.46)
In other words, the type command accesses the name x, rather than the unevaluated expres-
sion 'x', since the type command accesses the result of its arguments that have been evalu-
ated.
In the example above quotes were also used around the type name uneval. This provides a
measure of protection just in case the variable name, uneval has an assigned value (which
is unlikely because uneval is protected). During normal function evaluation, each argu-
ment, x and uneval is evaluated. With quotes, 'x' becomes x, and 'uneval' becomes uneval
as seen by the type procedure. Without quotes, x would become the value of x (which may
be the symbol x itself), and uneval would become the value of uneval, which is usually the
symbol uneval itself. Unevaluation quotes make the displayed call robust against cases
3.4 Unevaluated Expressions 57
where the variable you are using unexpectedly has a value. It is rarely necessary to use this
level of caution in interactive use, but when you write programs, it is a good practice to in-
clude unevaluation quotes to make your code as robust as possible.
Suppose f is not assigned to anything. Since evaluating f does not call a procedure, Maple
returns the unevaluated function call f(a).
> f(a);
(3.47)
Similarly, using uneval quotes around a function evaluation will cause Maple to behave as
if the named function had no value.
> ''sin''(Pi);
(3.48)
> (3.48);
(3.49)
> (3.49);
(3.50)
You will find this facility useful when writing procedures that need to act on the whole
original expression, not the evaluated result.
For more examples and information on unevaluated expressions, refer to the uneval help
page.
> f := proc( x )
if x > 2 then
x
else
2
end if
end proc:
The correct calling sequence would be either plot( 'f'(x), x=-10..10), which puts uneval
quotes around f, or plot( f, -10..10), which avoids computing f(x) by omitting the variable
altogether. Remember that arguments in a function call are evaluated first before the called
procedure sees them.
Here, the precursor evaluation of f(x) tries to apply f to the unassigned symbol, x.
> f( x );
Error, (in f) cannot determine if this expression is true or false:
2 < x
The procedure could be rewritten so that it returns unevaluated whenever it encounters ar-
guments that cannot be processed. This trick causes f(x) to evaluate to itself when non-nu-
meric input is passed in.
> f := proc( x )
if type( x, 'numeric' ) then
if x > 0 then
x
else
2
end if
else
'procname( _passed )'
end if
end proc:
The unevaluated expression 'procname( _passed )' returns the full calling sequence une-
valuated.
3.5 Numbers 59
> f( x );
(3.51)
3.5 Numbers
Maple supports computation with exact numerical quantities, as well as approximate com-
putation to arbitrarily high accuracy with floating-point numbers.
Integers
A natural integer is any sequence of one or more decimal digits.
> 12345;
(3.52)
(3.53)
(3.54)
> +42;
(3.55)
You can use the length command to determine the number of digits in an integer.
> 2^42;
(3.56)
(3.57)
60 3 Maple Expressions
Fractions
A rational number (fraction) is the quotient of two integers, where the denominator is always
positive.
For example,
> 2 / 3;
(3.58)
You can enter a fraction in which the numerator and denominator have a common (integer)
factor, but Maple automatically simplifies this to the lowest terms.
> 4 / 6;
(3.59)
(3.60)
(3.61)
You can use the numer and denom commands to extract the numerator and denominator,
respectively, of a fraction.
> numer( 2/3 );
(3.62)
(3.63)
3.5 Numbers 61
Fractions can also be created by using the Fraction constructor with the numerator and
denominator as arguments.
> Fraction( 2, 3 );
(3.64)
Floats
Maple supports computation with floating-point numbers to arbitrary precision.
(3.65)
> 2.;
(3.66)
> .7;
(3.67)
> -.567;
(3.68)
Or, using exponent form using a suffix containing the letter "e" or "E" followed by an integer
with no spaces between.
> 4e3;
(3.69)
> 2.3e6;
(3.70)
> .2E3;
(3.71)
Observe that spaces are significant. The first example is a difference rather than a float in
exponent form.
62 3 Maple Expressions
(3.72)
> .2e-3;
(3.73)
Floats represent numbers of the form s*10^e, where the number s is called the significand
or mantissa of the float, and the number e is called the exponent. The significand is a Maple
integer. Therefore, it is restricted to values that have, at most, the number of digits indicated
by the kernelopts( 'maxdigits') command.
> kernelopts( 'maxdigits' );
(3.74)
The maximum value of the exponent is a platform-dependent quantity whose value may be
queried by using the Maple_floats command.
> Maple_floats( 'MAX_EXP' );
(3.75)
(3.76)
returned by the Maple_floats command. For more information, refer to the Maple_floats
help page.
You can also create software floats by using the constructor SFloat. This constructor accepts
the significand and exponent as arguments, and has the general form
SFloat( m, e )
(3.77)
To extract the significand and exponent of a software float, use the SFloatMantissa and
SFloatExponent commands.
3.5 Numbers 63
(3.78)
(3.79)
The significand and exponent are also the operands of a software float.
> op( 2.3 );
(3.80)
Two software floats are equal if they represent the same number. However, equal floats by
themselves do not need to be the same object in memory.
> evalb( 2.3 = 2.30 );
(3.81)
(3.82)
Observe that the significands (and therefore, also, the exponents) differ in this example.
> SFloatMantissa( 2.3 );
(3.83)
(3.84)
Note that equal floats with different significands inside of two otherwise identical objects
will require something stronger than evalb for comparison. evalb is the implicit comparison
used when evaluating conditionals in if statements.
> evalb( 2.3 + x = 2.30 + x );
(3.85)
(3.86)
64 3 Maple Expressions
Testing the difference of the two expressions, or calling a command to do a deeper compar-
ison may be necessary.
> evalb( (2.3 + x) - (2.30 + x) = 0 );
(3.87)
(3.88)
The names of the constructor SFloat and accessors SFloatMantissa and SFloatExponent
all begin with the letter S. The S stands for "software" because these floating-point numbers
are implemented in software. Maple also supports the floating-point numbers supported by
the underlying hardware, called hardware floats or hfloats. You can create a hardware float
by using the hardware float constructor HFloat.
> HFloat( 24375, -3 );
(3.89)
(3.90)
> op( h );
(3.91)
Note, however, that hfloats are binary floating-point numbers, rather than decimal floating-
point numbers. That means that unlike the example above, there is often round-off error
when decimal numbers are converted into hfloats. For more information, see Hardware
Floating-Point Numbers (page 286).
> op( HFloat(2.3) );
(3.92)
The SFloatMantissa and SFloatExponent commands also accept hardware floats as input.
> SFloatMantissa( h );
(3.93)
> SFloatExponent( h );
(3.94)
For more information on floating-point numbers, see Floating-Point Numbers (page 285).
3.5 Numbers 65
Complex Numbers
(3.95)
where re and im are the real and imaginary parts of the complex number, respectively. If
the expressions re and im are of type extended_numeric; the resulting complex number
will be of type complex( extended_numeric ). (It is not necessary that re and im are reals;
they may be arbitrary algebraic expressions. However, in this case, the result of the syntax
above will generally be an algebraic expression that will not be a complex numeric constant.)
You can also create complex numbers using the Complex constructor. It can be called using
either one or two arguments. The single-argument form has the following syntax.
Complex( expr )
If the argument expr is of type complex, the Complex constructor returns the value of expr.
Otherwise, if expr is of type extended_numeric, the Complex constructor returns expr *
I.
> Complex( 2 ), Complex( 0 ), Complex( 0.0 );
(3.96)
(3.97)
The first argument is interpreted as the real part and the second argument is interpreted as
the imaginary part, of the complex number constructed.
> Complex( 2, 3 ), Complex( 2.1, 3 ), Complex( 0, 0 );
(3.98)
66 3 Maple Expressions
Note that if either of the arguments is a float, the real and imaginary parts of the complex
number created are both of type float.
A complex zero with floating-point real and imaginary components can have four sign
combinations.
> z1 := 0.0 + 0.0*I; z2 := 0.0 - 0.0*I;
z3 := -0.0 - 0.0*I; z4 := -0.0 + 0.0*I;
(3.99)
Similar to 0.0 = -0.0, numerically, these four complex zeros are numerically equivalent.
> evalb( z1 = z2 and z2 = z3 and z3 = z4 );
(3.100)
If the arguments re and im are not of type extended_numeric, the Complex constructor is
returned unevaluated.
> Complex( u, v );
(3.101)
It is important to understand that there is a single complex infinity, which is a point on the
Riemann sphere. It can be denoted in different ways:
> inf1 := infinity + infinity * I; inf2 := infinity - infinity * I;
inf3 := -infinity - infinity * I; inf4 := -infinity + infinity *
I;
(3.102)
(3.103)
They are all treated as distinct from the positive and negative real infinities.
To select the real or imaginary part of a complex number, use the Re and Im commands,
respectively.
> Re( 2.3 + sqrt(2)*I );
(3.104)
(3.105)
Note that, for a symbolic expression of the form a + b*I, it is not assumed that a is the real
part and b is the imaginary part. Therefore, the Re and Im commands are not unevaluated
on such input.
> Re( a + b*I );
(3.106)
(3.107)
68 3 Maple Expressions
However, the evalc command uses special rules for processing complex expressions, in
which any unknown symbol is assumed to be real. Therefore, when the evalc is used, these
expressions are returned as follows.
> evalc( Re( a + b*I ) );
(3.108)
(3.109)
You can change the default name used to input and display the imaginary unit by using the
interface command.
> interface( 'imaginaryunit' = i );
(3.110)
(The previous value is returned.) After calling the command above, the name i is used to
represent the imaginary unit.
> Complex( 2, 3 );
(3.111)
When this command is used, the name i can no longer be used as a program variable. As
an example, the following statements display error messages.
> i := 2;
Error, illegal use of an object as a name
(3.112)
3.6 Indexed Expressions 69
where expr is an arbitrary expression and index represents a sequence of expressions. The
following are examples of indexed expressions.
> 2[ 3, 4 ];
(3.113)
> a[];
(3.114)
> a[ 1 ];
(3.115)
> a[ b ];
(3.116)
> a[ b, c ];
(3.117)
> map[ 2 ];
(3.118)
> [ 1, 2, 3 ][ 2 ..3 ][ 1 ];
(3.119)
Note that the last example above contains a nested (or iterated) indexed expression.
(3.120)
Note that the indices must be enclosed with square brackets in a list.
70 3 Maple Expressions
All or some of the elements of an index sequence can be extracted by using the op command.
The nops command will tell you how many elements are in the index sequence.
> nops( a[ b, c, d ] );
(3.121)
> op( a[ b, c, d] );
(3.122)
> op( 2, a[ b, c, d ] );
(3.123)
(3.124)
Indexed expressions are often used to perform selection operations. The behavior of a selec-
tion operation depends on the type of expression, expr, and the index sequence given.
If expr is itself a sequence of expressions, the index sequence must evaluate to a positive
integer, an integral range, or the empty sequence. The following are all examples of valid
ways to index a sequence.
> expr := (1,2,3,4);
(3.125)
> expr[ 3 ];
(3.126)
> expr[ 1 .. 3 ];
(3.127)
> expr[];
(3.128)
> expr[ 2 .. 1 ];
The result of evaluating an indexed sequence is a selection of the components of the sequence.
The indexing sequence must represent a valid index or range of indices. Attempting to select
an entry beyond the length of the sequence and will raise an error.
3.6 Indexed Expressions 71
> expr[ 88 ];
Error, invalid subscript selector
Similarly, components of lists, sets, arrays, matrices, and vectors can be selected
> L := [1,2,3,4];
(3.129)
> L[ 3 ];
(3.130)
> L[ 1 .. 3 ];
(3.131)
> L[];
(3.132)
> M := <1,2,3;4,5,6>;
(3.133)
> M[2,3];
(3.134)
> M[1..2,1..2];
(3.135)
(3.136)
> S[ 3 ];
(3.137)
Note that, because sets are sorted data structures, the order at construction time may not
match the order stored internally. It is not predictable what color will be returned by the
index used to specify the third entry above. (It may not be green.)
72 3 Maple Expressions
A negative number may be used as an index, which selects elements starting from the end
of the list. Positive and negative indices mixed in a range return an empty selection.
> L[ -1 ];
(3.138)
> L[ -3 .. -2 ];
(3.139)
> L[ -3 .. 1 ];
(3.140)
Lists can be used as an index to pick out specific entries, such as the first and third entries
of a list, or the four corners of a matrix.
> L[ [1,3] ];
(3.141)
> M[[1,2],[1,3]];
(3.142)
Indexing on arrays, matrices and vectors is very flexible. In the case of these data structures,
round-brackets can also be used to index in a way that is useful to programming. For example,
where M[1] will return the first row of the matrix, M(1) will return the first entry (regardless
of the number of dimensions).
> M[1];
(3.143)
> M(1);
(3.144)
This class of data structures are known as rectangular tables, or "rtables" for short. For more
information on what ways they can be indexed, refer to the rtable_indexing help page.
If expr is a name with no assigned value, the result of evaluating the indexed expression is
an indexed name. In this case, the index can be any sequence of expressions, and if desired,
it is up to your program to define the meaning of the expression.
3.6 Indexed Expressions 73
(3.145)
A string may be indexed by a positive integer, a positive integral range, or a general sequence.
The indexed string expression evaluates to itself, unless the indexing sequence is an integer
or integral range, in which case, the result is a substring of the indexed string.
> "abcde"[ 3 ];
(3.146)
> "abcde"[ 2 .. 4 ];
(3.147)
(3.148)
> "abcde"[];
(3.149)
If expr evaluates to a table, and if the index given is found in the table the expression eval-
uates to the corresponding entry. Otherwise, the indexed expression evaluates to itself.
> t := table( [ a = 1, b = 2, (c,d) = 3 ] );
(3.150)
> t[ a ];
(3.151)
> t[ c, d ];
(3.152)
> t[ u, v ];
(3.153)
If expr evaluates to a module, the index must evaluate to the name of an export of the
module, and then the entire indexed expression evaluates to the value of expr:-index.
74 3 Maple Expressions
(3.154)
> evalb( e = m[ e ] );
(3.155)
> m[ f ];
(3.156)
For more information about modules, see Programming with Modules (page 323).
(3.157)
(3.158)
> m:-f;
(3.159)
The first operand, modexpr, must evaluate to a module. The second operand, expname, must
be a literal name; it is not evaluated. If expname is not a name, or is not the name of an export
of the module modexpr, an exception is raised. The syntax m:-e is similar to m[e], in that
they both evaluate module m's export e. The difference is that the index selection form will
evaluate e before resolving the export.
In the second form, the operand name must be a literal name. The expression :-name then
evaluates to the global instance of the name name.
3.8 Functions 75
The following example defines, and then immediately calls, a procedure which declares a
local variable t. Since this local variable is never assigned, it evaluates to itself. The call to
the evalb command then compares, on the left-hand side of the equation, the local name t
to the global name t resulting from applying the member selection operator to t. The result
is false because the global name t and the name t local to the procedure are different expres-
sions.
> proc() local t; evalb( t = :-t ) end proc();
(3.160)
For more information on modules and member selection, see Programming with Mod-
ules (page 323).
3.8 Functions
A function expression is a Maple expression of the form
expr( sequence )
that contains zero or more expressions in the sequence within the parentheses. It represents
a function call.
> F();
(3.161)
> F( x );
(3.162)
> F( x, y );
(3.163)
> sin( x + y );
(3.164)
(3.165)
> op( F( x, y, z ) );
(3.166)
(3.167)
(3.168)
(3.169)
can be formed. Note that Maple applies such "operator expressions" to the arguments. @
is the composition operator. For more information on composition of functions, see Com-
position (page 114).
It is important to know that Maple computes numeric quantities as applicable operators with
constant values. Therefore, for example, the expression
> 2( x );
(3.170)
(3.171)
(3.172)
(in which unevaluation quotes are used to delay the evaluation process) appears to be a
product. However, this expression is, in fact, a function expression. When permitted to
evaluate fully, the result is the constant value of the operator.
3.9 Arithmetic Expressions 77
> 2( 3 );
(3.173)
Calls to Procedures
The most important kind of function expression to understand is the case in which the zeroth
operands is a procedure or, more commonly, an expression (typically, as a name) that
evaluates to a procedure.
p( arg1, arg2, ..., argN )
For example, the name sin evaluates to a procedure that computes the mathematical sin
function. A function expression of the form
sin( expr )
computes the sin of its argument expr. This is performed as follows: Maple evaluates the
name sin and finds that it is assigned a procedure. The argument expr is evaluated to produce
a result. That result is then passed to the procedure assigned to the name sin and the result
computed by that procedure for the specific input is returned as the overall value of the
function call sin( expr ).
For information on defining functions and procedures, see Functional Operators (page 113)
and Procedures (page 209).
(3.174)
> a + b + 3;
(3.175)
> u - v;
(3.176)
> +7;
(3.177)
> -42;
(3.178)
A sum resulting from the evaluation of either an addition or subtraction operation is an ex-
pression of type `+`.
> type( u - v, '`+`' );
(3.179)
The expression u-v has the operands u and -v; that is, it is a sum of the summands u and
-v.
> op( u - v );
(3.180)
(3.181)
(3.182)
Although sums are formed by using the binary operator `+`, they are actually expressions
of arbitrarily large arity (greater than unity). Since addition is associative, Maple "flattens"
all sums of more than two summands during the automatic simplification process. Therefore,
an expression of type `+` can have many operands.
3.9 Arithmetic Expressions 79
> nops( a + b + c + d + e );
(3.183)
(3.184)
Since Maple performs automatic simplification, the number of operands of a sum may not
be apparent from the input.
> nops( a + 2 + b + 3 + c + 4 );
(3.185)
(3.186)
To see that this occurs during the automatic simplification process, enclose the input in
unevaluation quotes to delay evaluation.
> 'a + 2 + b + 3 + c + 4';
(3.187)
In a sum such as
> '2 + 3';
(3.188)
the addition is performed, as indicated, during the automatic simplification process. The
same sum can be computed in another way:
> u := 3:
> '2 + u';
(3.189)
In this example, the arithmetic is not performed because the value of the variable u does
not replace the name u during the automatic simplification process. If the unevaluation
quotes are removed to allow the full evaluation of the expression, numeric addition is per-
formed.
80 3 Maple Expressions
> 2 + u;
(3.190)
Since addition is commutative, the order of summands in an expression of type `+` is arbit-
rary. It is fixed within a Maple session, but may vary from one session to another. Therefore,
you must not rely on the operands of a sum occurring in any specific order.
(3.191)
Since procedures are not called during the automatic simplification process, the example
above does not fully simplify to the result
> 2/3 + sin( 5*Pi/6 - 2*Pi/3 );
(3.192)
during the automatic simplification process. However, the argument to the sin command is
computed to the simpler form , just as it would if it had been entered by itself.
(3.193)
If any numeric literal in a sum is a float, all the numeric operands are converted to floats
and their sum is computed as a float. For more information, see Floating-Point Conta-
gion (page 294).
> 'a + 2 + b + 3.7 + c + Pi';
(3.194)
Arithmetic computed during the automatic simplification process includes arithmetic with
values of infinity, undefined values, and signed (floating-point) zeroes.
> '2.3 + undefined';
(3.195)
3.9 Arithmetic Expressions 81
(3.196)
(3.197)
(3.198)
(3.199)
Sums of non-algebraic summands can be formed. A sum of lists of the same length returns
the corresponding list of sums. This occurs during the automatic simplification process.
> '[ a, b, c ] + [ x, y, z ]';
(3.200)
Sums of arrays, matrices, and vectors occur during the regular evaluation process.
> <1,2;3,4> + <5,6;7,8>;
(3.201)
Since the addition of sets (which are not ordered) is not well-defined, a sum formed with a
set is returned unevaluated.
> { 1, 2 } + { 3, 4 };
{ 1, 2 } + [ 3, 4 ];
(3.202)
82 3 Maple Expressions
(3.203)
You can use the dismantle command to print a representation of the internal structure of
any Maple expression.
> dismantle( a / b );
PROD(5)
NAME(4): a
INTPOS(2): 1
NAME(4): b
INTNEG(2): -1
The output shows that the quotient is actually stored as a product of two factors: one con-
sisting of the expression a with a power of and the other consisting of the expression b
with a power of : .
Similar to sums, products are commutative and associative. Also, products are flattened due
to associativity, even though the `*` operator is binary. Automatic simplification is applied
to products, so as with sums, numeric factors are automatically combined.
> '2 * 3 * x * y';
(3.204)
Also like sums, the order of factors in an expression of type `*` is arbitrary, and may vary
between Maple sessions.
> 'y * x * 3 * 2';
(3.205)
The number of operands reflects the number of factors remaining after automatic simplific-
ation has taken place.
3.9 Arithmetic Expressions 83
> nops( 2 * 3 * x * y );
(3.206)
> op( 2 * 3 * x * y );
(3.207)
(3.208)
If any numeric constant in a product is a float, the result of gathering all of the constants
into a single factor is a float.
> '3.1 * a / 2 / b * 4';
(3.209)
(3.210)
(3.211)
You can multiply a list by a number and the product is applied to all of the list elements
during the automatic simplification process.
> '2 * [ 2, 3 ]';
(3.212)
Matrix multiplication is done with the `.` operator rather than `*`. Division is not defined
for matrices.
> <1,2;3,4> . <5,6;7,8>;
(3.213)
84 3 Maple Expressions
(3.214)
Multiplying or dividing two arrays of the same size will perform paired element-wise oper-
ations on the individual entries. The element-wise operators *~ and /~ can be used on both
arrays and matrices to achieve the same result.
> Array([[1,2],[3,4]]) * Array([[5,6],[7,8]]);
(3.215)
(3.216)
(3.217)
(3.218)
Exponentiation
Powers are formed by using the `^` operator.
3.9 Arithmetic Expressions 85
> a^b;
(3.219)
(3.220)
> a^(b^c);
(3.221)
Rational number powers are used to represent roots. Exact roots are left uncomputed, while
floating-point roots are computed during the automatic simplification process.
> 4^(1/2);
(3.222)
> '(2.1)^(1/3)';
(3.223)
Expressions to a power of 0 are reduced to unity during the automatic simplification process.
The type of the resulting 1 depends on the type of the zero power, unless the base of the
expression is a float, in which case the result is a float.
> 'a ^ 0';
(3.224)
(3.225)
(3.226)
86 3 Maple Expressions
There are some exceptions when infinity and undefined values are raised to a float zero
power.
> 'Float( undefined ) ^ 0.0';
(3.227)
(3.228)
(3.229)
Note the distinction between Float( -infinity ) ^ (-0.0) and -Float( infinity ) ^ (-0.0): the
latter is first automatically simplified to - Float(undefined) and then to Float(undefined).
(3.230)
> 0.0 ^ 0;
(3.231)
> 0 ^ 0.0;
(3.232)
Although a complex floating-point zero does not automatically simplify to a real zero, ex-
pressions raised to a complex zero are simplified automatically to an exact or floating-point
unity.
> a^(0.0 + 0.0*I);
(3.233)
Powering of matrices is done in the mathematical sense achieving repeated matrix products.
Powering of arrays is done element-wise.
> <1,2;3,4> ^3;
(3.234)
3.9 Arithmetic Expressions 87
(3.235)
Rational Expressions
Using sums and products, more complicated expressions can be formed.
> expr := ( a + a*b ) / ( a*b - b );
(3.236)
By using the dismantle command, you can see the tree structure of the expression that is
formed.
> dismantle( expr );
PROD(5)
SUM(5)
NAME(4): a
INTPOS(2): 1
PROD(5)
NAME(4): a
INTPOS(2): 1
NAME(4): b
INTPOS(2): 1
INTPOS(2): 1
INTPOS(2): 1
SUM(5)
PROD(5)
NAME(4): a
INTPOS(2): 1
NAME(4): b
INTPOS(2): 1
INTPOS(2): 1
NAME(4): b
INTNEG(2): -1
INTNEG(2): -1
88 3 Maple Expressions
Or, as a graph:
(3.237)
(3.238)
(3.239)
(3.240)
3.9 Arithmetic Expressions 89
Maple does not automatically simplify the following expression. To perform such simplific-
ations, use the normal command.
> expr := (x - 1)/(x^2 - 1);
(3.241)
(3.242)
The normal command only performs normalization of rational expressions with rational
coefficients.
> expr := ( (sin(t)^2 + cos(t)^2)*(x - 1)/(x^2 - 1));
(3.243)
(3.244)
(3.245)
Use the expand command (or the normal command, with the expanded option) to perform
such expansions.
> expr := (a + b) * (c + d);
(3.246)
(3.247)
(3.248)
90 3 Maple Expressions
Similarly, you must use the normal command to simplify the following rational expression.
> expr2 := expand( expr ) / (a + b);
(3.249)
(3.250)
Noncommutative Multiplication
Noncommutative multiplication is represented by the dot operator (.), which is used mainly
in linear algebra computations for multiplication of matrices and vectors. It may also be
used to represent the noncommutative product of other types of mathematical expressions.
A . B;
If A and B are of type constant, then A . B = A * B during the evaluation process (but not
during the automatic simplification process). However, if one of A and B is a Matrix or a
Vector, and the other is a Matrix, Vector, or constant, the product is interpreted as a matrix
or vector product. If A or B is an Array (and the other is not a Matrix or Vector), then A .
B is interpreted as element-wise multiplication. For arguments that are not of type Matrix,
Vector, or constant, A . B remains unevaluated, but more importantly, it is not automatically
simplified to or interpreted as being equal to B . A.
> 7 . 6;
(3.251)
(3.252)
(3.253)
> M:=<<1,0,2>|<0,1,2>|<0,0,2>>;
(3.254)
3.9 Arithmetic Expressions 91
> V:=<10,0,0>;
(3.255)
> M . V;
(3.256)
> lambda . M . V;
(3.257)
> A := Array([[1,2],[3,4]]);
(3.258)
> B := Array([[a,b,c],[d,e,f]]);
(3.259)
> A . B;
(3.260)
> 3 . B;
(3.261)
(3.262)
> 2. 3;
Error, unexpected number
Factorials
The unary, postfix factorial operator ! is used to represent the mathematical factorial opera-
tion.
> 5!;
(3.263)
(3.264)
(3.265)
The argument of the ! operator is subject to automatic simplification, but factorials are not
computed during the automatic simplification process.
> '(2+3)!';
(3.266)
If the argument of the ! operator is a float, the expression is computed using the GAMMA
function.
> 2.3! = GAMMA( 3.3 );
(3.267)
3.9 Arithmetic Expressions 93
If the argument is a non-negative integer, Maple computes the factorial. If the argument is
a negative integer, a numeric event is triggered.
> (-3)!;
Error, numeric exception: division by zero
However, if the argument is a negative integer float, the complex number Float(-infinity)
- Float(infinity)*I is returned.
> (-3.0)!;
(3.268)
For other arguments, the factorial operator is returned unevaluated after first evaluating its
argument.
> sin( Pi / 6 )!;
(3.269)
(3.270)
where i is a name, m and n are numeric values, and expression is an expression that depends
on i.
The add command is more efficient since it does not build each of the many intermediate
sums. The semantics of mul are similar with the exception that if , the result is 1,
rather than 0.
> mul(a+i, i=1..4);
(3.271)
(3.272)
For more information on the add and mul commands, refer to the add help page. For more
information on the concatenation operator, ||, see The Concatenation Operator (page 119).
Note: The add and mul commands differ from sum and product in that the former are
straightforward construction commands while the latter are commands for computing closed
forms for symbolic sums and products.
When you call the Boolean evaluator evalb, the expression passed as its argument is inter-
preted as a Boolean-valued expression if possible, and evaluated as such.
Boolean Operators
Maple supports several operators for the Boolean combination of expressions: not, and,
or, xor, and implies.
For example,
> not true;
(3.273)
(3.274)
(3.275)
If both operands evaluate to a truth value, the entire expression is evaluated according to
the following truth table.
expr1 expr2 expr1 and ex-
pr2
true true true
true false false
true FAIL FAIL
false true false
false false false
false FAIL false
FAIL true FAIL
FAIL false false
FAIL FAIL FAIL
(3.276)
96 3 Maple Expressions
(3.277)
The or Operator
The or operator represents logical disjunction. It is a binary operator of the form
expr1 or expr2
If both operands evaluate to a truth value, the entire expression is evaluated according to
the following truth table.
expr1 expr2 expr1 or ex-
pr2
true true true
true false true
true FAIL true
false true true
false false false
false FAIL FAIL
FAIL true true
FAIL false FAIL
FAIL FAIL FAIL
(3.278)
(3.279)
If both of its operands evaluate to truth values, the entire expression is evaluated according
to the following truth table.
3.10 Boolean and Relational Expressions 97
If both of its operands evaluate to truth values, the entire expression is evaluated according
to the following truth table.
expr1 expr2 expr1 implies
expr2
true true true
true false false
true FAIL FAIL
false true true
false false true
false FAIL true
FAIL true true
FAIL false FAIL
FAIL FAIL FAIL
(3.280)
(3.281)
98 3 Maple Expressions
(3.282)
Relational Operators
Relational operators are used to form comparisons to be evaluated in a Boolean context.
The relational operators in Maple are =, <>, <, <=, and in. Each is a binary operator that
accepts two operands. When evaluated in a Boolean context, each of these operators determ-
ines whether its two operands have a certain relationship.
(3.283)
It represents an equation with expr1 as the left-hand side and expr2 as the right-hand side.
When evaluated in a Boolean context, it returns a value of true if its operands are equal,
and returns a value of false otherwise.
> evalb( 1 = 2 );
(3.284)
> evalb( 2 = 2 );
(3.285)
(3.286)
The names x and y are distinct and unequal names in Maple and, when they are unassigned,
they are considered different expressions in a Boolean comparison. If the names x and y
have assigned values, those values are first substituted into the comparison, and the equality
computation is performed on the assigned values, rather than the names themselves.
In general, expressions are compared for equality according to their memory address. That
is, two expressions are considered equal in a Boolean context if they have the same address
in memory. However, for certain expressions, a more mathematical test for equality is used.
3.10 Boolean and Relational Expressions 99
For example, the floating-point numbers 2.0000 and 2.0 are considered numerically equal,
even though they are distinct objects in memory.
> evalb( 2.0000 = 2.0 );
(3.287)
(3.288)
(3.289)
In fact, when the floating-point number 2.0 is compared to the integer 2, they are considered
equal.
> evalb( 2.0 = 2 );
(3.290)
(3.291)
An inequation can be formed by using the <> operator. The general form is
expr1 <> expr2
This expression represents non-equality and returns a value of true if its operands are un-
equal, and false if its operands are equal.
> x <> y;
(3.292)
(3.293)
100 3 Maple Expressions
(3.294)
Testing for inequality is performed similarly to testing for equality. Comparing two distinct
unassigned names using the <> operator computes the equality of the names. The expression
> evalb( x <> y );
(3.295)
returns a value of true because the names x and y are distinct as names.
A strict inequality is created by using the < operator. This has the general form
expr1 < expr2
For example,
> x < y;
(3.296)
(3.297)
Maple automatically converts this to the same expression as results from the first form.
When evaluated in a Boolean context, Maple performs the indicated mathematical compar-
ison, or returns the inequality as unevaluated if the operands do not evaluate to comparable
expressions. If the operands are comparable, the inequality evaluates to the value true if
the first operand is less than, but not equal to, the second operand, and evaluates to false
otherwise. If the operands are not comparable, the inequality evaluates to itself.
A non-strict inequality is formed using the <= operator. This has the general form
expr1 <= expr2
For example,
> x <= y;
(3.298)
When evaluated in a Boolean context, and when the operands are comparable, it returns a
value of either true or false according to whether the first operand is less than, or equal to,
the second operand.
When evaluated in a Boolean context, it evaluates to the value true if its first operand expr1
is a member of its second operand expr2. If expr1 does not belong to expr2, the expression
evaluates to false. Maple can determine a truth value if the second operand expr2 is a con-
tainer object; that is, either a set or list, or an unevaluated function call of the form SetOf(
T ), where T is a Maple type. An expression of the form
expr in SetOf( T )
(3.299)
> evalb( 5 in { 1, 2, 3 } );
(3.300)
> evalb( x in X );
(3.301)
(3.302)
(3.303)
Note the simplification applied to the statement with the evalb command in the following
example.
> x in A union B;
(3.304)
102 3 Maple Expressions
(3.305)
If the second operand is not an explicit container object, the expression remains an unevalu-
ated in expression. However, some automatic simplifications may be applied.
The following example considers type(element,name) for each element of the list. ormap
determines whether this statement is true for at least one element of the list. andmap de-
termines whether this statement is true for all the elements of the list.
> ormap(type, [1, "a", `a`, a()], name);
(3.306)
(3.307)
The main difference between these commands and map is that andmap and ormap have
short-circuit ("McCarthy") semantics, which means that an answer is returned as soon as it
can be determined.
> andmap(proc(x) print(x); x<2 end proc, [1,2,3,4]);
(3.308)
3.11 Expressions for Data Structures 103
Sequences
The most basic aggregate expression type in Maple is the sequence. Sequences are formed
by using the `,` (comma) operator.
> a, 2/3, sin( x ), 5.1;
(3.309)
A sequence consists of zero or more other expressions, called elements or members. A se-
quence with exactly one member is automatically simplified to its unique member. The
empty sequence, containing zero members, is the value of the name NULL, and may be
written as ().
> evalb( () = NULL );
(3.310)
Sequences occur in many other data structures as a (principal) component, within which
they acquire additional semantics. Some examples include lists, sets, and function calls.
(3.311)
Nested sequences are also flattened during the automatic simplification process.
> '( 1, 2 ), 3, ( 4, 5 )';
(3.312)
Because sequences are used to pass multiple arguments to procedures, it is not normally
possible to operate on a sequence as such (the list type described below is designed for ex-
actly for that reason). For example, you cannot pass a (nontrivial) sequence to the type
command to check its type. Therefore, there is no Maple type for sequences. However, the
whattype command returns the name exprseq when it is passed either zero or more than
one argument.
104 3 Maple Expressions
> whattype();
(3.313)
> whattype( 1, 2 );
(3.314)
Note that the name exprseq is not the name of any valid type in Maple.
Similarly, you cannot query the zeroth operand of a sequence. For example, the following
results in an error.
> op( 0, ( 1, 2, 3 ) );
Error, invalid input: op expects 1 or 2 arguments, but received 4
There is no constructor for sequences, but there is a built-in command for creating sequences,
called seq. The basic syntax of seq is below. It accepts many other types of arguments as
well.
seq(expression, i = integer1..integer2)
(3.315)
> seq( 2 .. 14 );
(3.316)
(3.317)
For more information on the seq command, refer to the seq help page.
Another way to create sequences is to use the dollar sign ($) operator.
expression $ i = integer1 .. integer2
> i^2 $ i = 1 .. 5;
(3.318)
3.11 Expressions for Data Structures 105
The dollar sign operator is a binary operator that performs a similar function to the seq
command, but behaves slightly differently: the $ operator evaluates the expression argument
once before any substitutions, while the command does not evaluate until after each substi-
tution of i.
> cat(a,x) $ x= 1..2;
(3.319)
(3.320)
In general, it is recommended that you use the seq command instead of the dollar sign op-
erator.
Lists
Lists are created by enclosing a sequence of expressions between square brackets. Lists are
essentially sequences, which are designated as a single unit for other operations.
[ sequence ]
> [ 1, 2, 3 ];
(3.321)
(3.322)
Use the numelems command to determine the number of members in the enclosed sequence.
Note that lists can contain sublists. These are still counted as a single entry.
> numelems( [ 1, 2, 3 ] );
(3.323)
> numelems( [ 1, 2, [ 3, 4 ] ] );
(3.324)
To access the -th operand of a list, use an index to the list expression.
> L := [ a, b, c, d ];
(3.325)
106 3 Maple Expressions
> L[ 3 ];
(3.326)
To access the sequence of all elements in a list, use the op command. Converting back and
forth between lists and sequences can be a common operation, and is very efficient.
> Lseq := op(L);
(3.327)
(3.328)
It is common to create a list by using the seq command to create the enclosed sequence.
> [ seq( i^2, i = 1 .. 5 ) ];
(3.329)
Lists are ordered; two lists with the same members in a different order are distinct.
> evalb( [ 1, 2, 3 ] = [ 2, 1, 3 ] );
(3.330)
Lists are immutable; you cannot change the elements of a list once it has been created. You
can, however, create a new list using members of an existing list or lists.
(3.331)
> L2 := [ L[ 1 ], d, L[ 3 ] ];
(3.332)
You can also use the subsop command for this purpose.
> L3 := subsop( 2 = d, L );
(3.333)
> evalb( L2 = L3 );
(3.334)
3.11 Expressions for Data Structures 107
The example above creates a new list using the original list L by substituting its second
operand for the expression d. If you need to change elements frequently it is usually better
to use an array. Arrays can be changed in-place avoiding the need for a copy. For more in-
formation on the subsop command, refer to the subsop help page.
Sets
Sets, similar to lists, are created from a sequence of expressions. However, sets use braces
({}) to enclose the sequence.
{ sequence }
(3.335)
In addition to the syntactical differences, sets differ from lists in that they are implicitly
sorted and do not have duplicate entries. These two properties are enforced during the
automatic simplification process.
> '{3, -1, 0}';
(3.336)
(3.337)
In Maple 11 and earlier, the ordering of sets was unpredictable as it was based on the posi-
tions of the elements in memory. In Maple 12 and later, set ordering is deterministic, session
independent, and based on properties of the contents. This just means that the same set will
now appear in the same order even after restarting Maple. For more information on the or-
dering of sets, refer to the set help page.
For more information on how to use sets in programming, see Sets (page 142). More inform-
ation on Maple expressions related to sets will be described later in this chapter.
Tables
Tables are mutable data structures that associate an index with an entry. Both the index and
entry can be arbitrary expressions. The underlying structure is sparse (a hash table), and
expands as more entries are inserted.
108 3 Maple Expressions
> T := table();
(3.338)
(3.339)
> T[color];
(3.340)
(3.341)
Assigning values to indexed names is further described in Indexed Expressions (page 69).
Tables can be initially populated by providing a list of equations as an argument to the table
constructor.
> T := table([a=1, b=2, c=3, d=4]);
(3.342)
(3.343)
For names with tables assigned to them, last name evaluation rules apply. Last name evalu-
ation is explained in more detail in Evaluation Rules for Tables (page 151). The most visible
effect of last name evaluation is that the name of the table is displayed by default rather
than all of its entries.
> T;
(3.344)
> eval(T);
(3.345)
Rectangular Tables
Rectangular tables, or rtables, are mutable data structures that associate a numeric index
sequence with an arbitrary entry. The bounds of the index are predefined and directly cor-
respond to the amount of memory reserved to hold entries.
3.12 Set-Theoretic Expressions 109
The same rtable data structure is used to implement arrays, matrices, and vectors.
> A := Array(0..5,i->2*i);
(3.346)
> A[0];
(3.347)
> A[5];
(3.348)
> V := Vector([1,2,3]);
(3.349)
> V[1];
(3.350)
> M := Matrix(3,3,shape=identity);
(3.351)
> M[2,2];
(3.352)
Rectangular tables are very flexible and offer a rich set of features. For a more in-depth
discussion of them, see Arrays (page 155).
Membership
In Maple, the set membership relation is expressed by using the in operator. It has the fol-
lowing syntax.
a in b
(3.353)
> 1 in { 1, 2, 3 };
(3.354)
However, when evaluated in a Boolean context, one of the values true and false is returned
if the expression b evaluates to a set or list and Maple can determine whether the expression
a belongs to the expression b. For more information on Boolean evaluation of membership
expressions, see Boolean and Relational Expressions (page 94).
Use the rhs and lhs commands to extract the right or left hand side of the an in operator.
> lhs( a in b );
(3.355)
> rhs( a in b );
(3.356)
Set Inclusion
Set inclusion (the subset relation) is represented in Maple by the binary subset operator. It
has the following syntax.
a subset b
(3.357)
(3.358)
3.12 Set-Theoretic Expressions 111
> {} subset T;
(3.359)
(3.360)
If Maple can determine whether the expressed relation is true or false, the expression eval-
uates to true or false. Otherwise, the expression is returned unevaluated.
(3.361)
(3.362)
The individual operands can be accessed by using the lhs and rhs commands.
> lhs( a subset b );
(3.363)
(3.364)
The union of two sets is created by using the union operator, which has the following syntax.
a union b
(3.365)
> { 1, 2 } union { 2, 3, 4 };
(3.366)
112 3 Maple Expressions
> { 1, 2 } union T;
(3.367)
The following expression displays an error message, since the second operand cannot
evaluate to a set.
> { a, b, c } union "a string";
Error, invalid input: `union` received a string, which is not valid
for its 2nd argument
A union expression may be returned unevaluated, and the operands of an unevaluated union
expression a union b are the expressions a and b.
> nops( a union b );
(3.368)
(3.369)
(3.370)
> b union a;
(3.371)
The union operation is also associative. A union of three or more operands returns an une-
valuated function call.
> a union b union c;
(3.372)
(3.373)
> {} union a;
(3.374)
3.13 Other Expressions 113
Intersections of sets are represented using the intersect operator, which has the general
syntax.
a intersect b
(3.375)
> { 1, 2, 3 } intersect { 3, 4, 5 };
(3.376)
> {} intersect T;
(3.377)
Note that although union and intersection are mutually distributive, neither distributes
automatically over the other in a symbolic expression. However, the expand command can
distribute intersections over unions.
> expand( a intersect (b union c) );
(3.378)
(3.379)
The following two procedures are identical except in how they are displayed:
> x -> x^2;
(3.380)
114 3 Maple Expressions
(3.381)
(3.382)
(3.383)
For more information on the arrow operator, refer to the operators,functional help page.
For more information on procedures, see Procedures (page 209).
Composition
Use the operators @ and @@ to represent the composition (of functions). The operator @
denotes the composition of functions and takes the general form
f @ g
(3.384)
Note that @ has lower precedence than function application, so that the parentheses sur-
rounding f@g above are necessary:
> f@g(x);
(3.385)
The @ operator performs numerous simplifications and normalizations, and is (left) associ-
ative.
> (exp @ ln)( s );
(3.386)
> a @ b @ c @ d;
(3.387)
3.13 Other Expressions 115
Repeated composition is represented by the operator @@. It has the general form
f @@ n
(3.388)
Note that the iterated composition is not automatically expanded in the example above. It
is necessary to apply the expand command.
(3.389)
> (f^2)( x );
(3.390)
The first example above denotes the -fold composition of with itself, while the second
denotes the arithmetic square of . In particular, although the inverses of the circular
functions are commonly denoted by a power-like notation in written mathematics, in Maple,
for example, sin^(-1) denotes the reciprocal of the function, while sin@@(-1) denotes
the arcsine (arcsin).
> sin@@(-1);
(3.391)
> (sin@arcsin)( x );
(3.392)
> sin^(-1);
(3.393)
116 3 Maple Expressions
> (sin^(-1))( x );
(3.394)
Neutral Operators
Neutral operators are constructions that are treated as operators by Maple, but that have no
predefined meaning so that they can be customized.
A neutral operator symbol is formed by the ampersand character (&) followed either by a
valid Maple name not containing ?, or by a sequence of one or more special characters. For
more information, refer to the neutral help page.
a &name b
(3.395)
A commonly used neutral operator is &* which is often used for representing a non-com-
mutative multiplication. Unlike dot (.), it does not automatically combine scalar constants.
> 1 &* 2;
(3.396)
> 1 . 2;
(3.397)
Ranges
The .. operator is used to construct ranges, and usually has the following syntax.
a .. b
(3.398)
A consecutive sequence of two or more dots (.) is parsed as a range operator. For example,
> 2 ......... 7;
(3.399)
If the left-hand endpoint of a range is a float ending with a decimal point, or if the right-
hand endpoint is a float beginning with a decimal point, it is therefore necessary to separate
the endpoint from the range operator with one or more space characters.
> 2....3;
(3.400)
> 2. .. .3;
(3.401)
(3.402)
(3.403)
Use the lhs and rhs commands to extract the individual operands of a range expression.
> lhs( a .. b );
(3.404)
> rhs( a .. b );
(3.405)
(3.406)
118 3 Maple Expressions
(3.407)
Ranges can be used to index complex data structures as well as strings and sequences.
> [ 1, 2, 3, 4, 5 ][ 2 .. 3 ];
(3.408)
> { 1, 2, 3, 4, 5 }[ 2 .. 3 ];
(3.409)
> "abcde"[ 2 .. 3 ];
(3.410)
> ( 1, 2, 3, 4, 5 )[ 2 .. 3 ];
(3.411)
There is a special form of input syntax for ranges in which one or both endpoints is missing.
> ..;
(3.412)
In the example above, each endpoint is the empty sequence () (or NULL). It is valid to omit
just one of the endpoints.
> a ..;
(3.413)
> .. b;
(3.414)
When used in this way to index a data structure, a missing endpoint denotes the end of the
valid range of indices.
> [ 1, 2, 3, 4, 5 ][ 3 .. ];
(3.415)
> [ 1, 2, 3, 4, 5 ][ .. 4 ];
(3.416)
3.13 Other Expressions 119
> [ 1, 2, 3, 4, 5 ][ .. ];
(3.417)
Note the distinction between the third example above and the following example
> [ 1, 2, 3, 4, 5 ][];
(3.418)
in which the first operand a can be either a name or a string, and the second operand b can
be a name, a string, an integer, an integral range, a character range, or an expression sequence
of names, strings, and integers. If the second operand b is another kind of expression, an
unevaluated || expression is returned.
> "foo" || "bar";
(3.419)
(3.420)
(3.421)
(3.422)
> x || 1;
(3.423)
> x || (1..3);
(3.424)
120 3 Maple Expressions
(3.425)
(3.426)
(3.427)
> f( y ) || t;
(3.428)
The type of the result, if not an unevaluated || expression, is determined by the type of the
first operand. If the first operand a is a string, the type of the result (or results, in the case
of a sequence) is a string. If the first operand is a name, the type of the result, or results, is
a name.
The first operand of the || operator is not evaluated, but the second operand is.
> u := 2: v := 3:
> u || v;
(3.429)
The symbol `||`, which must be enclosed in left single quotes when not used as an infix op-
erator, is a type name.
> type( f( y ) || t, '`||`' );
(3.430)
(3.431)
> op( f( s ) || t );
(3.432)
For most applications, the cat command is more appropriate, as it evaluates all of its argu-
ments. For more information, refer to the cat help page.
3.13 Other Expressions 121
(3.433)
(3.434)
In addition to its use as a general Boolean expression, it is used to introduce type annotations
on parameters and type assertions for local variables and procedure return types. For more
information, see Procedures (page 209).
Outside of a Boolean context, the :: operator is essentially inert, and returns an expression
of type :: with two operands.
> type( a :: b, '`::`' );
(3.435)
> nops( a :: b );
(3.436)
> op( a :: b );
(3.437)
You can use the lhs and rhs commands to access the operands of a :: expression.
> lhs( a :: b );
(3.438)
> rhs( a :: b );
(3.439)
122 3 Maple Expressions
Series
Maple supports generalized power series expansions using a series data structure. This is
a basic symbolic data structure that is used in many fundamental algorithms, such as the
computation of symbolic limits and symbolic integration. There is no syntax for the input
of series; a series structure is created by calling the series constructor, which has the general
forms
series( expr, eqn, ord )
series( expr, name, ord )
where name is a name and pt is the point of series expansion. The optional argument ord
specifies the order of truncation of the series. This is, by default, equal to the value of the
environment variable Order, whose default value is . If the second form using name is
used, the expansion point is taken to be .
> series( exp( x ), x );
(3.440)
(3.441)
(3.442)
(3.443)
In general, a truncated power series expansion to order ord of expr, about the point pt is
computed. If the expansion point pt is infinity, then an asymptotic expansion is computed.
3.13 Other Expressions 123
In general, the series expansion is not exact, so there will be an order term of the form
O( pt^ord )
present as the last term in the series. This is not always present, however. For example, a
series expansion of a low-degree polynomial is exact.
> series( x^2 + x + 1, x );
(3.444)
(3.445)
(3.446)
A series data structure prints very much like a polynomial, but it is a distinct data structure.
In certain cases, a polynomial (sum of product) data structure is returned. This happens
when the generalized series expansion requires fractional exponents.
> s := series( sqrt( sin( x ) ), x );
(3.447)
(3.448)
(3.449)
124 3 Maple Expressions
(3.450)
Note that the series data structure is sparse in the sense that terms with coefficient are
not part of the data structure.
A series structure can be converted to a polynomial by using the convert command with
the name polynom as the second argument.
> s := series( exp( x ), x );
(3.451)
(3.452)
(3.453)
(3.454)
3.14 Attributes
In addition to their operands, certain types of expressions can have other information asso-
ciated with them in the form of attributes. As described earlier in this chapter, protected
names are a type of attribute. If an expression has attributes, they can be examined by using
the attributes command.
> attributes(sin);
(3.455)
Attributes can be assigned to expressions of the following types: name, string, list, set, Array,
Matrix, Vector, equation, procedure, unevaluated function call, or float using the setattribute
command.
setattribute(expression, attributes)
3.14 Attributes 125
The setattribute command returns a copy of the expression with the attributes assigned. If
the expression is a symbol or string, it is modified in-place. For other data types, the original
expression is left unchanged.
> x := 1.0;
(3.456)
(3.457)
> attributes('x');
(3.458)
(3.459)
> attributes("Johanessphere");
(3.460)
> y := setattribute('f(z)',"common");
(3.461)
> attributes(y);
(3.462)
> attributes('f(z)');
You can check whether an expression has attributes by using the attributed type. For more
information, refer to the type,attributed help page.
> type(`just a name`, 'attributed');
(3.463)
(3.464)
126 3 Maple Expressions
Example 1
To understand how Maple evaluates and simplifies expressions, consider the following ex-
ample.
> x := Pi/6:
> sin(x) + 2*cos(x)^2*sin(x) + 3;
(3.465)
Maple first reads and parses the input. As the input is parsed, Maple builds an expression
tree to represent the value.
Maple simplifies the expression tree and then evaluates the result. The evaluation process
substitutes values for variables and invokes any commands or procedures. In this case, x
evaluates to . Therefore, with these substitutions, the expression is
When the sin and cos commands are called, Maple obtains a new "expression tree,"
Example 2
Alternatively, consider the next example: evaluation occurs, but no simplification is possible.
> x := 1;
(3.466)
3.15 Using Expressions 127
(3.467)
Substituting Subexpressions
The simplest method of substitution in Maple is to use the subsop command. This is an
operation on the expression tree. It creates a new expression by replacing an operand in the
original expression with the given value.
subsop( n=value, expr);
subsop( list=value, expr);
(3.468)
(3.469)
Note that most operations in Maple do not alter expressions in-place but, in fact, create new
expressions from old ones. For a list of exceptions, see Mutable Data Structures (page 147).
Also, note that the subsop command acts on the expression tree by changing an arrow in
the DAG, and not by changing the value of a node which would change all identical instances.
That is, in the following example only one instance of a is changed, not all. See Figure 3.2.
> expr := (a+a*b)/(a*b-b);
(3.470)
(3.471)
128 3 Maple Expressions
The subsop command is powerful, but generally useful only in very specific programming
applications. The most generally useful command for substitution is the two-argument
version of the eval command.
The eval command has the following syntax, where s is an equation, list, or set of equations.
eval( expr, s );
(3.472)
(3.473)
(3.474)
(3.475)
The eval command performs substitution on the expression considered as a DAG rather
than a tree, so it can be quite efficient for large expressions with many repeated subexpres-
sions.
An alternative to the eval command is the subs command, which performs syntactic substi-
tution. It computes the expression as a tree and replaces subexpressions in an expression
with a new value. The subexpressions must be operands, as identified by the op command.
Using the subs command is equivalent to performing a subsop operation for each occurrence
of the subexpressions to be replaced.
The subs command has the following syntax, where s is an equation, list, or set of equations.
3.15 Using Expressions 129
subs( s, expr );
> f := x*y^2;
(3.476)
(3.477)
The other difference between the eval and subs commands is demonstrated in the following
example.
> subs( x=0, cos(x) + x^2 );
(3.478)
(3.479)
In the preceding subs command, Maple substitutes 0 (zero) for x and simplifies the result.
Maple simplifies cos(0) + 0^2 to cos(0). By using the eval command, Maple evaluates
cos(0) to 1 (one).
During the substitution process, operands are compared in the expression tree of expr with
the left-hand side of an equation.
> eval( a*b*c, a*b=d );
(3.480)
The substitution does not result in d*c because the operands of the product a*b*c are a, b,
c. That is, the products a*b, b*c, and a*c do not appear specifically as operands in the ex-
pression a*b*c. The easiest way to make such substitutions is to solve the equation for one
unknown and substitute for that unknown.
> eval( a*b*c, a=d/b );
(3.481)
You cannot always use this method; for certain expressions, it may not produce the expected
results. The algsubs command provides a more powerful substitution facility.
> algsubs( a*b=d, a*b*c );
(3.482)
130 3 Maple Expressions
Two more useful substitution commands are subsindets and evalindets. These commands
perform substitution on all subexpressions of a given type; the former uses the subs func-
tionality and the latter uses the eval functionality.
subsindets( expr, atype, transformer, rest )
evalindets( expr, atype, transformer, rest )
(3.483)
(3.484)
Structured Types
A simple type check may not always provide sufficient information. For example, the
command
> type( x^2, `^` );
(3.485)
verifies that x^2 is an exponentiation, but does not indicate whether the exponent is, for
example, an integer. To do so, you must use structured types. Consider the following ex-
ample.
> type( x^2, 'name^integer' );
(3.486)
To learn more about structured types, study the following examples. The square root of x
does not have the structured type name^integer.
> type( x^(1/2), 'name^integer' );
(3.487)
The expression (x+1)^2 does not have type name^integer, because x+1 is not a name.
> type( (x+1)^2, 'name^integer' );
(3.488)
(3.489)
An expression matches a set of types if the expression matches one of the types in the set.
> type( 1, '{integer, name}' );
(3.490)
(3.491)
(3.492)
(3.493)
(3.494)
(3.495)
(3.496)
Because is the typeset version of exp(2), it does not match the type anything^2.
> type( exp(2), 'exp'(integer) );
(3.497)
The next example illustrates why you should use unevaluation quotes (') to delay evaluation
when including Maple commands in type expressions.
132 3 Maple Expressions
(3.498)
This is not a valid type. If you enclose the int command in unevaluation quotes, the type
checking works as intended.
> type( int(f(x), x), 'int'(anything, anything) );
(3.499)
The type specfunc(type, f) matches the function f with zero or more arguments of type
type.
> type( exp(x), 'specfunc(name, exp)' );
(3.500)
(3.501)
The type function(type) matches any function with zero or more arguments of type type.
> type( f(1,2,3), 'function(integer)' );
(3.502)
(3.503)
In addition to testing the type of arguments, you can test the number of arguments. The type
anyfunc(t1, ..., tn) matches any function with n arguments of the listed types in the correct
order.
> type( f(1,x), 'anyfunc(integer, name)' );
(3.504)
(3.505)
3.15 Using Expressions 133
(3.506)
Another useful variation is to use the And, Or, and Not type constructors to create Boolean
combinations of types. Note that these are different from the and, or, and not logical oper-
ators.
> type(Pi, 'And( constant, numeric)');
(3.507)
(3.508)
For more information on structured types, refer to the type,structured help page. For more
information on how to define your own types, refer to the type,defn help page.
The indets command is useful for extracting a set of all the subexpressions of a given type.
indets( expr, atype)
(3.509)
(3.510)
(3.511)
(3.512)
Note that the indets command analyzes the entire expression so that the base of the exponent
is recognized as an integer. If you want to select only subexpressions from the top
level, use the command select described in The select, remove, and selectremove Com-
mands (page 196).
134 3 Maple Expressions
If you want to test whether that an expression has a subexpression of a given type, use the
hastype command rather than the indets command since it avoids building a potentially
large set of expressions.
> hastype([1,2,3.,5.,6.,7.], 'float');
(3.513)
3.16 Exercises
1. Find the numerator and denominator of the irreducible form of 4057114691 divided by
4404825097799.
2. Construct floating-point numbers using the floating-point number constructor. Construct
the number 917.3366 using a positive exponent, and then using a negative exponent.
Construct a floating-point approximation of 1/3.
3. Without using the Digits environmental variable, find the difference between estimated
to 20 digits and 10 digits.
4. Calculate the negative complex root of -1369, and then sum 3 and the root. Find the in-
verse of this complex sum. Find the inverse of (a*b)/c+((a-d)/(b*e))*I) in standard form,
where a, b, c, d, and e are real.
5. The Fibonacci numbers are a sequence of numbers. The first two numbers in the sequence
are zero (0) and one (1). For n greater than two, the th number in the sequence is the
sum of the two preceding numbers. Assign values to indexed names representing the
first, second, and general Fibonacci numbers.
6. Using the time command, determine the time required to multiply two ten-by-ten matrices.
7. Use Maple to verify de Morgan's laws.
8. Contrast the behavior of functions and expressions by performing the following com-
mands.
b. Evaluate f and g at 2.
c. Evaluate f and g at y.
d. Assign the value 2 to x. Evaluate f and g.
9. Swap the values of two variables using one statement.
10. Sum the smallest 100 prime integers.
4.2 Introduction
Maple provides various data structures that you can use for programming and interacting
with Maple functions. This chapter focuses on the use of data structures in programming.
However, the sections Lists (page 136) and Sets (page 142) may be useful for users who want
to construct arguments for Maple functions.
Maple has many data structures that provide similar functionality, but certain data structures
are better suited for certain types of operations. Therefore, when choosing which data
structures to use, it is important to select a structure that performs well on the operations
used in your code.
Many aspects affect the performance of data structures. However, in Maple, the provided
data structures can be divided into two basic classes: mutable and immutable. The mutable
data structures can be modified, that is, the values they store can change. The immutable
data structures cannot be changed after they are created. Instead, copies of these structures
can be made with different contents. This difference in behavior can have significant impact
on the performance of code that uses these structures.
In Maple, there are two basic immutable data structures: lists and sets.
135
136 4 Basic Data Structures
Lists
A list stores an ordered sequence of expressions. The ordering of the elements in a list is
fixed when the list is created. Lists, in contrast to sets, will maintain duplicate elements.
Creating Lists
The easiest way to create a list is to enclose a sequence of expressions in square brackets
([]). A sequence of expressions is a series of comma-separated expressions.
[ sequence ]
This creates a list that contains the elements of sequence in the specified order. In the case
where sequence is empty, [] represents an empty list. Compare the results of these examples
to those in the Sets (page 142) section.
> [x, y, y];
(4.1)
(4.2)
(4.3)
(4.4)
In Maple, nested lists whose inner lists have the same number of elements have a special
name, listlist.
> M := [[a,b], [1,2], [3, 4]];
(4.5)
(4.6)
(4.7)
4.3 Immutable Data Structures 137
(4.8)
Many Maple functions return sequences. Thus, enclosing a call to one of those functions
in square brackets [] creates a list. For example, the seq command generates sequences.
> [ seq( x^j, j=1..3) ];
(4.9)
(4.10)
> op(L);
(4.11)
Thus op can be used to create new lists based on existing lists. For example, you can create
a new list with an additional element added to the start of the list.
> L2 := [ 0, op(L) ];
(4.12)
A list with another element added to the end of the list can be created in a similar way.
> L3 := [ op(L2), 4 ];
(4.13)
(4.14)
(4.15)
138 4 Basic Data Structures
> L[1];
(4.16)
> L[2];
(4.17)
> L[3];
(4.18)
You can also specify a range in the selection operation to extract a sublist containing the
elements that are indexed by that range.
> L := [ seq( i^2, i=1..10 ) ];
(4.19)
> L[3..6];
(4.20)
> L[5..8];
(4.21)
While it is possible to make an assignment to a list index, this operation can be inefficient
since it creates a new list. In fact, assignment to a large list is not permitted in Maple and
will produce an error. Assigning a list element is a common error, so if you find yourself
wanting to do this, consider using a mutable data structure instead. For more information,
see Mutable Data Structures (page 147).
> L := [1,2,3]:
> L[1] := 3;
(4.22)
> L;
(4.23)
L is now a new list with a different element at index 1. Thus, assigning to a single element
of a list causes the entire list to be copied in the same way as using the subsop command.
In fact, the previous example is equivalent to the following except in how the result is dis-
played.
4.3 Immutable Data Structures 139
> L := [1,2,3]:
> L := subsop(1=3, L);
(4.24)
If you attempt to assign to an index to a large list, an error will occur. Therefore, if you need
to make a copy of a list with one changed element, it is recommended that you use the
subsop command instead.
> LL := [ seq( i, i=1..200 ) ]:
> LL[1] := -1;
Error, assigning to a long list, please use Arrays
(4.25)
(4.26)
(4.27)
140 4 Basic Data Structures
(4.28)
(4.29)
(4.30)
(4.31)
(4.32)
This can be useful for many tasks, for example, using lists in a loop. For more information
on selectremove, see Filtering Data Structure Elements (page 165).
> L := [seq( i, i=2..100)]:
> divisor := 2:
> while ( numelems( L ) > 0 )
do
divisible, L := selectremove( i->(i mod divisor = 0), L ):
n := numelems( divisible );
if ( n > 0 ) then
printf( "%d integer%s whose smallest prime divisor is %d\n",
Sorting a List
The sort command can create a new list with sorted elements from any given list. By default,
sort arranges elements in ascending order.
> sort( [ 4,2,3 ] );
(4.33)
The sort command can also accept a second argument that specifies the ordering to use
when sorting the elements.
> sort( [4,2,3], `>` );
(4.34)
(4.35)
(4.36)
Maple provides other operations that can work with the members of a list, such as add and
mul.
> add( i, i in [ seq( j, j=1..100 ) ] );
(4.37)
(4.38)
Finally, a for loop can be combined with the in operator to loop over the contents of a list.
> for i in [1,2,3,4]
do
print( i^2 );
end do;
(4.39)
Sets
A set is an unordered sequence of unique expressions. When a set is created, Maple reorders
the expressions to remove duplicate values and to make certain operations faster.
Creating Sets
The easiest way to create a set is to enclose a sequence of expressions in braces ({}).
4.3 Immutable Data Structures 143
{ sequence }
When Maple creates the set, it performs automatic simplification. This process creates a set
that contains the elements of sequence; however, during the automatic simplification process,
any duplicate elements are removed and the remaining elements are reordered.
Compare the results of these examples to those in the Lists (page 136) section.
> {x, y, y};
(4.40)
(4.41)
> {y[1],x,x[1],y[1]};
(4.42)
Similar to lists, sets can be created using functions such as seq that return sequences.
> { seq( i mod 3, i=1..10 ) };
(4.43)
Again, similar to lists, the op command can be used to extract the sequence of elements in
a set.
> S := {1,2,3};
(4.44)
> op(S);
(4.45)
However, unlike lists, Maple provides operations for set arithmetic, so for sets op is some-
what less important.
Set Arithmetic
Maple provides operators for mathematical set manipulations: union, minus, intersect, and
subset. These operators allow you to perform set arithmetic in Maple.
> s := {x,y,z};
(4.46)
144 4 Basic Data Structures
> t := {y,z,w};
(4.47)
> s union t;
(4.48)
> s minus t;
(4.49)
> s intersect t;
(4.50)
> s subset t;
(4.51)
(4.52)
(4.53)
> S[2];
(4.54)
> S[3];
(4.55)
Unlike lists, you cannot use the selection operation to create new sets.
> S[1] := 4;
Error, cannot reassign the entries in a set
4.3 Immutable Data Structures 145
You can specify a range in the selection operation to extract the elements indexed by the
range.
> S2 := { seq( i^2, i=1..10 ) };
(4.56)
> S2[3..6];
(4.57)
> S2[5..8];
(4.58)
(4.59)
(4.60)
(4.61)
(4.62)
(4.63)
(4.64)
146 4 Basic Data Structures
(4.65)
In this example, the features of sets are used to test Collatz's conjecture on the first million
integers. Collatz's conjecture states that given any integer, i, if the following function is
applied repeatedly, the result will eventually be 1.
> collatz := proc( i )
if ( i = 1 ) then
1;
elif ( type( i, even ) ) then
i/2;
else
3*i+1;
end if;
end proc:
Begin with a set S that consists of the integers from 1 to 1 million. Under repeated application
of collatz, as numbers converge to 1, the set automatically removes duplicate values, until
eventually there is only 1 element left. For more information on the use of map, see Applying
a Function to the Contents of a Set (page 146).
> S := {seq( i, i=1..1000000)}:
> while ( numelems( S ) > 1 )
do
S := map( collatz, S ):
end do:
> S;
(4.66)
(4.67)
4.4 Mutable Data Structures 147
(4.68)
Notice that when applying a function to a set, the output is also a set, which means the ele-
ments are reordered and duplicate elements are removed.
Maple provides other operations that can work with the members of a list, such as add and
mul.
> add( i, i in { seq( j, j=1..100 ) } );
(4.69)
(4.70)
Finally a for loop can be combined with the in operator to loop over the contents of a set.
Note that the set has been reordered.
> for i in {1,4,3,2}
do
print( i^2 );
end do;
(4.71)
The most flexible mutable data structure provided by Maple is the table.
Tables
A table stores a collection of index/entry pairs. For a given index, the table contains a par-
ticular value, called an entry. Index/entry pairs can be created or removed, or the value as-
sociated with an index can be modified.
148 4 Basic Data Structures
Creating Tables
A new table can be created by calling the table function.
> t := table();
(4.72)
With no arguments, table creates a new empty table. To create a table that contains certain
index/entry pairs, specify the pairs as a list of equations. The left-hand side of an equation
is the index and the right-hand side is the entry.
> t := table( [ 1=2, a=b, f(x)=y ] );
(4.73)
If the given list contains one or more expressions that are not equations, the list is treated
as a list of entries and the indices are the positions of the entries in the list (1, 2, 3, ...).
> t := table( [ a, b, c=d ] );
(4.74)
Tables are also created implicitly when you assign to an indexed name.
> t2[new] := 10;
(4.75)
> eval(t2);
(4.76)
(4.77)
> t[1];
(4.78)
4.4 Mutable Data Structures 149
> t[a];
(4.79)
> t[f(x)];
(4.80)
If the table does not contain a entry associated with the index, an unevaluated table reference
is returned.
> t[2];
(4.81)
The selection operation can also be used to add new index/entry pairs to the table.
> t[2] := 3;
(4.82)
> t[c] := d;
(4.83)
> t[sin(x)] := 1;
(4.84)
> t[2];
(4.85)
> t[c];
(4.86)
> t[sin(x)];
(4.87)
Removing an Element
The best way to remove an element from a table is to call the unassign function.
> t[1] := x;
(4.88)
150 4 Basic Data Structures
> t[sin(x)] := y;
(4.89)
(4.90)
(4.91)
The selection operation can also be used to remove an index/entry pair from a table. By
assigning the unevaluated table entry to its name, that element is removed from the table.
This can be done by using unevaluation quotes ( ' ) or the evaln command.
> t[1] := x;
(4.92)
(4.93)
> t[1];
(4.94)
> t[sin(x)] := y;
(4.95)
(4.96)
> t[sin(x)];
(4.97)
(4.98)
(4.99)
(4.100)
(4.101)
(4.102)
(4.103)
> t;
(4.104)
To get the assigned value (the table), use the eval command.
> eval(t);
(4.105)
152 4 Basic Data Structures
Extracting Data
Tables are often used as simple containers for data. Sometimes, it is useful to have a list of
the indices used in the table. Maple provides the indices function for this purpose.
> t := table( [a=1, b=2, c=3, d=4] );
(4.106)
> indices( t );
(4.107)
You may not expect to see that indices returns a sequence of lists, where each list contains
the index. This is because Maple allows sequences to be used as indices in tables.
> t2 := table( [ a=1, b=2, (a,b,c)=3 ] );
(4.108)
> indices( t2 );
(4.109)
If the indices were not wrapped in a list, it would be impossible to determine if an index is
a single expression or a sequence of expressions. Since using sequences as indices is uncom-
mon, indices accepts a nolist option, for which indices returns a simple sequence and does
not wrap each index in a list.
> indices( t, 'nolist' );
(4.110)
Note that, with the nolist option, indices that are sequences are not returned properly.
> indices( t2, 'nolist' );
(4.111)
You can also use the entries function to get all the values stored in the table.
> entries( t );
(4.112)
(4.113)
4.4 Mutable Data Structures 153
To extract the index/entry pairs as a sequence of equations, use the pairs option to either
of the indices or entries commands.
> entries( t, 'pairs' );
(4.114)
Copying Tables
If you assign a table to multiple names, all the names reference the same table. Thus, changes
to the table using one name are visible from the other names.
> t := table( [a=1,b=2,c=3] );
(4.115)
> t1 := eval( t );
(4.116)
> t[d] := 4;
(4.117)
> eval( t );
(4.118)
> eval( t1 );
(4.119)
If you want to create a copy of a table, use the copy function so that the tables can be
modified independently.
> t1 := copy( t );
(4.120)
> t[e] := 5;
(4.121)
> eval( t );
(4.122)
154 4 Basic Data Structures
> eval( t1 );
(4.123)
(4.124)
(4.125)
You can use the indices and entries functions to produce a list that can be mapped over or
used in a for-in loop. You can also use this technique to modify the original table.
> for i in entries(t,'pairs')
do
t[lhs(i)] := int( rhs(i), x );
end do;
(4.126)
4.4 Mutable Data Structures 155
> eval(t);
(4.127)
Arrays
In Maple, an Array stores data as an n-dimensional rectangular block (rtable), that is, an
Array has 1 or more dimensions and each dimension has an range of integer indices. By
specifying one integer from each range, an element of the Array can be indexed.
Because Arrays are mutable, the values stored in an Array can change.
Creating Arrays
To create an Array in Maple, use the Array command and specify the ranges for the dimen-
sions. This creates a new Array with each entry initialized to 0. For Arrays, the ranges do
not need to start at 1.
> Array( 1..3 ); # 1 dimensional Array
(4.128)
(4.129)
(4.130)
When creating an Array, you can also specify a generator function to populate the Array
with data. The generator function takes an index as an argument and returns a value for the
corresponding entry.
156 4 Basic Data Structures
(4.131)
(4.132)
You can also provide the data for the Array by specifying the data as a list or nested lists.
> Array( [1,2,3] );
(4.133)
(4.134)
The basic indexing operator, [], provides mathematical indexing. Programmer indexing is
accessed by using round brackets, (). For Arrays whose dimension ranges all start at 1, the
two indices behave similarly.
> A := Array( 1..2, 1..3 ):
> A[1,1] := 1;
(4.135)
4.4 Mutable Data Structures 157
> A(2,1) := 2;
(4.136)
> A(1,1);
(4.137)
> A[2,1];
(4.138)
You may notice that the assignment that uses programmer indexing is displayed differently
than the assignment that uses mathematical indexing. This is because the result of an assign-
ment to a programmer indexed Array is the entire array. This can be important when
working with large sub-Arrays.
When the ranges do not start at 1, mathematical and programmer indexing are different.
Mathematical indexing requires that the indices match the specified ranges, but programming
indexing always normalizes the ranges to start at 1.
> A := Array( 3..4, 5..6, (x,y)->x+y ):
> A[3,5];
(4.139)
> A(3,5);
Error, index out of bounds
> A(1,1);
(4.140)
This means that programmer indexing can always take advantage of negative indexing,
which normally only works when the ranges start at 1. Negative indexing counts backwards
from the end of the range.
> A[3,-1];
Error, Array index out of range
> A[3,6];
(4.141)
158 4 Basic Data Structures
> A(1,-1);
(4.142)
Sub-Array Access
A sub-Array of an Array can be accessed by specifying a subrange in place of the indices.
> A := Array( 1..5, 1..5, (x,y)->x+y );
(4.143)
> A[1..2,1..3];
(4.144)
> A(2..4,2..3);
(4.145)
(4.146)
4.4 Mutable Data Structures 159
(4.147)
Note that the commands perform the same operation, but display the result differently. This
is the consequence of an important difference in how the modification is performed. This
can be important when working with large sub-Arrays. Compare the time to perform the
assignment in the following examples:
> N := 4000:
> A := Array( 1..N, 1..N, (x,y)->rand() ):
> B := Array( 1..N, 1..N ):
> t := time():
> B[1001..4000,1001..4000]:=A[1..3000,1..3000]:
> time()-t;
(4.148)
> t := time():
> B(1001..4000,1001..4000):=A(1..3000,1..3000):
> time()-t;
(4.149)
The difference in running time of these copies is due to the difference in the result of an
assignment to an Array index. For mathematical indexing, a new 3000 by 3000 Array must
be created as the result. With programmer indexing, the result is the Array being assigned
to in its entirety - an object that already exists.
Automatic Resizing
One of the most important differences between mathematical and programmer indexing is
automatic resizing. When reading from or writing to an entry using mathematical indexing,
an index that is outside the bounds of the Array will raise an exception.
160 4 Basic Data Structures
(4.150)
> A[1,4];
Error, Array index out of range
> A[1,4] := a;
Error, Array index out of range
However, programmer indexing allows you to write to an entry that is outside the bounds
of the current Array. Instead of raising an error, the Array are automatically resized so that
the element can be stored. Reading from an out-of-bounds index will still raise an exception.
> A(1,4) := a;
(4.151)
> A(3,5);
Error, index out of bounds
> A(3,5) := b;
(4.152)
(4.153)
(4.154)
4.4 Mutable Data Structures 161
As Array bounds may not start at , it is important that procedures that accept Arrays be
aware of this possibility. The upperbound and lowerbound functions can be used to get
the bounds on the ranges of an Array.
> printer := proc( A )
local lower, upper, i, j;
lower := lowerbound( A );
upper := upperbound( A );
for i from lower[1] to upper[1]
do
for j from lower[2] to upper[2]
do
printf( "%a ", A[i,j] );
end do;
printf( "\n" );
end do;
end proc:
> printer( Array( [[1,2],[3,4]] ) ):
1 2
3 4
Copying an Array
As with tables, having multiple variables referencing the same Array does not create new
copies of the Array. You can use copy to copy the Array.
> A := Array( 1..2, 1..2 ):
> B := A;
(4.155)
> A[1,1] := 1:
162 4 Basic Data Structures
> B;
(4.156)
> B := copy(A):
> A[1,2] := 2:
> A;
(4.157)
> B;
(4.158)
(4.159)
4.4 Mutable Data Structures 163
> AC := copy(A);
(4.160)
> CompareArray(A,AC);
(4.161)
> AR := A;
(4.162)
> CompareArray(A,AR);
(4.163)
(4.164)
> CompareArray(A,B);
(4.165)
There are some other advanced notions of equality such as whether or not arrays with un-
defined entries should be treated as having equal entries, and whether a Matrix and Array
with identical entries should be considered the same. The IsEqual command in the Ar-
rayTools package allows for different solutions for these two issues compared to
EqualEntries. The ArrayTools package contains a variety of functions for working with
Arrays. For more information, refer to the ArrayTools help page.
(4.166)
164 4 Basic Data Structures
indices, entries, and the in operator work with Arrays, so you can use Arrays in add, mul,
and for loops. entries(A,pairs) can also be used to obtain a list of index/value pairs in the
same way that it does for tables.
> A := Array( [x,x^3,sin(x)] ):
> for entry in entries(A,'pairs')
do
A[lhs(entry)] := diff( rhs(entry), x ):
end do:
> A;
(4.167)
When assigning values into the Array, Maple will raise an exception if the given value does
not match the specified type.
> A := Array( [1,2,3,4], datatype=float[8] );
(4.168)
> A[1];
(4.169)
(4.170)
If you are working with numeric values that can be stored in these hardware types, it can
be much faster to use an Array with a hardware type. For more information on numerical
programming in Maple, see Numerical Programming in Maple (page 281).
4.5 Other Data Structure Operations 165
Deprecated: array
The array data structure is an older implementation of Arrays. Its use has been deprecated;
use Array instead.
The parameter f must be a Boolean-valued function. This function is applied to each of the
elements of the data structure x. select returns the a data structure containing those elements
for which f returns true. remove returns a data structure containing those elements for which
f returns false. selectremove returns two structures, the first consisting of the elements for
which f returned true and the second consisting of the elements for which f returns false.
The type of the return value of these functions matches the type of the argument x.
> x := [seq(i,i=1..10)];
(4.171)
(4.172)
(4.173)
(4.174)
Calling selectremove is more efficient than calling select and remove separately.
convert( x, t )
convert attempts to convert the expression x into the form t. In particular, Maple supports
conversions between the list, set, table, and Array types.
> x := [1,2,3,4];
(4.175)
(4.176)
(4.177)
(4.178)
Create a Record
To create a new record, use the Record command. Record accepts a sequence of names
as parameters. Each name becomes a field in the returned record.
> r := Record( 'expression', 'variable' );
(4.179)
(4.180)
> r:-variable := x;
(4.181)
4.6 Other Data Structures 167
(4.182)
(4.183)
(4.184)
(4.185)
Note that you must call eval on r before passing it into Record. This is because records use
last name evaluation rules, similar to tables.
Record Equality
As with Arrays, two references to Records are considered equal if they reference the same
structure. Two different structures that have the same fields and values are not considered
equal.
> r := Record( 'a'=1, 'b'=2, 'c'=3 ):
> rc := r:
> r2 := Record( 'a'=1, 'b'=2, 'c'=3 ):
> evalb( r = rc );
(4.186)
> evalb( r = r2 );
(4.187)
To compare two different records, you can use the verify command with the record argu-
ment. verify/record returns true if the two records have the same set of fields with equal
values assigned to them.
168 4 Basic Data Structures
(4.188)
(4.189)
(4.190)
Packed Records
The Record constructor function can also be called with the indexed name Record[packed],
to produce a packed record.
Unlike a regular record, a packed record does not create a unique instance of each field
name for each record instance. When working with thousands of similar records each with
many fields, this can save a significant amount of memory.
Fields of packed records do not exhibit last name evaluation. That is, the expression r:-a
always produces a value, even if that value is a procedure, table, Matrix, Vector, or another
record.
Similarly, it is not possible for a packed record field to not have a value. The assigned
function will always return true, and unassigning a packed record field will set its value to
NULL instead.
Stacks
A stack is an abstract data type that provides two main operations: push and pop. A push
places a new value onto the top of the stack and pushes the existing elements down. A pop
removes the element from the top of the stack, moving the elements below up. This creates
a element access order referred to as last in first out (LIFO).
Stacks are useful for many operations. A typical use of a stack is to turn a recursive algorithm
into an iterative one. Instead of recursing on elements, those elements get pushed onto a
stack. When the current element has been handled, the element on top of the stack is removed
and handled next. By using a stack, the recently discovered elements are handled before
elements that were already in the stack, which is similar to how a recursive algorithm works.
4.6 Other Data Structures 169
Creating a Stack
In Maple, you can create a stack by calling stack:-new. If you do not specify any arguments,
stack:-new creates an empty stack. Maple stacks are implemented on top of tables.
> s := stack:-new():
> stack:-push( 1, s );
(4.191)
> stack:-push( 2, s );
(4.192)
> stack:-pop( s );
(4.193)
> stack:-pop( s );
(4.194)
You can also pass values into stack:-new that populate the stack. These elements are pushed
in the order specified.
> s := stack:-new(1,2,3,4,5):
> stack:-pop( s );
(4.195)
> stack:-pop( s );
(4.196)
(4.197)
> stack:-push( 3, s ):
170 4 Basic Data Structures
> stack:-pop( s );
(4.198)
> stack:-pop( s );
(4.199)
(4.200)
(4.201)
You can examine the element on the top of a stack, without removing it, by calling stack:-
top.
> s := stack:-new(x,x^2,sin(x)):
> stack:-depth(s);
(4.202)
4.6 Other Data Structures 171
> stack:-top(s);
(4.203)
> stack:-pop(s);
(4.204)
> stack:-depth(s);
(4.205)
> stack:-top(s);
(4.206)
> stack:-pop(s);
(4.207)
> stack:-depth(s);
(4.208)
> stack:-top(s);
(4.209)
Queues
The queue is an abstract data type similar to a stack; however, instead of the most recently
added element being returned first, the oldest element in the queue is returned first. Elements
in a queue are analogous to people waiting in a line. The main operations provided by a
queue are enqueue, which adds an element to the queue, and dequeue, which removes an
element from the queue. The access order used by a queue is called first in first out, or FIFO.
A queue is used when you want to handle elements in the order that they are discovered. A
typical example of using a queue is a breadth-first search of a graph. You dequeue a node
and then enqueue any unvisited nodes that are neighbors of the current node. By using a
queue, the order in which the nodes are visited is breadth-first.
Create a Queue
To create a queue in Maple, use the queue:-new command.
> q := queue:-new():
172 4 Basic Data Structures
> queue:-enqueue( q, 1 );
(4.210)
> queue:-enqueue( q, 2 );
(4.211)
> queue:-dequeue( q );
(4.212)
> queue:-dequeue( q );
(4.213)
You can also pass values into queue:-new to populate the new queue. The elements are
enqueued in the order they are specified.
> q := queue:-new( 1,2,3 ):
> queue:-dequeue( q );
(4.214)
> queue:-dequeue( q );
(4.215)
> queue:-dequeue( q );
(4.216)
(4.217)
> queue:-enqueue( q, 3 ):
4.6 Other Data Structures 173
> queue:-dequeue( q );
(4.218)
> queue:-dequeue( q );
(4.219)
(4.220)
(4.221)
You can examine the front element of a queue, without removing it, by calling queue:-
front.
> q := queue:-new(x,x^2,sin(x)):
> queue:-length(q);
(4.222)
174 4 Basic Data Structures
> queue:-front(q);
(4.223)
> queue:-dequeue(q);
(4.224)
> queue:-length(q);
(4.225)
> queue:-front(q);
(4.226)
> queue:-dequeue(q);
(4.227)
> queue:-length(q);
(4.228)
> queue:-front(q);
(4.229)
Indexing
The time to perform an indexed look-up into a list, set, table, and Array are all constant
time operations. This means that the time needed to find the element does not vary based
on the number of elements stored in the structure. Time to perform a look-up into a list or
set is relatively similar and is faster than Arrays, which is faster than a table.
Similarly, writing into a table or Array is also a constant time operation, with Array look-
ups being slightly faster than table look-ups.
4.7 Data Structure Performance Comparisons 175
Membership
The member function determines if a particular element is stored in a structure. For lists,
this requires a linear search of the data in the list. Therefore, the time is proportional to the
total length of the list. A set is sorted, so searches of the list can be performed more quickly.
Searching within a set takes time proportional to the log[2] of the number of elements in
the set.
You can use a table for very fast membership testing. Use the table key as objects you want
to test for, and anything you want for the value. You can then call the assigned command
to test if the element exists in the table. A table index is a constant time operation, so this
membership test is also constant time.
> N := 2*10^5:
> memtest := proc( D, N )
local i;
for i from 1 to N
do
member( i, D ):
end do:
end proc:
> L := [seq( i, i=1..N )]:
> time(memtest(L,N));
(4.230)
(4.231)
(4.232)
176 4 Basic Data Structures
Note that to benchmark the list and set membership functions, the call to member is within
a function. This is because of the Maple evaluation rules. If the call to the member command
is at the top level, the list or set is fully evaluated, which requires inspecting each element
of the list or set for each call to member. The overhead required for these full evaluations
would distort the results.
For more information on the Maple evaluation rules, see Unevaluated Expressions (page 52).
(4.233)
> t := table():
start:=time():
for i from 1 to N
do
t[i] := 1:
end do:
time()-start;
(4.234)
(4.235)
(4.236)
> op( 2, s );
Error, invalid input: op expects 1 or 2 arguments, but received 4
(4.237)
(4.238)
> L[0];
Error, invalid subscript selector
> L[9];
Error, invalid subscript selector
Further, when specifying the endpoints of a range, make sure that the left-hand side of the
range specifies an element before the element specified by the right-hand side.
178 4 Basic Data Structures
> L[6..3];
Error, invalid subscript selector
> L[6..-5];
Error, invalid subscript selector
The only exception to this is if the left-hand side of the range is n, then the right-hand side
can be n-1 and the result of this range is an empty structure (list or set).
> L[6..5];
(4.239)
(4.240)
> A[7];
Error, Array index out of range
> A[5..3];
Error, inverted range in Array index
t[i] := evaln(t[i]):
end do:
time()-start;
(4.241)
(4.242)
4.9 Exercises
1. Define a set with elements that are the powers of 13 modulo 100 for exponents ranging
from 1 to 1000. Is 5 a member of the set? Why is it beneficial to use a set instead of a
list?
Hint: You can determine the set by using one statement if you use the seq command.
2. Generate the sums of 4 and the first 100 multiples of 3. Determine the sums that are
square-free composite numbers.
Hint: The numtheory package has a function that you need to use.
3. Find floating-point approximations for the sum of the square root and cubic root of each
of the first 15 powers of 2.
5.2 Introduction
A statement is a single complete piece of code that Maple can execute. There are many
types of statements in Maple, including expression statements, assignment statements, se-
lection statements (if ... then), repetition statements (loops), and program instructions (quit,
save, read).
A statement differs from an expression in that it is normally evaluated for effect, rather than
for its value. Most statements that do not consist of a single expression are formed so as to
have a side effect.
Statements can be run in Maple one at a time, or multiple statements can be run on one line.
If multiple statements are run on one line, the statements must be separated by a statement
separator, either a semicolon (;) or a colon (:).
At the top level, the output of a statement that ends with a colon is hidden.
> a:=2: a^2;
(5.1)
Note: In the standard interface, for input in 2-D math, the semicolon at the end of a
statement can be omitted.
181
182 5 Maple Statements
(5.2)
> sin( Pi - x );
(5.3)
(5.4)
5.5 Assignments
Assignment statements allow you to associate a value or expression with a name. The as-
signment statement has the general form
lhs := rhs
Evaluating the assignment associates the value on the right-hand side of the assignment
with the name on the left-hand side. After the assignment has taken effect, the result is the
associated value when the assigned name is evaluated.
(5.5)
The following assignment statement associates the value 2 / 3 with the name a.
> a := 2 / 3;
(5.6)
(5.7)
5.5 Assignments 183
> a + 1 / 3;
(5.8)
Associate the symbolic expression Pi / 2 with the name b by executing the following assign-
ment statement.
> b := Pi / 2;
(5.9)
Subsequently, the assigned value of b is used whenever the name b appears in an expression.
> sin( b );
(5.10)
In this expression, the assigned value Pi / 2 of the name b is substituted to yield the expres-
sion sin( Pi / 2 ), and then the value of the procedure sin at this expression is computed,
resulting in the overall value 1 for the expression.
Multiple Assignment
You can perform several assignments in a single statement, known as a multiple assignment.
This has the general form
(lhs1, lhs2, ..., lhsN) := (rhs1, rhs2, ..., rhsN)
The parentheses on the right- and left-hand sides of the assignment are not required, but are
considered good practice.
(5.11)
(5.12)
> y;
(5.13)
184 5 Maple Statements
> z;
(5.14)
The number of components on each side of the assignment operator := must be the same.
> (a, b, c) := (2, 3);
Error, ambiguous multiple assignment
A common idiom is to use a multiple (double) assignment to swap the values of two variables
without introducing an additional temporary variable.
> (x, y) := (1, 2):
> x;
(5.15)
> y;
(5.16)
(5.17)
> y;
(5.18)
Note that using the swap idiom with unassigned names will lead to an infinite recursion.
> (u, v) := (v, u);
(5.19)
Evaluating u or v (full evaluation) produces an error. If you evaluate one level at a time
using eval(u, i), you can see what happens.
> u;
Error, too many levels of recursion
> v;
Error, too many levels of recursion
(5.20)
5.6 Flow Control 185
(5.21)
Sequencing
The simplest form of a Maple program is a sequence of zero or more statements, separated
either by semicolons or colons. A sequence of statements is run in the order in which they
are entered.
(5.22)
> b := 3;
(5.23)
> sin( a + b );
(5.24)
executes the assignment to the name a, then the assignment to the name b is executed and,
finally, the value of the expression sin( a + b ) is computed.
The flow of control in a Maple program consisting of a sequence of statements moves from
one statement to the next, in order.
Many Maple statements are compound statements that contain statement sequences as
constituents.
Branching
The simplest form of flow control is a branching, or if statement. Basically, an if statement
has the syntax
186 5 Maple Statements
if condition then
statseq
end if
in which condition is a Boolean-valued expression (that is, one which evaluates to one of
the values true, FAIL, or false), and statseq is a (possibly empty) sequence of Maple
statements, often called the body of the if statement.
The effect of an if statement is to divert the flow of control, under the right conditions, to
the body of the statement. If the condition expression evaluates to true, the flow of control
moves into the body of the if statement. Otherwise, if the condition expression evaluates
to FAIL or false, Maple exits the if statement and the flow of control continues at the
statement (if any) following the if statement.
> if 2 < 3 then
print( "HELLO" )
end if;
(5.25)
Here, consequent and alternative are statement sequences. If the condition expression
evaluates to true, the consequent branch of the if statement is executed. Otherwise, the
alternative branch is executed.
> if 2 < 3 then
print( "CONSEQUENT" )
else
print( "ALTERNATIVE" )
end if;
(5.26)
5.6 Flow Control 187
(5.27)
The most general form of an if statement can have several conditions, corresponding con-
sequents, and an optional alternative branch. This general form has the syntax:
if condition1 then
consequent1
elif condition2 then
consequent2
....
else
alternative
end if
in which there can be any number of branches preceded by elif. The effect of this general
form of the if statement is to divert the flow of control into the first branch whose conditional
expression evaluates to true. This means that the order of the elif branches can affect the
behavior of the if statement.
The branch introduced by else is optional. If it is present, and none of the earlier condition
expressions evaluates to true, then control flows into the else branch. If it is not present,
and none of the earlier condition expressions evaluates to true, then the flow of execution
continues with the first statement following the entire if statement.
> if 2 > 3 then
print( "CONSEQUENT1" )
elif 3 > 4 then
print( "CONSEQUENT2" )
elif 1 < 5 then
print( "CONSEQUENT3" )
elif 2 < 5 then
print( "CONSEQUENT4" )
else
print( "ALTERNATIVE" )
end if;
(5.28)
188 5 Maple Statements
(5.29)
An if statement can appear at the top level, as in the examples shown above, but is most
commonly used within a procedure or module definition.
A typical use of the if statement is to control the flow of execution inside a procedure, de-
pending on information coming from the arguments passed to it.
> p := proc( expr )
if type( expr, 'numeric' ) then
sin( 2 * expr )
elif type( expr, { '`+`', '`*`' } ) then
map( thisproc, _passed )
else
'procname'( _passed )
end if
end proc:
> p( 2 );
(5.30)
> p( x );
(5.31)
> p( x + 1 );
(5.32)
In this example, the procedure p uses the type command to examine its argument expr. If
the argument is numeric, then it computes the value as sin( 2 * expr ). Otherwise, if the
5.6 Flow Control 189
argument is either a sum or a product, the procedure maps itself over the operands of the
expression. Otherwise, the procedure returns unevaluated.
The first argument condition is a Boolean-valued expression. The second argument con-
sequent is an expression to evaluate if the first argument evaluates to the value true. The
third argument is an expression to evaluate if the first argument evaluates to either false or
FAIL.
> `if`( 1 < 2, a, b );
(5.33)
(5.34)
Note that the name if must be enclosed in name (left) quotes in this form.
The if command evaluates only one of its second and third arguments, determined based
on the value of the first argument. The other argument is not evaluated.
The value of the if command (as opposed to the statement form) is that you can embed it
within a larger expression.
> a := 2/3:
> sin( `if`( a > 0, Pi / 2, -Pi / 2 ) );
(5.35)
However, the if command is much more limited than the if statement. The consequent and
alternative must be single expressions, and there is nothing corresponding to the elif parts
of the statement form.
Loops
To cause a statement, or sequence of statements, to be run more than once, use a loop
statement. Maple has a general and flexible loop statement.
The simplest loop has the form do end do. This loop does not perform any tasks.
The first line in each of these forms is called the loop header or, more formally, the loop
control clause. The statseq part of the loop is a (possibly empty) sequence of statements,
referred to as the body of the loop.
Each clause that occurs before the keyword do in the loop header is optional.
Since most of the examples below are infinite loops; you must interrupt the Maple compu-
tation to terminate the loop. For more information, see Interrupting a Maple Computa-
tion (page 11).
> do end do;
> by -14 do end do;
> for i do end do;
> from 42 do end do;
> to 3 do end do;
> while true do end do;
If more than one of the optional clauses appears in the loop header, they may appear in any
order.
While Loops
One simple kind of terminating loop is the while loop.
while condition do
statseq
end do;
The loop header of a while loop involves only a single termination condition introduced by
the keyword while. The loop repeats the statement sequence statseq until the Boolean-valued
expression condition does not hold.
In this example, a loop counts the number of primes whose square is less than 1000.
> count := 0:
p := 2:
5.6 Flow Control 191
(5.36)
This example uses the nextprime command, which returns the least prime greater than its
argument. The name count is given the initial value 0, and the name p, which is used to
store the current prime, is initially set to 2. The loop condition is the expression p^2 < 1000,
appearing after the keyword while and before the keyword do. This condition is evaluated
at the beginning of each iteration of the loop. If the condition evaluates to true, the body
of the loop is executed. If the condition evaluates to false or FAIL, the code continues to
execute at the next statement following the loop statement.
If the condition expression evaluates to a value other than true, false or FAIL, an exception
is raised.
> while 3 do end do;
Error, invalid boolean expression: 3
For more information on Boolean expressions, see Boolean and Relational Expres-
sions (page 94).
Counted Loops
You can use a loop to repeatedly execute a sequence of statements a fixed number of times.
These loops use the from and to clauses.
> from 1 to 3 do print( "HELLO" ) end do;
(5.37)
or equivalently
192 5 Maple Statements
(5.38)
Inductive Loops
The most common kind of loop is an inductive loop which is similar to a counted loop, but
uses an induction variable whose value changes at each iteration of the loop. This is a par-
ticular kind of for loop with the general form
for var from start to finish by increment do
statseq
end do;
The default value for start is 1, for finish is infinity, and for increment is 1.
> for i to 3 do
print( i )
end do;
(5.39)
Maple assigns i the (default) value 1 since a starting value was not specified.
Because 1 is less than 3, Maple executes the statement in the body of the loop, in this case,
printing the value of i.
The loop executes until i>3. In this case, when the loop terminates, the final value of i is 4.
> i;
(5.40)
5.6 Flow Control 193
(5.41)
Loop control parameters (start, finish, and increment) do not need to be integers.
> for i from 0.2 to 0.7 by 0.25 do
print( i )
end do;
(5.42)
In addition to iterating over a numeric range, you can iterate over a range of characters. In
this case, you must specify both the initial value start and the final value finish for the in-
duction variable. Furthermore, the value of increment must be an integer.
> for i from "a" to "g" by 2 do
print( i )
end do;
(5.43)
The induction variable var takes on, successively, the operands of the expression expr.
There are a few exceptions. First, if expr is an expression sequence, it does not have operands
as such, but the induction variable var iterates over the operands of the list [ expr ]. If expr
is a table, the loop iterates over [entries]( expr ). (For more information on entries, see
Extracting Data (page 152).) The order in which these entries are visited is not specified
and may vary from one session to another. Finally, if expr is an rtable, the loop iterates
over the entries of expr, but the order of the iteration is not specified.
> for i in [ 1, 2, 3 ] do
print( i )
end do;
(5.44)
Note that there is a difference between the loop above and the seemingly equivalent loop
> for i from 1 to 3 do
print( i )
end do;
(5.45)
The difference is the value of the induction variable i at the end of the loop. To see this,
evaluate the induction variable i immediately after running the loop to display its value.
> for i in [ 1, 2, 3 ] do end do: i;
(5.46)
(5.47)
5.6 Flow Control 195
Looping Commands
Maple provides commands to create some commonly used types of loops. These commands
are generally meant to build expressions without creating many intermediate expressions.
where f is a function and x is an expression. The map command replaces each operand elem
of the expression x with f(elem).
For tables and Arrays (or other rtables), the function is applied to each entry.
> map( f, [a,b,c] );
(5.48)
Given a list of integers, you can create a list of their absolute values and of their squares by
using the map command.
> L := [ -1, 2, -3, -4, 5 ];
(5.49)
(5.50)
(5.51)
where f is a function, x is any expression, and y1, ..., yn are expressions. The action of map
is to replace each operand of x such that the ith operand of x is replaced by f(op(i,x), y1,
..., yn).
> map( f, a+b+c, x, y );
(5.52)
196 5 Maple Statements
(5.53)
For more information and examples, see Basic Data Structures (page 135).
(5.54)
(5.55)
(5.56)
For more information on these commands, see Other Data Structure Operations (page 165)
or refer to the select help page.
where f is a binary function, u and v are both lists or rtables, and d is any value. The zip
command takes each pair of operands u[i], v[i], and creates a new list or vector from f(u[i],
v[i]).
> zip( (x,y) -> x || y, [a,b,c,d,e,f], [1,2,3,4,5,6] );
(5.57)
5.6 Flow Control 197
If the lists or vectors are not the same length, the length of the result depends on whether
you provide the argument d.
If you do not specify d, the length of the result is the same as the length of the smaller list
or vector.
> zip( (x,y) -> x+y, [a,b,c,d,e,f], [1,2,3] );
(5.58)
If d is specified, the length of the result of the zip command is the same as the length of the
longer list or vector. Maple replaces the missing value(s) with d.
> zip( (x,y) -> x+y, [a,b,c,d,e,f], [1,2,3], xi );
(5.59)
In Command-line Maple, the return statement causes an error if it is run at the top level:
Error, return out of context. In the Standard worksheet interface, return can be used at the
top level in conjunction with DocumentTools:-RunWorksheet.
In the second case, string contains several placeholders of the form %n or %-n, where n
is a positive integer, to include the provided parameters in the message.
The placeholder %n is replaced by the nth parameter given. The placeholder %-n is replaced
by the ordinal form of the nth parameter, which should evaluate to an integer. The special
placeholder %0 is replaced with the sequence of all parameters separated by commas and
spaces.
> x := a+b: n := 10;
(5.60)
Trapping Errors
The try statement is a mechanism for executing procedure statements in a controlled envir-
onment so that if an error occurs, it does not immediately terminate the procedure. The try
statement has the following syntax
try tryStatSeq
catch catchStrings : catchStatSeq
finally finalStatSeq
end try
This statement can include several catch clauses. The finally clause is optional.
If a matching catch clause is found, or the catch clause contains no catchStrings, the
catchStatSeq of that catch clause is executed, and the exception is considered to have been
caught. If no matching catch clause is found, the exception is considered not caught, and
is re-raised outside of the try block.
When Maple searches for a matching catch clause, the following definition of "matching"
is used.
5.6 Flow Control 199
Neither the exception object nor the catchStrings are evaluated (the exception object
has already been evaluated by the error statement that produced it).
The catchStrings are considered to be prefixes of the exception object's msgString. If
a catchString has n characters, only the first n characters of the msgString need to match
the catchString. This permits the definition of classes of exceptions.
A catch clause without a catchString matches any exception.
The "result" of a try statement (the value that % returns if it is evaluated immediately
after execution of the try statement) is the result of the last statement executed in the try
statement.
A catchStatSeq can contain an error statement with no arguments, which also re-raises
the exception. When an exception is re-raised, a new exception object is created that records
the current procedure name, and the message and parameters from the original exception.
Normally, the finalStatSeq of the finally clause, if there is one, is always executed before
control leaves the try statement. This is true in the case that an exception occurs, independent
of whether it is caught or whether another exception occurs in the catch clause.
This is true even if a catchStatSeq re-raises the exception, raises a new one, or executes a
return, break, or next statement.
Example 1
A useful application of the try and error statements is to stop a large computation as quickly
and cleanly as possible. For example, suppose that you are trying to compute an integral by
using one of several methods, and in the middle of the first method, you determine that it
will not succeed. You want to stop that method and try another one. The following code
implements this example.
> try
result := MethodA(f,x)
catch "FAIL":
result := MethodB(f,x)
end try:
MethodA can stop its computation at any time by executing the statement error "FAIL".
The catch clause catches that exception, and proceeds to try MethodB. If any other error
occurs during the execution of MethodA, or if an error occurs during the execution of
MethodB, it is not caught.
Example 2
Another useful application of the try statement is to ensure that certain resources are made
available when you are done with them, regardless of whether anything went wrong while
you were using them.
Use the following code to access the Maple I/O facilities to read the lines of a file and process
them in some way.
> f := fopen("myfile",READ,TEXT):
try
line := readline(f);
while line < 0 do
ProcessContentsOfLine(line);
line := readline(f)
end do
finally
fclose(f)
end try:
In this example, if any exception occurs while reading or processing the lines of the file, it
is not caught because there is no catch clause. However, fclose(f) is executed before execution
leaves the try statement, regardless of whether there was an exception.
The next example uses both catch and finally clauses to write to a file instead of reading
from one.
5.7 The use Statement 201
> f := fopen("myfile",WRITE,TEXT):
try
for i to 100 do
fprintf(f,"Result %d is %q\n",i,ComputeSomething(i))
end do
catch:
fprintf(f,"Something went wrong: %q\n",lastexception);
error
finally
fclose(f)
end try:
If an exception occurs, it is caught with the catch clause that has no catchString, and the
exception object is written into the file. The exception is re-raised by executing the error
statement with no msgString. In all cases, the file is closed by executing fclose(f) in the
finally clause.
For more information about modules and member selection, see Programming with Mod-
ules (page 323).
Running a use statement executes the body of the statement. Each occurrence of a name
that appears on the left side of any of the binding equations is replaced by the right side of
the corresponding equation.
202 5 Maple Statements
For example,
> use f = sin, g = cos in
f( x )^2 + g( x )^2
end use;
(5.61)
The following example establishes local bindings for all of the exports of the StringTools
package.
> use StringTools in
s := Random( 10, 'lower' );
Reverse( s )
end use;
(5.62)
Among these are the names Random and Reverse. Without the use statement enclosing
them, the two statements would have to be written using fully qualified names.
> s := StringTools:-Random( 10, 'lower' );
(5.63)
> StringTools:-Reverse( s );
(5.64)
You can employ the use statement to establish general name bindings.
> use a = 2, b = 3, c = 4 in
a + b + c
end use;
(5.65)
(This is useful when the names bound are used many times within the body of the use
statement.)
The use statement is unique in Maple. It is the only Maple statement that is resolved during
the automatic simplification process rather than during the evaluation process. To see this,
consider the following simple procedure.
> p := proc( x, y )
use a = x + y, b = x * y in
5.7 The use Statement 203
a / b
end use
end proc;
(5.66)
Note that there is no use statement in the procedure after it has been processed, and a and
b in the body have been replaced by the values on the right-hand side of the binding equa-
tions. To see that this processing occurred during automatic simplification (of the procedure
definition), enclose the procedure definition in unevaluation quotes.
> p := 'proc( x, y )
use a = x + y, b = x * y in
a / b
end use
end proc';
(5.67)
(5.68)
If a name is bound in use statements at two different levels of nesting, the innermost binding
visible at the level of an expression is used.
> use a = 2 in
a^2;
use a = 3 in
a^2
end use
end use;
(5.69)
In the following example, the inner binding of the value 3 to the name a takes precedence,
so the value of the expression a + b (and therefore the entire statement) is the number 6.
204 5 Maple Statements
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;
(5.70)
The use statement also allows you to rebind Maple operators to override their default beha-
vior. The following is an example in which addition and multiplication are replaced by
nonstandard meanings.
> use `+` = ((x,y) -> (x+y) mod 3), `*` = ((x,y) -> (x*y) mod 3) in
1 + 2 * 4
end use;
(5.71)
The following operators can have their default behavior overridden by a use statement.
@ @@ % %% %%% .
+ * - / mod ^
! union minus intersect subset in
$ and or not xor implies
= <> < <= assuming <|>
<,> [] {} ?() ?[] ~
Notes:
The following operators cannot be rebound: concatenation operator (||), member selection
operator (:-), type operator (::), range (..), comma (,), functional operator (->), and the
5.8 Other Statements 205
assignment operator (:=). The relational operators > and >= can be rebound, but not inde-
pendently of < and <=, respectively.
All of the element-wise operators are processed through the element-wise operator (~).
The operators - and / are treated as unary operators (that represent negation and inversion,
respectively). Subtraction is represented internally in Maple by forming addition and
negation: a - b = a + (-b). Division is formed in a similar way. Therefore, it is not neces-
sary to override the binary infix operators - and /.
(5.72)
Note: The quit statement cannot be used in the Maple standard interface. In the standard
interface, use File > Close Document to end your Maple session.
quit, stop, and done are available as command names if quoted using name quotes. These
forms allow you to exit Maple while passing an integer in the range 0 .. 255 as an argument
to be returned to the calling process as exit status.
The names namei are names (that have assigned values) to be saved to the file file.
For example, make the following three assignments and run the subsequent save statement.
> a := proc( x ) sin( x / 2 ) end proc:
> b := 42:
> c := "some text":
> save a, b, c, "myfile.txt";
The file myfile.txt is created in the current directory (assuming adequate file permissions)
containing the following Maple assignment statements.
a := proc (x) sin(1/2\*x) end proc;
b := 42;
c := "some text";
(5.73)
The file named by filename must consist of valid Maple language statements. The statements
in the file are executed as they are read, as though they were input into the Maple session
in which the read statement was entered. Maple displays the results of executing each
statement. However, the input statements are not echoed to the interface, by default. To
change this, set the interface variable echo to a value of or higher.
> interface( 'echo' = 2 ):
5.9 Exercises 207
5.9 Exercises
1. Find the product of the square root of all prime numbers less than 100.
Chapter 1 gave a brief introduction to procedures. This chapter describes the syntax and
semantics of procedures in detail, and discusses how to best make use of procedures in your
programs.
6.1 Terminology
Several terms are used frequently when discussing procedures in Maple and other program-
ming languages. Some of these terms are sometimes used interchangeably, but the distinctions
between them are important:
Function Call - A function call, of the form name(arguments), evaluates the arguments
and then invokes a procedure if name has a value that is a procedure. The value of the
function call is then the value returned by the procedure. If name has no value, then the
value of the function call is just name(evaluatedArguments).
Argument - An argument is one of one or more values explicitly included in a function call.
Note that a default value is not an argument.
209
210 6 Procedures
The following is a simple procedure definition. It contains two formal parameters, x and y,
and one statement in the procedure body. There is no description, there are no options, and
the procedure does not make use of any local or global variables. In order to be able to use
the procedure later, we'll assign it to a name:
> SumOfSquares := proc( x, y )
x^2 + y^2
end proc;
(6.1)
This procedure computes the sum of the squares of its two arguments. The procedure can
be called with any two arguments and Maple will attempt to compute the sum of their
squares. Like any computation in Maple, the result can be symbolic. If you want to restrict
the types of arguments that are permitted, it is possible to specify the type for each argument
in the parameter declarations, as described in the next section.
The procedureName is usually the name that the procedure was assigned to, although it
can also be an actual procedure definition, or another expression that evaluates to a procedure.
The argumentSequence is a sequence of expressions that will be evaluated, and then sub-
stituted for the corresponding parameters before the execution of the statements comprising
the body of the procedure. Note that the arguments are evaluated only once before the exe-
6.3 Parameter Declarations 211
cution of the procedure begins. They are not evaluated again during execution of the pro-
cedure.
The value returned by the procedure is the result of the last statement executed within the
procedure. In the following function call, Maple executes the statements in the body of the
procedure SumOfSquares, replacing the formal parameters x and y with the arguments a
and 3. The result of the last (and in this case, only) statement in the procedure is the returned
value:
> SumOfSquares(a,3);
(6.2)
For more information about return values, see Returning Values from a Procedure (page 248).
You can extend a parameter declaration by adding a type specification and/or a default
value. A type specification ensures that, when the procedure is called, the value of the
parameter within the procedure will be of the indicated type, and a default value ensures
that a parameter will always have a value even if no corresponding argument was passed.
Maple procedures can also have keyword parameters. When invoking a procedure, the
corresponding arguments are of the form keyword=value, and can appear anywhere in the
argument sequence.
When you call a procedure, the arguments are evaluated and then bound to the parameters.
In the simplest case, there is a one-to-one correspondence between arguments and parameters;
the first argument is bound to the first parameter, the second argument to the second para-
meter, and so on. The presence of default values and keyword parameters can change this
correspondence, as described in this section.
The parameterName must be a valid symbol, and is used to refer to the parameter within
the procedure body. The :: parameterType is optional. If it is present and the corresponding
argument does not match the specified type, an exception is raised.
In this example, the procedure Adder is defined with two parameters, a and b. The procedure
returns the sum of its two arguments. For the parameter a, Adder expects an argument of
type integer.
> Adder := proc( a::integer, b ) a+b end proc:
> Adder(2,3);
(6.3)
The next call to Adder raises an exception because the second argument is missing.
> Adder(3);
Error, invalid input: Adder uses a 2nd argument, b, which is missing
This call raises an exception because the supplied first argument does not match the para-
meter's specified type.
> Adder(2.5,4);
Error, invalid input: Adder expects its 1st argument, a, to be of type
integer, but received 2.5
If a procedure has both required and ordered parameters (described below), all of the required
parameters must appear before the ordered parameters.
The presence of defaultValue allows the parameter to be optional. If there are no remaining
arguments or the next unused argument does not match the specified parameterType, the
parameter receives the default value. The non-matching argument, if any, remains available
for binding to a later parameter.
As was the case with a required positional parameter, :: parameterType can be omitted.
The parameter will receive its default value only when there are no more available arguments,
since any available argument would have been valid for an untyped parameter.
6.3 Parameter Declarations 213
Usually, defaultValue will be of the type specified by parameterType, but this need not
be the case. The default value can be a literal value of any other type, or NULL. If the default
value is not a literal value, but is an expression that evaluates to something other than itself,
then the result of that evaluation must conform to parameterType.
This class of parameters is called ordered because the arguments are bound to parameters
in the order they were passed. If the first unused argument is not bound to the current
parameter, it remains as the first available argument for the next parameter.
In this example, the procedure Adder is defined with two optional ordered parameters, a
and b, both of type integer, and returns their sum:
> Adder := proc(a::integer := 10, b::integer := 100.1)
a + b
end proc:
> Adder(3,4);
(6.4)
> Adder(3);
(6.5)
> Adder();
(6.6)
> Adder(3,6.6);
(6.7)
In the first call to Adder, the arguments 3 and 4 were bound to the parameters a and b, and
their sum returned. In the second call, only a single argument was passed, so b received its
default value. Notice that the default value is not an integer, but since it is a literal value,
is an acceptable default. In the third call, no arguments were passed and both parameters
received their default values.
You may have expected the result of the fourth call to Adder to be 9.6, but this is not the
case. Why? First, parameter a was given the value 3. Next, 6.6 was considered a candidate
for parameter b, but was rejected because it is not of type integer. Instead, b received its
default value.
This illustrates an important aspect of calling procedures in Maple, which is that in general,
it is acceptable to call a procedure with more arguments than it expects. You will see later
how to access these within a procedure, allowing you to write procedures that accept a
variable number of arguments, or how to disallow the passing of extra arguments.
214 6 Procedures
The declaration of an expected ordered parameter declaration differs from that of an optional
ordered parameter by enclosing parameterType in expects():
parameterName :: expects( parameterType ) := defaultValue
The procedure below is identical to the one from the previous section, except that parameter
b has been declared as an expected parameter. When it is called with a second argument of
the wrong type, instead of saving that argument for a later parameter, Maple raises an ex-
ception:
> Adder := proc(a::integer := 10, b::expects(integer) := 100.1)
a + b
end proc:
> Adder(3,6.6);
Error, invalid input: Adder expects its 2nd argument, b, to be of type
integer, but received 6.6
Keyword Parameters
Keyword parameters are not positional and not ordered. A keyword parameter is bound to
a value when an argument of the form keyword=value appears in a procedure invocation.
The left-hand side of such an argument specifies the keyword parameter name, and the
right-hand side specifies the value it will receive. If true is an acceptable value for the
parameter, then an argument of the form keyword is equivalent to keyword=true.
The declaration of a keyword parameter looks very much like that of an optional ordered
parameter, except that all keyword parameter declarations are enclosed in braces, much like
a set is:
{ ... parameterName :: parameterType := defaultValue ... }
The :: parameterType can be omitted, in which case any value can be passed as the right-
hand side of the keyword argument. If parameterType is specified, then the passed value
must be of that type.
As is the case with an ordered parameter, if defaultValue is a literal value, it need not match
parameterType.
A procedure can have multiple keyword parameters, which can be declared within a single
set of braces, or grouped into multiple sets of braces as desired to improve source code
6.3 Parameter Declarations 215
readability. When a procedure is compiled into Maple's internal form, the keyword parameters
are consolidated into a single set. If you then display that procedure using Maple's print
command, the keyword parameters are displayed as a single set, sorted lexicographically.
The simplest and most frequently encountered form of keyword parameter declaration has
a single Maple symbol for parameterName:
> Simple := proc( { simple::integer := 2 } )
sprintf("simple=%d",simple)
end proc:
> Simple(simple=3);
(6.8)
> Simple();
(6.9)
> Simple(simple=4.5);
Error, invalid input: Simple expects value for keyword parameter simple
to be of type integer, but received 4.5
It is also possible to declare keyword parameters that can be referred to by indexed names
when the procedure is called. If parameterName is of the form `symbol[symbol]` or
`symbol[integer]`, it matches indexed names.
The indexed parameter names are still symbols because of the enclosing left single quotes,
and are referenced that way within the procedure, but the argument names can be actual
indexed names. For more information on indexed keyword arguments, see Binding of Argu-
ments to Parameters (page 242).
As a convenience to the user of a procedure, multiple spellings of the keyword are allowed
by specifying a list of the permitted spellings in the declaration:
{ ... [ parameterName1, parameterName2, ... ] :: parameterType :=
defaultValue ... }
Within the procedure's statementSequence, you can refer to the parameter by any of the
declared spellings. If you display the procedure using print, however, only the first spelling
is used.
216 6 Procedures
(6.10)
> Spellings();
(6.11)
> Spellings(color=BLUE);
(6.12)
> Spellings(colour=GREEN);
(6.13)
> Spellings(color=ORANGE,colour=PURPLE);
(6.14)
> Spellings(colour=YELLOW,color=42);
Error, invalid input: Spellings expects value for keyword parameter
[color, colour] to be of type symbol, but received 42
If more than one keyword argument matches a keyword parameter, only the last one takes
effect.
Alternate spellings and indexed keywords can be combined by including the indexed keyword
symbols in the list of alternate spellings.
A procedure can be declared to disallow extra arguments (that is, arguments that were not
bound to any declared parameter) by ending the sequence parameterDeclarations with $.
If extra arguments remain at the end of argument processing, Maple raises an exception:
> TwoSine := proc( x::float := 0.0, $ ) 2 * sin(x) end proc:
6.3 Parameter Declarations 217
> TwoSine(2.3);
(6.15)
> TwoSine();
(6.16)
> TwoSine(2.3,-4.5);
Error, invalid input: too many and/or wrong type of arguments passed
to TwoSine; first unused argument is -4.5
> TwoSine(42);
Error, invalid input: too many and/or wrong type of arguments passed
to TwoSine; first unused argument is 42
(6.17)
> SubList([a,b,c,d,e],2);
(6.18)
There can be no cyclic dependencies, such as two parameters' default values depending on
each other:
> NotGood := proc( s := sin(c), c := cos(s) ) s^2 + c^2 end proc;
Error, cyclic dependency detected in parameter s := sin(c) in procedure
NotGood
Usually, Maple evaluates the arguments of a function call from left to right. The use of
parameter dependencies in default values will alter this order to ensure that the required
218 6 Procedures
values are available by the time they are needed. This is only of consequence if the evaluation
of one or more arguments has side effects.
Parameter Modifiers
Parameter modifiers change the way that arguments are evaluated and/or bound to parameters.
Modifiers appear as part of the parameter's declaration, in the form of a function call enclos-
ing the parameter type parameterType.
You have already seen the expects modifier, which changes an optional ordered parameter
into an expected ordered parameter.
If no arguments match memberType, the parameter will receive its default value if one
was specified, or NULL if there is no default value.
The seq modifier cannot be used together with the expects modifier, because seq is allowed
to match zero arguments, whereas expects implies that at least one argument must match.
The seq modifier also cannot be used with a keyword parameter.
You must be careful when working with the value of a seq parameter because it might have
just one element in it. Such a value is not considered to be a sequence, thus indexing it will
not select the element. The safest approach is to enclose the parameter in a list, as in this
example:
> LargestInteger := proc( x::seq(integer), other::seq(anything) )
local max, n;
max := -infinity;
for n in [x] do
if n > max then max := n end if
end do;
max, [other]
end proc:
> LargestInteger(4,7,8,2,1);
(6.19)
6.3 Parameter Declarations 219
(6.20)
The syntax for a parameter declaration with the depends modifier is:
parameterName :: depends( parameterTypeExpression )
For example, you might want to write a procedure like this to find one root of a polynomial:
> OneRoot := proc( p::depends(polynom(integer,v)), v::symbol )
local sols;
sols := [ solve(p=0,v) ];
if sols = [] then
error "no solution"
else
sols[1]
end if
end proc:
> OneRoot(x^2+3*x+5,x);
(6.21)
> OneRoot(x^2+3*x+5,y);
Error, invalid input: OneRoot expects its 1st argument, p, to be of
type polynom(integer, y), but received x^2+3*x+5
This procedure expects as an argument for its first parameter, p, a polynomial in the variable
specified by the second parameter, v. If the depends modifier were omitted, the procedure
would only accept polynomials in the global variable v.
The depends modifier can only be used for required parameters. It cannot be used for op-
tional or expected ordered parameters, nor keyword parameters. If the depends modifier is
220 6 Procedures
used together with the seq modifier, it must appear within it. That is, parameterType must
be written in the form seq(depends(memberType)).
A parameter with the uneval modifier prevents the corresponding argument from being
evaluated when the procedure is called. The effect is the same as if the argument had been
enclosed in unevaluation quotes ('...').
The uneval modifier can only be used for required positional parameters, and cannot be
used in conjunction with any other modifiers. It also cannot be used for any parameter de-
claration after one that uses the seq modifier.
> Square := proc( x::uneval ) x^2 end proc:
> (a, b) := (3, 4.5):
> r := Square(a+b);
(6.22)
> eval(r);
(6.23)
parameterName :: evaln(valueType)
In effect, declaring a parameter with the evaln modifier is equivalent to enclosing the argu-
ment with evaln at procedure invocation time, and allows you to write procedures where
the user of the procedure does not have to remember to do so.
Like uneval, the evaln modifier can only be used for required positional parameters, and
cannot be used for a parameter declaration after one having a seq modifier. The only other
modifier that can be used together with evaln is the depends modifier, in the form de-
pends(evaln(valueType)).
6.3 Parameter Declarations 221
In the first call, the argument a is evaluated to 'a', which is a name with an integer value.
> SquareName(a);
(6.24)
In the next call, the argument b is evaluated to 'b', which is a name, but not with an integer
value.
> SquareName(b);
Error, invalid input: SquareName expects its 1st argument, x, to be
of type evaln(integer), but received b := 4.5
In the next example, the procedure Accumulate accumulates all the values of its second
argument in the variable passed as its first argument. Notice that the first call fails, because
Accumulate expects a name with a numeric value, but total has not been initialized yet.
> Accumulate := proc( r::evaln(numeric), n::numeric )
r := eval(r) + n
end proc:
> Accumulate(total,2);
Error, invalid input: Accumulate expects its 1st argument, r, to be
of type evaln(numeric), but received total := total
> total := 0;
(6.25)
> Accumulate(total,2);
(6.26)
> Accumulate(total,3.5);
(6.27)
> total;
(6.28)
222 6 Procedures
However, just because a procedure has no declared parameters does not mean that it cannot
be passed arguments. Unless a procedure's parameterDeclarations ends with $, it is always
permissible to pass more arguments than there are declared parameters. All of the arguments
are accessible via the special sequence _passed, which has one entry corresponding to each
argument that was passed. The number of entries is given by _npassed. For example, the
following procedure produces the sum of all its arguments:
> SumOfArgs := proc( )
add(_passed[i], i=1.._npassed)
end proc:
> SumOfArgs(42,3.14,sin(-2.5));
(6.29)
For more information on _passed and _npassed as well as other special names for working
with parameters, see Special Sequences for Referring to Parameters and Argu-
ments (page 236).
(6.30)
6.5 The Procedure Body 223
> ReturnInteger(Pi);
Error, (in ReturnInteger) assertion failed: ReturnInteger expects its
return value to be of type integer, but computed Pi^2
The description, option, local variable, and global variable declaration parts are each intro-
duced by their own keyword, and can appear in any order. There can be only one description
clause and one option clause. There can be any number of variable declaration clauses.
Description
Use the description clause to give a procedure a short description that is displayed when
the procedure is displayed. The description has no effect on the execution of the procedure.
It is only used for documentation purposes.
description string, string, ... ;
The description keyword is followed by one or more string literals, separated by commas.
> Average := proc( x::integer, y::integer )
description "Compute the average of two integers.",
"Returns a rational.";
(x + y) / 2;
end proc;
(6.31)
224 6 Procedures
Options
A procedure can be tagged with one or more options which alter the behavior or display of
the procedure. Options are specified by the keyword option or options, followed by one or
more option names or equations, separated by commas:
option optionNameOrEquation, ... ;
options optionNameOrEquation, ... ;
(6.32)
For information on defining a procedure using arrow notation, see Functional Operators:
Mapping Notation (page 265)
This option is shown when you display a purely built-in procedure. Instead of displaying
the procedure statements, only the builtin option is displayed.
(6.33)
A procedure can have both the builtin option and a statement sequence. In that case, invoking
the procedure will first invoke the kernel built-in version. If that indicated that it did not
compute a result, the statement sequence is executed instead. This mechanism allows the
6.5 The Procedure Body 225
kernel to process common cases very quickly, and defer to library code to handle other
cases.
You can use the type function to test if an expression is a built-in procedure. An expression
is of type builtin if it is a procedure with option builtin:
> type(add, 'builtin');
(6.34)
(6.35)
You cannot create built-in procedures, although there is a mechanism for creating procedures
based on externally compiled code. Such procedures have the call_external option.
The hfloat option causes the following differences in the procedure's definition and execu-
tion:
Any floating-point constants appearing in the procedure body are converted into hardware
floating-point values when the procedure is first created.
Numeric arguments passed to the procedure are converted into hardware floating point
values when the procedure is invoked.
Extracting values from hardware floating-point Arrays, Matrices, and Vectors does not incur
a conversion to arbitrary precision floating-point form. Instead, the hardware floating-point
values are used directly.
Calls to evalhf made from within the procedure return a hardware floating point value, and
thus do not incur a conversion to arbitrary precision floating-point form.
226 6 Procedures
These differences, together with the rules for contagion of hardware floating-point values
in expressions, will usually cause arithmetic operations in the procedure to be performed
using hardware floating-point arithmetic.
The use of the hfloat option differs from using evalhf in a few ways:
When a procedure is executed within the evalhf environment, everything is computed using
hardware floats, and the operations available are restricted to those that can be done using
hardware floats. No other basic data types, such as integers or strings, are available.
The only data structures available within the evalhf environment are Arrays.
Performance of a procedure having option hfloat is generally better than one operating with
arbitrary precision floats, but usually not as good as a procedure operating within evalhf.
But, a procedure with option hfloat has the full power of Maple available to it. All Maple
operations, data types (except arbitrary precision software floating point), and data structures
can be used in such a procedure.
The hfloat option cannot be used in conjunction with the builtin, call_external, or inline
options.
Not all Maple procedures can take advantage of the inline option. Only procedures whose
body consists of a single expression or an expression sequence can be expanded in-line.
The body cannot consist of a statement or statement sequence. For details on further restric-
tions that may apply, refer to the inline help page.
A sequence of procedures with option overload can be combined into a single procedure
using the overload command. This will produce a new procedure that will, when called,
6.5 The Procedure Body 227
try each overload procedure in turn until one is encountered that will accept the arguments,
or no procedures remain. In the latter case, an exception will be raised.
The following example uses the overload command and procedures with the overload option
to append an entry to either a list (by creating a new list) or a 1-dimensional Array (in-
place):
> Append := overload(
[
proc( L::list, x::anything ) option overload;
[ op(L), x ]
end proc,
proc( A::Array(..), x::anything ) option overload;
A(ArrayNumElems(A)+1) := x
end proc
]
):
> Append([1,2],3);
(6.36)
Option overload can also be used to specify that a procedure exported by a package is only
applied to arguments of specific type. If non-matching arguments are passed, the default
behavior occurs instead.
For example, you can define a new implementation of `+` that works only on set arguments.
The system default `+` operator is used for all other cases.
> SetOperations := module() option package;
export `+` := proc( a::set, b::set ) option overload;
a union b
end proc;
end module:
> with(SetOperations);
(6.37)
(6.38)
(6.39)
If a procedure has the procname option, then the value of the procname special name
within the procedure is inherited from the procedure that called it. If an error then occurs
within the called procedure, the error is reported as having occurred in the calling procedure.
This allows you, for example, to break up your procedure into sub-procedures, yet still have
any errors reported as if they occurred in your main procedure.
For more information on the uses of procname, see Returning Unevaluated (page 251) and
Recursion (page 269).
The remember option allows writing an inherently recursive algorithm in a natural manner
without loss of efficiency. For example, the Fibonacci numbers can be computed by the
procedure:
> Fibonacci := proc( n::nonnegint )
option remember;
if n < 2 then
n
else
Fibonacci(n-1) + Fibonacci(n-2)
end if
end proc:
Without the remember option, the time required to compute Fibonacci(n) is exponential
in n. With option remember, the behavior becomes linear. For a comparison of the efficiency
of this procedure with and without option remember, see Profiling a Procedure (page 623).
6.5 The Procedure Body 229
Entries can be explicitly inserted into a procedure's remember table by writing a function
call on the left-hand side of an assignment. For example, the Fibonacci procedure can be
written:
> Fibonacci := proc( n::nonnegint )
option remember;
Fibonacci(n-1) + Fibonacci(n-2)
end proc:
> Fibonacci(0) := 0:
> Fibonacci(1) := 1:
A procedure's remember table can grow without bound, and for some procedures, may
eventually contain many entries that will never be needed again. Adding the system option
to a procedure allows Maple's garbage collector to clear out the remember table whenever
garbage collection occurs. If a discarded result is needed again later, it will be recomputed.
As an alternative to remember tables, Maple also provides the cache option. Unlike a re-
member table, which can grow without bound, a cache has a maximum number of entries.
When the cache becomes full, old entries are removed as new ones are inserted.
The cache option can be specified as just the symbol cache, or with an optional argument,
in the form cache(N) where N is an integer specifying the size of the cache. If (N) is not
specified, the cache is sized to hold 512 entries.
You can explicitly insert permanent entries into a procedure's cache using the Cache:-Ad-
dPermanent function.
When the interface variable verboseproc is 3, displaying a procedure also displays the
contents of its remember table or cache as comments following the procedure definition:
> Fibonacci(7);
(6.40)
> interface(verboseproc=3):
> print(Fibonacci);
(6.41)
The remember and cache options are mutually exclusive, and the system option can only
be used in conjunction with the remember option.
230 6 Procedures
(6.42)
Variables in Procedures
A variable is a name representing an item of data, such as a numerical value, character
string, or list of polynomials. The value of the variable, that is, which data item it represents,
can change during the execution of a procedure (or sequence of Maple commands at the
top-level, outside of any procedure). There are three different classes of variables that can
be used within a procedure: global, local, and lexically scoped.
Global Variables
A global variable has meaning within an entire Maple session. Many procedures may access
a global variable, and all those procedures will refer to the same instance of that variable.
A value assigned to a global variable during one function call will still be there the next
time the procedure is called (if it was not changed by another procedure in the meantime).
6.5 The Procedure Body 231
Global variables are introduced by the global keyword, followed by one or more declarations:
global variableName := value, ... ;
The optional := value part is an assignment that is executed at the beginning of procedure
execution. Semantically, it is equivalent to writing a separate assignment statement imme-
diately after all the variable declaration clauses.
A global variable continues to exist and retain its value after the procedure exits, and con-
ceptually, existed (and possibly had a value) before the procedure was executed. Its lifetime
is thus the duration of the entire Maple session.
Local Variables
A local variable has meaning only within a particular procedure. If the same variable name
is referenced outside of the procedure or within a different procedure, it refers to a different
instance of that name, and is therefore a different variable.
The lifetime of a local variable is the time that the procedure is executing. The variable is
created when the procedure is first invoked, and is usually discarded when the procedure
has finished executing. If the same procedure is later executed again, a new instance of the
variable is created. The variable does not retain its value from its previous lifetime.
The optional :: typeAssertion assertion specifies that the variable is expected to always
refer to values of the specified type. Since this is an assertion, if kernelopts(assertlevel)
is set to 2, the type is checked every time a new value is assigned to the variable. If the value
is not of the specified type, an exception is raised.
The optional := initialValue ensures that the variable is assigned the specified value before
its first use. The initialValue can be any Maple expression. If the value is a literal expression
sequence, it must be enclosed in parentheses, since otherwise the comma separating the
elements of the sequence is interpreted as the comma separating individual variable declar-
ations.
every element has been divided by the element with the largest magnitude, and then raised
to a specified integer power:
> PowerList := proc( L::list, power::integer )
local largest := max(abs~(L));
map( proc(x) (x / largest) ^ power end proc, L )
end proc:
> PowerList([1,1/2,-3.14],2);
(6.43)
This example uses an anonymous nested procedure, declared directly within the expression
that uses it. Notice that this inner procedure refers to both of the symbols power and largest.
Because there are no variable or parameter declarations in the inner procedure that declare
these symbols, lexical scoping ensures that they are automatically bound to the corresponding
symbol in the outer procedure. In other words, power in the inner procedure refers to the
parameter power of the outer procedure, and largest in the inner procedure refers to the
local variable largest of the outer procedure.
Scoping Rules
If you want a variable to be local to a procedure or global, you should declare that variable
using a local or global declaration. Declaring the scope of variables makes it easier to debug
your code, and also makes it easier for someone else to understand your procedure.
On the other hand, if a variable is intended to refer to a parameter or local variable declared
in an enclosing procedure, you must not declare it in the enclosed procedure. Doing so
would defeat lexical scoping by making the variable local to the enclosed procedure, and
thus a different variable with no connection to the one in the enclosing procedure.
If an undeclared variable does not correspond to a parameter or declared variable in a sur-
rounding procedure, Maple determines its scope, and either automatically declare the variable
as local or assume that it is global. When the variable is automatically declared local, such
an implicit declaration generates a warning:
> ImplicitLocal := proc( x, y )
z := x + y;
if z < 0 then z^2 else z^3 end if
end proc:
Warning, `z` is implicitly declared local to procedure `ImplicitLocal`
6.5 The Procedure Body 233
If the variable appears on the left-hand side of an assignment statement or as the controlling
variable of a for loop, Maple adds the variable to the procedure's local declarations. This
means that if an enclosed procedure also refers to the variable (without declaration), lexical
scoping binds it to the implicitly declared variable of the enclosing procedure. If a procedure
in which such an implicit local declaration is displayed using the print function, the variable
appears within the procedure's local declaration clause.
Otherwise, Maple assumes the variable is global. However, the variable is not added to the
procedure's global declaration clause, which means that it is not subject to lexical scoping
if the same name is used within an enclosed procedure.
If the variable is declared as a parameter, local, or global variable in the procedure in which
the variable is encountered, the scope is specified by the declaration.
If the variable is not declared and there is a surrounding procedure (or module), the parameter,
local (including implicit local), and global declarations of the surrounding procedure are
examined. If the variable is found there, that binding is used. If it is not found, the search
continues outward through the layers of surrounding procedures.
If the top level (outside of any procedure or module) is reached, the usage of the variable
in the original procedure is examined. If it appears on the left-hand side of an assignment
or as the controlling variable of a for loop, it is added to the procedure's local declarations.
Otherwise it is assumed to be a global variable.
The uses keyword is followed by one or more bindings, in a form identical to those of the
use statement, introduced in The use Statement (page 201). These bindings are in effect over
the entire body of the procedure, in the same way they would be if the procedure body had
been enclosed in a use statement.
The uses clause must appear at the top of the procedure body, together with the option,
description, and initial local and global declarations. If you wish to bind names in a subset
of the procedure body, use a use statement instead.
234 6 Procedures
Consider this example, which tries to use a parameter on the left-hand side of an assignment
statement:
> Add2 := proc( x, y )
x := x + y
end proc:
> Add2(3,4);
Error, (in Add2) illegal use of a formal parameter
A parameter can be used on the left-hand side of an assignment if the value of the parameter
is a name. The evaln parameter modifier can ensure that this is the case. Here is an example
you saw earlier:
> Accumulate := proc( r::evaln(numeric), n::numeric )
r := eval(r) + n
end proc:
> total := 0;
(6.44)
> Accumulate(total,2);
(6.45)
6.5 The Procedure Body 235
> Accumulate(total,3.5);
(6.46)
> total;
(6.47)
Here, the parameter r evaluates to the name `total`, an assignable object. Although it appears
that an assignment to the parameter r is being made within the procedure, it is really the
value of r, which in this case is the global variable total, that is being assigned to.
Required Parameters
Recall that a required parameter is one for which a corresponding argument must have
been passed if the parameter is used during the execution of the procedure. Failure to pass
an argument for a required parameter only raises an exception if an attempt is made to use
that parameter during the particular invocation of the procedure.
For example, a procedure may determine, based on the value of its first required parameter,
that it does not have to refer to the second required parameter.
> Require := proc( x::integer, y::integer )
if x < 0 then x^2 else x * y end if
end proc:
> Require(-3);
(6.48)
> Require(3,4);
(6.49)
> Require(3);
Error, invalid input: Require uses a 2nd argument, y (of type integer),
which is missing
To do anything with a seq parameter other than pass it on to another procedure, you should
convert the parameter value to a list and then work with the list:
> AddAndMax := proc( x::seq(numeric) )
local a := 0, i;
for i in [x] do
a := a + i
end do;
a, max(x)
end proc:
Without the [] brackets around x, this procedure would produce unexpected results if called
with a single floating-point number. A for var in expr loop iterates over the operands of
expr. If expr is a sequence of two or more numbers, it works as expected, but if expr were
a single float, the loop would iterate over the floats operands (the significand and exponent).
By enclosing x in a list, the loop will always iterate over the arguments bound to x.
Keyword Parameters
Keyword parameters also have declared default values, so using the parameter always yields
a value. Unlike ordered parameters, keyword parameters receive their values from arguments
of the form keyword=value. The value of a keyword parameter is the value portion of such
an argument, not the entire argument.
The special names _params and _nparams can be used within a procedure to refer to the
current values of the positional and ordered parameters. The _params symbol represents
6.5 The Procedure Body 237
The _params symbol can only be used when immediately followed by an index enclosed
in square brackets, _params[indexExpr]. It cannot be used in any other context. indexExpr
can evaluate to one of the following:
An integer, N, in the range 1 to _nparams, or -_nparams to -1. This is just the selection
operation on the sequence _params. It yields the value of the Nth parameter when N > 0,
or the (_nparams+1+N)th parameter when N < 0 (negative integers index _params from
the end instead of the beginning). If no argument was passed for the requested parameter
and no default was declared, the result is NULL.
A range of such integers. This yields an expression sequence of values, with any NULL
values omitted. A sequence of all the non-NULL positional and ordered parameter values
can be obtained using _params[..]. Note that due to elision of NULLs, this could produce
fewer than _nparams values.
The following example multiplies or divides the last three positional parameters by the first,
depending on the value of the keyword parameter multiply:
> MulDiv := proc( a, b, c, d, { multiply := true } )
if multiply then
_params[-3..] * a
else
_params[-3..] / a
end if
end proc:
MulDiv(100,1,2,3);
MulDiv(100,1,2,3,multiply=false);
(6.50)
Just as _params and _nparams can be used to work with positional and ordered parameters
in a flexible manner, _options and _noptions provide similar facilities for working with
keyword parameters (often called keyword options).
238 6 Procedures
If a keyword parameter was declared with multiple spellings, the corresponding member
of _options uses the first spelling.
Unlike _params, _options can be used directly, not only through the selection of members
of the sequence. Because _options returns a sequence of equations, even a member corres-
ponding to an argument with a NULL value is non-NULL. It is an equation of the form
keyword=NULL.
When _options is used with an index, the index must evaluate to an integer (or a range of
integers) in the range 1 to _noptions or -(_noptions) to -1, or the unevaluated name of a
keyword parameter.
The order of the equations in _options does not necessarily correspond to the order in which
the keyword parameters were declared. Instead, the equations are in lexicographic order by
keyword (the first spelling for keyword parameters with multiple spellings). This is the
same order in which the keyword parameters are printed when the procedure is displayed
by the print command. As a consequence of this, if a new keyword parameter is added to
the procedure definition, the numeric index of the _options entry corresponding to a partic-
ular keyword parameter could change. Thus, when indexing _options, it is safest to use the
_options['parameterName'] form.
The following example uses _options to pass all the keyword arguments on to another
procedure:
> MyRanMat := proc( a::integer, {density::float := 1.0, generator
:= 0..0.5} )
LinearAlgebra:-RandomMatrix(a, _options)
end proc:
> MyRanMat(2, density=0.75, generator=1..9);
(6.51)
(6.52)
6.5 The Procedure Body 239
The next example selects specific keyword arguments to pass to another procedure:
> MulRanMat := proc( a::integer, {density::float := 1.0, generator
:= 0..0.5, mult := 1.0} )
mult * LinearAlgebra:-RandomMatrix(a, _options['density'],
_options['generator'])
end proc:
> MulRanMat(4, density=0.75, generator=1..9, mult=x/2);
(6.53)
When there are more arguments in a function call than needed to match the called procedure's
parameters, you can access the remaining arguments inside the procedure by using the
special sequence _rest. The number of members in this sequence is given by _nrest.
Because these extra arguments do not correspond to any declared parameters, it is not possible
for such an argument to have a NULL value. Recall that the only way for a parameter to
be NULL is for no argument to have matched a parameter with no declared default value
(or a default value of NULL). Since there is no declared parameter corresponding to any
value in _rest, these conditions cannot hold.
This example uses _rest and _nrest to return the number of entries in a sequence of numbers,
together with the maximum, and optionally the mean:
> MaxMean := proc( {mean := false})
if mean then
_nrest, max(_rest), Statistics:-Mean([_rest])
else
_nrest, max(_rest)
end if
end proc:
> c := MaxMean(6,200,400, mean=true);
(6.54)
240 6 Procedures
All of the arguments that were passed to a procedure can be accessed using the special se-
quence _passed, having _npassed elements.
Prior to Maple version 10, _passed and _npassed were known as args and nargs. These
older names are still accepted as synonyms for the newer names for backwards compatibility.
Of historical interest, the earliest versions of Maple did not support declared parameters at
all; args and nargs were the only mechanism for processing arguments.
The _passed sequence can be used to do explicit argument processing within the body of
the procedure, although this is discouraged for two reasons:
Most argument processing requirements can be handled using the mechanisms described
so far in this chapter. Doing so is usually significantly faster (in terms of both execution
time and development time) than performing the same operations using your own custom
argument processing algorithms within the procedure.
When special argument processing requirements do arise, it is often easier to work with
_params, _options, and _rest. In many cases, the provided mechanisms can handle most
of the processing, and it is only necessary to look at _rest to handle additional arguments.
The clearest and most efficient way to write a procedure to find the maximum of an arbitrary
sequence of numbers is to use a single parameter with a seq modifier, and pass that parameter
directly to Maple's built-in max function. However, the following example uses _passed,
_npassed, and a for loop instead for demonstration purposes:
> Maximum := proc( )
local max := _passed[1], i;
for i from 2 to _npassed do
if _passed[i] > max then
max := _passed[i]
end if
end do;
max
end proc:
Care must be taken when the _options, _rest, or _passed sequences contain only a single
entry and that sequence is assigned to a variable (for example, myOpts := _options). The
variable will receive the value of that single element rather than an expression sequence.
The safest way to use these expression sequences is to transform them into lists (for example,
myOpts := [_options]).
6.6 How Procedures Are Executed 241
(6.55)
During automatic simplification, the division 0/2 has been removed (because it does not
contribute to the sum). More significantly, the entire if...then...else...end if statement has
been replaced by just the body of the first branch, since the if-condition is true.
Notice that the expression sqrt(x * 2.0 / 3.0) has not been simplified to .8164965809*x^(1/2)
as it would have been if entered at the top level, outside of a procedure. If this simplification
had been performed, then the result produced by the procedure would depend on the setting
of Digits (and other aspects of the floating-point environment) both when the procedure
was simplified, and a possibly different setting of Digits when the procedure is later executed.
By not performing any floating-point arithmetic during procedure simplification, the pro-
cedure will depend only on the state of the floating-point environment at execution time.
A procedure is executed after it has been invoked by a function call. Generally, the process
is:
All of the procedure's local variables are instantiated. That is, for each local variable, a
unique instance of the variable's name is created, with no prior value.
Interpretation continues until the last statement has been executed, an exception is raised
(either as the result of an operation, or by an explicit error statement), or a return statement
is encountered.
Whenever a matching keyword parameter is encountered, the right-hand side of the equation
becomes the value for that parameter, and the equation is removed from further consideration
as an argument. If more than one keyword argument matches a keyword parameter, only
the last one takes effect.
Keyword parameter names (the keyword part) are Maple symbols like any other. If that
symbol is in use as a variable, then using it in a keyword argument may not work as expected
since the variable may evaluate to its value. To ensure that this does not happen, it is best
to always use unevaluation quotes around the keyword part of a keyword argument:
> f := proc( x::integer, { y::integer := 1 }, $ ) x * y end proc:
> y := sin(z):
> f(3,y=2);
Error, invalid input: too many and/or wrong type of arguments passed
to f; first unused argument is sin(z) = 2
> f(3,'y'=2);
(6.56)
This is a good practice when calling any function, whether it is a procedure you defined or
a Maple command. See Protecting Names and Options (page 53).
6.6 How Procedures Are Executed 243
When calling a procedure that accepts a keyword argument from within another procedure
that has a parameter with the same name as the keyword argument, you must use both une-
valuation quotes and the scope resolution operator, :-, to ensure that (the global instance
of) the name itself is used instead of the value of the parameter:
> f := proc( x::integer, { y::integer := 1 }, $ ) x * y end proc:
> g := proc( y::rational ) f(numer(y), ':-y'=denom(y)) end proc:
> g(3/2);
(6.57)
If a keyword parameter has a declared parameterType for which true is a valid value (for
example, the types truefalse or boolean), the keyword name alone is interpreted as a syn-
onym for keyword=true.
> f := proc( x::integer, { square::truefalse := false } )
if square then x^2 else x end if
end proc:
> [ f(2), f(3,square=true), f(4,square) ];
(6.58)
axis_label[1] = "time"
Keyword arguments with multiple indices are also recognized by attempting to match them
using one index at a time. For example, the keyword argument,
axis_label[1,2] = ""
(6.59)
> Indexed(name[1]="bonjour",name[2]="aurevoir");
(6.60)
(6.61)
> Indexed(name[2]=42);
Error, invalid input: Indexed expects value for keyword parameter
`name[2]` to be of type string, but received 42
If the parameter has no parameterType, the argument matches trivially, and becomes the
value for that parameter.
If the parameter has a parameterType specification, the argument may or may not match.
If it matches, the argument becomes the value for that parameter. If it does not match, an
exception is raised.
> Accumulate := proc( r::evaln(numeric), n::numeric,
{ operation::symbol := `+` } )
r := operation(eval(r),n)
end proc:
> total := 0:
> Accumulate(total, 2.3);
(6.62)
6.6 How Procedures Are Executed 245
(6.63)
In the last call, an exception is raised because the first argument does not evaluate to a name.
Matching is done by traversing the parameter declarations from left to right. As each para-
meter is examined, an attempt is made to match it to the next unused argument as follows:
If the parameter has no parameterType, the argument matches trivially, and becomes the
value for that parameter.
If the parameter has parameterType, but no defaultValue, the argument may or may not
match. If it matches, the argument becomes the value for that parameter. If it does not match,
an exception is raised.
If the parameter has both parameterType and defaultValue, the argument may or may not
match. If it matches, the argument becomes the value for that parameter. If it does not match,
the parameter receives its default value, and the argument remains available for matching
a subsequent parameter.
In last two cases above, if the parameter's type uses the seq modifier, Maple continues to
match additional arguments against the parameter until one is encountered that is not of the
correct type. A seq parameter never results in an exception, because even if no arguments
match, a valid sequence has been produced (the empty sequence).
At the end of this process, if there are any arguments left over, they are either put into the
_rest sequence, or, if the procedure was declared with the end-of-parameters marker, $, an
exception is raised.
Conversely, if all the arguments were bound to parameters, but there are parameters remain-
ing to be assigned values, these receive their default values if they have one. Otherwise,
they have no value, and attempting to use them (by name) within the procedure raises an
exception.
246 6 Procedures
For example, an assignment statement is interpreted by evaluating the right-hand side (the
expression to be assigned), and resolving the left-hand side (the target of the assignment).
The latter involves evaluating any indices if the left-hand side contains indexed names. Fi-
nally, the value of the right hand side is assigned to the resolved variable on the left-hand
side.
When there are no further statements remaining, Maple behaves as if a return statement
had been encountered.
(6.64)
> x := z^2 / y;
(6.65)
> z := y^3 + 3;
(6.66)
6.6 How Procedures Are Executed 247
Since these statements undergo normal full recursive evaluation, the following result is re-
turned:
> f;
(6.67)
The same sequence of steps within a procedure would yield a different result:
> OneLevelEval := proc( )
local f, x, y, z;
f := x + y;
x := z^2 / y;
z := y^3 + 3;
f
end proc:
> OneLevelEval();
(6.68)
The concept of one-level evaluation is unique to symbolic languages like Maple, where the
value of a variable can be, or include, the name of another variable. One-level evaluation
avoids arbitrarily deep computation at every step of a procedure and is thus important for
efficiency. It has very little effect on the behavior of procedures, because most procedures
have a sequential structure. When full evaluation of a local variable is required within a
procedure, use the eval function:
> FullEval := proc( )
local f, x, y, z;
f := x + y;
x := z^2 / y;
z := y^3 + 3;
eval(f)
end proc:
> FullEval();
(6.69)
In addition to illustrating one level evaluation, this example also introduces the idea of an
escaped local. The expression returned by OneLevelEval is x + y and contains the symbols
x and y. However, these are not the global variables of the same names; they are the local
x and y declared in OneLevelEval. Because these variables have escaped, they continue
248 6 Procedures
to exist beyond their normal lifetime even though the procedure has finished executing.
Usually, an escaped local indicates a programming error such as forgetting to assign a value
to a local variable before using it. There are situations where letting a local escape can be
useful, such as generating unique instances of a name that will be guaranteed never to
evaluate further.
Except when a procedure raises an exception, a value is always returned. In the absence of
an explicit return statement, the returned value is the value of the last statement executed
in the procedure. The value of a statement means:
The value of the last statement executed within the branches of an if statement or within
the body of a loop.
Note that NULL is a valid expression (and thus a valid statement). A procedure that returns
NULL is still returning a value, although at the interactive level, nothing is displayed.
You can use an explicit return statement to end the execution of the procedure and return
a value immediately:
return expression;
Upon encountering a return statement during execution, Maple evaluates the expression,
and then immediately terminates the execution of the procedure, with the result of the
evaluation as the returned value.
This example uses an explicit return statement to immediately return the position i of a
value x in a list when the value is found. If the value is not found, the procedure returns 0:
> Position := proc( x::anything, L::list )
local i;
for i to numelems(L) do
if x = L[i] then
return i
end if
end do;
6.6 How Procedures Are Executed 249
0
end proc:
> Position(3, [2,3,5,7,1,3,7,9,3,9]);
(6.70)
(6.71)
The following procedure computes the greatest common divisor, g, of two integers a and
b. It returns the expression sequence g, a/g, b/g. The case a = b = 0 is treated separately
because in that case, g is zero:
> GCD := proc( a::integer, b::integer, $ )
local g;
if a = 0 and b = 0 then
return 0, 0, 0
end if;
g := igcd(a,b);
g, iquo(a,g), iquo(b,g)
end proc:
> GCD(0,0);
(6.72)
(6.73)
This example illustrates that you can return a sequence of values from a procedure, and that
those values can then be assigned to a sequence of names by the caller. Whenever a procedure
returns a sequence of values, the result can be assigned to a sequence of the same number
of names (a multiple assignment). If you assigned the result to a single name, then the value
of that name would be the entire sequence.
g
else
g, iquo(a,g), iquo(b,g)
end if
end proc:
> div := GCD(12,8);
(6.74)
> GCD(12,8) ^ 2;
(6.75)
> { GCD(12,8) };
(6.76)
(6.77)
The _nresults variable has the value undefined if the procedure was called from within an
expression or within the arguments of another function call. It has an integer value if the
call was from the top level of an expression appearing on the right-hand side of an assign-
ment. The value of _nresults is the number of variables on the left-hand side of the assign-
ment statement.
Do not use _nresults in a procedure with the remember or cache options. Only the first
computed result is stored in the remember table or cache. Subsequent calls with the same
input but a different number of expected results will not return the expected number of
results. (The Cache package can be used to manually implement a simulated remember
table that works correctly in conjunction with _nresults.)
Another alternative for returning more than one value from a procedure is to assign values
to variables whose names were passed in as values. The following procedure determines
whether a list L contains an expression of type T. If found, the procedure returns the index
of the (first matching) expression. If the procedure is called with a third argument, then it
also assigns the expression to that name.
> FindType := proc( T::type, L::list, V::evaln, $ )
local i;
for i to numelems(L) do
if L[i] :: T then
if _npassed = 3 then
V := L[i]
6.6 How Procedures Are Executed 251
end if;
return i
end if
end do
end proc:
> FindType(string, [2,3/4,"Hello",x+y]);
(6.78)
(6.79)
> s;
(6.80)
When FindType was called with two arguments, the procedure just returned the index of
the found list element.
When called with three arguments, parameter V received the name, not the value of global
variable s. The evaln declaration of V ensures that V will always refer to a name. Just before
returning, the procedure assigned the found expression to s, as referenced by V.
If, during the execution of the procedure, you need to refer to the value that has been assigned
to a name via an evaln parameter, enclose such references to the parameter within a call to
eval:
> Accumulate := proc( r::evaln(numeric), n::numeric )
r := eval(r) + n
end proc:
Returning Unevaluated
If a procedure cannot perform a requested computation, it can return the unevaluated form
of the function call that invoked it. For example, the procedure below computes the larger
of two values if it can, or returns unevaluated if it cannot:
> Larger := proc( x, y )
if x :: numeric and y :: numeric then
if x > y then
x
else
y
end if
else
252 6 Procedures
'Larger'(x,y)
end if
end proc:
> Larger(3.2, 2);
(6.81)
(6.82)
The unevaluation quotes around Larger within the procedure specify that the function call
expression will be constructed, but no procedure invocation will take place (therefore this
is not a recursive call).
The returned unevaluated function call can later be re-evaluated. If a and b have numeric
values at that time, Larger will return a number, otherwise it will return unevaluated once
again.
> a, b := 3, 2;
(6.83)
> r;
(6.84)
Because of one level evaluation, the last line in the example above would have to be written
as r := eval(r) if r were a local variable in a procedure.
Rather than using the procedure's name to construct an unevaluated function call to return,
you can also use the special name procname. The statement, 'Larger'(x,y) could have been
written 'procname'(x,y). The advantage to using procname is that such unevaluated returns
are immediately apparent to anyone reading the source code of your procedure.
Note that if your procedure was called from within another procedure and has the procname
option, then an unevaluated call of the form 'procname'(x,y) refers to the procedure that
invoked your procedure.
6.6 How Procedures Are Executed 253
By writing procedures to return unevaluated when it is not possible to carry out the compu-
tation, you make it easier for the user of the procedure to use it in contexts where otherwise
it would produce an error:
> plot( Larger(x, 1/x), x = 1/2 .. 2 );
(6.85)
If Larger had been implemented without the unevaluated return, both of the above commands
would have failed because the first argument to plot and int could not have been evaluated:
> LargerNoUneval := proc( x, y )
if x > y then
x
else
y
end if
end proc:
> plot( LargerNoUneval(x, 1/x), x = 1/4 .. 2 );
Error, (in LargerNoUneval) cannot determine if this expression is true
or false: 1/x < x
254 6 Procedures
Many Maple functions use the technique of returning unevaluated. For example, the sin
and int functions return a result when they can, or return unevaluated when it is not yet
possible to compute a result.
Passing arguments by value ensures that the procedure cannot modify the passed data as a
side-effect, but requires making a copy of the data. Passing by reference is more efficient
for large data objects, but allows the procedure to (possibly unintentionally) modify the
caller's copy of the data.
In Maple, data is always passed by reference, but the immutability of most data types ensures
that the procedure cannot modify the caller's copy of the data. The exceptions are Maple's
mutable data structures: tables, Arrays, Matrices, Vectors, records, and objects. Modifying
these within a procedure will modify the caller's copy. Fortunately, these larger data structures
are the ones that you would most often want to pass by reference, since copying such data
consumes time and space.
This does not contradict the earlier statement that Maple always passes by reference, because
it is now the variable name that is being passed by reference.
When returning a table or procedure from a procedure, care must be taken to ensure that it
is the data structure itself and not the name referring to it that is returned. This is because
tables and procedures use last name evaluation.
> IncorrectListToTable := proc( L :: list )
local T := table(), i;
for i to numelems(L) do
T[i] := L[i]
end do;
T
end proc:
> IncorrectListToTable(["String",123,Pi]);
(6.86)
The example above returns the local variable T instead of the actual table. Although the
returned value can be used as if it were the actual table, every access to it involves an extra
level of addressing behind the scenes, thus consuming more time.
> ListToTable := proc( L :: list )
local T := table(), i;
for i to numelems(L) do
T[i] := L[i]
end do;
eval(T)
end proc:
> ListToTable(["String",123,Pi]);
(6.87)
256 6 Procedures
A common problem is to write a procedure that computes the average of n data values ,
Before writing the procedure, think about which data structure and Maple functions to use.
You can represent the data for this problem as a list. The numelems function returns the
total number of entries in a list X, while the ith entry of the list is obtained by using X[i]:
> X := [1.3, 5.3, 11.2, 2.1, 2.1];
(6.88)
> numelems(X);
(6.89)
> X[2];
(6.90)
(6.91)
Using these ideas, write the procedure Average which computes the average of the entries
in a list. It handles empty lists as a special case:
> Average := proc( L::list, $ )
local n := numelems(L), i, total;
if n = 0 then
error "empty list"
end if;
total := add(i,i=L);
total / n
end proc:
6.7 Using Data Structures with Procedures 257
Using this procedure you can find the average of list X defined above:
> Average(X);
(6.92)
(6.93)
A list is a good choice for the data in this example because the data is stored and used in a
calculation, but the list itself does not need to be modified.
Unfortunately, if the dictionary is large, this approach can take a long time. You can reduce
the execution time required by sorting the dictionary before you search it. If you sort the
dictionary into ascending order, then you can stop searching as soon as you encounter a
word greater than w. On average, it is still necessary to search half the dictionary.
Binary searching provides an even better approach. Check the word in the middle of the
sorted dictionary. Since the dictionary is already sorted, you can determine whether w is in
258 6 Procedures
the first or the second half. Repeat the process with the appropriate half of the dictionary
until w is found, or it is determined not to be in the dictionary.
> BinarySearch := proc( D::list(string), w::string )
local low := 1, high := numelems(D), mid;
while low <= high do
mid := trunc((low + high) / 2);
if w < D[mid] then
high := mid - 1
elif w > D[mid] then
low := mid + 1
else
return true
end if
end do;
false
end proc:
> Dictionary := [ "induna", "ion", "logarithm", "meld" ];
(6.94)
(6.95)
(6.96)
(6.97)
You can make use of this to write a procedure that plots the complex roots of a polynomial.
For example, consider the polynomial .
> y := x^3-1;
(6.98)
First, find the roots of this polynomial. You can find the numeric roots of this polynomial
by using fsolve. By enclosing the call to fsolve in square brackets, you create a list of the
roots.
> R := [ fsolve(y=0, x, complex) ];
(6.99)
Next, change this list of complex numbers into a list of points in the plane. The Re and Im
functions return the real and imaginary parts of a complex number respectively. You can
use the map function and an anonymous procedure to convert the entire list at once.
260 6 Procedures
(6.100)
You can automate the process by writing a procedure that follows the same sequence of
steps. The input must be a polynomial in x with constant coefficients.
> RootPlot := proc( p::polynom(constant,x) )
description "Plots the roots of a polynomial in x";
local R := [ fsolve(p, x, complex) ];
local points := map( z -> [Re(z), Im(z)], R );
plot(points, style=point, symbol=point, color=black)
end proc:
Generate a random polynomial using the randpoly function, and then test the RootPlot
procedure again.
> y := randpoly(x, degree=100);
(6.101)
262 6 Procedures
> RootPlot( y );
Procedures are more easily readable if written with one statement per line, and with the
statements enclosed within the bodies of loops and if-statements indented:
> BinarySearch := proc( D::list(string), w::string )
local low := 1, high := numelems(D), mid;
while low <= high do
mid := trunc((low + high) / 2);
if w < D[mid] then
high := mid - 1
elif w > D[mid] then
low := mid + 1
else
return true
end if
end do;
false
end proc:
Sometimes, a single statement is too long to fit on a single line. Maple's syntax allows you
to insert line breaks and white space between any two syntactic tokens such as reserved
words, variable names, numbers, and punctuation. Indentation can be used within a statement
to clarify the grouping of expressions. For example, the polynomial root plotting procedure
could have been written like this:
> RootPlot := proc( p::polynom(constant,x) )
description "Plots the roots of a polynomial in x";
plot(map(z -> [Re(z), Im(z)],
[fsolve(p, x, complex)]),
style=point, symbol=point, color=black)
end proc:
In this version of RootPlot, the procedure body consists of a description and a single
statement. The indentation makes it clear that z -> [Re(z), Im(z)] and [fsolve(p, x, complex)]
are arguments of the call to map, and that the result of this call together with the style,
symbol, and color options are the arguments of plot.
A # character anywhere within a procedure except inside a "string" or `quoted name` in-
troduces a comment. Everything following # until the end of the line is considered to be a
comment and is ignored by Maple. This form is useful for short comments of one or two
lines, or to annotate a line.
> Average := proc( )
# Compute total.
local total := add(_passed[i],i=1.._npassed);
# Divide total by number of values.
total / _npassed;
end proc;
(6.102)
Comments enclosed in (* and *) can begin and end anywhere except within a "string" or
`quoted name`. Everything between the delimiters is ignored by Maple. This form can be
used within a line or to write a multiline comment.
> BetterAverage := proc( )
(* This procedure computes the average of its
arguments. It is an error if no arguments were
passed. *)
if _npassed = 0 then
error "too few values"
else
add(_passed[i],i=1.._npassed) (*TOTAL*) / _npassed
end if
end proc;
(6.103)
Notice that comments are discarded by Maple when the procedure is simplified. Comments
are purely for the benefit of the programmer(s) who write, read, and maintain the procedure.
6.9 Other Methods for Creating Procedures 265
As described earlier, a procedure in Maple can also have a description section. One or more
strings can follow the description keyword. Like comments, these have no effect on the
execution of the procedure, but they are retained when the procedure is simplified.
> AnotherAverage := proc( )
description "Compute the average of one or more values.",
"At least one value must be passed.";
if _npassed = 0 then
error "too few values"
else
add(_passed[i],i=1.._npassed) / _npassed
end if
end proc:
You can use Maple's Describe command to print a procedure's declared parameters, return
type, and description.
> Describe(AnotherAverage);
> Describe(RootPlot);
The parameterSequence can be empty, and the expression must be a single expression or
an if statement.
266 6 Procedures
(6.104)
If the procedure requires only a single parameter, you can omit the parentheses around
parameterSequence:
> G := n -> if n < 0 then 0 else 1 end if;
(6.105)
Internally, a procedure created using operator notation is the same as any other procedure,
except that it will have options operator, arrow. You can invoke such a procedure in the
usual way:
> F(1,2);
(6.106)
> G(-1);
(6.107)
You can use declared parameter types when defining a functional operator:
> H := ( n::even ) -> n! * (n/2)!;
(6.108)
> H(6);
(6.109)
> H(5);
Error, invalid input: H expects its 1st argument, n, to be of type
even, but received 5
The arrow notation is designed for simple one-line function definitions. It does not provide
a mechanism for specifying local or global variables, options, a description, or more than
a single statement. If these are required, use the more general proc...end proc notation.
(6.110)
(6.111)
> F(3,4);
(6.112)
The functional operator notation (or arrow notation) is a syntax for writing an operator. The
unapply function is a function mapping expressions to procedures. Use the unapply function
to create a procedure from an expression that was computed instead of one that was entered.
This works because unapply first evaluates the expression and then encloses the result in
a procedure. The arrow notation always produces a procedure containing the expression
that was entered.
> IntExpr := int(1/(x^3+1), x);
(6.113)
(6.114)
> IntFunc(3.5);
(6.115)
If you had tried to use operator notation to create the IntFunc procedure, you would not
get what you expected:
> BadIntFunc := x -> evalf(IntExpr);
(6.116)
268 6 Procedures
> BadIntFunc(3.5);
(6.117)
Notice that the result still contains the symbol x. This is because the x appearing in IntExpr
is the global variable x, not the parameter x of BadIntFunc.
Anonymous Procedures
Recall from the beginning of this chapter that a procedure is a valid Maple expression, in-
dependent from any name that it may have been assigned to. You can in fact create, manip-
ulate, and invoke a procedure without ever assigning it to name. Such procedures are called
anonymous.
(6.118)
(6.119)
Syntactically, this is a Maple function call. Instead of specifying the procedure to call by
giving its name, the procedure is given directly. The same method can be used to directly
call a procedure defined using the proc...end proc notation:
> proc( x, y ) x^2 + y^2 end proc (u, v);
(6.120)
(6.121)
They are also used to initialize Arrays in Arrays (page 155). You can find numerous other
examples of anonymous procedures in this guide.
6.10 Recursion 269
(6.122)
> F := D( exp + 2 * ln );
(6.123)
> F(x);
(6.124)
6.10 Recursion
A procedure is termed recursive if it contains a call to itself, either directly, or indirectly
through another procedure that it calls. In order for a recursive procedure to produce a result,
it must test for some condition under which the recursion terminates. Otherwise, it would
go on calling itself forever (until Maple runs out of stack space).
You have already seen one example of recursion used to compute Fibonacci numbers in
The remember, cache, and system Options (page 228). Another well-known example of re-
cursion is the computation of the factorial of an integer. For any integer , the
factorial (denoted by ) is defined by . For , is defined to be
equal to . This definition naturally lends itself to a recursive implementation:
> Fact := proc( n::nonnegint, $ )
if n > 0 then
n * Fact(n-1)
else
1
end if
end proc;
(6.125)
270 6 Procedures
> Fact(0);
(6.126)
> Fact(4);
(6.127)
> Fact(-4);
Error, invalid input: Fact expects its 1st argument, n, to be of type
nonnegint, but received -4
Rather than using the name to which the procedure has been assigned to make the recursive
call, you can also use procname or thisproc. This ensures that the recursion continues to
work even if the procedure body is later assigned to a different name. The special symbol
procname refers to the name that the procedure was called with. In the Fact example,
procname would be equivalent to Fact. The symbol thisproc on the other hand refers to
the procedure itself. Calling the procedure recursively using thisproc is slightly more effi-
cient, and works within anonymous procedures.
This example uses an anonymous version of the Fact procedure above to compute the
factorials of a list of numbers:
> map( n -> if n > 0 then n * thisproc(n-1) else 1 end if,
[0, 1, 2, 3, 4] );
(6.128)
The BinarySearch procedure you saw earlier also lends itself to a recursive implementation.
> BinarySearch := proc( D::list(string), w::string,
low::integer := 1, high ::integer :=
numelems(D) )
local mid;
if low > high then
# Nothing left to search. Word is not in list.
false
else
mid := trunc((low + high) / 2);
if w < D[mid] then
# Search within the left part of the range.
thisproc(D,w,low,mid-1)
elif w > D[mid] then
6.11 Procedures that Return Procedures 271
(6.129)
(6.130)
(6.131)
(6.132)
You use this procedure by passing it a sorted list of strings and a word to search for. The
two optional parameters, low and high, specify which range of list elements to search and
have default values specifying the entire list. After determining that the word is lexicograph-
ically less than or greater than the middle value, this procedure calls itself recursively,
passing the list and word, as well as appropriate values for the low and high parameters to
restrict the search. The recursion (and thus the search) ends when the procedure is asked to
search a zero-length section of the list (in which case the word was not found), or when the
middle element of the specified range contains the word.
If your procedure has the procname option, any attempt to make a recursive call via proc-
name instead of thisproc calls the procedure that invoked your procedure.
You can write procedures that return procedures too. This section discusses how values are
passed from the outer procedure to the inner procedure.
272 6 Procedures
Choose a point on the -axis that you think might be close to a root.
Draw the tangent to the curve at that point and observe where the tangent intersects the
-axis. For most functions, this second point is closer to the real root than the initial guess.
Use the new point as a new guess and repeat this process.
where is the initial guess, and is the result of the kth iteration.
The following procedure takes a function and creates a new procedure which expects an
initial guess and, for that particular function, generates the next guess. The new procedure
6.11 Procedures that Return Procedures 273
is specific to the function that it was generated for, and does not work for other functions.
To find the roots of a new function, use MakeIteration to generate a new iterating procedure.
> MakeIteration := proc( expr::algebraic, x::name )
local iteration := x - expr / diff(expr, x);
unapply(iteration, x);
end proc:
The procedure returned by the MakeIteration procedure maps the name x to the expression
assigned to the iteration.
(6.133)
(6.134)
The generated procedure, which is assigned to iter, returns the solution, after a few
iterations.
> x0 := 2.0:
> to 4 do x0 := iter(x0); print(x0) end do:
(6.135)
Observe that the MakeIteration procedure above requires its first argument to be an algeb-
raic expression. You can also write a version of MakeIteration that works on other proced-
ures (such as functional operators).
> MakeIteration := proc( f::procedure )
(x->x) - eval(f) / D(eval(f));
end proc:
274 6 Procedures
This example uses Maple's ability to treat expressions containing procedures as procedures.
The result of calling this version of MakeIteration is an expression with procedures as
operands (x->x is just a procedure that maps any value to itself).
Because of last name evaluation, MakeIteration will accept either a procedure or a name
whose value is a procedure. The calls to eval within MakeIteration ensure that the result
refers to the actual procedure that was passed in, instead of to the name of that procedure.
> g := x -> x - cos(x);
(6.136)
(6.137)
Note that the procedure generated by the call to MakeIteration is independent of the name
g (because of the aforementioned calls to eval). Thus, you can later change g without
breaking iter. You can find a good approximate solution to in a few itera-
tions.
> x0 := 1.0;
(6.138)
(6.139)
Consider the problem of writing a procedure that takes a function, , as input and returns
a function, , such that . You can write such a procedure like this:
> ShiftLeft := ( f::procedure ) -> ( x -> f(x+1) ):
> ShiftLeft(sin);
(6.140)
Maple lexical scoping rules declare the f within the inner procedure to be the same f as the
parameter of the outer procedure. Therefore, the ShiftLeft procedure works as written.
The previous example of ShiftLeft works with univariate functions but it does not work
with functions of two or more variables.
> h := (x,y) -> x*y;
(6.141)
> hh := ShiftLeft(h);
(6.142)
> hh(x,y);
Error, (in hh) invalid input: h uses a 2nd argument, y, which is
missing
(6.143)
> hh(x,y);
(6.144)
Because the ShiftLeft procedure does not call eval on parameter f, the function hh depends
on h. Changing the value assigned to h implicitly changes hh:
> h := (x,y,z) -> y*z^2/x;
(6.145)
> hh(x,y,z);
(6.146)
276 6 Procedures
(6.147)
(6.148)
(6.149)
(6.150)
(6.151)
The procedure type is a structured type (see Structured Types (page 130)). Using a structured
type allows you to verify that a name refers to a procedure, and additionally verify the spe-
cified types of the procedure's parameters.
> G := proc( n::integer, s::string )
print(s);
2 * n * length(s)
end proc:
> type(G, procedure(integer,string));
(6.152)
6.12 The Procedure Object 277
Procedure Operands
Every Maple procedure has eight operands, corresponding to sub-parts of the procedure
definition. The following table lists each operand and the corresponding op call that can be
used to access it. In the table, the name P represents the name of the procedure, and the
eval call is necessary so that op will be passed the procedure, not the name (because proced-
ures have last name evaluation).
Operand op Command
Parameters op(1,eval(P))
All local variables op(2,eval(P))
Options op(3,eval(P))
Remember table op(4,eval(P))
Description op(5,eval(P))
Declared global variables op(6,eval(P))
Lexical scoping table op(7,eval(P))
Return type op(8,eval(P))
The value of any operand can be a single item, an expression sequence if there are two or
more items (such as local variables), or NULL if there were no items (for example, no op-
tions).
The lexical scoping table is an internal structure that records the correspondence between
undeclared variables within the procedure and locals (or exports), globals, and parameters
of surrounding procedures (or modules). It does not correspond to any part of the procedure
as written.
The procedure's statement sequence is not one of the operands of the procedure, and thus
cannot be extracted by op. This is because statements and statement sequences are not ex-
pressions, and thus cannot be assigned to names or otherwise manipulated.
The following nested procedure illustrates how the parts of the procedure map to the oper-
ands. Note that this example refers to the procedure that appears within, and is returned by
procedure MakeProc (in order to illustrate lexical scoping). This procedure is not intended
to illustrate good programming style, but merely provide an example showing all the possible
operands.
> MakeProc := proc( offset::integer )
description "Create and return a procedure";
proc( n::integer, s::string ) :: integer;
description "An example to illustrate procedure operands";
option remember;
278 6 Procedures
> P := MakeProc(3):
> P; # The name of the procedure
(6.153)
(6.154)
(6.155)
(6.156)
(6.157)
6.13 Exercises 279
(6.158)
(6.159)
(6.160)
(6.161)
(6.162)
(6.163)
6.13 Exercises
2. You can use to compute the least common multiple of two integers, a and b, where
g is the greatest common divisor of a and b. For example, the least common multiple of
4 and 6 is 12. Write a Maple procedure, LCM, which takes as input n>0 integers ,
, ... , and and computes their least common multiple. By convention, the least
common multiple of zero and any other number is zero.
3. Write a Maple procedure called Sigma which, given n>1 data values, , , ..., ,
computes their standard deviation. The following equation gives the standard deviation
of n>1 numbers, where is the average of the data values.
280 6 Procedures
4. Write a Maple procedure which, given a list of lists of numerical data, computes the
mean of each column of the data.
5. Write a Maple procedure called Position which returns the position i of an element x in
a list L. That is, Position(x,L) should return an integer i>0 such that L[i]=x. If x is not
in list L, 0 is returned.
7 Numerical Programming in Maple
An important part of efficient scientific and mathematical programming is numerical com-
putation. Maple provides many tools for computing with floating-point numbers, some for
improving efficiency and some for improving accuracy.
Integers
The most basic numeric type in Maple is the integer. Small integers are represented directly
as hardware integers (similar to the int type in C or integer type in Fortran), which allows
for maximum efficiency of both CPU time used for arithmetic and memory used for storage.
That is, the number can be stored in one machine word and arithmetic operations can be
performed with one CPU operation. On 32-bit architectures, integers in the range
to are stored in this way, while on 64-bit architectures, integers in the range
Larger integers are stored in DAGs of type INTPOS or INTNEG, which contain pointers
to arrays of digits that can store integers up to magnitude on 32-bit architectures
INTPOS(6): 1208925819614629174706175
281
282 7 Numerical Programming in Maple
> dismantle(-2^101+6);
INTNEG(6): -2535301200456458802993406410746
The arithmetic for these large integers is computed using the GNU Multiple Precision
Arithmetic (GMP) library. This library is quite efficient, but still several orders of magnitude
slower than arithmetic on immediate integers since each arithmetic operation will require
more than one CPU operation and the larger the integer, the more operations and memory
will be needed for arithmetic.
> CodeTools:-Usage(add(i,i=-2^15..2^16));
memory used=160.49KiB, alloc change=127.98KiB, cpu time=7.00ms, real
time=7.00ms
(7.1)
> CodeTools:-Usage(add(i,i=2^88-2^15..2^88+2^16));
memory used=8.92MiB, alloc change=9.00MiB, cpu time=23.00ms, real
time=33.00ms
(7.2)
> CodeTools:-Usage(add(i,i=2^4097-2^15..2^4097+2^16));
memory used=109.33MiB, alloc change=65.61MiB, cpu time=147.00ms, real
time=151.00ms
(7.3)
Any transitions between GMP integers and immediate integers will be completely transparent
and it is not possible to tell them apart in general without use low-level tools such as ad-
dressof. However, you can check if an integer is small enough to fit into a single machine
word with types integer[4] and integer[8] for 4-byte and 8-byte words respectively.
> Integer(-2^160);
(7.4)
> Integer(infinity);
(7.5)
> Integer(1/2);
(7.6)
The system dependent value for the largest immediate integer can be found with kernel-
opts(maximmediate), the maximum number of decimal digits in an integer can be found
with kernelopts(maxdigits), and the version of the GMP library being used can be found
with kernelopts(gmpversion).
Rationals
Exact rational numbers are stored in DAGs of type RATIONAL, which consist of a pair of
integers. The first integer is the numerator and can be a POSINT or NEGINT. The second
integer is the denominator and is a POSINT. Most low-level programming languages such
as C or Fortran do not have an equivalent rational number type.
> dismantle(1/2);
RATIONAL(3): 1/2
INTPOS(2): 1
INTPOS(2): 2
> dismantle(-10/3);
RATIONAL(3): -10/3
INTNEG(2): -10
INTPOS(2): 3
Rational numbers can be constructed by using the division operator or the Fraction con-
structor. In either case, automatic simplification will occur to ensure that the denominator
is positive and that the fraction is in lowest terms (the numerator and denominator do not
have factors in common). This means that the Fraction constructor may return integers in
some cases.
284 7 Numerical Programming in Maple
> dismantle(Fraction(21,7));
INTPOS(2): 3
> dismantle(Fraction(40,-14));
RATIONAL(3): -20/7
INTNEG(2): -20
INTPOS(2): 7
Rational number arithmetic is performed in the natural way using integer arithmetic and
the igcd and ilcm operations to reduce to lowest terms.
> Fraction(2^20+2^12,2^27-2^13) + Fraction(2^12-1,2^13);
(7.7)
(7.8)
Rational numbers of all types will pass a type,rational type check. Only rational numbers
that are not also integers will pass a type,fraction type check. Additionally, type,exten-
ded_rational includes all rationals as well as the extended numeric symbols infinity, -in-
finity, and undefined.
> type(1, fraction);
(7.9)
Like the Integer constructor, the Fraction constructor will return unevaluated if it cannot
return a value of type extended_rational.
> Fraction(x,1);
(7.10)
> Fraction(infinity);
(7.11)
7.2 Numeric Types in Maple 285
Floating-Point Numbers
Floating-point numbers are stored in DAGs of type FLOAT.
(7.12)
Maple floating-point numbers can also be constructed with the SFloat constructor (or the
equivalent Float constructor) and can be checked with the nearly equivalent type,sfloat
and type,float types. We will generally refer to these numbers as sfloats to when we need
to distinguish them from hardware floating-point numbers (hfloats), introduced below.
> Float(1);
(7.13)
> dismantle(SFloat(0.3333));
FLOAT(3): .3333
INTPOS(2): 3333
INTNEG(2): -4
(7.14)
(7.15)
(7.16)
A floating-point number represents a rational number with a fixed precision. That rational
number can be recovered with convert/rational.
> convert(.3333333333, rational, exact);
(7.17)
286 7 Numerical Programming in Maple
However, not every rational number can be represented exactly by a floating-point number.
For example, the closest floating-point number to is .
(7.18)
Also, unlike numeric types integer and rational, integer and float do not have compatible
arithmetic. Floating-point arithmetic has a fixed finite precision, and does round off if the
result of arithmetic does not fit into that precision.
> 9123456789 + 8123456789 <> convert( 9123456789. + 8123456789.,
rational, exact);
(7.19)
(7.20)
Unlike many other programming languages the precision of sfloat arithmetic can be changed.
For this reason, sfloats are known as arbitrary precision floating-point numbers.
More information on sfloats and how they differ from the floating-point types in languages
such as C and Fortran will be discussed in greater detail in More about Floating-Point
Numbers in Maple (page 290).
(7.21)
> dismantle(HFloat(0.3333));
HFLOAT(2): .3333
7.2 Numeric Types in Maple 287
The advantage of hfloats over sfloats is that their arithmetic is computed directly using a
single CPU operation for each arithmetic operation. Maple sfloats, however, offer much
more flexibility and precision. In many ways the difference is analogous to the difference
between immediate integers and GMP integers.
Hardware floats can be distinguished from sfloats with the type,hfloat type.
> type(HFloat(1), float);
(7.22)
(7.23)
(7.24)
(7.25)
For more information on hardware floats and how they differ from sfloats, see More about
Floating-Point Numbers in Maple (page 290).
(7.26)
> 1/2*infinity;
(7.27)
> 1/infinity;
(7.28)
The undefined symbol is usually produced as the result of attempting to carry out an oper-
ation that cannot result in a number for the given operands. Almost every arithmetic operation
involving undefined returns undefined. For details, refer to the type,undefined help page.
288 7 Numerical Programming in Maple
> infinity-infinity;
(7.29)
> undefined-undefined;
(7.30)
> undefined+1;
(7.31)
Integer and rational numbers share exact undefined and infinite symbols while sfloat and
hfloat numbers have their own versions of these, which are displayed differently but treated
similarly.
> Float(infinity);
(7.32)
> HFloat(undefined);
(7.33)
Complex Numbers
A complex number in Maple is a DAG of type COMPLEX, which consists of a pair of any
of the two numeric types. They can be constructed in the natural way using the symbol
for the imaginary unit, or using the Complex constructor.
> dismantle(1+I);
COMPLEX(3)
INTPOS(2): 1
INTPOS(2): 1
7.2 Numeric Types in Maple 289
> dismantle(Complex(1/2,1/3));
COMPLEX(3)
RATIONAL(3): 1/2
INTPOS(2): 1
INTPOS(2): 2
RATIONAL(3): 1/3
INTPOS(2): 1
INTPOS(2): 3
Automatic simplification will ensure that if one of the parts of a complex number is a float
(or hfloat), then other will be made into a float (hfloat).
> dismantle(Complex(1., 1/1001));
COMPLEX(3)
FLOAT(3): 1.
INTPOS(2): 1
INTPOS(2): 0
FLOAT(3): .9990009990e-3
INTPOS(2): 9990009990
INTNEG(2): -13
COMPLEX(3)
HFLOAT(2): 1.
HFLOAT(2): .000999000999
COMPLEX(3)
HFLOAT(2): 1.
HFLOAT(2): 1.
Complex numbers are not of type type,numeric but can be checked with type type,complex
which can also be structured to check for the numeric subtypes of its two components.
> type(1+I,numeric);
(7.34)
290 7 Numerical Programming in Maple
> type(1+I,complex(integer));
(7.35)
Non-numeric Constants
Many Maple expressions represent constants, but are not considered to be of type numeric.
This means that arithmetic performed on these constants will be more generic symbolic
operations on DAGs of type SUM, PROD, NAME, or FUNCTION. Some examples of non-
numeric constants are Pi ( ), , , , and .
(7.36)
(7.37)
The dismantle command shows that the two numbers and have different internal
representations. is simply stored as an integer while is stored as a pair of integers.
> dismantle(1);
INTPOS(2): 1
> dismantle(1.);
FLOAT(3): 1.
INTPOS(2): 1
INTPOS(2): 0
7.3 More about Floating-Point Numbers in Maple 291
Similarly, the numbers and are also different even though they are both stored as
pairs of integers.
> dismantle(1/2);
RATIONAL(3): 1/2
INTPOS(2): 1
INTPOS(2): 2
> dismantle(0.5);
FLOAT(3): .5
INTPOS(2): 5
INTNEG(2): -1
In Maple, the FLOAT DAG-type represents a floating-point number in the form S * 10^E
where both S and E are integers. For , the significand (or mantissa) is and the
exponent is . In addition to being specified in decimal notation, floats of this form
can be constructed by using scientific notation, or the Float constructor.
> Float(2,0);
(7.38)
> 2*1e0;
(7.39)
The advantage of using this significand-exponent representation is that fixed precision ap-
proximations of large and small numbers can be stored compactly and their arithmetic can
be done efficiently. Storing the integer 10^50 needs at least 167 bits or 3 words on a 64-bit
machine. The floating-point number 1e50 can be stored in less than 8 bits but in in practice
uses 2 words (one for each integer).
> dismantle(10^50);
INTPOS(8): 100000000000000000000000000000000000000000000000000
292 7 Numerical Programming in Maple
> dismantle(1e50);
FLOAT(3): .1e51
INTPOS(2): 1
INTPOS(2): 50
Using two immediate integers, a float can store a much larger range of numbers than a ra-
tional number with two immediate integers. The range a rational can represent is about
while a float can represent a range of about .
This is a much larger range for the same storage cost. Of course, that larger range means
that floats of a fixed size can represent fewer numbers in that range. And since floating-
point numbers are always of a fixed size, this means that arithmetic will always be of limited
precision. That is, each operation will have to round the result to a number that can be rep-
resented as another floating-point number.
In Maple, the significand is limited to 10 decimal digits of precision by default but can be
changed while the exponent is restricted to being a word-sized integer.
More information on the restrictions on the size of software floats in Maple can be found
by using the Maple_floats command.
By contrast, hfloats, are represented in base 2, rather than base 10. So they represent numbers
using the form S * 2^E, where the significand, S, is a 52-bit integer and the exponent, E,
is a 10-bit integer. Thus, the range of numbers representable as hardware floats is
. Because the largest possible significand of a
hardware float has about base-10 digits of precision, hardware
floats can be converted to software floats without meaningful loss of precision when Digits
is 15. Conversely, so long as their exponent is smaller than 307 and their significand had
fewer than 15 digits sfloats can be converted to hfloats without loss of precision.
For example, storing 10^50+1 requires 50 decimal digits so it will be rounded in floating-
point arithmetic. By contrast, 10^50+10^41 can be stored with 10 digits so it will still be
computed accurately.
> .1e51 + 1.;
(7.40)
(7.41)
The Digits environment variable can be used to change the working precision used by Maple.
Larger values of Digits will allow more accurate computation, but at the cost of slower
arithmetic.
> Digits := 100:
> .1e51 + 1.;
(7.42)
The maximum value for Digits is system dependent and can be found with the Maple_floats
command.
> Maple_floats(MAX_DIGITS);
(7.43)
For the default value of Digits, the significand is an immediate integer and so arithmetic
will be fast in general. It also means that some numerical function evaluations (such as sin
in the following example) will be able to use the CPU's native hardware floating-point
arithmetic to achieve the needed precision. However, raising Digits about the default value
will lead to slower arithmetic and slower function evaluation.
> Digits:=10:
> CodeTools:-CPUTime(add(sin(1e-6*i),i=1..100000));
(7.44)
> Digits:=22:
> CodeTools:-CPUTime(add(sin(1e-6*i),i=1..100000));
(7.45)
294 7 Numerical Programming in Maple
Reducing Digits below its default value does not usually lead to large improvements in
performance.
> Digits:=5:
> CodeTools:-CPUTime(add(sin(1e-6*i),i=1..100000));
(7.46)
It is also important to note that changing Digits does not necessarily change the accuracy
of sequences of multiple floating-point computations; it changes only the precision of the
individual operations performed. The following example computes two additions using 10
digits of precision, but catastrophic cancellation leads to a mere one digit of accuracy in
the final answer.
> Digits := 10:
> x := 1234567890.;
(7.47)
> y := -x+1;
(7.48)
> z := 3.141592654;
(7.49)
> x+z+y<>z+1;
(7.50)
Ensuring accuracy requires careful study of the problem at hand. In this example, you need
19 digits of precision to get 10 digits of accuracy.
> Digits := 19:
> x+z+y=z+1;
(7.51)
Floating-Point Contagion
An important property of floating-point numbers in Maple, and nearly every other computing
environment, is contagion. When numerical expressions are created involving both floating-
point numbers and exact numbers, the floating property is contagious and causes the answer
to become a floating-point number.
7.3 More about Floating-Point Numbers in Maple 295
> 1. * 10;
(7.52)
> 0. + 10;
(7.53)
As you can see, this contagion property can be used as a quick method to convert exact
values to floating-point numbers. However, while floating-point contagion extends to all
Maple structures of type numeric (except, in some cases, hfloats), it does not apply to non-
numeric constants.
> type(3/4, numeric);
(7.54)
(7.55)
> 1.*sqrt(3);
(7.56)
The hfloat type is also contagious, but the precise behavior of the contagion is determined
by the UseHardwareFloats environment variable. By default, hfloats are contagious for
small values of Digits:
> type(4/3 + HFloat(0.), hfloat);
(7.57)
(7.58)
(7.59)
For large values of Digits, hfloats in computations will be converted to sfloats so that the
results are sfloats.
> Digits := 20;
(7.60)
296 7 Numerical Programming in Maple
(7.61)
(7.62)
(7.63)
(7.64)
> a := 10.^19+1;
(7.65)
> b := a + HFloat(0.1);
(7.66)
(7.67)
(7.68)
(7.69)
> c := 1 + HFloat(0.1);
(7.70)
7.3 More about Floating-Point Numbers in Maple 297
(7.71)
(7.72)
The IEEE 754 standard defines five rounding algorithms. Two methods called nearest and
simple round to nearest values, and the other three are directed roundings that round up or
down (as needed) towards , , or 0. Maple implements all of these rounding modes
and the desired mode can be selected by setting the Rounding environment variable.
> Rounding;
(7.73)
> 1.4^10;
(7.74)
> Rounding := 0;
(7.75)
> 1.4^10;
(7.76)
298 7 Numerical Programming in Maple
Another important feature of this system is that the floating-point representation 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 that have a discontinuity across the negative real axis, such as , 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 monitoring events and their associated status
flags. For more information about this system, refer to the numerics help page.
(7.77)
You can alter the number of digits of the approximation by changing the value of the envir-
onment variable Digits, or by specifying the number as an index to evalf (which leaves the
value of Digits unchanged).
> Digits := 20:
> evalf(Pi);
(7.78)
> evalf[200](Pi);
(7.79)
> evalf(sqrt(2));
(7.80)
7.4 Maple Commands for Numerical Computing 299
Remember that the Digits command specifies the precision in decimal digits, unlike hardware
floating-point numbers which specify precision in binary digits.
All floating-point computations are performed in finite precision, with intermediate results
generally being rounded to Digits precision. As such, it is possible for round-off errors to
accumulate in long computations. Maple ensures that the result of any single floating-point
arithmetic operation (+, -, *, /, or sqrt) is fully accurate. Further, many of the basic functions
in Maple, such as the trigonometric functions and their inverses, the exponential and logar-
ithmic functions, and some of the other standard special functions for mathematics, are ac-
curate to within .6 units in the last place (ulps), meaning that if the Digits + 1st digit of the
true result is a 4, Maple may round it up, or if it is a 6, Maple may round it down. Most
mathematical functions in Maple, including numerical integration, achieve this accuracy
on nearly all inputs.
It is possible to create software floats with different precisions. Changing the value of Digits
will not change these numbers; it affects only the precision of subsequent operations on
those numbers.
> Digits := 50;
(7.81)
> a := evalf(Pi);
(7.82)
(7.83)
> a;
(7.84)
> a+1;
(7.85)
> evalf(a);
(7.86)
300 7 Numerical Programming in Maple
From this example, you can see that evalf can be used to create a lower precision float from
one of higher precision. This can be used to round a result to a desired number of digits.
However, evalf will not increase the precision of a low precision float.
> evalf[100](1.0);
(7.87)
> evalf[10000](a);
(7.88)
The evalf command also provides an interface to purely numerical computations of integrals,
limits, and sums.
(7.89)
> value(r);
(7.90)
> evalf(r);
(7.91)
In other cases, Maple can find an exact solution, but the form of the exact solution is almost
incomprehensible. The function in the following example is a special function that
appears in mathematical literature.
> q := Int( x^99 * (1-x)^199 / Beta(100, 200), x=0..1/5 );
(7.92)
7.4 Maple Commands for Numerical Computing 301
> value(q);
(7.93)
> evalf(q);
(7.94)
The two previous examples use the Int command rather than int for the integration. If you
use int, Maple first tries to integrate the expression 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. In general,
the symbolic computation is more difficult, and thus slower than the numerical computation.
> evalf( int(x^99 * (1-x)^199 / Beta(100, 200), x=0..1/5) );
(7.95)
Similarly, evalf can be used on the inert forms Limit and Sum to compute using numerical
algorithms for computing numeric limits and sums.
> evalf(Limit(sin(erf(1)*x)/(erf(1)^2*x),x=0));
(7.96)
(7.97)
302 7 Numerical Programming in Maple
(7.98)
It would have been computed correctly with non-float values in the input.
> evalf( limit(n*(1/3 - 1/(3+1/n)), n=infinity) );
(7.99)
Numeric Solvers
There are also a number of numerical algorithms available in Maple in commands other
than evalf. One of the most important is fsolve which is short for floating-point solve. This
command computes numerical solutions to equations or systems of equations. In general,
it is much more efficient than calling evalf on the result of solve, especially if you are inter-
ested in only a single solution.
> fsolve( exp(x) + 2*sin(x), x);
(7.100)
The fsolve command is a sophisticated heuristic that chooses among many different al-
gorithms depending on the input. There are several more special purpose solving tools
available in the RootFinding package.
Several symbolic solvers in Maple also have numeric modes. The dsolve and pdsolve
commands both accept a numeric option, which indicates that a numerical answer should
be computed using purely numeric methods. For extensive information on these numeric
commands, refer to the dsolve/numeric and pdsolve/numeric help pages.
FLOAT(3): .333333333333333315
INTPOS(2): 333333333333333315
INTNEG(2): -18
The evalhf command is affected by the value of Digits, but since intermediate calculations
are done with hfloats, at most 18 digits will be returned.
> Digits := 100;
(7.101)
> evalhf(1/3) ;
(7.102)
Notice that in this example the result is only correct to 16 digits. In general, the results from
evalhf are guaranteed to 15 digits of accuracy.
To find the number of guaranteed digits for your version of Maple, use evalhf(Digits):
> evalhf(Digits);
(7.103)
In fact, evalhf is, despite superficial similarities, very different from evalf. The evalhf
command uses a completely separate evaluation environment which uses only simple types
rather that the Maple DAG types. This means that it can be very fast, but at the cost of being
limited in the types of computations it can perform.
> Digits := 15;
(7.104)
> c := 10.^14;
(7.105)
(7.106)
304 7 Numerical Programming in Maple
(7.107)
> c := HFloat(c);
(7.108)
(7.109)
In particular evalhf only handles a specific list of functions. For the list of functions that
evalhf recognizes, refer to the evalhf/fcnlist help page.
> evalhf(sin(exp(gamma+2)+ln(cos(Catalan))));
(7.110)
> evalhf( b /3 );
Error, cannot handle unevaluated name `b` in evalhf
evalhf works with Arrays of hardware floats. It cannot handle symbols, lists, sets, and most
other Maple data structures.
> evalhf(map(t->t+1, [1, 2, 3, 4]));
Error, unable to evaluate expression to hardware floats: [1, 2, 3, 4]
To create an Array of hardware floats, you can use the option datatype=float[8], which
specifies that the elements in the Array are 8-byte hardware floats.
> A := Array([1, 2, 3, 4], datatype=float[8]);
(7.111)
(7.112)
7.4 Maple Commands for Numerical Computing 305
You can also create an Array that can be used by evalhf by using the constructor hfarray.
Both constructors create an Array of hardware floats. The only difference is that hfarray
defaults to C_order instead of Fortran_order.
> A := hfarray(1..4, 1..4, (i,j)->ithprime(i)*isqrt(3*(i+j)));
(7.113)
> lprint(A);
Array(1 .. 4, 1 .. 4, {(1, 1) = HFloat(4.), (1, 2) = HFloat(6.), (1,
3) = HFloat(6.), (1, 4) = HFloat(8.), (2, 1) = HFloat(9.), (2, 2) =
HFloat(9.), (2, 3) = HFloat(12.), (2, 4) = HFloat(12.), (3, 1) =
HFloat(15.), (3, 2) = HFloat(20.), (3, 3) = HFloat(20.), (3, 4) =
HFloat(25.), (4, 1) = HFloat(28.), (4, 2) = HFloat(28.), (4, 3) =
HFloat(35.), (4, 4) = HFloat(35.)}, datatype = float[8], storage =
rectangular, order = C_order)
User-defined Maple procedures can be evaluated in the evalhf environment as long as they
comply with the restrictions outlined in the evalhf/procedure help page.
> SlowPower := proc(a,n) local i, s; s:=1; for i to n do s := a*s;
end do; end proc;
(7.114)
> SlowPower(2,10);
(7.115)
(7.116)
For example, floating-point Matrix times Matrix products can been computed very quickly
in the BLAS libraries and quickest dispatch to the BLAS commands will happen if the
Matrices are created with datatype=float[8].
> A := Matrix(5^3,5^3,(i,j)->(i-j+1)/(i+j));
(7.117)
> CodeTools:-Usage(A^2);
memory used=0.58GiB, alloc change=64.24MiB, cpu time=3.22s, real
time=3.24s
(7.118)
(7.119)
> CodeTools:-Usage(Ahf^2);
memory used=332.30KiB, alloc change=0 bytes, cpu time=0ns, real
time=4.00ms
(7.120)
Of course, many of the linear algebra commands will try to determine if you have a Matrix
of low precision floats and will convert to the appropriate datatype automatically. In the
7.4 Maple Commands for Numerical Computing 307
next example, Af has datatype=anything, but the result of Af^2 has datatype=float[8]
and requires only a small, but noticeable, copy and conversion overhead.
> Af := Matrix(5^3,5^3,(i,j)->(i-j+1.)/(i+j));
(7.121)
> CodeTools:-Usage(Af^2);
memory used=0.64MiB, alloc change=0 bytes, cpu time=10.00ms, real
time=13.00ms
(7.122)
(7.123)
> Ahf[1,1];
(7.124)
If a Matrix contains only floats, but does not have a datatype=float[8] restriction, then ad-
dition of symbolic elements results in the more expensive symbolic commands to be used.
308 7 Numerical Programming in Maple
(7.125)
> CodeTools:-Usage(Af^2);
memory used=358.73MiB, alloc change=48.24MiB, cpu time=3.36s, real
time=3.38s
(7.126)
Another advantage of float[8] is that these Matrices are stored in the same way as an hfarray
which is analogous to an array of floats in the C or Fortran programming languages and
different from a Matrix of datatype=anything or datatype=sfloat which are arrays of
Maple DAGs each of which will take more memory than a single 8-byte float. Note the
difference in memory used in the following two examples.
> restart;
> CodeTools:-Usage(Matrix(10^3,3*10^3,(i,j)->10.^4*j+j,
datatype=sfloat));
memory used=0.80GiB, alloc change=114.35MiB, cpu time=6.71s, real
time=6.72s
(7.127)
> restart;
7.5 Writing Efficient Numerical Programs 309
> B1:=CodeTools:-Usage(Matrix(10^3,3*10^3,(i,j)->10^4*j+i,
datatype=float[8]));
memory used=218.62MiB, alloc change=190.34MiB, cpu time=2.76s, real
time=2.76s
(7.128)
It is also important to note that elements extracted from a float[8] rtable will be of type
hfloat and so hfloat contagion will apply subject to the current setting of UseHardware-
Floats.
> type(B1[1,1], hfloat);
(7.129)
There are also many optimized commands for Matrices of complex hfloats. These Matrices
can be created using the option datatype=complex[8], and work similarly to those of
datatype=float[8].
If you are constructing very large Matrices in your programs, use the ArrayTools package
to construct and copy Matrices as efficiently as possible.
In the following example, the procedure myevalf takes an unevaluated parameter, expr.
Without the uneval declaration, Maple would evaluate expr symbolically before invoking
myevalf.
310 7 Numerical Programming in Maple
The evalhf command evaluates many Maple functions, but not all. For example, you cannot
evaluate an integral using hardware floating-point arithmetic.
> myevalf( Int(exp(x^3), x=0..1) );
Error, (in myevalf) unable to evaluate function `Int` in evalhf
You can improve the procedure myevalf so that it traps such errors and tries to evaluate the
expression using software floating-point numbers instead.
> myevalf := proc(expr::uneval)
if Digits < evalhf(Digits) then
try
return evalf(evalhf(expr));
catch:
end try;
end if;
return evalf(expr);
end proc:
> myevalf( Int(exp(x^3), x=0..1) );
(7.130)
This procedure provides a model of how to write procedures that use hardware floating-
point arithmetic whenever possible.
The myevalf procedure returns sfloats. A version that returns hfloats is easiest to write using
the hfloat procedure option. This option will cause the procedure to use hfloat arithmetic
as much as possible so long as digits less than 15. In particular it convert all floats in the
procedure definition into hfloats, and causes evalhf to not convert its output to an sfloat.
> myevalf := proc(expr::uneval)
option hfloat;
if Digits < evalhf(Digits) then
try
return evalhf(expr);
catch:
end try;
7.5 Writing Efficient Numerical Programs 311
end if;
return evalf(1. * expr);
end proc:
The multiplication by was added to the evalf return line to induce hfloat contagion
causing the output to be an hfloat when possible.
> type( myevalf( Int(exp(x^3), x=0..1) ), hfloat);
(7.131)
For more information on the hfloat option, see The hfloat Option (page 225) or refer to the
option_hfloat help page.
The procedure iterate takes a function, f, its derivative, df, and an initial approximate
solution, x0, as input to the equation . The procedure iterate calculates at most N
successive Newton iterations until the difference between the new approximation and the
previous one is small. The procedure prints the sequence of approximations to show suc-
cessive 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;
return xnew;
end proc:
312 7 Numerical Programming in Maple
The following procedure calculates the derivative of f and passes all the necessary inform-
ation to iterate.
> Newton := proc( f::procedure, x0::numeric, N::posint:=15, $ )
local df;
df := D(f);
print(x0);
return iterate(f, df, x0, N);
end proc:
(7.132)
(7.133)
This version of Newton uses sfloats unless the arguments passed in are hfloats. If you add
option hfloat to the procedure iterate, then hfloats are used automatically, provided the
value of Digits is small enough.
> iterate := proc( f::procedure, df::procedure,
x0::numeric, N::posint, $ )
option hfloat;
local xold, xnew;
xold := 1. * x0;
xnew := 1. * 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;
7.5 Writing Efficient Numerical Programs 313
return xnew;
end proc:
Now the procedure Newton will return hfloats instead of sfloats when Digits is less than
15.
> type( Newton(f, 1.5), hfloat);
(7.134)
In this case, the procedure is simple enough that we can go beyond option hfloat and use
the evalhf command to achieve best performance. This next version of Newton uses evalhf
for floating-point arithmetic if possible and reverts to sfloats otherwise. Since iterate only
tries to find a solution to an accuracy of 10^(1-Digits), Newton uses evalf to round the
result of the hardware floating-point computation to an appropriate number of digits.
> Newton := proc( f::procedure, x0::numeric, N::posint:=15, $ )
local df, result;
df := D(f);
print(x0);
if Digits < evalhf(Digits) then
try
return evalf( SFloat( evalhf(iterate(f, df, x0, N)) ));
catch:
end try;
end if;
return evalf( SFloat( iterate(f, df, x0, N) ) );
end proc:
Newton uses hardware floating-point arithmetic for the iterations and rounds the result to
software precision. Hardware floating-point numbers have more digits than the software
floating-point numbers, given the present setting of Digits.
314 7 Numerical Programming in Maple
(7.135)
Newton must use software floating-point arithmetic to find a root of the following Bessel
function.
> F := z -> BesselJ(1, z);
(7.136)
(7.137)
Software arithmetic is used because evalhf does not recognize BesselJ and the symbolic
code for BesselJ uses the type command and remember tables, which evalhf does not allow.
> evalhf( BesselJ(1, 4) );
Error, unsupported type `('complex')('float')` in evalhf
Using a try-catch block (as in the previous Newton procedure) allows the procedure to
work when evalhf fails.
The previous Newton procedure prints many digits when it is trying to find a ten-digit ap-
proximation. The reason is that the print command is located inside the iterate procedure,
7.5 Writing Efficient Numerical Programs 315
which is inside a call to evalhf, where all numbers are hardware floating-point numbers,
and print as such.
Here we construct a random Matrix that is strongly diagonally dominant to test the procedure.
Note that, while in practice Jacobi iteration would not be used on dense Matrices, we use
dense Matrices in these examples to illustrate some efficiency principles.
> N := 25:
> M := Matrix(N,N,(i,j)->`if`(i<>j,
RandomTools:-Generate(integer(range=-100..100))/1000.,
RandomTools:-Generate(integer(range=100..10000))/10.),datatype=float);
(7.138)
> b := LinearAlgebra:-RandomVector(N,datatype=float);
(7.139)
(7.140)
The code is written in such a way that it will automatically work for software floats at
higher values of digits.
7.5 Writing Efficient Numerical Programs 317
(7.141)
> b := LinearAlgebra:-RandomVector(N,datatype=float);
(7.142)
(7.143)
This implementation works well for small Matrices, but when the dimension becomes large,
it becomes very slow.
> Digits := 10:
> N := 500:
318 7 Numerical Programming in Maple
> M := Matrix(N,N,(i,j)->`if`(i<>j,
RandomTools:-Generate(integer(range=-100..100))/1000.,
RandomTools:-Generate(integer(range=100..10000))/10.),datatype=float);
(7.144)
> b := LinearAlgebra:-RandomVector(N,datatype=float);
(7.145)
(7.146)
Adding option hfloat to Jacobi is not likely to increase performance, since hfloat contagion
from the float[8] Matrix elements means that hfloat arithmetic is likely being used every-
where possible already. However, it is possible to rewrite the internal loops as a procedure
that can be evaluated with evalhf. (It might be possible to rewrite all of Jacobi to be eval-
uatable to evalhf, but it would be difficult and potential gains would be modest.)
> JacobiHelper := proc(A, b, x_old, x_new, n)
local s, i, j, l;
option hfloat;
# this procedure acts by side effect on x_new
for i from 1 to n do
s := 0;
for j from 1 to n do
if i<>j then
7.5 Writing Efficient Numerical Programs 319
s := s + A[i,j] * x_old[j];
end if;
end do;
x_new[i] := (b[i] - s) / A[i,i];
end do;
end proc:
(7.147)
Using evalhf here achieves an impressive speed-up but you can achieve even better speed
by taking advantage of the built-in Matrix and Vector operations. In general you code will
be faster if you can replace nested loops with calls to external commands for Vectors or
Matrices. Those commands will be highly optimized for your platform taking advantage of
multiple cores and cache hierarchy where possible.
> Jacobi := proc(A::Matrix(numeric), b::Vector(numeric),
x0::Vector(numeric):=b, MAXIter::posint:=25,
tolerance::positive:=evalf(LinearAlgebra:-Norm(b,2)*10^(1-Digits)),
$)
local k, x_new, S, S_inv, residual;
x_new := evalf(x0);
S := LinearAlgebra:-Diagonal(A,datatype=float);
S_inv := 1 /~ S;
residual := evalf(LinearAlgebra:-Norm(A.x_new-b,2));
for k to MAXIter while residual > tolerance do
# computing R.x as A.x - S.x is probably a bad idea
numerically
# but we do it anyway to avoid making the code overly
complicated
x_new := S_inv *~ (b - ( A . x_new - S *~ x_new));
residual := evalf(LinearAlgebra:-Norm(A . x_new-b,2));
end do;
if k < MAXIter then
return x_new;
else
WARNING("Achieved tolerance of only %1 after %2 iterations",
residual, i-1);
return x_new;
end if;
end proc:
7.5 Writing Efficient Numerical Programs 321
(7.148)
This sort of speed-up is typical. The built-in numerical linear algebra commands are easily
an order of magnitude faster than loops run in Maple, and generally also faster than loops
in evalhf.
322 7 Numerical Programming in Maple
8 Programming with Modules
This chapter describes the structure and flexibility of Maple modules.
Modules allow you to associate related procedures and data in one structure. By creating a
module, you can write code that can be reused, transported, and easily maintained. You can
also use modules to implement objects in Maple.
This chapter provides several example modules, many of which are available as Maple
source code in the samples directory of your Maple installation. You can load these examples
into the Maple library to modify and extend them, and use them in custom programs.
8.2 Introduction
You may decide to create a module for one of the purposes described below.
Encapsulation
Encapsulation is the act of grouping code together in one structure to separate its interface
from its implementation. By doing so, you can create applications that are transportable and
reusable and that offer well-defined user interfaces. This makes your code easier to maintain
and understand--important properties for large software systems.
Creating Objects
Objects can be represented using modules. In software engineering or object-oriented pro-
gramming, an object is defined as an element that has both a state and behavior. Objects
are passed the same way as ordinary expressions, but also provide methods which define
their properties.
323
324 8 Programming with Modules
(8.1)
The module definition format, which will be described in more detail in the next section, is
similar to a procedure definition in that the body is contained within a delimited code block.
Also, elements such as local variables, options, and description are declared at the top of
the module. Unlike a procedure, the body of the module is evaluated only once when it is
declared. The values that are defined during this evaluation process, and the values that are
defined in subsequent usage of the module, are stored and can be used again.
In a module definition, you can declare exported variables, which are names that will be
made available once the module has been run. These exported variables can be accessed by
using the member selection operator (:-) or the indexing operation ( [] ) , while local variables
remain private (that is, they are accessible only by methods within the module). The example
8.4 Syntax and Semantics 325
above declares and uses one exported local variable called getNext and one local variable
called count.
(8.2)
which does not contain exported variables, local variables, references, global variables, or
a body of statements.
Module Parameters
Unlike procedures, module definitions do not have explicit parameters because modules
are not called (or invoked) with arguments.
Implicit Parameters
All module definitions have an implicit parameter called thismodule. Within the body of
a module definition, this special name evaluates to the module in which it occurs. You can,
therefore, refer to a module within its own definition before the result of evaluating it has
been assigned to a name.
thismodule is similar to thisproc in procedures, but is not the same as procname. The
difference between thismodule and procname is that procname evaluates to a name, while
thismodule evaluates to the module expression itself. There is no concept of a modulename
implicit variable because the invocation phase of evaluating a module definition is part of
its normal evaluation process, and it occurs immediately. Procedures, on the other hand,
are not invoked until they are called with arguments. Normally, at least one name for a
procedure is known by the time it is called; this is not the case for modules.
Implicit parameters related to passing arguments (for example, _params, _options, _passed,
and others) cannot be referenced in module definitions. They are only available within the
scope of a procedure.
Named Modules
In a module definition, an optional symbol can be specified after the module keyword.
Modules created in this way are called named modules.
Semantically, named modules are almost identical to normal modules, but the exported
variables of named modules are printed differently, allowing the module from which it was
exported to be identified visually. In the following example, a normal module is assigned
to the name NormalModule.
> NormalModule := module() export e; end module;
NormalModule:-e;
(8.3)
In the following example, the symbol (the name of the module) after the module keyword
is NamedModule.
8.4 Syntax and Semantics 327
(8.4)
> NamedModule:-e;
(8.5)
When the definition of a named module is evaluated, the name (which appears immediately
after the module keyword) is assigned the module as its value and the name is protected
(that is, it cannot be modified). Therefore, a named module is usually created only once.
For example, an error occurs when the same named module definition above is executed.
> module NamedModule() export e; end module;
Error, (in NamedModule) attempting to assign to `NamedModule` which
is protected
Executing the normal module definition again creates a new instance of the module and
does not result in an error. It simply reassigns the variable NormalModule to the new
module instance.
> NormalModule := module() export e; end module;
(8.6)
If you save a normal module to a Maple library archive, which is a file used to store a col-
lection of internal files, the normal module becomes a named module the next time it is
loaded from the library archive. The savelib command, which is the command used to save
a file to a library archive, takes the name of the variable assigned a module, and saving the
file associates this name with the module.
For more information about library archive files, see Writing Packages (page 387).
(8.7)
> SomeName:-e;
(8.8)
Exports of named modules are printed using the distinguished name that was given to the
module when it was created, regardless of whether it has been assigned to another name.
Whether a module has a name also affects the reporting of errors that occur during its
evaluation. When the second attempt to evaluate the named module definition above gener-
328 8 Programming with Modules
ated an error, the error message reported the location of the error by name. In contrast, when
an error occurs during the evaluation of a normal module definition, the name unknown is
used instead.
> NormalModule := module() export e; error "oops"; end module;
Error, (in unknown) oops
This process differs from procedure error reporting. Maple cannot report the name of a
normal module (that is, the name of the variable to which the module is assigned) because
the evaluation of the right-hand side of an assignment occurs before the assignment to the
name takes place. Therefore, the error occurs before the association between a variable and
the module has occurred.
Declarations
The declarations section of the module must appear immediately after the parentheses. All
of the statements in the declarations section are optional, but, at most, one of each kind can
be specified. Most module declarations are the same as those for procedures.
Description Strings
You can provide a brief description to summarize the purpose and function of your modules.
Providing a description is valuable to other users who read your code. Include text after the
description keyword as you would in a procedure definition.
> Hello := module()
description "my first module";
export say;
say := proc()
print( "HELLO WORLD" )
end proc;
end module:
(8.9)
Global Variables
Global variables referenced in a module definition are declared by using the global keyword.
Following the global keyword is a sequence of one or more symbols, which are associated
with their global module instances. In certain cases, you must declare a name as a global
variable to prevent implicit scoping rules from making it local.
> Hello := module()
export say;
global message;
say := proc()
message := "HELLO WORLD!"
end proc;
end module:
> message;
(8.10)
> Hello:-say();
(8.11)
> message;
(8.12)
Local Variables
You can define variables that are local to the module definition by using the local declaration.
Its format is the same as for procedures. The following example is a variant of 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 (or locals) cannot be used or changed outside of the module definition in
which they occur. In other words, they are private to the module.
A local variable in a module is a distinct object from a global variable with the same name.
While local variables in procedures are typically used only for the duration of the execution
330 8 Programming with Modules
time of the procedure body, module local variables are stored after the module definition
is executed. They can be used to maintain a state. For example, in the Counter example
described at the beginning of this chapter, a local count variable stores the current value of
the counter. The count local variable increments each time the getNext procedure is invoked.
Its new value is stored and can be used the next time the procedure is called. At the same
time, because count is local, no external programs can change its value and end the sequence
defined by the module.
Module exports are declared by using the export declaration. It begins with the keyword
export, followed by a (nonempty) sequence of symbols. A name is never exported implicitly;
exports must be declared.
The result of evaluating a module definition is a module. You can view a module as a col-
lection of its exports, which are also referred to as members of the module. These are simply
names that can (but need not) be assigned values. You can establish initial values for the
exports by assigning values to them in the body of the module definition.
The word export is a short form for exported local variable. In most cases, a module export
is a local variable such as those declared with the local declaration. The difference is that
you can access the exported local variables of a module after it has been created.
To access an export of a module, use the member selection operator (:-). Its general syntax
is
modexpr :- membername
modexpr must be an expression that evaluates to a module and membername must be the
name of an export of the module to which modexpr evaluates. Anything else signals an
exception. You cannot access the local variables of an instantiated module by using this
syntax.
The Hello example above has one export named say. In the following example, say is as-
signed a procedure. To call it, enter
> Hello:-say();
(8.13)
The following expression raises an exception because the name noSuchModule is not as-
signed a module expression.
8.4 Syntax and Semantics 331
> noSuchModule:-e;
Error, `noSuchModule` does not evaluate to a module
In the following example, a module expression is assigned to the name m and the member
selection expression m:-e evaluates to the value of the exported variable e of m.
> m := module() export e; e := 2 end module:
m:-e;
(8.14)
Since m does not export a variable named noSuchExport, the following expression raises
an exception.
> m:-noSuchExport;
Error, module does not export `noSuchExport`
In addition to the :- syntax, square brackets can also be used to reference a module export.
> m[e];
(8.15)
The square bracket notation has different evaluation rules than member selection. When
using the member selection operator (:-), the export name must be known in advance. When
using [], the name of the export can be computed. In this example, an exported variables
value can be selected from an arbitrary module.
> m := module() export a := 1, b := 2, c := 3; end module:
FirstExport := proc( m::`module` ) local ex := exports(m); return
m[ex[1]]; end proc;
FirstExport(m);
(8.16)
Important: Exports do not need to have assigned values. The following module exports
an unassigned name. This illustrates the importance of distinguishing module exports
from global variables.
> m := module() export e; end module:
332 8 Programming with Modules
(8.17)
Note, however, that this is a local name e and not the global instance of the name.
> evalb( e = m:-e );
(8.18)
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. As-
signing a value to the export sin does not affect the protected global name sin.
(8.19)
(8.20)
(8.21)
(8.22)
You can also obtain the local instances of those names by using the option instance.
> exports( m, 'instance' );
(8.23)
(8.24)
8.4 Syntax and Semantics 333
(8.25)
You cannot have the same name declared as both a local variable and an exported local
variable.
> module() export e; local e; end module;
Error, export and local `e` have the same name
(The declared exports and locals actually form a partition of the names that are local to a
module.)
(8.26)
(8.27)
(8.28)
The first argument is a global name whose membership is to be tested, and the second argu-
ment is the name of a module. The member command returns the value true if the module
has an export whose name is the same as the first argument.
The member command also has a three-argument form that can be used with lists to determ-
ine the first position at which an item occurs.
> member( b, [ a, b, c ], 'pos' );
(8.29)
The name pos is now assigned the value 2 because b occurs at the second position of the
list. [ a, b, c].
334 8 Programming with Modules
> pos;
(8.30)
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' );
(8.31)
> which;
(8.32)
(8.33)
If the return value from the member command is false, the name remains unassigned or
maintains its previously assigned value.
> unassign( 'which' ):
> member( cry, Hello, 'which' );
(8.34)
(8.35)
Module Options
Similar to procedures, a module definition can contain options. The options available for
modules are different from those for procedures. Only the options trace and copyright are
common to both procedures and modules. The following four options have a predefined
meaning for modules: load, unload, package, and record. The load and unload options
cover functionality defined by the ModuleLoad and ModuleUnload special exports de-
scribed in the next section.
For more information, refer to the module,option help page.
well-defined problem domain. Packages can contain data other than procedures and can
even contain other packages (subpackages).
The package option is used to designate a module as a Maple package. The exports of a
module created with the package option are automatically protected.
Special Exports
Certain specially named exports, when defined in a module, affect how modules behave in
Maple. These special exports are described below. In most cases, they can be declared as
either exported local variables or local variables.
Consider the Counter example described at the beginning of this chapter. Since it only has
one method, the calling sequence can be shortened by using the ModuleApply function.
> Counter := module()
export ModuleApply;
local count;
count := 0;
ModuleApply := proc()
count := 1 + count;
end proc;
end module:
Counter();
Counter();
Counter();
(8.36)
336 8 Programming with Modules
In this example, calls to Counter:-ModuleApply() are not needed and the results are the
same as those generated by the original Counter example. The ModuleApply function can
specify and accept any number of parameters.
You can also use the ModuleApply function to create module factories, a standard object-
oriented design pattern described later in this chapter.
In the example below, the ModuleIterator procedure returns two procedures: hasNext and
getNext. These procedures can have any names, and in fact, do not require names. When
the ModuleIterator procedure is called, an iterator is initialized for the instance, the details
of which are kept hidden from the caller. The two returned procedures can then be used to
iterate over the instance to perform a specific task. For example, consider a class that imple-
ments a form of a set of which mySet is an instance. You can iterate over this set as follows:
> (hasNext,getNext) := ModuleIterator(mySet);
while hasNext() do
e := getNext();
# Do something with e.
od;
The example above is an explicit use of the ModuleIterator procedure. However, this
mechanism is also used implicitly by the Maple for-loop construct,
The hasNext procedure returns a value of true or false depending on whether remaining
elements need to be processed. Successive calls to hasNext with no intervening calls to
getNext return the same result. The getNext procedure returns the next element to process,
and increments the iterator. These procedures should be implemented so that it is always
safe to call getNext after the most recent call to hasNext returns a value of true. The result
of calling getNext after hasNext has returned a value of false, or before hasNext has ever
been called, is up to the implementer of the class.
The Counter example already contains a getNext procedure. A bounded count should be
added so that the iteration can terminate by using lower and upper exported values. A
statement also needs to be added to determine if any numbers are left in the sequence. The
hasNext and getNext procedures are returned directly by ModuleIterator.
8.4 Syntax and Semantics 337
hasNext := proc()
evalb( count < upper );
end proc;
getNext := proc()
count := 1 + count;
return count;
end proc;
ModuleIterator := proc()
return hasNext, getNext;
end proc;
end module;
for e in Counter do
e;
end do;
(8.37)
When the module iterator is used by the seq, add, or mul commands, Maple first checks if
the module is an object that exports the numelems command. If so, it will call the numelems
command to determine how large to preallocate the result, and the hasNext and getNext
procedures will return exactly that many elements. If the module does not export a numelems
method, Maple will increase the result, which will consume more space (as intermediate
results are discarded) and time (garbage collection).
338 8 Programming with Modules
Consider the Counter example at the beginning of the chapter. The count index can have
any value when it is saved. The next time you use it, you might want to reset the count to
zero so that it is ready to start a new sequence. This can be done by using the ModuleLoad
procedure.
> Counter := module()
export getNext, ModuleLoad;
local count;
ModuleLoad := proc()
count := 0;
end proc;
ModuleLoad();
getNext := proc()
count := 1 + count;
end proc;
end module:
Counter:-getNext();
(8.38)
Note that the initialization code is contained within the ModuleLoad procedure. After that,
the ModuleLoad procedure is also called. By defining the module in this way, you will get
the same results when executing the module definition as you would when loading a saved
module from a library archive.
The results of ModuleLoad can be duplicated using a procedure with a different name by
using the load=pname option in the option sequence of the module.
ModulePrint
If a module has an export or local named ModulePrint, the result of the ModulePrint
command is displayed instead of the module when a command in that module is executed.
8.4 Syntax and Semantics 339
The ModulePrint procedure does not display output. Instead, it returns a standard Maple
expression that will be displayed. The expression returned can be customized to another
object that portrays or summarizes the module.
In the following example, the Counter example will be extended from the ModuleIterator
example to display a summary of what the module does.
> Counter := module()
export ModuleIterator, getNext, lower := 0, upper := 5;
local ModulePrint, hasNext, count := 0;
hasNext := proc()
evalb( count < upper );
end proc;
getNext := proc()
count := 1 + count;
return count ;
end proc;
ModuleIterator := proc()
return hasNext, getNext;
end proc;
ModulePrint := proc()
return [[ sprintf("Counter from %d to %d", lower, upper)
]];
end proc;
end module;
(8.39)
ModuleUnload
The ModuleUnload procedure is called immediately before a module is discarded. A
module is discarded either when it is no longer accessible and is garbage collected, or when
you end your Maple session.
> M := module()
export ModuleUnload;
ModuleUnload := proc() print("I am gone"); end proc;
end module:
unassign(M);
1;2;3;4; gc();
340 8 Programming with Modules
(8.40)
You may not see the "I am gone" message after executing the code above because several
factors determine exactly when memory is free to be garbage collected. At a minimum, no
references can be left in the module. It must not be assigned or contained in any other live
expression. This includes the ditto operators and the list of display reference handles (that
is, the undo/redo buffer of the GUI). Also, it must not be in the current active generation
known to the garbage collector.
A module can become inaccessible, and therefore subject to garbage collection before the
unload= procedure is executed, but can then become accessible again when that procedure
is executed. In that case, the module is not garbage collected. When it eventually is garbage
collected, or if you end your Maple session, the unload= procedure is not executed again.
The behavior of ModuleUnload can be duplicated using a procedure with a different name
by using the unload=pname option in the option sequence of the module.
Modules can be nested, in the sense that a module can have any of its exports assigned to
a module whose definition occurs within the body of the outer module.
export e;
e := proc()
print( "HELLO WORLD!" )
end proc;
end module
end module:
The global name m is assigned a module that exports the name s. Within the body of m,
the export s is assigned a module that exports the name e. As such, s is a submodule of m.
The Shapes package, which is described in Writing Packages (page 387), illustrates the use
of submodules.
Modules and procedures can both be nested to an arbitrary depth. The rules for the access-
ibility of local variables (including exported locals of modules) and procedure parameters
are the same as the rules for nested procedures.
Module Factory
The Counter example used up to this point would be more useful if you could have many
Counter modules running at the same time, and if they could be specialized according to
specified bounds. Modules do not take explicit parameters, but you can write a generic
module that could be specialized by using the factory design pattern.
To do this, write a constructor procedure for the module that accepts the lower and upper
bound values as arguments. The following module creates a Counter.
> MakeCounter := proc( lower::integer, upper::integer )
return module()
export ModuleIterator, getNext;
local ModulePrint, hasNext, count := lower;
hasNext := proc()
evalb( count < upper );
end proc;
getNext := proc()
count := 1 + count;
return count ;
end proc;
ModuleIterator := proc()
return hasNext, getNext;
end proc;
ModulePrint := proc()
return [[ sprintf("Counter from %d to %d", lower,
upper) ]];
342 8 Programming with Modules
end proc;
end module;
end proc;
c1 := MakeCounter(6,10);
c1:-getNext();
c1:-getNext();
c2 := MakeCounter(2,4);
c2:-getNext();
c1:-getNext();
8.4 Syntax and Semantics 343
(8.41)
In the above example, two specialized Counters operate at the same time with different in-
ternal states.
344 8 Programming with Modules
(8.42)
(8.43)
Second, a type called moduledefinition identifies expressions that are module definitions.
In the previous example, the module definition
> module() end module:
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 module', 'moduledefinition' );
(8.44)
Other important type tests satisfied by modules are the types atomic and last_name_eval.
> type( module() end module, 'atomic' );
(8.45)
The procedure map has no effect on modules; modules passed as an argument to map remain
unchanged.
> map( print, module() export a, b, c; end module );
(8.46)
Modules also follow last name evaluation rules. For more information on last name evaluation
rules, refer to the last_name_eval help page.
8.5 Records 345
(8.47)
Although the type module is a surface type, which checks information at the top level of
your code, it acts also as a structured type. Parameters passed as arguments to the unevaluated
name module are interpreted as export names. For example, the module
> m := module() export a, b; end module:
(8.48)
(8.49)
because any module that exports symbols a and b is a module that exports the symbol a.
For more information about structured types, refer to the type,structure help page.
8.5 Records
The Record command, which was introduced in Records (page 166), is an example of a
module factory that can help you to write reusable code. Like an Array, a record is a fixed-
size collection of items but, like a table, individual items stored within the record can be
referenced by a name, rather than a numeric offset. In Maple, records, which are called
structures in C++, are implemented as modules.
Creating Records
To create a record, use the Record constructor. In the simplest form, it takes the field names
as arguments.
> rec := Record( 'a', 'b', 'c' );
(8.50)
346 8 Programming with Modules
The name rec is now assigned a record with fields named a, b, and c. You can access and
assign values to these fields by using the expressions rec:-a, rec:-b, and rec:-c.
> rec:-a := 2;
(8.51)
> rec:-a;
(8.52)
If unassigned, a record field evaluates to the local instance of the field name.
> rec:-b;
(8.53)
(8.54)
This is useful because the entire record can be passed as an aggregate data structure.
The record constructor accepts initializers for record fields. That is, you can specify an initial
value for any field in a new or unassigned record by passing an equation with the field name
on the left side and the initial value on the right.
> r := Record( 'a' = 2, 'b' = sqrt( 3 ) );
(8.55)
> r:-b;
(8.56)
In addition, you can associate Maple types with record fields. To associate a type, use the
`::` operator with the field name specified as the first operand.
(8.57)
8.5 Records 347
(8.58)
If the initializer for a record field is a procedure, you can use the reserved name self to refer
to the record you are creating. This allows records to be self-referential. The name self is
applicable only to creating records and not to modules in general. 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();
(8.59)
Record Types
Expressions created with the Record constructor are of the type record.
> type( rec, 'record' );
(8.60)
This is a structured type that works the same way as the `module` type, but recognizes re-
cords specifically.
> r := Record( a = 2, b = "foo" ):
> type( r, 'record( a::integer, b::string )' );
(8.61)
Note: In a record type, the field types are used to test against the values assigned to the
fields (if any), and are not related to type assertions on the field names (if any).
> r := Record( a::integer = 2, b::{symbol,string} = "foo" ):
348 8 Programming with Modules
(8.62)
(8.63)
(8.64)
(8.65)
Object Inheritance
The Record constructor supports a simple form of prototype-based inheritance. An object
system based on prototypes does not involve classes; instead, it uses a simpler and more
direct form of object-based inheritance. New objects are created from existing objects (called
8.5 Records 349
prototypes) by cloning, that is, by copying and augmenting the data and behavior of the
prototype.
(8.66)
(8.67)
> r := Record[p]( c = 4 );
(8.68)
(8.69)
In this example, the record p is the prototype, and the second record r inherits the fields a
and b, and their values, from the prototype p. It also augments the fields obtained from p
with a new field c. The prototype p is not changed.
> r:-a := 9;
(8.70)
> p:-a;
(8.71)
Behavior, as well as data, can be copied from a prototype. To copy behavior, use a constructor
procedure for both the prototype and its clones.
> BaseComplex := proc( r, i )
Record( 're' = r, 'im' = i )
end proc:
NewComplex := proc( r, i )
Record[BaseComplex(r,i)]( 'abs' =
(() -> sqrt( self:-re^2 + self:-im^2 )) )
end proc:
> c := NewComplex( 2, 3 ):
350 8 Programming with Modules
(8.72)
An object created from a prototype can serve as a prototype for another object.
> NewerComplex := proc( r, i )
Record[NewComplex(r,i)]( 'arg' =
(() -> arctan(self:-im,self:-re)) )
end proc:
> c2 := NewerComplex( 2, 3 ):
> c2:-re, c2:-im, c2:-abs(), c2:-arg();
(8.73)
(8.74)
For example, NewComplex creates objects of a type that is a subtype of the objects created
by BaseComplex.
This section describes how the use statement can be used with modules. For more information
about the use statement, see The use Statement (page 201).
A module m can appear in the binding sequence of a use statement. The module is regarded
as an abbreviation for the sequence of equations a = m:-a, b = m:-b, ..., where a, b, ... are
the exports of the module m.
For example,
> m := module() export a, b; a := 2; b := 3; end module:
use m in a + b end use;
(8.75)
(8.76)
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 ne-
cessary because the simplification of the use statement must be able to determine the exports
of the module. For example, the following attempt to pass a module as a parameter to a
procedure does not work, and an error occurs when the procedure is simplified.
> proc( m, a, b )
use m in e( a, b ) end use
end proc;
Error, no bindings were specified or implied
The correct way to use a module as a parameter is to specify the names to be bound explicitly,
for example,
> proc( m, a, b )
use e = m:-e in e( a, b ) end use
end proc;
(8.77)
352 8 Programming with Modules
This is necessary because, until the procedure is called with a module expression as first
argument, the reference to e is ambiguous. The variable e could refer to a module export or
to another value (such as a global name). To expand the use statement, this must be known
at the time the procedure is simplified.
Operator Rebinding
The use statement also allows most infix and prefix operators in the Maple language to be
rebound. This is not operator overloading, which can be performed in some programming
languages (such as C++), because the rebinding occurs during the automatic simplification
process in Maple.
If an operator name appears on the left side of a binding equation for a use statement (con-
sequently, if it is an exported name of a module that is bound with use), then the correspond-
ing operator expressions in the body of the use statement are transformed into function
calls. For example,
> use `+` = F in a + b end use;
m := module()
export `*`, `+`;
`+` := ( a, b ) -> a + b - 1;
`*` := ( a, b ) -> a / b;
end module:
s * ( s + t );
use m in s * ( s + t ) end use;
(8.78)
When a module-based package is loaded by running the with command, all of exported
operators are rebound at the top level so you do not need to write use statements to get the
overloaded implementations. If a module, M, exports a procedure named +, and you use
the command with(M), subsequent sums will be processed through M:-+.
In most cases, the new operator procedure should contain the overload function. This
provides a softer binding where your operator implementation will only be invoked when
the arguments passed in match the specified type.
> PairMath := module()
option package;
export `+`;
8.6 Modules and use Statements 353
(8.79)
In the example above, PairMath:-+ will only be invoked when the left side of + is a PAIR
structure. No error occurs when computing 1+1, which is not handled by PairMath:-+ be-
cause option overload has been specified for the PairMath:-+ procedure. When option
overload is specified, a mismatched type simply moves on to the next + implementation.
Bypassing the current overload occurs on a mismatched parameter type check, or on any
invalid input: error raised within the procedure. The module above can be rewritten as
follows.
> PairMath := module()
option package;
export `+`;
`+` := proc( a, b )
option overload;
if type(a,PAIR(integer,integer)) then
if type(b,PAIR(integer,integer)) then
PAIR( op(1,a) + op(1,b), op(2,a) + op(2,b) );
354 8 Programming with Modules
else
PAIR( op(1,a) + b, op(2,a) + b );
end if;
elif type(b,PAIR(integer,integer)) then
PAIR( a + op(1,b), a + op(2,b) );
else
error("invalid input: a or b should be a PAIR
structure");
end if;
end proc;
end module;
with(PairMath);
1 + PAIR(2,3);
2 + 2;
(8.80)
option overload;
print("in AP");
PAIR( a + op(1,b), a + op(2,b) );
end proc;
`+` := overload( [ PP, PA, AP ] );
end module;
with(PairMath);
1 + PAIR(2,3);
PAIR(2,3) + 4;
PAIR(1,1) + PAIR(3,4);
5+5;
(8.81)
The essential idea behind programming with objects is that the objects carry their behavior
with them. Clients of an object can elicit behavior by sending the object messages. Objects
respond to these messages by performing a prescribed computation that is determined by
both the recipient of the message (the object) and the message itself (which may be paramet-
erized by other arguments). This is in contrast to non-object-oriented approaches to program-
ming, in which the objects in a software system merely contain static data and serve as inputs
and outputs of the algorithms, which are represented separately from the objects by proced-
ures or other commands.
A constructor must be used to create objects that have no input syntax (such as series and
tables in Maple), but can also be used for expressions that do have an input syntax (the
Float constructor is an example of the latter case). Therefore, most user-defined objects
must be created by using a constructor. Most of the object examples in this section are
defined by specifying a constructor for the object.
(8.82)
The procedure MakeComplex is a constructor for complex number objects. The value re-
turned by the procedure is the instantiation of the module whose definition appears in the
body of MakeComplex. Although the value returned is a module, because ModulePrint
is defined, the result does not look like a module.
The local state of the complex number is represented by the local variables of the module,
real_part and imag_part. The behavior is represented by the exported local variables re,
im, abs, and arg.
The exports of a module that represents an object are sometimes viewed also as messages.
Objects respond to these messages by exhibiting the behavior that the messages elicit.
> z:-re(), z:-im();
(8.83)
> z:-abs();
(8.84)
> z:-arg();
(8.85)
For example, the expression z:-abs() sends the abs message to the complex number object
z. The object responds by computing its absolute value.
Note that each time the procedure MakeComplex is invoked, a new module is created using
the module definition that can be accessed within the procedure body. Therefore, complex
numbers created by different calls to the constructor are distinct, even if the arguments real
and imag are the same. Whether a constructor should produce distinct objects for the same
358 8 Programming with Modules
input (instance) data depends on the nature of the objects being modeled. For complex
number objects, multiple calls with the same inputs can produce the same object. This can
be achieved by using the remember option in the constructor.
storage layout of their concrete representation. This behavior is sometimes called a contract.
Generic programs rely only on the object behavior specified by the contract. They do not
rely on information of how an object is implemented; therefore, generic programs separate
interfaces from implementations.
Before the introduction of the module system, design by contract was enforced in Maple
only by convention. Maple commands whose names had to be enclosed in name quotes (`)
were considered private, and not for client use. However, this was only a convention. Also,
it was necessary to use global variables to communicate information and state among the
commands that comprised a subsystem (such as solve or assume). Now, using modules, it
is possible to design software systems that enforce their contracts by a mechanism embedded
in the Maple language.
Interfaces
In Maple, contracts are represented by an interface, which is a special kind of structured
type. It has the form
`module`( symseq );
where symseq is a sequence of symbols or expressions of the form symbol::type. For ex-
ample, an interface for a ring can be written as
> `type/ring` := '`module`( `+`, `*`, `-`, zero, one )':
These symbols are the ones to which clients have access as module exports.
one := 1;
end module:
> type( z5, 'ring' );
(8.86)
(8.87)
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 interfaces. 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.
# Implement subtyping.
extend := proc( new, old )
description "extend an existing inteface";
if map( type, {args}, 'symbol' ) <> { true } then
error "arguments must all be symbols"
end if;
if not type( totype( old ), 'interface' ) then
error "cannot find an interface named %1", old
end if;
define( new, op( totype( old ) ), args[3..nargs] )
end proc;
# Test whether ifc2 is an extension of ifc1.
extends := proc( ifc1, ifc2 )
description "test whether the second interface "
"extends the first";
local t1, t2;
t1, t2 := op( map( totype, [ ifc1, ifc2 ] ) );
if not type( [t1,t2], '[interface,interface]' ) then
if not type( t1, 'interface' ) then
error "arguments must be interface names, "
"but got %1", ifc1
else
error "arguments must be interface names, "
"but got %1", ifc2
end if
end if;
toset( ifc1 ) subset toset( ifc2 )
end proc;
# Save an interface to the Maple library archive.
savelib := proc()
description "save a named interface to a "
"Maple library archive";
local ifc;
for ifc in map( totype, [ args ] ) do
if not type( ifc, 'interface' ) then
error "arguments must be interfaces, "
"but got %1", ifc
end if;
:-savelib( totype( ifc ) )
end do
end proc;
# Test whether a module satisfies an interface.
8.8 Interfaces and Implementations 363
This package implements the interface abstraction. It allows you to manipulate interfaces
without having to consider how they fit into the Maple type system.
> with( Interface );
(8.88)
(8.89)
364 8 Programming with Modules
(8.90)
(8.91)
(8.92)
(8.93)
(8.94)
(8.95)
(8.96)
(8.97)
that appears in the module definition indicates that, when the instantiated module is read
from a Maple library archive, the procedure setup is to be called. The procedure named
must be a local variable or an exported local variable 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 assign-
ment is also executed in the session in which the module is instantiated. This is done for
illustrative purposes. A better approach would be to invoke setup in the body of the module
definition.
9 Input and Output
9.1 In This Chapter
Introduction
Input and output in the worksheet
Input and output with files
Reading and writing formatted data
Useful utilities
2-D math
9.2 Introduction
This chapter explores the ways in which you can read input and write output programmat-
ically. Here are a few examples of I/O operations in Maple.
Example 1: An integral can be printed in various ways. The two outputs below show the
integral in 2-D and 1-D representations, respectively.
> y := Int(x^2, x=1..2);
(9.1)
> lprint(y);
Int(x^2, x = 1 .. 2)
Example 2: The checkfile procedure defined below uses commands in the FileTools
package to examine the properties of a file.
> checkfile := proc(fname :: string)
return FileTools:-Exists(fname) and
FileTools:-IsReadable(fname);
end proc:
Example 3: A Matrix is written to a file using the ExportMatrix command.
365
366 9 Input and Output
(9.2)
(9.3)
Example 4: The sscanf command is used here to read three floating-point numbers from
the string given as the first argument.
> z := sscanf("X=123.4 Y=-27.9 Z=2.3E-5", "X=%f Y=%f Z=%f");
(9.4)
The first example shows the difference between 1-D and 2-D output in Maple. Note that
input can also be provided in both forms. In Maple, 1-D math is character-based, is available
in all interfaces, and can be controlled by many of the basic I/O commands discussed in
this chapter. Typeset or 2-D math is available only with the standard worksheet interface
and is generally manipulated interactively using the Maple GUI tools. However, it can be
controlled programmatically in a limited way.
Most of this chapter is devoted to the manipulation of files, which is the main way data is
shared between Maple and external applications. Many of the file-processing commands
also apply to interactive input and output, when 1-D mode is used. For example, the command
for formatted writing, printf, produces output in a Maple worksheet or document. However,
it is essentially identical to the fprintf command for printing to a file.
This chapter starts with a discussion of input and output in the worksheet, including some
notes on using other interfaces. The next section covers manipulation of files. Tools for
importing and exporting general files as well as those specially designed for numerical data
are discussed. Later in the chapter, low-level commands for formatted reading and writing,
along with other useful utilities, are shown. The chapter concludes with an explanation of
2-D math and how it can be customized programmatically.
9.3 Input and Output in the Worksheet 367
Interfaces
Maple has several user interfaces, all described on the versions help page. In this chapter,
it is assumed you are using either the standard worksheet interface or the command-line
interface. Most of the I/O operations described here apply to either interface. The major
exception is typeset or 2-D math, which is available only with the standard worksheet inter-
face.
You can use the interface command to communicate with the user interface. It allows you
to query or set certain user interface variables. This is one way of controlling the look of
the output. Note that the interface command does not affect the actual computation. A few
examples are shown below. A complete list of the variables is available on the interface
help page.
The version variable returns the interface version, platform information, build date and
build number.
> interface(version);
(9.5)
The prettyprint variable controls how Maple expressions are rendered as output.
> interface(prettyprint=1):
Diff(f(x), x);
d
--- f(x) (9.6)
dx
> interface(prettyprint=3):
Diff(f(x), x);
(9.7)
The rtablesize variable specifies the largest-sized rtable that will be displayed inline. If an
rtable has a dimension that is larger than this integer, then it is displayed as a placeholder.
368 9 Input and Output
> interface(rtablesize);
(9.8)
(9.9)
(9.10)
Interactive Output
By default, the output from a statement entered in the worksheet is automatically printed
to the screen, unless the statement is terminated by a colon. In the previous section, you
saw how to use the interface command to customize certain aspects of the output. Another
way to adjust the output is to set the printlevel environment variable. The default value of
printlevel is 1. When it is set to a higher value, additional information pertaining to procedure
calls is printed. This is one way of tracing a procedure for debugging purposes. For more
information about debugging programs, see Testing, Debugging, and Efficiency (page 583).
The print command prints Maple expressions using the current setting of the prettyprint
interface variable. In the worksheet, the default output is 2-D math and in the command-
line version, the default is a simulated math notation using text characters. Note that the
print command returns NULL and thus the output cannot be regenerated with the ditto
commands.
The print command is particularly useful in two situations. First, it can be used to print in-
termediate results calculated within a procedure. Normally, only the returned value of a
procedure is printed when it is called.
> p := proc(n)
local i;
for i to n do
9.3 Input and Output in the Worksheet 369
i^2;
end do;
end proc:
> p(5);
(9.11)
> q := proc(n)
local i;
for i to n do
print(i^2);
end do;
end proc:
> q(5);
(9.12)
The print command can also be used to print procedures and other expressions that follow
last name evaluation rules. For more information, refer to the last_name_eval help page.
> print(q);
(9.13)
The lprint command prints Maple expressions in a character-based notation that is similar
to the format used for 1-D input. Like the print command, the value returned by a call to
lprint is NULL, and the output cannot be recalled using the ditto operator. When the
prettyprint interface variable is set to 0, Maple uses lprint to print all expressions to the
interface.
> lprint(expand(x+y)^5);
(x+y)^5
Another commonly used command is printf, which produces formatted output. This com-
mand will be discussed in Reading and Writing Formatted Data (page 380).
370 9 Input and Output
If you want to redirect all output that normally goes to the screen to a file, use the writeto
and appendto commands. This is an easy way to log the input and output of a Maple session,
particularly if you are using the command-line interface. In the standard worksheet interface,
you can simply save the current worksheet or document. For more information on writing
to files, see Input and Output with Files (page 370).
Interactive Input
Normally, input is passed to Maple procedures directly through the procedure's parameters.
In the standard worksheet interface, input can also be provided through Maplets and com-
ponents. For more information about these topics, see Programming Interactive Ele-
ments (page 489).
The readline and readstat commands are also available for interactive input, though these
are less commonly used. The readline command reads the next line from the terminal or a
file and returns it as a string, while the readstat command reads the next statement from
the terminal and returns the value of that statement.
Customization
You can customize the prettyprinting of a function fnc in a limited way by defining a
print/fnc procedure. In the following example, expressions of the form g(x) should be
printed so that the argument is repeated three times in a list.
> `print/g` := proc(x) [x, x, x] end proc:
> g(b^2);
(9.14)
> g(5.8);
(9.15)
There is a similar facility for prettyprinting a module. If a module has an export or local
named ModulePrint, then the result of the ModulePrint command is displayed instead of
the module when a command in the module is executed. For more information, see Program-
ming with Modules (page 323).
the default output stream to a terminal or worksheet output region. Below is a brief intro-
duction to a few concepts related to files. For more detailed information, refer to the file
and file_types help pages.
Text and binary files: The Maple I/O library works with both text files (streams of
characters) and binary files (streams of bytes). The I/O commands allow you to specify
the type of file and generally assume a text file if no information is given.
Read and write modes: At any given time, a file may be open either for reading or for
writing. If you attempt to write to a file which is open for reading, Maple closes and re-
opens the file for writing. If you do not have permission to write to the file, then an error
occurs.
The default and terminal files: The Maple I/O library treats the user interface as a file.
The identifier default refers to the current input stream, the one from which Maple reads
and processes commands. The identifier terminal refers to the top-level input stream,
the current input stream when you started Maple. When Maple is run interactively, default
and terminal are equivalent.
File names and descriptors: Maple I/O commands refer to files in one of two ways: by
name (given as a string) or by descriptor. A file descriptor identifies a file after you have
opened it using its name and offers slight advantages in terms of efficiency and flexibility.
The commands described in this section accept either a name or a descriptor as the file
identifier.
Current directory: If you create files using the examples in this chapter, the files are
saved in the current directory. To query or set the current working directory, use the
currentdir command.
There are two sets of commands that you can use. The first subsection below describes the
basic top-level commands for file manipulation. Alternatively, you can use the FileTools
package, which provides a simpler interface to the other commands and offers additional
functionality. For most file operations, the FileTools package is recommended, but the two
sets of commands are generally compatible and can be used interchangeably on a file.
Before you can read from or write to a file, you must open it. When referring to files by
name, this happens automatically with the first file operation. When you use descriptors,
however, you must explicitly open the file first to create the descriptor. The fopen com-
mand takes as arguments the filename, a mode (READ, WRITE or APPEND) and op-
tionally, the file type (TEXT or BINARY).
> f := fopen("testfile", 'WRITE', 'TEXT');
When you are finished with a file, you can close it with the fclose command, which takes
the file identifier as its argument. This operation ensures that all information is written
to the disk. When you exit Maple or use the restart command, Maple automatically
closes any open files, whether they were opened explicitly with fopen or implicitly
through one of the other I/O commands.
Reading and writing lines of text
The readline command reads one newline-terminated line from a file and returns a string
containing that line. The writeline command writes one or more strings to a file, separated
with newline characters, and returns a count of the characters. If the file is not already
open with type TEXT, then the readline or writeline command will open the file auto-
matically.
> writeline("testfile", "The first line", "The second line");
Reading and writing bytes
The readbytes command reads one or more or bytes from a file and returns a list of in-
tegers. Optionally, you can specify that the file is to be opened in text rather than binary
mode, and in this case, a string is returned. You can also provide a previously created
rtable to the readbytes command and it will return the data in the rtable. Similarly, the
writebytes command writes bytes from a string or list to a file. More information about
both commands can be found on their help pages.
Reading and writing formatted input and output
The fscanf command parses expressions from a file based on a format string. Similarly,
the fprintf command prints expressions to a file based on a format string. Both commands
are similar to the C standard library commands of the same names. Both fscanf and
fprintf are described in greater detail in Reading and Writing Formatted Data (page 380).
Other file utilities
There are a number of other useful file utilities, including iostatus (obtain the status of
an open file), fremove (remove a file), fflush (flush output), filepos (sets or returns the
position), and feof (check if the current position is at the end). For more information
about these commands, refer to their help pages.
9.4 Input and Output with Files 373
Below is a simple example that uses a few of the commands introduced here. The file gen-
erated will be placed in your current working directory, which you can set with the current-
dir command.
Next, create a new file called prices1.txt and write a number of lines, one for each Vector
entry.
> fid := fopen("prices1.txt", 'WRITE', 'TEXT'):
> writeline(fid, "List of Prices"):
for i to n do
fprintf(fid, "Item %d costs %.2f\n", i, V[i]):
end do:
> fclose(fid):
Now open the file again and read the values from each line, adding them up as they are
read.
> fid := fopen("prices1.txt", 'READ', 'TEXT'):
> readline(fid):
> pricesum := 0.:
while not feof(fid) do
t := fscanf(fid, "Item %d costs %f\n"):
pricesum := pricesum + t[2]:
end do:
> fclose(fid):
Finally, reopen the file to append a line showing the sum of the prices.
> fid := fopen("prices1.txt", 'APPEND', 'TEXT'):
> fprintf(fid, "\nThe sum of the prices is: %.2f\n", pricesum):
> fclose(fid):
If you encounter an error while using any of the I/O commands listed in this section, refer
to the IO_errors help page for more information about the source of the error. Common
mistakes include reading from a file that does not exist and writing to a file for which you
do not have permission to alter.
374 9 Input and Output
Some of the commonly used commands are listed below. A full list of commands is available
in the FileTools help page.
Opening and closing files
The FileTools:-Text subpackage has a large number of commands for reading and writing
text. The FileTools:-Text:-Readline and FileTools:-Text:-Writeline commands read
and write a line at a time. The FileTools:-Text:-ReadFile command reads all lines in a
file at once.
The FileTools package has commands that allow you to examine the properties of a file,
such as FileTools:-Status, FileTools:-Exist, and FileTools:-AtEndOfFile. There are a
number of additional commands that check if a file is open, readable, writable, lockable,
or executable. The package also includes commands that return a file's size and position.
The FileTools package also includes commands to work with directories and file paths,
such as FileTools:-ListDirectory, FileTools:-MakeDirectory and FileTools:-Absolute-
Path.
The following example is similar to the one shown in the previous section using the basic
I/O commands, but this time, you will use the FileTools package.
First, create a new file prices2.txt containing a title and a line for each of the values in V.
Here, you can use the commands for writing strings, integers and floats, without worrying
about specifying the formatting precisely.
> with(FileTools:-Text):
> V := Vector([1.20, 4.85, 6.23, 2.45, 7.99]):
> fid := Open("prices2.txt", 'overwrite'):
> WriteLine(fid, "List of Prices"):
for i to n do
WriteString(fid, "Item");
WriteInteger(fid, i, 'delim'=" ");
WriteString(fid, "costs");
WriteFloat(fid, V[i], 'leftdelim'=" "):
WriteLine(fid, ".");
end do:
> Close(fid);
Now, open the file again and read the floating-point values from each line. The CountLines
and ReadNextFloat commands make this task easier, as you do not have to check for the
end of file or explicitly read other characters in each line.
> fid := Open("prices2.txt"):
> ReadLine(fid):
> pricesum := 0.:
numlines := CountLines(fid):
for i to numlines do
376 9 Input and Output
ReadNextInteger(fid):
pricesum := pricesum + ReadNextFloat(fid):
end do:
> Close(fid):
Finally, open the file again to append a line showing the sum of the prices.
> fid := Open("prices2.txt", 'append'):
> WriteLine(fid, "", "The sum of the prices is:"):
> WriteFloat(fid, pricesum):
> Close(fid):
These commands support different types of files, including some that are generated or re-
cognized by other software applications. The formats supported are: MATLAB, Matrix
Market, comma-separated values (.csv), and generic delimited files. The source and target
options are used to indicate the desired format.
Files created with MATLAB versions 5, 6 or 7 can be imported. By default, the ExportMat-
rix command generates a MATLAB Version 7 binary file, using data compression as de-
scribed on the StringTools:-Compress help page, when the target=Matlab option is
provided. However, it is possible to generate a Version 6 file without compression by adding
the mode=v6 option. You can also read and write MATLAB ASCII files using the
mode=ascii option. Import and export of both dense and sparse Matrices are supported with
MATLAB format.
Matrix Market files are imported and exported using the MatrixMarket value for the source
and target options. The Matrix Market coordinate and array formats are supported; the
pattern format is not supported.
For .csv and general delimited files, the format option can be used to indicate whether the
storage is dense or sparse. In the latter case, only the nonzero entries are present in the im-
ported or exported file.
9.4 Input and Output with Files 377
Below is a small example showing how ImportMatrix and ExportMatrix work with
MATLAB arrays. The file will be placed in your current working directory.
Generate two random Matrices and export them to a MATLAB Version 6 file. The number
of bytes written is returned by the ExportMatrix command.
> A := LinearAlgebra:-RandomMatrix(3, 4, 'datatype'=float[8]);
(9.16)
(9.17)
(9.18)
Now, read the MATLAB arrays back into Maple using the ImportMatrix command. It is
not necessary to use the source and mode options in this case. The ImportMatrix command
can automatically recognize MATLAB binary files. With most text files (MATLAB or
otherwise), you will have to specify the source type.
> M := ImportMatrix("testfile.mat");
(9.19)
A sequence of two lists is returned, with each list containing a string and a Matrix. The
string shows the name stored with each matrix in the MATLAB file. The names are auto-
matically assigned by the ExportMatrix command, but you can specify your own names
with the arraynames option.
Now, export the Matrix A to a text file with values delimited by spaces.
378 9 Input and Output
(9.20)
Import the contents of the file back into Maple. In this case, it is necessary to specify the
source and the character used as delimiter.
> ImportMatrix("anotherfile.txt", 'source'='delimited', 'delimiter'="
");
(9.21)
Notice that only a single Matrix is returned. Multiple Matrices can be exported to MATLAB,
but with other formats, only a single Matrix can be saved in a file. Also, only MATLAB
arrays have names associated with them.
Other Commands
The readdata command reads numeric data from a text file into Maple. The data in the file
must consist of integers or floating-point values arranged in columns, separated by white
space, and it is returned in a list or list of lists.
The writedata command writes numeric data from a Maple vector, matrix, list or list of
lists into a text file. This command accepts an optional argument in the form of a procedure
that allows you to control the formatting of the output.
A Maple language file contains statements conforming to the syntax of the Maple language.
These are the same as statements that can be entered interactively. Any filename can be
used for a Maple language file, but the name cannot end with ".m". The standard file
extension for Maple language files is ".mpl".
9.4 Input and Output with Files 379
Maple language files can be created using a text editor or the save statement. Maple
procedures and complex scripts of commands are usually written in a text editor, while
the save statement is used to save results or procedures that were entered into Maple in-
teractively.
Maple language files may be read using the read statement. The statements within the
file are read as if they were being entered into Maple interactively, except that they are
not echoed to the screen unless the echo interface variable has been set to 2 or higher.
Maple includes a preprocessor modeled on the C preprocessor and Maple language files
may include preprocessor directives such as $include and $define.
Internal format files
Maple internal format files are used to store expressions compactly. Expressions stored
in this format can be read by Maple faster than those stored in the Maple language format.
These files are identified by filenames ending with the two characters ".m" (or ".M" on
platforms where filenames are not case-sensitive).
Like Maple language files, Maple internal format files are read and written using the
read and save statements. The presence of the ".m" ending in the filename specifies that
the file is an internal format file, and not a language file.
Library archives
Maple uses library archive files to store collections of internal format files. These files
end with the extension ".mla" (or, for older library archive files, with extension ".lib").
For more information about creating Maple libraries, see Writing Packages (page 387).
Help databases
A Maple help database is a file that stores a collection of files representing help pages in
the Maple help system. It contains the information required to index, navigate, and search
the help system, and its filename has the extension ".hdb". For more information, refer
to the worksheet/reference/maplehdb help page.
Worksheet files
If you are using Maple with a graphical user interface, you can save your worksheet. In
the standard worksheet interface, files are identified by names ending with ".mw". In the
classic worksheet interface, files end in ".mws". Both types of files are portable between
the graphical user interfaces on different platforms.
Maplet Files
Maple worksheets can be saved as ".maplet" files. The MapletViewer runs such files
independent of the Maple worksheet environment.
380 9 Input and Output
(9.22)
Print each value on a single line, preceded by an integer indicating its position. The format
string is the first argument to the printf command. This string consists of two conversion
specifications, "%d" and "%.2e", along with other characters to be printed, including the
newline character "\n". The first conversion specification indicates that the first argument
following the format string should be printed as an integer. The second conversion specific-
ation indicates that the second argument following the format string should be printed in
scientific notation, with two digits after the decimal point.
> for i to LinearAlgebra:-Dimension(V) do
printf("%d%12.2e\n", i, V[i]);
end do;
1 8.43e-01
2 9.95e-01
3 1.00e+00
4 1.00e+00
5 1.00e+00
The scanf and printf commands belong to a family of related commands that provide
formatted I/O capabilities. The other commands will be discussed later in this chapter. These
commands are based on similarly named functions from the C programming language library.
9.5 Reading and Writing Formatted Data 381
For example, the sscanf command below reads an integer, a space, a character, and a
floating-point value from the string given as the first argument. The conversion specifications,
"%d", "%c" and "%f", will be explained in the next section.
> sscanf("892 123.456E7","%d %c%f");
(9.23)
Format Strings
As you saw in the previous examples, the format string passed to scanf or printf specifies
exactly how Maple is to parse the input or write the output. It consists of a sequence of
conversion specifications that may be separated by other characters.
First, consider the specification for the scanf command, which has the format shown below.
What follows is a brief explanation of the specification. For more information, refer to the
scanf help page.
%[*][width][modifiers]code
The character "%" begins each conversion specification.
The optional character "*" indicates that the item scanned is to be discarded and not re-
turned as part of the result.
The optional width indicates the maximum number of characters to be scanned for this
object. You can use this to scan one larger object as two smaller objects.
The optional modifier affects the type of value to be returned. The most common of these
is "Z", which, when preceding any of the numeric format codes, indicates that a complex
value is to be scanned.
Several format codes are available for use with scanf. A few of the more commonly used
ones are mentioned here.
"d" -- integer
"c" -- character
"s" -- string
The specification for the printf command is similar to that for scanf. The differences are
summarized here. For more information, refer to the printf help page. The specification
has the following format.
%[flags][width][.precision][modifiers]code
382 9 Input and Output
As with scanf, the conversion specification for printf begins with "%". The optional
width and modifiers are similar to those described earlier. The width value indicates the
minimum number of characters to output for the field.
The optional flag can be one of several characters affecting how numeric values are dis-
played. For example, the flag "+" indicates that signed numeric values are output with a
leading "+" or "-" sign.
The format codes for printf are similar to those for scanf. One notable difference is that,
while "e" and "g" are equivalent to "f" for scanf, they produce different output in printf.
The code "e" causes a numeric value to be printed in scientific notation, while output
using the code "g" uses one of integer, fixed-point or scientific notation, depending on
the numeric value.
The scanf and printf commands can also be used to print rtables. For more information
about the flags used for this purpose, refer to the rtable_printf help page.
Related Commands
Several commands are related to scanf and printf:
fscanf and fprintf
These commands read from and write to a file instead of the terminal. They take a filename
or descriptor as an additional argument, but otherwise use the same calling sequence as
scanf and printf.
sscanf and sprintf
These commands read from and write to a string (which is then returned) instead of the
terminal. The sscanf command takes a string as an additional argument, but otherwise
these commands use the same calling sequence as scanf and printf.
nprintf
This command is the same as sprintf except that it returns a Maple symbol instead of a
string.
All these commands are described fully on the scanf and printf help pages.
or converting data that is read or written. The StringTools package includes numerous
commands; for brevity, we will describe only a few commands that may be of interest to
users performing input/output operations in Maple. These include commands for
converting the case of characters (e.g., StringTools:-LowerCase)
performing character class tests (e.g., StringTools:-HasDigit)
comparing strings (e.g., StringTools:-IsPrefix)
doing pattern-matching and text searching (e.g., StringTools:-Substitute)
handling whitespace (e.g., StringTools:-TrimRight)
Two commands that are relevant to file I/O are StringTools:-Compress and StringTools:-
Uncompress. The first command uses an algorithm from the zlib library to compress the
input into a lossless and more compact format, while the second reverses the process. These
commands are compatible with the commands for reading and writing bytes described in
Input and Output with Files (page 370).
Conversion Commands
Some additional commands may be useful when you are performing input and output oper-
ations in Maple.
The convert/bytes help page shows how to transform strings into bytes using the convert
command.
The parse command allows you to parse a string as a Maple statement. For example, the
following command parses the given string, evaluates it, and returns the expression 4*x^2.
> parse("x^2+3*x^2");
(9.24)
There are two available modes for typesetting: standard and extended. The mode can be
changed by using the interface command. The following command shows the current setting
in your worksheet or document:
> interface(typesetting);
(9.25)
Standard typesetting uses default rules for displaying expressions. With extended typesetting,
the rules can be customized using the Typesetting Rule Assistant (TypesettingRuleAssist)
or exports from the Typesetting package. The Typesetting Rules Assistant and the Type-
setting package exports can also be used to adjust how 2-D input is parsed, regardless of
the typesetting mode used for output.
The commands available to users are described on the Typesetting help page. A subset of
the commands are listed below:
Typesetting:-Settings: adjust general extended typesetting settings, such as whether dot
notation for derivatives is used and whether functions such as should be interpreted
as implicit multiplication.
Typesetting:-Suppress: suppress dependencies of functions (so that can be interpreted
as , for example).
Extended typesetting output is produced by the Typeset command When this command is
called, an unevaluated function is returned. This output, which is recognized by the Maple
GUI, is not intended to be altered by users. Because the structure is meant for internal use,
the tag names and format of the structure may change from one Maple release to another.
9.8 Exercises 385
Additional Tips
Users are discouraged from manipulating the typesetting structures created for internal
use. However, in rare circumstances, it may be useful to call the Typesetting:-Typeset
command. For example, standard typesetting mode is generally used for typeset text in
plots. Extended typesetting output produced by the Typeset command may be passed to
plays inside the typeset structure. For more information, see Typesetting (page 464).
Occasionally, you may find it necessary to manipulate a typeset expression programmat-
ically without having the expression evaluate. For example, you want to print +
without having it evaluate to , or you want to use , which gives an error when
9.8 Exercises
1. Write a loop (with a single statement in its body) that prints strings listing the cubes of
the integers 1 to 10.
2. Create a file in a text editor that contains the following lines.
x := 1; # valid input line
if := 2;} # invalid assignment
y := 3; # valid input line
two words := 4; # invalid assignment
Save the file. In a Maple session, open the file by using the read statement. Observe how
Maple reacts to invalid statements.
3. Create a data file in a text editor that contains the following information.
386 9 Input and Output
1 2 3
4 5 6
Save the file. Read this file into a Maple session, convert the data to a list, and reverse
its order. Write the reversed data in the same format to a different file.
10 Writing Packages
This section describes how to collect a large software project in Maple into a package that
is easy to maintain. Packages can be configured to load automatically when you start Maple
and distributed to other users as a library rather than as Maple source code.
For more information about modules, see Programming with Modules (page 323).
Some older and deprecated Maple packages such as simplex and networks are not imple-
mented using modules; they are implemented using tables. In table-based packages, the
name of a package command is used as the index into a table of procedures. It is not recom-
mended to write new packages using tables since modules allow much more flexibility.
387
388 10 Writing Packages
Package Exports
Some of the data in a package is normally made accessible to the user as an export of the
package. For packages implemented as modules, the package exports are the same as the
exports of the underlying module. For packages implemented as tables, the package exports
are the names used to index the underlying table.
calls the procedure Print in the subpackage Coverage in the subpackage Profiling, which
is part of the CodeTools package. You can use indexed notation for this.
> CodeTools[Profiling][Coverage][Print]();
(10.1)
This command makes the names exported by the numtheory package (a list of which is
returned by the call to with) available temporarily as top-level Maple commands.
> cfrac( ( 1 + x )^k, x, 5, 'subdiagonal', 'simregular' );
(10.2)
return _passed;
end proc; # axpy
sqrm1 := proc(x::algebraic, $)
description "square minus one";
return x^2 - 1;
end proc;
end module; # Some Tools
(10.3)
This example is simply a module that consists of a few exported members that are procedures
and an option called package. As with all modules, its members can be accessed by using
the :- and [] operators.
> SomeTools:-axpy(1,1,1);
(10.4)
> SomeTools[sqrm1](2);
(10.5)
The package option also allows you to call the package by using the with command to access
the package exports.
> with(SomeTools);
(10.6)
> identity("nothing");
(10.7)
If the with command is used to call a module that does not include the package option, an
exception will occur:
> SomeOtherTools := module() export Nothing; Nothing := x->x; end
module;
(10.8)
10.3 Writing Maple Packages By Using Modules 391
> with(SomeOtherTools);
Warning, SomeOtherTools is not a correctly formed package - option
`package' is missing
(10.9)
Packages generally include many lines of Maple code, so you will probably want to create
and modify them in a specialized editor designed for programming such as vim or emacs.
In the example above, the definition of the module SomeTools have been put in a file called
SomeTools.mpl in the samples/ProgrammingGuide/ directory of your Maple installation).
If you copy this file into the current directory, it can then be loaded in Maple by using the
read command.
> read("SomeTools.mpl");
This allows you to access the SomeTools package commands in a Maple worksheet or
document. You can include a read statement as the first executed command or in the startup
code of a Maple worksheet or document.
Custom Libraries
If you prefer not to call the read command to load your custom package, you can save your
package as a Maple library archive (.mla) file. This allows your package to be available
whenever Maple is started; however, it is loaded into memory only if it is required. In con-
trast, the read method automatically loads the package into memory.
Before a package can be saved to a library archive, it must be loaded into Maple, either
directly in a worksheet or by using the read command. Then, the simplest method for saving
it to a library archive is to call the savelib command.
Before calling the savelib command, you may want to set up a directory to store your custom
library. For example, you can create a directory called maple in your home directory and
use the LibraryTools commands to create an empty .mla file in which to store your library.
> mylibdir := cat(kernelopts(homedir), kernelopts(dirsep), "maple",
kernelopts(dirsep), "toolbox", kernelopts(dirsep), "personal",
kernelopts(dirsep), "lib");
(10.10)
(10.11)
To save a new value for libname (a predefined variable, which specifies the location of the
main Maple library, and package and user libraries), and to make sure that this directory is
the default location for saving in the future after you use the restart command, add the line
above to your Maple initialization file, which specifies initialization settings for Maple.
You can modify the Maple initialization file manually in a text editor or by using the
FileTools commands in Maple:
> mapleinitfile := cat(kernelopts(homedir), kernelopts(dirsep),
`if`(kernelopts(platform)="unix", ".mapleinit", "maple.ini"));
(10.12)
Finally, save the package to the library archive by calling the savelib command.
> savelib( 'SomeTools' );
10.4 A Larger Example 393
Running the savelib command saves the module to the first library archive found in the
path specified by the global variable libname or the library archive in the path specified by
the global variable savelibname, if it is defined. (At least one of these values must be
defined.) You can save your package to a different library archive by using the Librar-
yTools:-Save command or by providing a file name as a second argument to the savelib
command.
You will want to remove this example package from your library when you are done. This
can be done using the Delete command in the LibraryTools package.
> LibraryTools:-Delete('SomeTools', libname[1]);
Important: Always make sure that the standard Maple library directory is write-protected
to avoid saving expressions in it. If you accidentally save a file to the standard Maple
library, you may need to reinstall Maple to restore the main library archive.
ModuleLoad
Often, a package needs to initialize the internal or global state when it is loaded. For example,
many packages define new types for the type system. Generally, these types are not needed
unless the package is loaded, and so they are created by the ModuleLoad local member of
the package. In this example, we will define a type BinarySequence that is a linear data
type containing only zeros and ones.
> ModuleLoad := proc()
TypeTools:-AddType
394 10 Writing Packages
(
':-BinarySequence',
proc(L)
type(L, list({identical(0),identical(1)}))
or type(L, 'Vector'({identical(0),identical(1)}))
or ( type(L, 'Array'({identical(0),identical(1)}))
and nops([rtable_dims(L)])=1
and op([1,1], [rtable_dims(L)])=1 );
end proc
);
end proc;
The local named ModuleLoad will automatically run when a module is loaded from a library.
If you are using the read command to load the definition of the module into Maple or if
you enter it in a worksheet, this procedure will not run automatically.
You can also define the local ModuleUnload, which will run if the module is removed
from memory because it is not being used. In this case, the custom type definition Bin-
arySequence is removed.
> ModuleUnload := proc()
TypeTools:-RemoveType(':-BinarySequence');
end proc;
It is also possible to use the load=proc and unload=proc options in the module definition
to specify different procedures to be invoked when a package is loaded and unloaded re-
spectively. However, the use of the ModuleLoad and ModuleUnload procedures is recom-
mended.
To include the file name of an export in an .mpl file, you can use the $include directive.
In our example, we have a file called RandomnessTests.mpl, which has several exports
stored in the same directory in the files: WaldWolfowitz.mm, BitFrequency.mm, Compress-
ibility.mm, BinaryRank.mm. The following example shows the contents of the Random-
nessTests.mpl file.
10.4 A Larger Example 395
##MODULE RandomnessTests
##
##DESCRIPTION
##- A package containing commands for testing the randomness of
## binary sequences.
$define RANDINFO ':-RandomnessTests'
$define MAINLEVEL 2
RandomnessTests := module()
option package;
export
WaldWolfowitz,
BitFrequency,
Compressibility,
BinaryRank;
local
Runs,
ModuleLoad,
ModuleUnload;
ModuleLoad := proc()
TypeTools:-AddType
(
':-BinarySequence',
proc(L)
type(L, list({identical(0),identical(1)}))
or type(L, 'Vector'({identical(0),identical(1)}))
or ( type(L, 'Array'({identical(0),identical(1)}))
and nops([rtable_dims(L)])=1
and op([1,1], [rtable_dims(L)])=1 );
end proc
);
end proc;
ModuleUnload := proc()
TypeTools:-RemoveType(':-BinarySequence');
end proc;
$include "WaldWolfowitz.mm"
$include "BitFrequency.mm"
$include "Compressibility.mm"
$include "BinaryRank.mm"
end module; # RandomnessTests
When this file is loaded into Maple from the command-line interface or by using the read
command in a Maple worksheet or document, Maple automatically replaces each $include
directive with the contents of the file specified.
396 10 Writing Packages
The file also includes two $define directives which are macros that are not used in this file,
but will be used in the included files. Including them in the top-level source file allows us
to make package-wide changes by editing the macros in one place. In our example, these
macros will be used to control the userinfo definitions throughout the package. The following
line appears in the WaldWolfowitz.mm file:
userinfo(MAINLEVEL, RANDINFO, nprintf("sequence has %d runs", runs));
When loaded, the preprocessor will transform this line into the following:
> userinfo(2, ':-RandomnessTests', nprintf("sequence has %d runs",
runs));
Macros can also have parameters such as the following macro in the BinaryRank.mm file:
$define GF2RankProbability(m,n,r)
2^(r*(n+m-r)-m*n)*mul(((1-2^(l-n))*(1-2^(l-m)))/(1-2^(l-r)),l=0..(r-1))
Subpackages
When creating large packages, it is useful to organize commands into smaller subpackages.
Our package does this with a Visualization subpackage and a Data submodule to store
sample random inputs.
Achieving this is as simple as including the definition for these other modules and packages
within the top-level package.
Modules allow you to create packages with a hierarchical structure; this cannot be done
with table-based implementations of packages. This section covers the following topics:
Organizing the source code for a large package that has a nontrivial substructure.
A description of the Shapes package, including details of its design and implementation
Hints related to source code organization.
The package presented in this section provides the means to compute areas and circumfer-
ences of various planar figures, which are called shapes.
10.5 Example: A Shapes Package 397
Note: Only portions of the source code for this package are shown. The fully commented
source code can be found in the samples/ProgrammingGuide/shapes directory of your
Maple installation.
To avoid platform-specific differences, all of the source files are located in the same directory
or folder.
To define the module that implements this package, use the Maple preprocessor to include
the remaining source files at the appropriate point in the master source file shapes.mpl. A
number of $include directives are included in shapes.mpl, such as
$include "point.mm"
$include "segment.mm"
Splitting a large project into several source files makes it easier to manage and allows sev-
eral developers to work on a project at the same time. The source file is divided into shape-
specific functionality. Most of the functionality for points, for instance, is implemented by
the source code stored in the point.mm file.
Package Architecture
The Shapes package is structured as a module with several exported procedures. Individual
submodules provide specific functionality for each shape type supported by the package.
Each of these shape-specific submodules is stored in its own source file; these files are in-
cluded into the main package source file, shapes.mpl.
The package module Shapes has a submodule, which is also called Shapes. The submodule
Shapes:-Shapes contains one submodule for each shape supported. The submodule hierarchy
is illustrated in Figure 10.2.
The result of preprocessing the main file shapes.mpl produces a module whose source has
the following general form.
Shapes := module()
option package;
export make, area, circumference;
local Shapes, circum_table;
Shapes := module()
10.5 Example: A Shapes Package 399
(10.13)
(10.14)
In each case, the name of the shape is passed as the first argument to specify which kind of
shape to return.
(10.15)
400 10 Writing Packages
(10.16)
Shape Representation
Shapes are represented as unevaluated function calls. The arguments to the call are the in-
stance-specific data for the shape. For example, a point with coordinates (2,3) is represented
by the unevaluated function call POINT( 2, 3 ). Some instance data are shapes themselves.
For example, a segment is represented, using its endpoints, as an unevaluated function call
of the form SEGMENT( start_point, end_point ). The start and end points of the segment
can be obtained by calling the point constructor.
Procedure Dispatching
The Shapes package illustrates three types of procedure dispatching.
Dispatching on submodule exports
Conditional dispatching
Table-based dispatching
The first argument to make is a symbol that specifies the shape to create (point, circle,
triangle). This symbol is used as an index in the Shapes:-Shapes submodule. The first
statement uses the member command to test whether the symbol passed in the what para-
meter is exported by the Shapes:-Shapes submodule. If it is not found, an appropriate dia-
gnostic 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 implements operations on circles. The same idea
is used to select the shape-specific constructor; it is the value assigned to the local variable
ctor when the value true is returned from the second call to the member command. 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.
Conditional Dispatching
The procedure area uses a simple conditional dispatching mechanism. The tag of the input
shape is extracted and is used in direct comparisons with hard-coded values to determine
which shape-specific area subcommand to call to perform the area computation.
> area := proc( shape )
description "compute the area of a shape";
local tag;
if not type( shape, 'function' ) then
error "expecting a shape expression, "
"but got %1", shape
end if;
# Extract the "tag" information from the shape
tag := op( 0, shape );
# Dispatch on the "tag" value
if tag = ':-POINT' then
Shapes:-point:-area( shape )
elif tag = ':-SEGMENT' then
Shapes:-segment:-area( shape )
elif tag = ':-CIRCLE' then
402 10 Writing Packages
Shapes:-circle:-area( shape )
elif tag = ':-SQUARE' then
Shapes:-square:-area( shape )
elif tag = ':-TRIANGLE' then
Shapes:-triangle:-area( shape )
else
error "not a recognized shape: %1", tag
end if
end proc:
Table-based Dispatching
The third dispatch method illustrated in the Shapes package is table-based. This technique
is used by the exported procedure circumference, which references the table circum_table
to look up the appropriate procedure to call. This table is built by assigning its entries in
the body of the Shapes package.
> circum_table := table();
> circum_table[ 'POINT' ] := Shapes:-point:-circumference;
> circum_table[ 'SEGMENT' ] := Shapes:-segment:-circumference;
> circum_table[ 'CIRCLE' ] := Shapes:-circle:-circumference;
> circum_table[ 'SQUARE' ] := Shapes:-square:-circumference;
> circum_table[ 'TRIANGLE' ] := Shapes:-triangle:-circumference;
Minimal checking is done to ensure that the input has the right structure. If an entry is found
in the table circum_table for the shape tag (as with the area procedure), 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 submodule can extract the instance data from it.) Oth-
erwise, a diagnostic is issued and an exception is raised.
Shape-specific Submodules
As already noted, each shape is implemented in a shape-specific submodule. The set of
exports of each module varies, but each supports the required exports make, area, and
circumference in the top-level Shapes module. Certain shapes support other operations.
Only two submodules are described here. You can see the source for the other submodules
in the sample source code.
Since the area and circumference of a point are both 0, these procedures are easy to imple-
ment. In addition to the required exports, the point submodule also exports two utility pro-
cedures, xcoord and ycoord, for retrieving the x and y coordinates of a point. Providing
these values makes it possible for clients of this submodule to use it without requiring in-
formation 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 are the same as the
names with the same external representation at the top-level Shapes module.
area, circumference;
option package;
make := proc( cntrPt, radius )
'CIRCLE'( cntrPt, radius )
end proc;
center := circ -> op( 1, circ );
radius := circ -> op( 2, circ );
diameter := circ -> 2 * radius( circ );
circumference := circ -> Pi * diameter( circ );
area := circ -> Pi * radius( circ )^2;
end module:
Again, some extra commands are provided in addition to those required at the top level of
the Shapes package. The exported procedure radius is used to define other commands. It
can be made local to this submodule.
11 Graphics
Maple offers a variety of ways to generate 2-D and 3-D plots. This chapter shows you how
to create and manipulate such plots programmatically. You will learn about the Maple
plotting library, the plot data structure, and how to write your own graphics procedures.
11.2 Introduction
Plots in Maple
A plot in Maple is a 2-D or 3-D graphical representation of a mathematical object, such as
a function or a set of statistical data. In most of this chapter, we will be assuming a Cartesian
coordinate system. We will generally refer to the horizontal and vertical axes in 2-D plots
as the and axes, respectively, while the axes in 3-D plots will be called the , ,
and axes.
The following command generates a 2-D plot of the function over the range
-2 to 2.
405
406 11 Graphics
The plot command is commonly used to create 2-D plots and it is described in more detail
in Generating 2-D and 3-D Plots (page 408). There is a corresponding plot3d command for
creating 3-D plots. In the previous statement, the three arguments to the plot command are
the Maple expression x^3-x^2+3 representing the function to be plotted, an equation spe-
cifying the plotting variable and range, and an option indicating that a title be added.
If you are using Maple's Standard Interface, executing the previous statement produces a
plot in the current document. You can also create plotting output for other interfaces (for
example, plots that open in a different window) and plots that are saved as a graphics file.
For more information, see Interfaces and Devices (page 482).
Generating a Plot
The plot and plot3d commands are not the only ways to generate plots. Maple has an ex-
tensive library of commands for plotting and related tasks. These are described in The Plot
Library (page 407). A plot that is generated with a library command is returned as a plot
data structure. This is described in the section Data Structures (page 445).
Maple also provides interactive ways to create and modify plots, including:
the Interactive Plot Builder,
dragging and dropping, and
11.3 The Plot Library 407
context menus.
More information about these topics can be found in the Maple User Manual. If you are
interested in simply creating a plot to be displayed in a Maple worksheet, then these easy-
to-use, interactive methods are recommended. However, if you want to write programs that
build and manipulate plots, then it is necessary to be familiar with Maple's plotting library
commands.
Plots can also be used as embedded components, which means they are created as graphical
interface components that can be manipulated through various actions. For more information,
see Programming Interactive Elements (page 489)
Many of the commands introduced in this section are from the plots package, so we will
make the short form of the names available.
> with(plots);
(11.1)
Most plotting commands accept optional arguments that change the default look of the plots.
These options are summarized on the plot/option and plot3d/option help pages, and the
most commonly used ones are described in Customizing Plots (page 453). We will refer to
options that apply to the entire plot, such as title or gridlines, as global options. Options
that apply to individual elements, such as color, are called local options.
408 11 Graphics
Introduction
There are two basic plotting commands in Maple: plot and plot3d. The plot command
produces 2-D curves from representations of mathematical functions in one independent
variable, and the plot3d command generates 3-D surfaces from representations of functions
in two variables.
The following command generates a 2-D plot of the function over the
range 1 to 5.
> plot(exp(x/2)-5*x+x^2, x=1..5, 'color'="NavyBlue", 'thickness'=2);
The following command generates a 3-D plot of the function over and
ranges of to .
11.3 The Plot Library 409
In both these statements, the first argument is an expression representing the curve or surface
to be plotted. This is followed by ranges for the independent variables. Finally, optional
arguments may be added.
In the operator form of the calling sequence, the first argument must be a procedure. It can
be written using proc ... end proc, as in the call to plot or with arrow notation, as in the
call to plot3d. It can be the name of any predefined procedure, including ones from the
Maple library, as in the following example.
412 11 Graphics
The procedure must accept one floating-point input argument in the 2-D case and two such
arguments in the 3-D case, and it must return a floating-point value. For the operator form
of the calling sequence, the range arguments are simple ranges rather than equations, as
with the expression form of the calling sequence.
The operator form is primarily used when the function to be plotted is not easily written as
a Maple expression in the plotting variables.
> p := proc(x)
if abs(x)<0.1 then
100*x
elif abs(x)>0.5 then
4*x
else
1/x
end if;
end proc:
11.3 The Plot Library 413
Normally, ranges given to a plotting command must have endpoints that evaluate to floating-
point numbers. However, you can get an infinity plot by including infinity as one of the
endpoints. For more information, refer to the plot/infinity help page. With 3-D plots, you
can also use, in the range for one variable, an expression that depends on the other variable.
414 11 Graphics
To display multiple curves or surfaces in a single plot, provide a list of expressions or pro-
cedures to the plot or plot3d command. With the plot command, the options that affect the
look of an individual curve, such as color or thickness, also accept lists of values.
11.3 The Plot Library 415
If you want to generate a 3-D plot containing three surfaces, you must add the option plot-
list=true to distinguish this plot from a parametric plot, which is described in the next section.
Occasionally, users get unexpected results by mixing the two calling sequences. Common
errors are described in Mixing Expression and Operator Forms (page 482)
Parametric Form
In the previous examples, the first argument is used to calculate the value of the dependent
variable as a function of the independent variable or variables. A parametric curve, where
the and values are functions of a single parameter , can also be plotted by the plot
command. Similarly, a parametric surface, where the , , and values are functions
of two parameters and , can be plotted by the plot3d command.
To generate a 2-D parametric plot, provide as the first argument a list containing three items:
an expression for the value, an expression for the value, and an equation containing
the parameter and its range.
416 11 Graphics
To generate a 3-D parametric plot, provide as the first argument a list containing three ex-
pressions, for the , , and values respectively. Two additional arguments are required,
each in the form of an equation containing one of the parameters and its range.
11.3 The Plot Library 417
Operator form can also be used with parametric plots. The two previous examples are
written using operator form as follows. As with non-parametric plots, options may be added.
418 11 Graphics
The calling sequences for parametric plots are explained in detail on the plot/details and
plot3d help pages.
Points
The plots:-pointplot and plots:-pointplot3d commands are used to plot collections of 2-
D or 3-D points. These points can be provided as lists of two-element or three-element lists.
Alternatively, they can be provided as two or three Vectors. The options symbol and
symbolsize, described on the plot/options help page, can be used to change the look of
each point.
420 11 Graphics
> pointplot([[0, 1], [1, -1], [3, 0], [4, -3]], 'color'= ["Red",
"Green", "Black", "Blue"], 'symbol'='asterisk', 'symbolsize'=15,
'view'=[-1..5, -4..2]);
The plot command also offers a calling sequence in which a collection of points is provided.
The plot command differs from the pointplot command in that a connecting line is drawn
by default. To draw discrete points with the plot command, use the style=point option.
If you have a large data set, it is highly recommended that you create the data set as a
Matrix with the datatype option set to float. You can import data sets created by applications
other than Maple by using the ImportMatrix command or the ImportData assistant.
The plots:-polyhedraplot command can be used to display any of the polyhedra described
on the plots/polyhedra_supported help page.
11.3 The Plot Library 423
The polyhedraplot command makes use of the geometry and geom3d packages. Use these
packages if you want to do more computations with geometric objects. These objects can
be plotted with the geometry:-draw and geom3d:-draw commands.
The plottools package also offers commands to create geometric objects. The result of these
commands is a plot data structure. For more information about this package, see Creating
Plot Structures (page 450).
Text on Plots
Text can be added to plots with the title and caption options. Axis and tickmark labels can
also be specified with the labels and tickmarks options. For details on specifying tickmarks,
refer to the plot/tickmarks help page. The text can be a string, a Maple expression, or an
unevaluated typeset call, which allows you to combine mathematical expressions and strings.
The mathematical expressions are typeset as described in Typesetting (page 464).
424 11 Graphics
The plots:-textplot and plots:-textplot3d allow you to create plots containing text objects
at arbitrary locations. The first argument is a list consisting of the coordinate values followed
by the text to be placed at the location defined by the coordinates. Common options used
with the textplot and textplot3d commands include the font option, to change the font type
and size, and the align option, to position the text relative to the coordinate values.
11.3 The Plot Library 425
Usually, text objects are displayed in combination with other plot elements. In the next
section, you will learn how to merge plots created with the commands introduced so far.
Combining Plots
This section describes how to use the plots:-display command to combine plots, or more
specifically, plot data structures. Two or more plots can be merged into a single plot or they
can be placed side-by-side in a tabular arrangement.
When a plot is generated using any library command, a plot data structure is created. This
structure can be assigned to a variable to be reused or modified. A detailed explanation of
how the structures are formed is available in Data Structures (page 445), but it is not necessary
to understand the details in order to manipulate the structures as described here.
Merging Plots
To combine the elements of two or more plots into a single plot, use the plots:-display
command, with the plots contained in a list as the first argument.
> sinecurve := plot(sin(x), x=-Pi..Pi):
> maxtext := textplot([Pi/2, 1, "maximum"], 'align'='above'):
426 11 Graphics
Any combination of plot structures created from arbitrary Maple plotting commands can
be combined using the display command, with a some restrictions. Usually, different types
of plots (2-D, 3-D, arrays of plots, and animations) cannot be mixed.
When plots are merged, their options are also merged if possible. If there is a conflict, the
display command will try to resolve it. For example, if two plots with different titles are
merged, the display command will arbitrarily choose one for the merged plot.
The display command allows additional options to be provided. In the case of global options
(ones that apply to the entire plot such as caption), these additional options will override
those given for the individual plots. However, local options specified within the plots are
generally not overridden.
The display command accepts the option insequence=true, which causes the plots to be
displayed sequentially in an animation rather than merged. This use of the display command
is discussed in Animations (page 473).
plot structure can also be given, in which case the frames of the animation are displayed in
tabular form.
Unlike the situation where plots are merged, you can mix different types of plots in an array
of plots. For example, you can create an array containing both 2-D and 3-D plots.
A limited number of options can be passed to this command. Most global plot options are
accepted and are applied to each plot in the Array individually.
For more information about this feature, including the aligncolumns option that allows you
to align the -axes of plots within a column, refer to the plot/arrayplot help page.
Specialty Plots
In this section, a few commonly used commands for specialty plots are introduced. Refer
to the Maple Plotting Guide for a complete list of commands available.
The plots:-contourplot command generates a contour plot for an expression in two variables.
The plots:-contourplot3d command does the same but generates a 3-D display.
> contourplot3d(-5*x/(x^2+y^2+1), x=-3..3, y=-3..3,
'filledregions'=true, 'coloring'=["Red", "Blue"]);
The plots:-polarplot command generates a plot in polar coordinates with polar axes. This
command offers a number of options to control the look of the radial and angular axes.
11.3 The Plot Library 429
To plot in other coordinate systems, you can use the coords option. However, unlike with
the polarplot command, these plots are drawn with Cartesian axes. For a complete list of
supported coordinate systems, refer to the coords help page,
430 11 Graphics
The plots:-dualaxisplot command creates a plot with two -axes located at the left and
right sides of the plot. You can provide either two expressions or two plot data structures.
11.3 The Plot Library 431
The plots:-fieldplot and plots:-fieldplot3d commands generate plots of 2-D or 3-D vector
fields.
11.3 The Plot Library 433
Other Packages
Many of the plotting commands introduced so far are part of the plots package. Several
other packages in Maple also contain visualization commands.
The plottools package includes commands for generating and transforming graphical objects.
This package is described in Creating Plot Structures (page 450).
The Student package consists of several subpackages designed to assist with the teaching
and learning of mathematics. Each Student package has a collection of visualization com-
mands. For example, for Calculus, refer to the Student/Calculus1/VisualizationOverview
help page.
434 11 Graphics
The Statistics package contains a large number of commands for visualizing univariate and
multivariate data. These are listed in the Statistics/Visualization help page.
11.3 The Plot Library 435
As mentioned in Polygons and Polyhedra (page 421), geometric objects can be created and
displayed with the geometry and geom3d packages.
In the GraphTheory package, directed and undirected graphs can be drawn with the
GraphTheory:-DrawGraph command. In addition, the package provides many predefined
graphs as well as visualizations of some algorithms.
436 11 Graphics
> with(GraphTheory):
with(SpecialGraphs):
DrawGraph(PetersenGraph());
A 2-D Example
This first example shows how plotting commands can be combined to create a single plot.
> f := x*sin(x):
fderiv := diff(f, x):
fplot := plot(f, x=-2*Pi..2*Pi, 'color'="DarkRed", 'legend'=f,
'thickness'=2):
fdplot := plot(fderiv, x=-2*Pi..2*Pi, 'color'="Navy",
'legend'=fderiv):
11.4 Programming with Plots 437
You can make this code more general and reusable by defining a procedure to produce a
similar plot given an expression as the first argument, and an equation involving the plotting
variable and range as the second argument.
> derivativeplot := proc(f, r :: name=range)
local fderiv, v, fplot, fdplot;
There are a few modifications that can be made to improve derivativeplot, such as error-
checking and processing of additional plotting options.
In the derivativeplot procedure, the name=range type is specified for the r parameter, and
the type-checking of this argument is done automatically by the built-in parameter processing
facilities in Maple. However, it is useful to check for correctness of the first expression.
Specifically, derivativeplot should check that the first argument is an expression in one
variable and that variable matches the one given in the second argument. For example, the
following incorrect call would produce an empty plot.
11.4 Programming with Plots 439
Many visualization commands in the Maple library that use the basic plotting procedures
plot, plot3d, and plots:-display assume that additional arguments are global plot options
to be passed along for processing by these commands. In the modified derivativeplot pro-
cedure below, the unprocessed arguments in _rest are passed to the display command. For
more information on _rest, see Special Sequences for Referring to Parameters and Argu-
ments (page 236).
Notice that the title option in the last derivativeplot call replaces the default title specified
within the procedure. The convention followed by many Maple commands is that, when
there are duplicate option names in the calling sequence, the last value given is the one that
is applied. However, if you added the option color="Green", that would not change the
default colors of the two curves. That is because the color="DarkRed" and color="Navy"
11.4 Programming with Plots 441
options have been saved in the plot data structures p1 and p2 as local options, and they will
not be overridden by plot options passed to the display command.
A 3-D Example
The next example defines a procedure that accepts a list of expressions in one variable and
displays these as "ribbons" in a 3-D plot.
The following ribbonplot procedure accepts a list of expressions in one variable, an equation
containing the variable name and range, and an optional keyword parameter, numberpoints.
The numberpoints option specifies the number of points along the ribbon and is set to 25
by default. For brevity, this procedure does not include the error-checking of the list of ex-
pressions as for the derivativeplot example.
> ribbonplot := proc(f::list, r::name=range, {numberpoints::posint
:= 25})
local i, p, y, n;
n := nops(f);
p := Vector(n);
# Generate a 3-D plot for each expression and combine with
# plots:-display.
for i to n do
p[i] := plot3d(f[i], r, y=i-0.75..i, 'grid'=[numberpoints,
2]);
end do;
plots[display](convert(p, 'list'), _rest);
end proc:
In the procedure ribbonplot, a Vector is used to store the n plot structures generated by
the plot3d command. The grid option is passed to plot3d to specify the number of sample
points in each plot direction. As with the previous example, additional global plot options
are passed directly to the plots:-display command.
Call ribbonplot with four expressions as the input and using the default options. Then, call
it again with more sample points and constrained scaling.
442 11 Graphics
Now, change the ribbonplot procedure so that it accepts input in operator form instead of
expression form. Following the convention of the plot and plot3d command, in the modified
procedure, the second argument is the plotting range, with no plotting variable specified.
In addition, this version of ribbonplot allows a ribboncolors option that lets the user specify
the color of each ribbon. If no such option is provided, the ribbons are colored with the default
surface shading for 3-D plots.
> ribbonplot := proc(f::list, r::range, {numberpoints::posint :=
25,
ribboncolors::list({name,string}):=[]})
local i, p, y, n, nr;
n := nops(f);
p := Vector(n);
# Check that the number of ribbon colors matches the number
of
# procedures.
nr := nops(ribboncolors);
if nr>0 and nr<>n then
error "%1 ribbon colors needed", nr;
end if;
444 11 Graphics
This procedure contains a check to ensure that the number of ribbon colors is the same as
the number of procedures in the list f. Also, each procedure in f must be turned into a pro-
cedure with two input parameters before being passed to the plot3d command. In the call
to plot3d, the color option is passed only if the ribboncolors option had originally been
provided.
> g := proc(x) if x < 0 then cos(x) else cos(2*x); end if; end proc:
ribbonplot([g, sin, cos+sin], -Pi..Pi, 'transparency'=0.5,
'ribboncolors'=["DarkBlue", "DarkRed", "DarkGreen"]);
11.5 Data Structures 445
These structures are Maple expressions; therefore, they can be assigned to variables and
used in the same way as other expressions. When you enter a plotting command, the plot
data structure is generated. Then, the output displayed depends on the current interface or
plotting device requested. If you are using the standard worksheet interface and the plot
output has not been redirected to a non-default device, you will see the plots rendered as
they are shown in this guide.
In this section, you will learn about the components of the plot data structure and the tools
available to manipulate them. An understanding of the internal data structure is useful when
writing programs that create and transform plots. However, it is strongly recommended that
you use the available Maple library commands to generate plots and plot components
whenever possible, rather than building the structures directly. Because these structures are
part of the internal representation of plots, new graphics features offered in future Maple
releases may necessitate minor updates to the format.
446 11 Graphics
Basic Structures
PLOT -- 2-D plot. Contains any of the object data structures listed below, except for
MESH and ISOSURFACE, followed by any number of 2-D option structures. Can also
contain an ANIMATE structure.
PLOT3D -- 3-D plot. Contains any of the object data structures listed below, followed
by any number of 3-D option structures. Can also contain an ANIMATE structure.
_PLOTARRAY -- Array of plots. Contains a single Matrix, each element of which is
a PLOT or PLOT3D structure.
ANIMATE -- Animation. Contains a sequence of lists, each corresponding to a single
frame in the animation and containing object and option structures.
Object Structures
ISOSURFACE -- 3-D volume of data, which consists of function values over a regular
grid. This results in a rendering of a 3-D surface approximating the zero surface of the
function. Contains , , , and values over an by by grid. The
data is contained in a four-dimensional Array or in nested lists.
Each object structure may contain one or more local option structures following the required
data. For example, the following structure produces two polygons, one blue and one purple.
> PLOT(POLYGONS([[0., 0.], [0., 1.], [1., 1.], [1., 0.]], COLOR(RGB,
0., 0., 1.)), POLYGONS([[1., 1.], [1., 2.], [2., 2.], [2., 1.]],
COLOR(RGB, .5, 0., .5)));
Option Structures
There are a large number of option structures, some used for either 2-D plots or 3-D plots
only, and some that apply to both. Most of the option structures have a direct correspondence
to plot options described on the plot/options and plot3d/option help pages. (Note that the
converse is not necessarily true. Several plot options do not have associated option structures.)
Here are a few examples.
The symbol=asterisk option is translated to SYMBOL(_ASTERISK) in the plot data
structure.
448 11 Graphics
In The Plot Library (page 407), the concept of global options (options that apply to the entire
plot) and local options (ones that apply to a particular plot object) was introduced. This
concept applies in a similar way to plot structures. Local option structures, such as STYLE
or TRANSPARENCY can appear inside one or more plot object structures (CURVES,
TEXT, etc.). Global option structures, such as TITLE, appear outside the plot object
structures and usually there is only one instance of each kind of structure.
Some options, such as COLOR, can appear as a global option as well as a local option. In
this situation, the value of the local option is applied to the plot object with which it is asso-
ciated, and it is not overridden by the global option. If a plot structure has duplicate options
at the same level (all global, or all local within the same plot object structure), such as two
CAPTION entries, then the last one appearing in the structure is the one that is applied
when the plot is rendered.
The plots:-display command can be used to merge plot data structures. Details on how
option structures are combined are given in Merging Plots (page 425). The display command
can also be used to add an option structure to an existing plot. In the following example,
the display command accepts the thickness=3 plot option and adds THICKNESS(3) to
the data structure.
> p := plot(x^2-2*x+1, x=-4..4, 'color'="DarkViolet"):
11.5 Data Structures 449
> p;
450 11 Graphics
The plottools commands do not produce complete plots, so the output will consist of the
data structure in text form rather than a rendered image. To view the result of a plottools
command, you must pass it to the plots:-display command.
> with(plottools):
> t := torus([1, 1, 1], 1, 2, 'color'="LightBlue",
'transparency'=.5):
> s := sphere([5, 2, 3], 2, 'color'="LightGreen", 'transparency'=.7):
11.5 Data Structures 451
As shown in this example, options may be passed to the plottools commands. These must
be local options that are applicable to the type of structure that is produced. The color option
applies to any plot object, while the transparency option is used only with plot objects that
are rendered as surfaces. The scaling option is a global option that applies to an entire plot;
thus, it is included in the call to display instead of the calls to torus and sphere.
You can apply an arbitrary transformation to a 2-D or 3-D plot using the plottools:-trans-
form command. To do this, define a procedure f that represents a mapping from to
, where and can take values 2 or 3. The procedure must take as input argu-
ments and return a list of components. If you pass f to the transform command, it returns
a procedure that takes a 2-D or 3-D plot data structure as its argument and returns a trans-
formed plot.
> p := plots:-contourplot(2*x^2+y^2, x=-3..3, y=-3..3, 'filled',
'coloring'=["Purple", "Teal"]):
> f := (x, y)->[x, y, 0]:
> tf := transform(f):
11.6 Customizing Plots 453
In the descriptions in this section, it is assumed that you are using the plot and plot3d
commands. However, many other plotting commands in Maple accept these options as well.
Number of Points
The numpoints option sets the minimum number of sampling points used by the plot and
plot3d commands. Because the plot command uses an adaptive plotting scheme, it usually
generates more points than this number. The plot3d command does not use an adaptive
scheme and generates a number of points close to the specified numpoints value.
The grid option is an alternative way of specifying the number of sample points in 3-D
plots. This option takes a list of two positive integers that specify the dimensions of the
rectangular grid over which the points are generated.
> p1 := plot3d(.5*sin(x+y), x = -Pi .. Pi, y = -Pi .. Pi, grid =
[10, 10]):
> p2 := plot3d(.5*sin(x+y)+1, x = -Pi .. Pi, y = -Pi .. Pi):
> p3 := plot3d(.5*sin(x+y)+2, x = -Pi .. Pi, y = -Pi .. Pi, grid=[40,
40]):
> plots:-display([p1, p2, p3]);
Adaptive Plotting
In 2-D plotting, the numpoints value is used to set the initial sample points. When the ad-
aptive option value is set to true (the default value), the intervals defined by the initial
sample points are further subdivided in an attempt to get a better representation of the
11.6 Customizing Plots 455
function. The attempts at subdivision are based on the current sample values, and intervals
are subdivided a maximum of six times. The adaptive option can also take a positive integer
value that controls the maximum times intervals are subdivided.
If the adaptive option is set to false, the number of points generated is the same as the
numpoints value (or the default value, if it is not provided).
The sample option allows you to provide a set of points at which the function is to be
sampled. If adaptive plotting is allowed, the final set of sample points includes those in the
provided list but normally consists of many more. To use exactly the list of points given by
the sample option, specify adaptive=false.
> plot(x^2, x=0..4, 'adaptive'='false', 'sample'=[seq(0.1*i,
i=0..40)], style=point);
The adaptive and sample options are not available for 3-D plotting.
Discontinuities
If a function with a discontinuity is being plotted, then evaluation at the point of discontinuity
will lead to an undefined value, which results in a gap in the plotted curve or surface. It is
more likely that the function is evaluated at points very close to the discontinuity, and this
can lead to a distorted view of the plot when the values are extremely large or small and an
inappropriate connecting of the points over the discontinuity.
456 11 Graphics
In the 2-D case, the discont option can be used with the plot command when you suspect
a discontinuity. The plot command uses the discont and fdiscont commands to detect dis-
continuities and divides the plotting range into subranges over which the plot is continuous.
In the following example, the plot on the left contains extraneous vertical lines. These are
avoided in the right-hand-side plot generated with the discont option.
> plots:-display(Array([plot(tan(x), x=-Pi..Pi, y=-4..4),
plot(tan(x), x=-Pi..Pi, y=-4..4, 'discont'=true)]));
Usually, removable discontinuities are ignored. However, you can use the showremovable
suboption to draw a circle on the plot to mark the point of discontinuity. Another suboption
is usefdiscont, which controls how the fdiscont command is used to find discontinuities
numerically. For more information about all the suboptions available for the discont option,
refer to the plot/discont help page.
Colors
The color option is used to specify the color of plot objects in 2-D and 3-D plots, and its
value can take several forms. A few of these are described in this section. For more details,
refer to the plot/color help page. Default colors are chosen by Maple when none are specified.
values is available on the plot/colornames help page. These names correspond to commonly
used HTML color names.
> plot3d(binomial, 0..5, 0..5, 'color'="LightSteelBlue");
Alternatively, a plot color structure can be used as the value for the color option. This takes
one of the following forms: COLOR(RGB, v1, v2, v3), COLOR(HSV, v1, v2, v3), or
COLOR(HUE, v1). More information about the COLOR structure is available in the
plot/structure help page.
If no colors are provided, the plot command uses a default list of colors. To see the default
list used by plot and several other 2-D plotting commands, use the plots:-setcolors command.
> plots:-setcolors();
(11.3)
11.6 Customizing Plots 459
The plots:-setcolors command also allows you to set new default colors. If there are fewer
colors than curves, the colors are repeated in order.
> plots:-setcolors(["Indigo", "ForestGreen"]):
460 11 Graphics
The following command resets the colors to the original default colors.
> plots:-setcolors('default'):
You can obtain a customized shading parametrized by the plot variables. To do this, provide
an expression or a list of three expressions in terms of the plot variables. If a single expression
is given, it is taken to be a hue value; if a list of three expressions is given, the triplet is
taken to be RGB values.
462 11 Graphics
Similarly, if the input is given as procedures instead of expressions in two plotting variables,
the color can be specified by a procedure or a list of three procedures that take two input
parameters and return a single value. More details are provided in the plot3d/colorfunc
help page.
View
The view option determines the extent of the axes in the rendered plot. In the next example,
the plot data structure produced by the plot command contains all the points generated from
the given range, -2..4. However, the view is restricted to the portion of the plot in the box
defined by values in the range -1..3 and values in the range -3..0.
11.6 Customizing Plots 463
In the next example, the specified -axis view is much larger than the range. Since the
computed points are based on the range , the displayed curve is only shown for
this range. To generate the curve for the entire range from -5 to 7, you must re-execute
the plot with x=-5..7 as the second argument.
464 11 Graphics
In certain cases, the plotting command automatically sets the view option based on the
provided ranges. Otherwise, the default view is determined by the minimum and maximum
values of the data points generated.
Typesetting
Typeset text and mathematics can appear anywhere in a 2-D or 3-D plot where text is al-
lowed. This includes text provided by the plots:-textplot command and the following options:
caption, labels, legend, tickmarks, and title.
You can provide arbitrary expressions to the textplot command or as values for the options
to the command. These expressions are displayed on the plot as typeset output whenever
possible. Strings are displayed as plain text without the quotation marks, but names such
as x, y, and Pi are typeset. To concatenate several expressions, wrap them inside a typeset
structure.
11.6 Customizing Plots 465
The plot/typesetting help page provides more details about using typeset mathematics in
plots. It includes tips on using 2-D math input to generate typeset expressions that are not
easily expressed in 1-D math input. These expressions can then be made into atomic identi-
fiers through the context menu and then converted to 1-D math to be used programmatically.
For more information about 1-D and 2-D math input modes, see 2-D Math (page 383).
You can obtain greater control over the look of each axis by using the axis or axis[dir] option
and providing a list of suboptions. To apply suboptions to one axis, use the indexed axis[dir]
option, where the direction dir is 1, 2, or 3, with 3 applicable to 3-D plots only. To apply
the suboptions to all axes, use the axis option without the index.
The suboptions include gridlines and tickmarks options, which are similar to the regular
options of these names but offer more flexibility. Other suboptions are color (to change the
color of an axis), location (to move an axis to the lowest or highest value of the view range
or to the origin), and mode. This last suboption allows you to use logarithmic scaling for
an axis. (Note that you can also create log plots with the plots:-logplot, plots:-semilogplot,
and plots:-loglogplot commands.)
11.6 Customizing Plots 467
Coordinate Systems
Plots are normally plotted in the Cartesian coordinate system, but you can use the coords
option to plot in a different system. The 2-D and 3-D coordinate systems recognized by
Maple and the transformations that they represent are listed in the coords help page.
468 11 Graphics
The coords option must be used at the time the plot is generated. However, the plots:-
changecoords command can be used to transform a plot structure that has already been
created to one that uses a different coordinate system.
When working with alternate coordinate systems, two useful commands are plots:-coordplot
and plots:-coordplot3d, which provide graphical representations of coordinate systems
using lines or surfaces of constant value.
11.6 Customizing Plots 469
Because the polar coordinate system is commonly used, Maple has a plots:-polarplot
command for plotting in this system. Polar axes are displayed by default, and special options
are available for customizing the look of these axes. In particular, the coordinateview option
can be used to restrict the view in the polar coordinate system.
470 11 Graphics
The style of the coordinate axes (either polar or cartesian) can be changed with the axisco-
ordinates option. This option is available for 2-D plots in general and is not restricted to
the polarplot command.
11.6 Customizing Plots 471
Setting Options
The section Colors (page 456) describes the use of the plots:-setcolors commands to set the
default curve colors for 2-D plots. More general commands for setting options for all 2-D
and 3-D plotting commands are plots:-setoptions and plots:-setoptions3d.
The plots:-setoptions command allows you to specify options that are applied to all 2-D
plots created in the same Maple session. The plots:-setoptions3d command performs a
similar function for 3-D plots. These settings are recognized by the plot, plot3d, and plots:-
display commands, as well as a number of other plotting commands in Maple.
In the following example, the default symbol for point plots is set to solidcircle with a
symbol size of 20.
> plots:-setoptions('symbol'='solidcircle','symbolsize'=20);
472 11 Graphics
The default value is overridden in the following example, which provides the symbol option
explicitly in a command.
11.7 Animations 473
11.7 Animations
Building an Animation with plots:-display
The plots:-display command can be used to build an animation. The calling sequence is
plots:-display(L, insequence=true)
where L is a list of plot structures, all 2-D or all 3-D. An animation will be created in which
the plots in L appear in sequence, one in each frame.
> for i to 10 do
plotframe[i] := plot(0.75*x^i, x=0..1):
end do:
plots:-display([seq(plotframe[i], i=1..10)], insequence=true,
scaling=constrained);
474 11 Graphics
The following example shows how to create an animation with the plots:-spacecurve
command. The first argument to animate is the procedure name. The second argument is
the list of arguments to be passed the the given procedure. This list contains a parameter
whose range, over which the animation will vary, is given in the final argument to animate.
11.7 Animations 475
There are several ways to create the animation, which are all described on the
plot3d/viewpoint help page. The simplest way is to use one of the standard viewpoint paths,
such as circleleft.
11.7 Animations 477
If you specify the insequence option to plots:-display, then it is displayed as a regular an-
imation.
11.8 Miscellaneous Topics 481
It is recommended that you build plot structures by using the commands in the plotting
library. However, if you want to build the plot structures directly, similar guidelines apply.
Most plot data structures (described in Data Structures (page 445)) allow the data to be
stored in either a list or an rtable. rtables should be used for those structures that support
them, and they should be created with the datatype=float[8] and order=C_order options.
With any of these interfaces, you can redirect plotting output to an alternative device. The
devices available are listed in the plot/device help page and include common graphics
formats such as JPEG and PostScript.
Plot output is controlled by a number of interface variables such as plotdevice (the name
of the plotting device) and plotoutput (the name of an output file). These are described in
the interface help page. The plotsetup command provides a simpler way to set up these
interface variables, without having to use the interface command directly.
For example, the following command specifies that all subsequent plot output be in PostScript
format and be saved in a file called plot.ps. Furthermore, the PostScript driver will use a
portrait orientation with no border. The plotsetup(default) command restores the default
output options.
> plotsetup('ps', 'plotoutput'="plot.ps", 'plotoptions' =
"portrait,noborder");
> plotsetup('default');
a mistake to provide a procedure as the first argument but then to use variable names in the
subsequent range arguments. The first example below generates an error, while the next
one produces an empty plot with a warning that the plotting function could not be evaluated
to numeric values.
> plot(sin, x=0..Pi);
Error, (in plot) expected a range but received x = 0 .. Pi
Another common mistake is to use the expression form when you mean to provide an oper-
ator. A typical example is the following:
> p := proc(x) if type(x, 'positive') then sqrt(x) else 0 end if;
end proc;
(11.5)
The correct way to plot this procedure over the range -1 to 1 is the use operator form.
484 11 Graphics
If you attempt to use expression form, then p(x) is evaluated immediately, the value 0 is
passed to the plot command as the first argument, and the resulting plot is a horizontal line.
11.9 Avoiding Common Problems 485
In the first example below, the ragged edge is caused by the fact that some of the values are
undefined. The second example, which has the range of t going from 0 to s instead of 0 to
1, produces a more accurate plot.
486 11 Graphics
Non-numeric data can also be produced as the result of mistyping a variable name. In the
example below, the second argument mistakenly contains the variable z instead of x. Con-
sequently, when a numeric value in the range 0..Pi is substituted for the variable z in the
expression sin(x), the result remains the algebraic expression sin(x), which is non-numeric.
This leads to a warning from Maple and an empty plot.
488 11 Graphics
The Maple library also includes a package called Maplets for building custom user interfaces
to allow users to perform analysis tasks related to your mathematics. Several Maple com-
mands, such as the tutors in the Student package, use Maplets that allow users to perform
various tasks.
489
490 12 Programming Interactive Elements
You can use a table to lay out the components in your document. To insert a table, from the
Insert menu, select Table. You can then insert buttons, plots, gauges, math, text, and so
on in each table cell.
to display a window in which you can enter the code (see Figure 12.1). By default, this
window contains sample code that you can start with.
Each embedded component has different properties that you can edit. For more information
about the properties for a specific component, refer to the EmbeddedComponents help
page and browse to the page of the component that you want to use.
Tip: If you are working with multiple components in a document, you may find it easier to
include the code for all of the component actions in one area, for example, the startup code
region of your document. For more information, see the worksheet,documenting,startup-
code help page.
It is possible to associate code with each of the list boxes individually. However, if you
want to change this code in the future, you would need to change it in nine places. A simpler
approach is to write a Maple module that contains the code to perform the action and call
that code from each list box.
1. Open a new Maple document.
2. To insert a table, from the Insert menu, select Table...
492 12 Programming Interactive Elements
Tip: Make sure that all of the embedded components in your document have unique names.
13. To close the Properties dialog box, click OK.
14. In the document, select the list box, and then copy and paste it into all of the remaining
cells.
Note: You can copy and paste embedded components within a document or from one doc-
ument to another. A unique name is assigned to each pasted component - the number in the
component name is incremented. Other details associated with the component, for example,
properties and actions, are copied in their original form.
15. Below the table, enter the following module to perform the action.
> TicTacToe := module()
uses DT = DocumentTools;
export Select;
Select := proc( what )
if DT:-GetProperty( what, 'value' ) <> "-" then
DT:-SetProperty( what, 'enabled', false );
end if;
end proc;
end module;
The %this argument, which is passed as the value of the what parameter to the TicTacToe:-
Select procedure, is set to the name of the component that generates the action. Therefore,
12.2 Programming Embedded Components 493
the result is that if you select either the "X" or the "O" list element, the list box is dimmed
and the user's selection cannot be changed.
For more information about modules, see Programming with Modules (page 323).
Alternatively, you can save this module in a Maple library archive, which is a separate file
in which you can store Maple procedures, modules, and other data. For more information,
refer to the repository help page.
For more information about the properties that can be retrieved and set for each component,
refer to the EmbeddedComponents help page and browse to the help page of the component
that you want to use.
Code associated with a component can perform many different tasks. In particular, it can
update other components. For example, you can create a plot component that returns certain
values displayed in TextArea components or makes changes to other plots when the plot
component is clicked.
When code is run as a result of a mouse event that updates other components, those updates
occur after that code runs successfully. While this process is efficient, in some cases, you
might want these updates to occur immediately. In such cases, you can use the optional re-
fresh = true parameter, or simply refresh.
Consider the following points when deciding whether to use the DocumentTools:-Do
command to retrieve or update components.
The embedded component type determines the default property retrieved or set by the
Do command. For most components, the value property is the default property that is
retrieved or set. This means that the Do command must query the GUI to determine which
information to retrieve. The GetProperty and SetProperty commands avoid this step
by requiring you to specify which property to retrieve. However, if you are working with
a small number of components, this extra step will usually be insignificant.
The names of components appearing in the first argument to Do must be literal names
prefixed by %. That is, you cannot use the Do command to access or update a component
with a name that is determined programmatically.
12.3 Programming Maplets 495
Maplets support standard UI elements including buttons, drop lists, text fields, and sliders.
Some of the available UI elements are specific to Maple, for example, math fields and plot
regions. For more information about these elements, refer to the Maplets,Elements help
page.
Before creating a Maplet, familiarize yourself with the features of the various layout
managers, which define how the elements of your Maplets are positioned and laid out.
This section describes basic information related to laying out elements in Maplets. For ad-
vanced information, refer to the examples,AdvancedMapletsLayout worksheet.
Layout Managers
A layout manager defines the locations and positions of the UI elements in a Maplet window.
Three layout managers are available.
Box layout (BoxLayout): this layout manager can be used to create boxes that contain
elements positioned horizontally or vertically relative to other elements.
Grid layout (GridLayout): this layout manager positions elements in a specific cells in
a grid, similar to a spreadsheet.
Border layout (BorderLayout): this layout manager allows you to lay out elements in
specific regions, according to compass directions.
For simple Maplets, using a layout manager is often sufficient; however, for more complex
Maplets, you may need to use multiple layout managers or nest layout managers one inside
the other.
Note: For clarity, the full command for each layout control is used in this chapter.
The three layout managers are available in the Maplets,Elements subpackage of the Maplets
package. In this chapter, assume that the following command has been run.
> with(Maplets:-Elements):
This command allows the Maplets:-Elements package commands to be used without pre-
fixing them with Maplets:-Elements:-....
496 12 Programming Interactive Elements
Box Layout
The box layout is the most commonly used layout manager. It is a nested construct of
containers where elements can be displayed either horizontally or vertically in the Maplet
window. For example,
> mlet := Maplet(BoxLayout(border=true, caption="outer",
BoxColumn(border=true, caption="inner1",
Button("OK1", onclick=Shutdown()),
Button("OK2", onclick=Shutdown())
),
BoxColumn(border=true, caption="inner2",
TextBox("Misc. Text", height=5)
)
)):
Maplets:-Display(mlet);
As shown in the example above, you can use a BoxColumn element to specify a column
in a box layout. You can also use a BoxRow element to specify a row in a box layout.
For detailed information about box layouts, refer to the Maplets,Elements,BoxLayout help
page.
In a row or column of a box layout, you can also use the spacing option to specify the
amount of spacing that separates individual elements in that row or column.
The following examples demonstrate the use of these options.(Note: In both cases, the
outer BoxLayout element has the inset option set to 0 so that the formatting of the Box-
Column element can be displayed more easily):
In this example, the spacing option is set to 0, so the buttons are positioned close to each
other in the generated Maplet window. The buttons are positioned in the center of the Maplet
because the inset value displays space between the buttons and the border of the box element.
> mlet := Maplet(BoxLayout(inset=0,
BoxColumn(inset=10, spacing=0,
Button("OK1", onclick=Shutdown()),
Button("OK2", onclick=Shutdown())
)
12.3 Programming Maplets 497
)):
Maplets:-Display(mlet);
In this example, the spacing option displays space between the buttons in the generated
Maplet window. Also, since the inset option is set to 0, no spacing is displayed between
the border of the box element and the buttons. As a result, the buttons are closely aligned
with the top and bottom borders of the box element.
> mlet := Maplet(BoxLayout(inset=0,
BoxColumn(inset=0, spacing=10,
Button("OK1", onclick=Shutdown()),
Button("OK2", onclick=Shutdown())
)
)):
Maplets:-Display(mlet);
The following example shows two buttons that are positioned vertically and closely spaced.
> mlet := Maplet(BoxLayout(inset=0, vertical=true,
Button("OK1", onclick=Shutdown()),
Button("OK2", onclick=Shutdown())
)):
Maplets:-Display(mlet);
In the following example, the spacing has been removed by nesting the buttons in a Box-
Column element.
> mlet := Maplet(BoxLayout(inset=0,
BoxColumn(inset=0, spacing=0,
Button("OK1", onclick=Shutdown()),
Button("OK2", onclick=Shutdown())
)
)):
Maplets:-Display(mlet);
498 12 Programming Interactive Elements
Grid Layout
In a grid layout, all of the elements are displayed in a rectangular grid.
Each grid layout must contain one or more GridRow elements which must, in turn, contain
one or more GridCell elements.
Note: Each row or column of the grid is sized according to the maximum height or width
of all the elements in that row or column. By default, no spacing is displayed between the
largest elements in a grid layout.
In the following example, the full alignment option is used, so it is not necessary to specify
a size for each button.
> mlet := Maplet(GridLayout(halign=full, valign=full,
GridRow(
GridCell(height=2,
Button("2h", onclick=Shutdown())
),
GridCell(
Button("1a", onclick=Shutdown())
),
GridCell(
Button("1b", onclick=Shutdown())
)
),
500 12 Programming Interactive Elements
GridRow(
# This is where the button above extends into
GridCell(
Button("1c", onclick=Shutdown())
),
GridCell(
Button("1d", onclick=Shutdown())
)
),
GridRow(
GridCell(
Button("1e", onclick=Shutdown())
),
GridCell(width=2,
Button("2w", onclick=Shutdown())
)
)
)):
Maplets:-Display(mlet);
Border Layout
Unlike the other layout managers, the border layout is a container element for other layout
managers, though, it can be used for simple Maplets.
Five positions in the layout can be filled: north, south, west, east, and center, each of
which has its own layout rules. See Figure 12.2.
12.3 Programming Maplets 501
In terms of resizing, north and south extend horizontally, east and west extend vertically,
and center extends in both directions.
Button("Center", onclick=Shutdown()))
)):
Maplets:-Display(mlet);
Not all constraint options need to be specified; however, including more than one of the
same constraint returns an error.
Example:
> mlet := Maplet(BorderLayout(border=true, caption="borderlayout",
GridCell2(constraint=north,
Label("This is a long title")),
GridCell2(constraint=south,
Label("This could be a status bar")),
GridCell2(constraint=east,
Button("East", onclick=Shutdown())),
GridCell2(constraint=west,
Button("West", onclick=Shutdown())),
GridCell2(constraint=center,
Button("Center", onclick=Shutdown()))
)):
Maplets:-Display(mlet);
Examples
The following example is the same as the example above, except that the halign option has
been specified for the north and south labels (note the alignment is specified in the Label
elements):
> mlet := Maplet(BorderLayout(
GridCell2(constraint=north,
Label("This is a long title", halign=left)),
GridCell2(constraint=south,
12.3 Programming Maplets 503
In the following example, the element in the center cell stretches to fit a larger layout.
> mlet := Maplet(BorderLayout(
GridCell2(constraint=north,
Label("This is a very long title which causes "
"the center to stretch")
),
GridCell2(constraint=east, Button("East", onclick=Shutdown())),
GridCell2(constraint=center, Button("Center",
onclick=Shutdown()))
)):
Maplets:-Display(mlet);
504 12 Programming Interactive Elements
13 Advanced Connectivity
This chapter describes how to connect Maple to other applications. Maple can be connected
as the main interface (for example, to a database), as a hidden engine (for example, as part
of a Microsoft Excel plug-in), or side-by-side with another application (for example, a
CAD application). You can also use Maple to generate code.
For more information, see MapleNet (page 506), OpenMaple (page 508), and The Maple
Command-line Interface (page 519).
For more information, see External Calling: Using Compiled Code in Maple (page 520).
505
506 13 Advanced Connectivity
CAD applications. Other methods are available, such as setting up a communication channel
to the other program using the Sockets package.
For more information, see CAD Connectivity (page 543), Maple Plug-in for Excel (page 544),
Connecting MATLAB and Maple (page 545), and Accessing Data over a Network with
TCP/IP Sockets (page 532).
Code Generation
The Maple programming language and the worksheet environment are ideal for creating
prototypes. They are also ideal for error-free algebraic manipulations and long calculations.
When you create a prototype that includes various formulas, you can easily write that program
in the native language that is used by your application. The generated code can then be
compiled and embedded directly in your application.
The CodeGeneration package provides commands for translating Maple code into other
programming languages such as C, Visual C#, Fortran, Java, MATLAB, and Visual Basic.
The resulting code can be compiled and used in applications other than Maple.
13.2 MapleNet
You can use MapleNet to publish your Maple applications on the web. Two key features
are provided by MapleNet: in-browser interfaces and the ability to connect to a Maple
computation engine over the Internet or an intranet.
Computation on Demand
A web service is software on a web server that listens for requests and waits to perform a
specific job. The MapleNet web service, in particular, waits to run Maple commands.
A desktop application that has a network connection can communicate with the MapleNet
web service to perform computations. The client computer that runs the desktop application
does not require Maple, a Maple license, or specific software to communicate with the web
service.
The server-side infrastructure for Maple web services is provided by the MapleNet API.
You can build client applications to use the MapleNet web service by using tools such as
the Microsoft C# toolkit, Apache Axis plug-in for Eclipse, IBM WebSphere, and the
NetBeans IDE.
13.2 MapleNet 507
Web services are defined by using a file containing the Web Services Description Language
(WSDL). Before creating a client application, download the definition of the MapleService
by using the WSDL file located at the following URL.
http://yourserver.com:port/maplenet/services/MapleService?wsdl
This URL points to a link on your server where MapleNet is installed. yourserver.com is
the name or IP address of your MapleNet server and port is the HTTP port on which your
server listens for requests (for example, 80 or 8080).
This URL can be used in your web services toolkit so that you can create the code to connect
to the MapleService web service.
The following language-independent APIs are the most commonly used methods in the web
service.
String result = evaluate(String expression);
String[] results = callMaple(String[] expressions);
The evaluate method accepts a string that is a valid Maple expression and returns a string
corresponding to the results generated by Maple after evaluating that expression.
The callMaple method accepts an array of strings of valid Maple expressions and returns
an equivalent number of strings corresponding to the results generated by Maple after each
expression is evaluated.
When a call to the web service is complete, the Maple kernel is released. In other words,
the state (for example, variable assignments) is not preserved.
Results are returned from the web service call as text. The text can either be a 1-D Maple
output expression or a Base64-encoded plot. Plot results take the following form.
"_Base64GIF(width,height,data)"
In this example, _Base64GIF is a literal text string, which indicates that the result is an
encoded plot. Following this string are two integers indicating the width and height of the
output plot. Finally, data is the binary plot data, which is a Base64-encoded .gif image. A
Base64 decoder must be used to translate the data into binary data that can either be rendered
by your client application or written out to form the bytes of a .gif file.
For more information and examples, refer to the MapleNet Publisher's Guide.
Maple can also be embedded in larger web applications and used as the underlying compu-
tation engine for your application. For example, by using MapleNet functionality, you can
use Maple as the graphics engine for scientific or business plots; you can also create an
equation editor to read formula input or display formatted equations.
You can also use two other technologies to include Maple as part of your web applications:
Java applets and JavaServer Pages (JSP).
Java applets, in conjunction with the MapleNet Java API, extend the browser capabilities
beyond those provided by HTML alone. For example, the Maple equation editor is an applet
that you can use to capture input using natural math notation. Another applet allows you to
embed 3-D plots with controls for rotation, zooming, and changing properties such as con-
strained access and color shading. By using the Java API, you can create custom applets,
for example, an applet that plots points in-place where you click, and uses Maple to compute
a smooth curve that interpolates the points. To run applets, you require a Java-enabled
browser.
JSP provides an alternate technology. Unlike applets, JSP pages are resolved on the server;
the client computer only processes HTML code. Therefore, no special requirements are
needed for the browser or computer running the browser to display the JSP results.
JSP is an extension to HTML. An HTML file can be extended by adding <maple> tags.
When the web server displays one of these pages, it replaces the <maple> tags with the
result of the specified computation. The result is an embedded image, text, or any other
element that you want to include.
MapleNet comes with complete documentation and detailed examples. For more information
on the Java applet and JSP APIs, refer to the MapleNet Publisher's Guide.
13.3 OpenMaple
OpenMaple is an interface that lets you access the Maple computation engine by referencing
its dynamic-link library (.dll) file.
Note: The OpenMaple interface is available on all platforms supported by Maple. The
convention in this guide is to use the terminology related to .dll files, in place of .so or
.dylib on other systems.
You can use this interface to embed calls to Maple in other applications.
Interfaces to access the OpenMaple API are provided for use with C, C++, Java, Fortran,
C#, and Visual Basic. All of these interfaces are built on the C API, so they all reference
the primary library, maplec.dll, which is located in your Maple binary directory. This library
13.3 OpenMaple 509
can be accessed from other languages by following the protocol established in the maplec.dll
file.
In Windows, depending on the source programming language, calls to initialize the Open-
Maple interface will locate these paths automatically so that the Maple commands will
work without additional configuration steps.
Note: If your application does not initialize, set your Windows %PATH% environment
variable to include the Maple bin.win or bin.X86_64_WINDOWS directory. To find out
which path to use, run the kernelopts(bindir) command in Maple.
These commands run the maple launch script to configure your environment without
starting Maple. The period (.) prefix in a Bourne shell causes the commands to be sourced,
thus, applying the settings to future sessions. Starting the application would be done via the
above script rather than calling the executable directly.
Interface Overview
Each OpenMaple application contains the following components:
a text callback to display or hide the output generated by Maple when an expression is
evaluated
510 13 Advanced Connectivity
The examples in this section show how to run a Maple computation using the OpenMaple
API. Each example evaluates an expression and then exits. The examples in this section are
intended to help you get started using the API. For detailed examples and descriptions, refer
to the OpenMaple help page.
Text Callbacks
In each example, a text callback is defined. Output that would normally be displayed after
an expression is evaluated in a Maple session is routed through callbacks. This output in-
cludes the following:
results from evaluated commands that are terminated with a semicolon
output from the print command, the printf command, and other display-related commands
userinfo and warning messages
diagnostic output from the debugger, high settings from printlevel, and trace output
error messages if no error callback is defined
resource status messages if no status callback is defined
displayed help pages
The text callback is not the only way to control output in your OpenMaple application. For
more control, individual results can be converted to strings, which can be displayed in text
boxes or directed in any way you want. When using this method of controlling output, the
text callback can be defined as a procedure that does not perform any operations (that is,
does not direct the output anywhere). Note that the Java example below uses the predefined
EngineCallBacksDefault class, which configures a method to print output using the Sys-
tem.out method. In general, if the text callback is left undefined by setting it to 0 or null,
the output is directed to the console.
The initialization process follows standard Maple start-up steps, including reading and
running initialization files, setting library paths, and setting default security options. The
startup state can be controlled by using the first parameter passed to the StartMaple function.
13.3 OpenMaple 511
This parameter is an array of strings that specify options accepted by the command-line
interface. For more information about these options, refer to the maple help page.
The OpenMaple interface manages Maple internal data structures and performs garbage
collection. The data structures that are returned by an API function are automatically pro-
tected from garbage collection. The Maple command unprotect:-gc must be called to clean
the memory reserved for these tasks. The OpenMaple Java interface is the only exception
to this rule. Because the OpenMaple Java interface implements Maple structures as native
objects, it manages object references by using a weak hash map, and therefore Maple data
does not need to be unprotected.
Maple data structures are all declared as a single black box ALGEB, IntPtr, or similar type.
Methods for inspecting and manipulating these data structures are provided. The API
methods should be used, rather than dereferencing them directly in the data.
C/C++ Example
#include <stdio.h>
#include <stdlib.h>
#include "maplec.h"
/* callback used for directing result output */
static void M_DECL textCallBack( void *data, int tag, char *output
)
{
printf("%s\n",output);
}
int main( int argc, char *argv[] )
{
char err[2048]; /* command input and error string buffers */
MKernelVector kv; /* Maple kernel handle */
MCallBackVectorDesc cb = { textCallBack,
0, /* errorCallBack not used */
0, /* statusCallBack not used */
0, /* readLineCallBack not used */
};
ALGEB r; /* Maple data-structures */
/* initialize Maple */
if( (kv=StartMaple(argc,argv,&cb,NULL,NULL,err)) == NULL ) {
printf("Fatal error, %s\n",err);
return( 1 );
}
r = EvalMapleStatement(kv,"int(x,x);");
StopMaple(kv);
return( 0 );
}
Additional examples are available in the samples/OpenMaple directory of your Maple in-
stallation.
The method used to build this program depends on which compiler you are using. The fol-
lowing command is specific to the GCC compiler on a 64-bit version of Linux; it is useful
as a reference for other platforms.
gcc -I $MAPLE/extern/include test.c -L $MAPLE/bin.X86_64_LINUX
-lmaplec -lmaple -lhf -lprocessor64
In this example, $MAPLE is your Maple installation directory. Note that the C header files
can be found in the $MAPLE/extern/include directory and the library files can be found
in the $MAPLE/bin.$SYS directory. In this case,$SYS is X86_64_LINUX; check the
library path you need to specify by running the kernelopts(bindir) command in Maple.
The remaining -l options specify which libraries need to be linked. In Windows, you only
need to link to the maplec.lib library. Other platforms may require several libraries to be
linked, including libmaplec.so, libmaple.so, and libhf.so. If you do not specify a library
as required, the compiler returns a message indicating that undefined references to functions
exist, or a dependent library cannot be found.
When this example is built, a file called test.exe is created. Note: The file might be called
a.out or another name, depending on your compiler. Before this executable file can be run,
you must specify the path of the Maple dynamic libraries. For more information, see Runtime
Environment Prerequisites (page 509).
After setting up your environment, run the binary file as you would run any other executable
file. For example, create a shortcut icon and double-click it, or enter the file name at a
command prompt.
13.3 OpenMaple 513
test.exe
C# Example
using System;
using System.Text;
using System.Runtime.InteropServices;
class MainApp {
// When evaluating an expression, Maple sends all of the displayed
catch(EntryPointNotFoundException e) {
Console.WriteLine(e.ToString());
return;
}
// make sure we have a good kernel vector handle back
if( kv.ToInt64() == 0 ) {
// If Maple does not start properly, the "err" parameter
will be filled
// in with the reason why (usually a license error).
// Note that since we passed in a byte[] array, we need to
remove
// the characters past \0 during conversion to string
Console.WriteLine("Fatal Error, could not start Maple: "
+
System.Text.Encoding.ASCII.GetString(err,0,Array.IndexOf(err,(byte)0))
);
return;
}
MapleEngine.EvalMapleStatement(kv,"int(x,x);");
MapleEngine.StopMaple(kv);
}
}
To build this example, you can open a Microsoft .NET Framework SDK Command Prompt.
Browse to the directory that contains the test.cs file and enter the following command.
csc test.cs $MAPLE\\extern\\include\\maple.cs
$MAPLE is the directory in which Maple is installed. The maple.cs file contains the
MapleEngine class definition. and defines an interface to the maplec.dll file.
When this example is built, a file called test.exe is created. This file can usually be run
without additional environment settings. For more information, see Runtime Environment
Prerequisites (page 509).
Run the binary file as you would run any other executable file. For example, create a
shortcut icon and double-click it, or enter the file name at a command prompt.
test.exe
Java Example
import com.maplesoft.openmaple.*;
import com.maplesoft.externalcall.MapleException;
class test
{
public static void main( String args[] )
{
String a[];
Engine t;
int i;
a = new String[1];
a[0] = "java";
try
{
t = new Engine( a, new EngineCallBacksDefault(), null,
null );
t.evaluate( "int( x,x );" );
}
catch ( MapleException e )
{
System.out.println( "An exception occurred" );
return;
}
System.out.println( "Done" );
}
}
To build this program, enter the following at a command prompt, where $JDKBINDIR is
the directory in which your Java development tools are installed, and $MAPLE is the dir-
ectory in which Maple is installed.
$JDKBINDIR/javac -classpath
"$MAPLE/java/externalcall.jar;$MAPLE/java/jopenmaple.jar" test.java
Note: The same command can be used to build the example in UNIX and Macintosh;
however, use a colon (:) to separate the directories in the classpath instead of a semicolon.
When this example is built, a test.class file is created in the current directory. Before this
file can be run, the path of the Java OpenMaple native library must be specified for your
Java Virtual Machine. For more information, see Runtime Environment Prerequis-
516 13 Advanced Connectivity
ites (page 509). In Windows, Java OpenMaple applications also require the %PATH%
environment variable to be set.
You can use the Java Virtual Machine to run the generated class file by entering the following
command. Note that the third entry in the classpath is a period (.) indicating the current
directory.
$JDKBINDIR/java -classpath
"$MAPLE/java/externalcall.jar;$MAPLE/java/jopenmaple.jar;." test
Note: The same command can be used to build the example in UNIX and Macintosh;
however, use a colon (:) to separate the directories in the classpath instead of a semicolon.
End If
dim result as Long = EvalMapleStatement(kv, "int(x,x);" )
End Sub
To build this example, create a new project, and add both the test.bas and $MAPLE/ex-
tern/include/maple.bas files to your project. $MAPLE is the directory in which Maple is
installed. Create a form called "MainForm" and add a text box named "OutputText" to the
form.
Build and run this example by pressing F5. When this example is built, a form that shows
a text box filled with the value 1/2*x^2 is displayed.
args(1) = "-A2"
kv = StartMaple(1, args, cb, 0)
Catch e As StartMapleException
MsgBox("Error starting Maple: " & e.Message, "")
Me.Close()
End Try
Dim result As IntPtr
result = EvalMapleStatement(kv, "int( x,x );" )
If result = 0 Then
tbOutput.Text = "invalid expression"
Else
tbOutput.Text = MapleToString(kv, MapleALGEB_SPrintf1(kv,
"%a", result))
End If
End Sub
End Class
To build this example, create a new project, and add both the test.bas and $MAPLE/ex-
tern/include/maple.vb files to your project. $MAPLE is the directory in which Maple is
installed. Create a form called "MainForm" and add a text box named "tbOutput" to the
form.
Build and run the example by pressing F5. When this example is built, a form that shows
a text box filled with the value 1/2*x^2 is displayed.
Memory Usage
Maple allocates memory from the operating system in large portions. On most platforms,
this process is performed by the C malloc function; 64KB of memory is allocated during a
Maple session. This memory is not returned to the operating system (that is, by free) until
the Maple session ends.
When Maple no longer requires a block of memory, that memory block is added to an in-
ternal list of free storage. Maple has several storage lists for different sizes of memory blocks
so that it can quickly find blocks of the required size. For example, Maple uses three-word
blocks, so it maintains a list of blocks of that size that are free. Maple allocates additional
memory from the operating system only when it cannot respond to a request using its storage
lists.
The more memory Maple is allocated by the operating system, the less it is allocated in the
future because it reuses memory. For most applications, 4MB of memory must be available
for allocation.
13.4 The Maple Command-line Interface 519
The command-line version of Maple is a simple interface that, when used interactively,
displays an input prompt (>), runs commands, and displays the output as text-based results.
You can use this interface in batch mode to direct input to an application, specify a text file
to run, or evaluate a command using the -c option.
In Windows, the command-line interface is called cmaple.exe. You can run this file from
either the bin.win or bin.X86_64_WINDOW directory of your Maple installation, depending
on your platform. On other platforms, you can start the command-line interface by running
the maple script located in the bin directory of your Maple installation.
Starting the Maple command-line interface, automatically executing a command file, and
stopping the Maple session can take about one tenth of a second, depending on which
commands are run and the speed of your system. The quick start-up time and the minimal
amount of processing required make the Maple command-line interface suitable to be called
from other applications, even for quick calculations.
Batch Files
A batch file is a Maple script that can run in the Maple command-line interface, run state-
ments, and exit. The results can be displayed or directed to a file.
One method of using the command-line interface to solve a problem is to create an .mpl
script, which includes the input data. If this script is called solve.mpl, you could run the
script as follows.
cmaple solve.mpl > solve.output
In this example, the output is redirected to a file named solve.output. You can configure
an application to read this output file to capture the result.
Note: .mpl is the standard file extension for a Maple language file, which is a text file that
can contain Maple statements. For more information, refer to the file help page.
You can use the -q option to hide extra output that interferes with parsing results automat-
ically. For more options on the Maple command-line interface, refer to the maple help page.
Statements that do not use characters that are special to the system interpreter can be left
unquoted, as with the case of -c N:=1; above. This statement does not use spaces, pipe
characters, redirect symbols, quotes, or other characters that have meaning to the interpreter.
External functions that accept and return basic types (for example, integers and floats) can
be called directly in Maple after defining the calling sequence of the external function. Al-
ternatively, if you want to call functions that use complicated types, or if you require more
control over the conversion of data structures you want to access, you can use the Maple
external function interface to create and compile a wrapper file.
To understand the Maple external calling facility, consider the following C code, which
adds two numbers and returns the result.
int add( int num1, int num2 )
{
13.5 External Calling: Using Compiled Code in Maple 521
return num1+num2;
}
The external library functions in a .dll file must have been compiled using the _stdcall
calling convention, which is the default convention used by most compilers in UNIX,
Macintosh, and 64-bit Windows, but must be specified when using most 32-bit Windows
compilers. Options are also available for calling .dll files created by Fortran compilers and
classes created in Java.
A function specification translates the external function into a form that can be recognized
and interpreted by Maple.
At a Maple prompt, you can define the function specification by calling define_external
as follows.
> myAdd := define_external(
'add',
'num1'::integer[4],
'num2'::integer[4],
'RETURN'::integer[4],
'LIB'="mylib.dll"
);
522 13 Advanced Connectivity
For information on specifying parameter types, see Specifying Parameter Types for Function
Specifications (page 523).
Specifying the parameter types is independent of the compiler. The specification is always
defined in the same way, regardless of the method used to compile the .dll file. The ex-
ample above uses the C type int, which is specified as integer[4] in Maple. The 4 in the
square brackets indicates the number of bytes used to represent the integer. Some C
compilers use 4-byte (32-bit) long data types, but other compilers use 8-bytes (64-bit)
for the same data structure. If you are using the long data type, the specification in Maple
will need to be either integer[4] or integer[8], depending on the way your .dll file was
built. For more information about common type relations, see Table 13.2.
The name of the .dll file containing the external function is specified by defining the LIB
parameter. In the example above, mylib.dll specifies the file name of the library in which
the function is located. The format of this name is system-dependent and certain systems
require a full path to the file. In general, the name should be in the same format as you
would specify for a compiler on the same system. If you are calling a Java method, dll-
Name is the name of the class containing the method.
Important: Specify the function exactly and make sure that the arguments are in the correct
order. Failure to do this will not cause any problems when you are defining the specification;
13.5 External Calling: Using Compiled Code in Maple 523
however, unexpected results may be produced or your program may stop responding when
the external function is called within Maple.
(13.1)
> a := 33:
> b := 22:
> myAdd(a,b);
(13.2)
(13.3)
The return value description is also defined by using a data descriptor, with the name RE-
TURN as the argumentIdentifier. If the function returns no value, no RETURN parameter
is specified. Also, if no parameters are passed, no argument identifiers are required.
Note: The C type long is typically 4 bytes on 32-bit systems and 4 or 8 bytes on 64-bit
systems. Use the sizeof operator or consult your compiler documentation to verify
sizeof(long).
This API, or external function interface, is a subset of the API provided by the OpenMaple
interface. Unlike the OpenMaple interface, you do not need to define stream callbacks be-
cause Maple is the primary interface. Also, the kernel-vector handle returned from a call to
the StartMaple function in the OpenMaple API is, instead, passed as an argument to the
external function defined in your .dll file.
Currently, the API is defined for C/C++ and Fortran, and certain portions of the API can
be used for external functions written in Java. Other languages such as Visual C# and
Visual Basic can interface through a small C++ layer.
The API function prototypes for manipulating Maple data structures are located in the
$MAPLE/extern/include directory where $MAPLE is the directory in which Maple is
installed. The header file maplec.h must be included when writing custom C wrappers. One
of the header files, maplefortran.hf or maplefortran64bit.hf, must be included when
writing custom Fortran wrappers. Other header files, mplshlib.h, and mpltable.h contain
macros, types, and data structures that are needed for direct manipulation of Maple data
structures.
In your C code, Maple uses the following lines as an entry point to call the external function
directly with no argument conversion.
526 13 Advanced Connectivity
ALGEB myExternalFunction(
MKernelVector kv,
ALGEB args
);
Two parameters are in the external function declaration. The first is a handle that will be
required to call any Maple API function. The second is a Maple expression sequence of all
the arguments passed when the external function is called. The API function MapleNumArgs
can be used to determine the number of elements in the expression sequence. This variable
can be treated as an array of DAGs starting at index 1 (not 0). Therefore, args[1] is the first
parameter passed to the external function.
> myFunc := define_external('myExternalFunction', 'MAPLE', 'LIB'=
"myStuff.dll"):
Again, consider the simple example that adds two numbers passed by Maple. This time,
with explicit data type conversions using the API, and defining the external function proto-
type, as described above, the C function appears as follows.
/* Program to add two numbers from Maple */
#include <stdio.h>
#include <stdlib.h>
#include <maplec.h>
ALGEB myAdd( MKernelVector kv, ALGEB args )
{
int a1, a2, r;
if( MapleNumArgs(kv,args) != 2 )
MapleRaiseError(kv,"Incorrect number of arguments");
a1 = MapleToInteger32(kv,((ALGEB*)args)[1]);
a2 = MapleToInteger32(kv,((ALGEB*)args)[2]);
r = a1 + a2;
return( ToMapleInteger(kv,(M_INT) r) );
}
This program first checks if the Maple function call passes 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 file using a C compiler of your choice. Ensure that
you link with the Maple API .dll file. The .dll file can be placed in the Maple binary directory,
as given by kernelopts(bindir), or a subdirectory within the directory specified by the
PATH environment variable. If you are using .dll files outside of the Maple binary directory,
13.5 External Calling: Using Compiled Code in Maple 527
you may need to specify the full path to the .dll file in the LIB argument to the define_ex-
ternal function. UNIX developers may also need to set their load-library path.
To complete the example, the myAdd function can be linked in Maple and used as any other
Maple procedure.
> myAdd := define_external('myAdd', 'MAPLE', 'LIB'= "myAdd.dll"):
> myAdd(2,3);
(13.4)
> myAdd(2.2,1);
Error, (in myAdd) integer expected for integer[4] parameter
> myAdd(2^80,2^70);
Error, (in myAdd) integer too large in contex
Once compiled into a .dll file, 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.
528 13 Advanced Connectivity
(13.5)
The Maple external calling mechanism supports C, Fortran, and Java calling conventions.
There is an external API for writing custom wrappers for C and Fortran, but not for Java.
The default convention used is C. To use Fortran calling conventions, specify the name
FORTRAN as a parameter to the define_external function.
> f := define_external(`my_func`,`FORTRAN`, ...);
To use Java calling conventions, specify the name JAVA as a parameter to the define_ex-
ternal command. Also, specify the CLASSPATH= option to point to the classes used.
> f := define_external(`my_func`,`JAVA`, CLASSPATH="...", ...);
Some other compiler implementations, such as Pascal and C++, can work with C external
calling by using the correct definitions and order of passed parameters.
The C code generated by Maple wraps the Maple data structures by translating them to
hardware equivalent types. Therefore, the code file is called the wrapper, and the library
generated by this code is called the wrapper library.
Generating wrappers can provide an easy way to start writing custom code that references
the Maple external function interface. The term wrapper also refers to the code you write
to communicate with existing .dll files, as it does for the code Maple generates for the same
reason. Your code is sometimes called a custom wrapper.
13.5 External Calling: Using Compiled Code in Maple 529
Consider the original add function that was introduced at the beginning of this chapter. In
the following example, the WRAPPER option is used to generate a wrapper using the
define_external function. As shown, you can also use the NO_COMPILE option to prevent
the generated wrapper from compiling. The name of the generated file is returned instead.
> myAdd := define_external(
'add',
'WRAPPER',
'NO_COMPILE',
'num1'::integer[4],
'num2'::integer[4],
'RETURN'::integer[4]
);
myAdd := "mwrap_add.c"
/* integer[4] */
a2 = MapleToInteger32(mapleKernelVec,args[2]);
r = (*fn)(a1, a2);
mr = ToMapleInteger(mapleKernelVec,(long) r);
return( mr );
}
The generated wrapper is a good starting point for writing your own code. Some extra
variables and declarations may be used because the wrapper generation is generic. For ex-
ample, the use of args rather than fn_args avoids the need for a cast with args[1], but it is
also a static global variable which is useful when working with callbacks that need access
to the argument sequence outside the main entry point.
When running this function, the argument 'i' is converted from the Maple internal repres-
entation of an integer to a 4-byte hardware integer. A pointer to the hardware integer is then
passed to the external function, 'double_it'. Although 'i' is declared as a pointer to an integer,
you can call 'double_it' with non-pointer input.
> double_it(3);
In this case, a pointer to the hardware integer 3 is sent to 'double_it'. The modified value
is not accessible from Maple. To access the modified value, the parameter must be assigned
to a name. The name must be enclosed in unevaluation quotes to prevent evaluation.
> n:=3;
> double_it(n); # n is evaluated first, so 3 is passed
> n;
(13.6)
13.5 External Calling: Using Compiled Code in Maple 531
(13.7)
For numeric data, the string "NULL" can be passed as a parameter to represent the address
0 (the C NULL). For strings, because "NULL" is a valid string, the integer 0 represents
the address 0.
> double_it("NULL");
concat := define_external('concat',
RETURN::string, a::string, b::string,
LIB="libtest.dll"):
concat("NULL","x");
(13.8)
> concat(0,0);
(13.9)
In the concat example, the C code might look like the following. Note that this function
does not clean the memory as it should.
char * concat( char* a, char *b )
{
char *r;
if( !a \\ !b ) return( NULL );
r = (char*)malloc((strlen(a)+strlen(b)+1)*sizeof(char));
strcpy(r,a);
strcat(r,b);
return( r );
}
External API
An external API is provided if you want to expand existing wrappers or write custom
wrappers. Because this API is the same as that of OpenMaple, most of the internal docu-
mentation is referenced in the OpenMaple help pages. In particular, refer to the Open-
Maple,C,API and OpenMaple,VB,API help pages and related pages.
code is precompiled in the HelpExamples.dll file provided with Maple so that you can run
the examples in your Maple session.
System Integrity
The Maple kernel cannot control 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 may result in a segmentation fault or system error. The external function
may stop responding and cause Maple to stop responding as well.
If an external function accesses memory outside of its address space but within the Maple
address space, the external function will likely not stop responding, but certain parts of
Maple may not function correctly, resulting in unexpected behavior or a crash later in the
Maple session. Similarly, an external function that directly manipulates Maple data structures
can produce unexpected results by misusing the data structure manipulation facilities.
Therefore, use external calling at your own risk. Whether an external function is one that
you have written, or supplied by a third party to which you have declared an interface (that
is, by using the define_external function), Maple must rely on the integrity of the external
function when it is called.
You can create a Maple server in a Maple session and configure the server to send a message
to that session.
Socket Server
A socket server can be a public web service such as a stock quote service. The following
example shows how to create a Maple procedure that acts as a service that listens for a
socket connection and sends a message when a connection is found.
The following commands cause the Maple session in which they are called to start the ser-
vicing requests. This call is not returned. To run the code, enter the procedure definition
13.7 Code Generation 533
above and the Serve command below in a Maple worksheet. (Remove the comment character
(#) before running the code.)
> #Sockets:-Serve( 2525, myServer );
Socket Client
To write a simple client, the socket must first be opened. To do so, specify the name of the
host and the port number of the host to which you want to connect.
> sid := Sockets:- Open ( "localhost", 2525 );
(13.10)
You can use many options with the CodeGeneration commands. For more information,
refer to the CodeGenerationOptions help page. Some of the commonly used options are
listed below.
optimize=value: This option specifies whether the Maple code is optimized before it is
translated. The default value is false. When this option is set to true, the codegen[optim-
ize] function is used to optimize the Maple code before it is translated.
output=value: This option specifies the form of the output. By default, the formatted
output is printed to the console. If a name (different from the name string) or a string is
specified as the value, the result is appended to a file of that name. If the value is the
name string, a string containing the result is returned. This string can then be assigned
and manipulated.
declare=[declaration(s)]: This option specifies the types of variables. Each declaration
has the form varname::vartype where varname is a variable name and vartype is one
of the Maple type names recognized by the CodeGeneration package, as described in
the TranslationDetails help page. Declarations specified using this option override any
other type declarations in the input code.
Translation Process
The CodeGeneration commands recognize a subset of the Maple types, which are listed
in the CodeGeneration/Details help page. The Maple types are translated to appropriate
types in the target language. Compatibility of types is checked before operations are trans-
13.7 Code Generation 535
lated, and type coercions are performed if necessary. The CodeGeneration commands at-
tempt to determine the type of any untyped variables. You can control type analysis and
deduction by using the coercetypes, declare, deducetypes, and defaulttype options. For
more information, refer to the CodeGenerationOptions help page.
The CodeGeneration commands can translate a subset of the Maple commands, which are
listed on the CodeGeneration/Details help page. Some commands are translated only to
certain target languages. For more information about a specific language, refer to its detailed
help page, for example, CodeGeneration/General/CDetails.
The return type of a procedure is determined automatically if you do not declare it. If more
than one return statement is specified, the types of all objects returned must be compatible
in the target language. If a return statement contains a sequence of objects, the sequence is
translated into an array. Implicit returns are recognized in some cases, but translations to
explicit returns can be suppressed by using the deducereturn=false option. When necessary,
an automatically generated return variable is used to store a return value.
Lists, Maple data structures of the type array, and rtables are translated to arrays in the
target language. It is recommended that you declare the type and ranges for all arrays. In
some target languages, arrays are reindexed to begin at index 0.
class CodeGenerationClass {
public static void f (double x)
{
double y;
y = Math.log(x) * Math.exp(-x);
System.out.print("The result is " + y);
}
}
> g := proc(x, y, z)
return x*y-y*z+x*z;
end proc:
> s := CodeGeneration[`C`](g, defaulttype=integer, output=string);
(13.11)
(13.13)
You can turn off the automatic type deduction process by setting the deducetypes option
to false. In the following example, the parameters y and z are now assigned the default type.
> CodeGeneration:-C(f, defaulttype=integer, deducetypes=false);
double f (double x, int y, int z)
{
return(x * (double) y - (double) (y * z) + x * (double) z);
}
You can turn off the explicit type coercion process by setting the coercetypes option to
false.
> CodeGeneration:-C(f, defaulttype=integer, deducetypes=false,
coercetypes=false);
double f (double x, int y, int z)
{
return(x * y - y * z + x * z);
}
The names that appear in intermediate code expressions are part of the CodeGeneration:-
Names subpackage.
Error and warning messages displayed from CodeGeneration package commands sometimes
refer to the intermediate form of the Maple expression that triggered the message.
13.7 Code Generation 539
When determining the cause of an error message or writing and debugging custom language
definitions, it is recommended that you determine the intermediate form of a Maple expres-
sion input. In general, you can determine the intermediate form by using the CodeGenera-
tion:-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 inter-
mediate code for a specific translator. This can be done by setting the command infolevel[Co-
deGeneration] to a value greater than 3 and performing a translation.
The following example shows the intermediate code for the expression 2x^2-1. The first
argument of the Scope structure is the name of a type table used internally during the
translation process.
> CodeGeneration[IntermediateCode](2*x^2-1);
Scope( nametab,
StatementSequence(
Assignment(GeneratedName("cg1"), Sum(Product(Integer(2),
Power(Name("x"), Integer(2))), Negation(Integer(1))))
)
)
Custom translators can define a complete language, extend existing language definitions,
overriding and extending only those language components that need to be changed.
To view a list of languages that are currently supported by the CodeGeneration package,
and thus available for extending, use the CodeGeneration:-LanguageDefinition:-
ListLanguages command.
For each name used in the intermediate form, there is a print handler procedure. During the
printing phase, Maple traverses the intermediate form recursively. For each subexpression
of the intermediate form, Maple invokes the print handler associated with that class of ex-
pressions.
540 13 Advanced Connectivity
The most frequently used Printer package command is Print. When it is given a string,
the Print command prints the string to a buffer. When given an intermediate-form expression,
the Print command invokes the print handler appropriate for the expression. In this manner,
Print recurses through the intermediate form until it is printed in its entirety to the buffer.
At this point, the translation process is complete.
Table 13.3 lists the important Printer commands. For a complete list and more detailed
information, refer to the CodeGeneration/LanguageDefinition/Printer help page.
The following commands illustrate how data is stored and retrieved from a Printer module.
> with(CodeGeneration:-LanguageDefinition):
> Printer := GenericPrinter():
> Printer:-AddOperator( Addition = "+" );
(13.14)
(13.15)
(13.16)
(13.17)
Within a language definition, the Printer module associated with the language definition
can be referenced by the name Printer. Note: This applies to both of the language definition
methods described in the next section.
For simple languages or small extensions of existing languages, use the LanguageDefini-
tion:-Define command. To create a translator that preprocesses or postprocesses the generated
output, or makes frequent use of a utility function in translations, create a language definition
module. The language definition module approach is used for all translators supplied with
the CodeGeneration package.
These function calls accept identical syntax and perform the same actions as the Printer
commands of the same name. That is, they define print handlers and other data specific to
the language translation you are defining. For more information on these commands, refer
to the CodeGeneration/LanguageDefinition/Printer help page.
542 13 Advanced Connectivity
The Define command automatically creates a Printer module for the language. You do not
need to create one using the LanguageDefinition:-GenericPrinter or LanguageDefinition:-
Get commands.
This example illustrates a C translator, in which the translated code uses a specialized library
function mymult for multiplication instead of the built-in * operator.
> CodeGeneration:-LanguageDefinition:-Define("MyNewLanguage",
extend="C",
AddPrintHandler(
CodeGeneration:-Names:-Product = proc(x,y)
Printer:-Print("mymult(", args[1], ", ", args[2],
")");
end proc
)
):
Note that in the previous example, one of the arguments of the LanguageDefinition:-Define
command is the function call AddPrintHandler, which takes a name and a procedure as
arguments. The supplied procedure therefore prints any Product subexpression of the inter-
mediate form. The call to Printer:-Print specifies that the translator uses the automatically
generated Printer module.
The body of the module definition must contain a sequence of calls to Printer commands
that define language-specific data and utility procedures.
Once defined, a language definition module can be added to to the set of languages recog-
nized by the CodeGeneration package by using the CodeGeneration:-LanguageDefini-
tion:-Add command.
When creating your language definition module, you must delay the evaluation of the
module by using unevaluation quotes before adding it using the LanguageDefinition:-Add
command. That is, the language definition module must be added as a module definition
and not as a module.
13.8 CAD Connectivity 543
The following example adds a definition module. Note the use of unevaluation quotes around
the module definition to delay its evaluation.
> UppercaseFortran77 := 'module()
export Printer, PrintTarget;
Printer := eval(CodeGeneration:-LanguageDefinition:-Get(
"Fortran")):-Printer;
PrintTarget := proc(ic, digits, prec, func_prec, namelist)
Printer:-SetLanguageAttribute("Precision" = prec);
StringTools:-UpperCase(Printer:-PrintTarget(args));
end proc:
end module':
> CodeGeneration:-LanguageDefinition:-Add("UppercaseFortran",
UppercaseFortran77);
The following example demonstrates the use of a new translator. Compare the output of the
Fortran command with that of the new translator.
> p1 := proc() sin(x+y*z)+trunc(x); end proc:
> CodeGeneration:-Fortran(p1);
doubleprecision function p1 ()
p1 = sin(x + y * z) + dble(int(aint(x)))
return
end
The commands available to the interface depends on the CAD application that you are using.
Each application uses a different naming convention for parts and hierarchies of parts in a
CAD drawing. In Maple, you can use the CAD Link Assistant as a starting point to find
out about the information available from your CAD application and how to reference it.
For more information, refer to the CADLink help page.
The Maple CAD package contains subpackages that are specific to individual CAD applic-
ations. After selecting the relevant subpackage to use, the first step is to establish a connection
to the CAD system. The OpenConnection command opens the CAD application, or connects
to a session that is already running on your computer. Both Maple and the CAD application
can run side-by-side; updates in either system are automatically reflected in the other system.
The next step is to connect to a particular part or assembly within the CAD application. The
GetActiveDocument, OpenPart, or another related command will establish this connection.
When a connection is established with the CAD application and part of the CAD drawing
is opened, Maple can extract parameter values, properties, and in some cases the geometry
of the part. Maple can then be used to analyze these values to optimize the configuration
or test aspects of the design (for example, whether the part can withstand applied force or
heat). If necessary, modified parameters can be saved in the CAD application.
In the following example, an Excel formula forms a quadratic polynomial from the coeffi-
cients in cells C1, D3, and B6. You can enter this formula in cell A1 of the Excel spreadsheet.
=Maple( "&1*x^2 + &2*x + &3;", $C$1, $D$3, $B$6 )
In the Excel spreadsheet, enter a string containing the Maple code that you want to return,
substituting any parameters contained in spreadsheet cells using an ampersand character
(&) followed by a number. Include a semicolon (;) after the Maple command. After the
string, list the cell references that should be substituted into the string in the order of the
numbers you entered.
You must first configure Maple to communicate with MATLAB. For more information,
refer to the Matlab,setup help page.
546 13 Advanced Connectivity
(13.18)
(13.19)
(13.20)
> setvar("ma",a)
> setvar("mb",b)
> evalM("result = yourmfile(ma,mb)")
> getvar("result")
The example above illustrates how Maple and MATLAB maintain separate name spaces,
and the specific commands for transferring data between both applications. The matrices a
and b are initially defined as Maple variables. By using the setvar command, they are copied
into MATLAB data structures and assigned to the MATLAB variables ma and mb. By using
the evalM command, you specify a command for MATLAB to parse and run. The result
can be copied into a Maple data structure by using the getvar command.
>> syms x y
>> f = x^2+3*y^2-5*x*y+2*x-7*y-12
f =
2 2
x + 3 y - 5 y x + 2 x - 7 y - 12
>> P = solve( diff(f,x), diff(f,y) )
P =
x: -23/13
y: -4/13
In MATLAB, the variables x and y are declared as symbolic by using the syms command.
These variables can then be used with normal MATLAB operators to create larger symbolic
expressions. The Maple Toolbox provides commands, such as solve and diff, to manipulate
the Maple expressions you created. In addition to these commands you can use a generic
maple command to evaluate arbitrary Maple commands.
548 13 Advanced Connectivity
14 Parallel Programming
Computers with multicore processors are now commonplace. To take advantage of the
power of multicore computers, Maple provides tools for parallel programming. This chapter
provides a basic introduction to parallel programming in Maple.
14.2 Introduction
Maple provides tools for two different types of parallel programming. The Task Programming
Model enables parallelism by executing multiple tasks within a single process. The second
type of parallelism comes from the Grid package, which enables parallelism by starting
multiple processes.
Each type of parallelism has advantages and disadvantages. The Task Programming Model
is a high level programming tool that simplifies many aspects of parallel programming. In
the Task Programming Model, tasks share memory thus they can work closely together,
sharing data with low overhead. However because they share memory, code running one
task must be careful not to interfere with code running in other tasks. As the Task Program-
ming Model is very new to Maple, much of the Maple library has not been verified to work
correctly with tasks. This means that much of Maple's core functionality cannot be used in
task-based code.
As Grid uses multiple process parallelism it does not suffer from this problem, each process
has its own independent memory. Thus you can use all of Maple's library routines in multiple
process execution. Further, with the addition of the Grid Computing Toolbox, you can execute
multiple process parallelism across multiple computers which can allow you to access far
more computing power. However because the processes are independent the cost of com-
munication between processes can be quite high. As well, balancing the computation evenly
across all the available processors, especially those on remote computers, can be difficult.
549
550 14 Parallel Programming
g contains the sequence, , , ..., . If these two procedures are run in serial, they
can be run in two possible orders: f followed by g, or g followed by f. In other words, the
order in which the statements are run can be either , , ..., , , , ..., or
, , ..., , , , ..., . The programmer defines the order in which the statements
are run. For example, if must run before for the code to execute correctly, the pro-
grammer can call f before g to make sure that the statements run in the correct order.
> f := proc()
local i;
for i from 1 to 5
do
print( procname[i] );
end do;
end proc:
14.3 Introduction to Parallel Programming with Tasks 551
> g := eval(f):
f();
g();
(14.1)
If f and g are called in parallel (that is, at the same time), different sequences can be gener-
ated. Although will run before , the order in which runs relative to cannot
, , , , , , ... or any other valid order. Also, the statements can be ordered
differently each time these procedures are run; every possible order will eventually happen,
given enough iterations.
The following example uses functions from the Task Programming Model. These functions
are described in the Task Programming Model (page 558) section of this chapter. For now,
consider these functions as a way to start tasks, which are functions that can run in parallel.
552 14 Parallel Programming
(14.2)
If the code requires to execute before to run correctly, running these functions in
parallel may produce errors, even if is the first statement of f and is the last statement
of g. Every possible order will eventually occur; therefore, to write correct parallel code,
you must guarantee that every possible order of execution leads to a correct result.
> f := proc( n )
local i, tmp;
global shared;
for i from 1 to n
do
tmp := shared;
shared := tmp+1;
end do;
14.3 Introduction to Parallel Programming with Tasks 553
NULL;
end proc:
> g := eval(f):
> shared := 0:
> Threads:-Task:-Start( null, Task=[f,1000], Task=[g,1000] ):
> shared;
(14.3)
In the example above, f and g increment the global variable shared 1000 times. You might
expect the final value of shared to be 2000; however, this is not the case. The for loop
contains two statements:
- :
- :
When f and g are running in parallel, these statements could run in the following order:
- :
- :
- :
- :
In some orders, the total will be 2000 and, in fact, every value from 1000 to 2000 could
occur. Therefore, even for this simple example, there are 1001 different possible outcomes
and even more possible orders.
In sequential programs, the order in which statements are run are defined by the programmer.
In parallel code, the order in which statements run is not defined exclusively by the code.
In other words, a programmer who writes parallel code must consider all of the different
possible orders to determine if the code is correct.
Functions that work correctly when run in parallel with other code are called safe. Functions
that do not work correctly when run in parallel with other code are called unsafe.
554 14 Parallel Programming
Therefore, many factors may cause a task to pause for a brief or long time period. In some
cases, the task may pause as a result of the action that is being performed; however, other
factors are beyond the task's control.
shared[procname] := 0;
for i from 1 to n
do
tmp := shared[procname];
shared[procname] := tmp+1;
end do;
14.3 Introduction to Parallel Programming with Tasks 555
NULL;
end proc:
> g := eval(f):
> shared := table():
> Threads:-Task:-Start( null, Task=[f,1000], Task=[g,1000] ):
> shared[f]+shared[g];
(14.4)
In this case, the result is 2000 each time you run the code. The difference between this ex-
ample and the example above is that although there are just as many statement orders, the
orders do not cause conflicts. In this example the two tasks are not writing to the same loc-
ation in memory. In general, statements that cause issues are statements that access shared
data. In the first example, the two tasks share the value stored by the variable, shared. As
the two tasks modify shared, conflicts occur. In this example, both threads write to different
variables, so no conflicts occur. The regions of code that contain statements that cause
conflicts are called critical sections.
By understanding this concept, you can limit yourself to worrying about the orderings that
involve critical sections. This also implies that if you have fewer critical sections in your
parallel code, it will be easier to make the code work properly.
The most common way data is shared is using global variables. A global variable can be
accessed from anywhere in the code, so whenever a procedure uses a global variable, it
could conflict with another procedure running in parallel. In a similar way, lexically scoped
variables that are used in tasks that run in parallel are also shared. Another way to share
data is to pass the same value into multiple tasks as an argument.
The simplest method for sharing data safely is to treat the shared data as read-only. If the
tasks do not modify the shared data, no conflicts will occur. However, if even one task
modifies the shared data, all of the tasks (even the ones that simply read data) must access
shared data carefully.
Another method is to share a data structure, such as an Array, but limit which elements of
the structure are accessible by certain tasks. For example, two tasks can share an Array if
each task only accesses one half of the Array. In such an example, the tasks share a structure,
but not the data within the structure.
for i from lo to hi
do
x := A[i];
A[i] := x^4+4*x^3+6*x^2+4*x+1;
end do;
end proc:
> N := 10^5:
> N2 := floor(N/2):
> A := Array( 1..N, x->(Float(x)/N) ):
Threads:-Task:-Start( null, Task=[task,A,1,N2],
Task=[task,A,N2+1,N] ):
To create a mutual exclusion zone, use a Mutex. A mutex can be in one of two states: locked
or unlocked. If a mutex is unlocked, any task can lock it. If a mutex is locked and a task at-
tempts to lock it, the task waits until the mutex is unlocked, and then it attempts to lock the
mutex again. This means that only one task can lock the mutex.
If all of the tasks only access the critical section while holding the lock, there will never be
more than one task accessing the shared data at a time. Therefore, by using a mutex, multiple
tasks can run in parallel and share data without conflicting with other tasks.
14.3 Introduction to Parallel Programming with Tasks 557
The following example takes the unsafe code from the first example and adds a mutex to
make it safe.
> task := proc(m, n)
local i, tmp;
global common_variable;
for i to n do
Threads:-Mutex:-Lock(m);
tmp := common_variable;
common_variable := tmp + 1;
Threads:-Mutex:-Unlock(m)
end do;
NULL
end proc:
> common_variable := 0:
> m := Threads:-Mutex:-Create():
> Threads:-Task:-Start( null, Task=[task,m,1000], Task=[task,m,1000]
):
> common_variable;
(14.5)
Note: The excessive use of mutexes may cause performance issues. First, simply having to
lock and unlock a mutex will add processing time to your code. However, more significantly,
if a thread tries to lock a mutex that is already locked, it must wait. This waiting period re-
duces the parallelism of the algorithm.
The mutex example shown above falls into this category. The body of the task runs while
holding a lock, which means that, at most, one task can run that code at a time. To fix this,
limit the access to the global variable. For this example, a local variable can be used and
the local results can be combined once at the end of the execution.
> task := proc(m, n)
local i, local_sum;
global common_variable;
local_sum := 0;
for i to n do
local_sum := local_sum + 1;
end do;
Threads:-Mutex:-Lock(m);
common_variable := common_variable + local_sum;
558 14 Parallel Programming
Threads:-Mutex:-Unlock(m)
end proc:
> common_variable := 0:
> m := Threads:-Mutex:-Create():
> Threads:-Task:-Start( null, Task=[task,m,1000], Task=[task,m,1000]
):
> common_variable;
(14.6)
Tasks
Consider the following Maple procedure.
> f := proc() fc( f1(args1), f2(args2), ..., fn(argsn) ); end proc;
To evaluate , the values are evaluated and their return values are computed. These
return values are then passed to as arguments. When completes, its return value is
passed as the return value of . The Task Programming Model takes this pattern, but creates
tasks for the values and . A task is a piece of executable code. In Maple, a task is a
procedure combined with a set of arguments to that procedure. Once a task is created, the
Maple kernel can run it. By allowing the kernel to schedule tasks, Maple can automatically
distribute them to available processors of your computer.
In the example code above, the value returned by is the return value of . Similarly,
when a task, , creates a continuation task, the value returned by the continuation task is
given to the parent of . When this happens, any value returned by the task is discarded.
is effectively replaced by . This occurs because, in the Task Programming Model,
tasks always run until they are complete. A task , does not need to stop in the middle of
its execution to wait for child tasks to complete; instead it can finish and the continuation
task will handle the return values of the child tasks. If does not create any tasks, its return
value is given to its parent.
Starting Tasks
To create the first task, call the Threads:-Task:-Start function.
> Start( task, arg1, arg2, ..., argn ):
task is the procedure to run as the first task and arg1, arg2, ..., argn are the arguments to
task.
> task := proc( )
`+`( _passed );
end proc:
> Threads:-Task:-Start( task, 1,x^3,q/3 );
(14.7)
This procedure creates one task. After the task runs and returns a value (or a continuation
function returns a value), that value is the returned by the Start function. Starting a task by
itself is not as useful as starting multiple tasks that run in parallel.
To start child tasks and a continuation function, call the Threads:-Task:-Continue function.
> Continue( cont, arg1, arg2, ..., argn ):
560 14 Parallel Programming
cont is the procedure to use for the continuation task. arg1, arg2, ..., argn are either argu-
ments to cont or child task specifiers of the form
Task=[task, targ1, targ2, ..., targn ]
Tasks=[task, [targs1], [targs2], ..., [targsm] ]
The first task specifier creates one task with the procedure task and arguments targ1, targ2,
..., targn. The second task specifier creates m tasks, each using task as the procedure and
the sequence of expressions targsi as arguments to task i.
As Continue replaces a running task with child tasks and a continuation task, it can only
be called from within a running task. In addition, it can only be called once per task. However
Continue can be called from any running task, including a continuation task.
When a child task completes, its return value is passed to its parent as a parameter. The
position of the parameter corresponds to the position the task was specified in the call to
the Continue function. The following example illustrates how this passing works.
> task := proc(i)
cat( t, i );
end proc:
> start := proc( )
Threads:-Task:-Continue( print, 1, Task=[task,2], 3,
Tasks=[task,[4],[5]], 6 );
end proc:
> Threads:-Task:-Start( start );
(14.8)
The simple example shown earlier can be modified to use the Task Programming Model.
> task := proc(n)
local sum, i;
sum := 0;
for i from 1 to n
do
sum := sum+1;
end do;
sum;
end proc:
> start := proc( )
Threads:-Task:-Continue( `+`, Task=[task,1000],
14.4 Task Programming Model 561
Task=[task,1000] );
end proc:
> Threads:-Task:-Start( start );
(14.9)
By using the value passing behavior of the Task Programming Model, this problem can be
solved without using global variables or a mutex. The return values of the two child tasks
are passed to the continuation function, +. It adds them together and returns the computed
value. This value is then returned by the Start function.
Task Management
Now that you have the functions to create tasks, you must determine how many tasks to
start. To understand this, a few parallel programming concepts must be considered. Parallel
algorithms are said to scale if they get faster when they run on more cores. A good parallel
algorithm will scale linearly with the number of available processors.
To achieve linear scaling, a parallel algorithm must consider load balancing. Load balancing
refers to techniques used to distribute the work evenly over all the cores of your computer.
If you want to use n cores, you would want to divide the work into n even parts. However,
you may not be able to determine how to divide the work evenly. Dividing the input into n
evenly sized parts may not divide the work evenly. It is possible that one task will require
more time to evaluate than the others. Once the other tasks complete, their cores will be
idle while the remaining task runs.
One way to solve this problem is to create a large number of small tasks. This way, each
task is relatively small. However, even if one task requires more time to run, the other cores
can run many other tasks while one core is running the long task. Another advantage is that
you can create the tasks without considering the number of cores. Thus your code does not
need to know about the underlying hardware.
One limitation is that creating tasks requires resources. If the tasks are too small, the resources
required to create the tasks may dominate the running time. Consider the following example,
which is run on a computer with four cores.
> add_range := proc(lo, hi)
local i;
add( i, i = lo..hi );
end proc:
(14.10)
d := hi-lo+1;
step := floor( d/n );
The parallel_add_range function also adds the numbers from lo to hi, but it distributes the
work over n tasks.
> start := time[real]():
Threads:-Task:-Start( parallel_add_range, 1, N, 2 );
time[real]()-start;
(14.11)
(14.12)
(14.13)
14.4 Task Programming Model 563
Increasing the number of tasks from 2 to 4 increases the performance, as you would expect
on a 4 core computer. However further increasing the number of cores from 4 to 100 also
increases the performance. By using a larger number of tasks, Maple is better able to
schedule the work onto available cores.
> start := time[real]():
Threads:-Task:-Start( parallel_add_range, 1, N, 10000 );
time[real]()-start;
(14.14)
However, running 10000 tasks introduces a slowdown. The overhead of managing the tasks
begins to become significant. The Task Programming Model is a relatively new feature in
Maple, so this overhead will be reduced in future versions of Maple.
(14.15)
In this example, the time taken by the work function depends on the input value n. This
process can be parallelized at a high level by subdividing over the input Array until a base
case is reached.
564 14 Parallel Programming
(14.16)
You can see that this provides a reasonable speedup. High-level parallelism, as shown in
the example above, is called coarse-grained parallelism. Generally, coarse-grained parallel-
ism refers to dividing a problem into subproblems at a high level, and then running the
subproblems in parallel with each other.
(14.17)
14.4 Task Programming Model 565
This happens because subdividing over the range does not take into account the actual
amount of work necessary to compute the subranges. In the example above, the first subrange
contains over half the work. Therefore, it may be difficult to divide the work into equal
subsections, by only looking at the input.
Another approach to parallelizing a problem like this is to parallelize the work function.
> workTask := proc(n)
local i, m;
if ( n > 10000 ) then
m := floor( n/2 );
Threads:-Task:-Continue( `+`,
Task=[ workTask, m ],
Task=[workTask, n-m ] );
else
for i from 1 to n
do
end do;
n;
end if;
end proc:
> work := proc(n) # do O(n) "work"
local i;
if ( n > 10000 ) then
Threads:-Task:-Start( workTask, n );
else
for i from 1 to n
do
end do;
n;
end if;
end proc:
> t:=time[real]():
add( work( A2[i] ), i=1..nops(A2) );
time[real]()-t;
(14.18)
parallelism also has flaws. In particular, although the work function is faster for large inputs,
it is not faster than the sequential version for small inputs. Thus, when you have an even
distribution of work, there is no advantage to using this approach.
> t:=time[real]():
add( work( A[i] ), i=1..nops(A) );
time[real]()-t;
(14.19)
The best solution is to use both coarse and fine-grained parallelism. Note: The work function
has been redefined, so task will now use the new definition.
> t:=time[real]():
Threads:-Task:-Start( task, A2, 1, nops(A2) );
time[real]()-t;
(14.20)
Using both coarse and fine-grained parallelism combines the best of both of these approaches.
14.5 Examples
The N Queens Problem
On an N by N chess board, find the positions for N queens such that no two queens can
capture each other. A queen can capture other chess pieces in the row and column in which
it is positioned, and along the two diagonals that pass through the queen's position.
We will represent the board position by an Array of length N. Each element of the Array
is an integer in the range 1..N, and each integer only appears once. The combination of the
Array index and the element stored at that index specify the position of a queen.
This representation is sufficient because only one queen can be in each row and column at
a time. These restrictions can be specified while creating the positions, so when the chess
board layouts are checked for valid solutions, we only need to look for conflicts along the
diagonals.
nQueens := module()
local checkBoard,
completeBoardAndCheck,
searchTask,
14.5 Examples 567
continuation,
subInit;
export ModuleApply;
index := board[i] - 1;
for j from i+1 to n while index >= 0
do
if ( index = board[j] ) then
return NULL;
end if;
index := index - 1;
end do;
end do;
if ( i < n ) then
568 14 Parallel Programming
if ( i <= m ) then
Threads:-Task:-Continue( passed,
Tasks = [ searchTask,
seq( [i+1, n, m, [ op(board), j] ], j in unused ) ]
);
else
# Turn lists into Arrays because we work in-place to save memory
return completeBoardAndCheck( n,
Array( 1..n, board, datatype=integer[8]), i, unused );
end if;
return NULL;
end proc;
Threads:-Task:-Start( searchTask, 1, n, m, [] );
end proc;
end module:
14.6 Limitations of Parallel Programming 569
By passing 0 as the second argument, child tasks are not actually created. The following is
the running time for sequential execution.
> time[real]( nQueens( 9, 0 ) );
(14.21)
New tasks are created for all of the permutations for the first two rows of the chess board.
> time[real]( nQueens( 9, 2 ) );
(14.22)
Library Code
Only certain Maple library commands are thread-safe. If a Maple command is thread-safe,
a note is included in its help page. If a Maple command that is not thread-safe is used in
parallel code, may not work correctly.
A list of all the thread safe functions is available in the Maple help system on the in-
dex/threadsafe help page.
Maple Interpreter
The Maple interpreter executes all the code written in Maple. It is able to execute most
Maple statements in parallel, however there are some internal systems that can reduce par-
allelism.
For a description of the performance issues in your version of Maple, see the multith-
readed/performancelimitations help page.
matter how unlikely it seems that the other statement could run first. Always use the parallel
programming tools in Maple (that is, the task dependencies in the Task Programming
Model or mutexes) to enforce the order of execution.
Sometimes, the best way to debug parallel code is to do careful code inspections (that is,
reading over the code) with the implications of parallel execution in mind. In the most ex-
treme case, you can consider the shared data as the state in a state machine and the critical
sections as transitions. This can allow you to see potential states and transitions that you
did not consider.
Grid:-Barrier();
end:
14.8 Introduction to Grid Programming 571
> Grid:-Launch(hello);
I'm node 1 of 2
I'm node 0 of 2
(14.23)
This example creates a number of nodes, and executes the hello function on each node. The
Grid:-NumNodes command returns the number of nodes that were started by Launch.
Grid:-MyNode returns an integer in the range 0 to NumNodes()-1 which can be used to
identify the executing node. The Grid:-Barrier command creates a synchronization point.
All the nodes must execute the Barrier command before any of them can proceed past it.
Node 0 is given special significance in Grid programming. The value returned by the function
executing in node 0 is returned by the Launch command. Thus when node 0 returns a value,
the whole Grid computation is considered complete. Nodes that are still running are termin-
ated. This is why the call to Barrier is necessary in the previous example, without it node
0 could exit before the other threads have completed executing their commands.
Launch
Launch allows you to specify data that will be passed to the given functions as arguments.
Additionally, Launch can automatically import global names to each node when nodes are
started. As well, Launch can export global names from node 0 when it exits. In the following
example, we pass two arguments into func, arg1 and arg2, and import the global variable
data1 into each node using the imports argument. We also set the value of data2 in node
0 and use the exports argument to update the value in the main context.
> func := proc(arg1, arg2)
global data1, data2;
printf( "%d: %a %a %a\n", Grid:-MyNode(), arg1, arg2, data1 );
Grid:-Barrier();
if ( Grid:-MyNode() = 0 ) then
data2 := 1;
end;
end:
572 14 Parallel Programming
> data2;
(14.24)
One important use of the imports option is the ability to pass user defined functions that
are needed on the nodes. These functions will not be available on the nodes if they are not
explicitly imported to the nodes.
The Grid package also contains two commands for explicitly sending data from one node
to another, Grid:-Send and Grid:-Receive.
Send
Send allows one node to send a Maple expression to another node. Send accepts two argu-
ments, an integer that identifies the destination node and the expression to send. Send does
not wait for the target node to receive the message before returning.
Receive
Receive receives an expression that was sent from another node. Receive has one optional
argument, an integer, that identifies the sender from whom an expression should be read.
Without the argument Receive will return an expression from any sender. If there is no ex-
pression available, a call to Receive will wait until an expression is received. Some care
should be taken as it is possible to cause a deadlock if all nodes are waiting to receive a
message and no one is sending.
(14.25)
The next section includes a more complex example using Send and Receive.
ModuleApply := proc ( ptsY, ptsX, iter, X1, X2, Y1, Y2, bailout )
local X, Y, imageArray, i:
return imageArray;
end proc:
end:
> N := 500:
s := time[real]():
points := Mandelbrot( N, N, 100, -2.0, .7, -1.35, 1.35, 10.0 ):
time[real]()-s;
(14.26)
We can implement a Grid-based implementation by dividing the input range into evenly
sized chunks. In the following example a node uses its node identifier to determine which
chuck of the final Array it should use. Once a node has completed its computation, it sends
the computed Array to node 0. Node 0 collects all the results and returns them. These results
are then combined into a single output Array.
Mandelbrot := module()
local MandelLoop,
MandelGrid,
ModuleApply;
k := 0;
while k < iter do
Xold := Xtemp;
Yold := Ytemp;
Xtemp := Xold^2-Yold^2+Xc;
Ytemp := 2*Xold*Yold+Yc;
t := Xtemp^2+Ytemp^2;
if t >= bailout then
imageArray[i, j, 1] := k - ln( ln( t ) )/ln(2.);
imageArray[i, j, 2] := imageArray[i, j, 1];
imageArray[i, j, 3] := imageArray[i, j, 1];
break;
end if;
k := k+1;
end do
end do;
end do;
end proc:
n := Grid:-NumNodes();
i := Grid:-MyNode();
step := floor( numelems( X )/n );
if ( i = 0 ) then
start := 1;
endp := step;
elif ( i = n-1 ) then
start := step*(n-1)+1;
endp := numelems(X);
else
start := step*i+1;
endp := step*(i+1);
end;
if ( i > 0 ) then
Grid:-Send(0,imageData);
else
576 14 Parallel Programming
ModuleApply := proc ( ptsX, ptsY, iter, X1, X2, Y1, Y2, bailout )
local X, Y, imageData, ret, i, l, u:
for i in ret
do
l := lowerbound( i );
u := upperbound( i );
imageData[l[1]..u[1], l[2]..u[2], 1..3] := i;
end;
imageData;
end proc:
end:
(14.27)
> s := time[real]():
points := Mandelbrot( N, N, 100, -2.0, .7, -1.35, 1.35, 10.0 ):
time[real]()-s;
(14.28)
Although we do see a speed up, it is not a good as we'd expect. If you execute this example
and watch the CPU utilization, you'll notice that some nodes complete quite quickly, while
others run for longer. This indicates that the distribution of work is uneven between nodes.
We can improve this by using a Client/Server model for work distribution. In this model,
one node (node 0 in our case) acts as a server handing out work to clients as they request
14.9 Grid Examples 577
it. As long as work is available the clients can continue computing. In the following example
the server passes row indexes to the clients. The client then computes the entire row. The
computed row is sent back to the server, which collects all the rows and reconstructs them
into the final Array.
It is important to notice that the following example starts an extra node. The server node
does relatively little work, compared to the other nodes. Thus we create one client for each
processor. The server node does not need a complete processor for itself.
Mandelbrot := module()
local
ComputeLine,
GridFunction,
Server,
Client,
ModuleApply;
imageArray[j, 1] := 0.0;
imageArray[j, 2] := 0.0;
imageArray[j, 3] := 0.0;
end do;
end proc:
# we've sent out all the data, receive the last results
for i from 1 to Grid:-NumNodes()-1
do
msg := Grid:-Receive();
imageData[ msg[2], 1..numelems(Y), 1..3 ] := msg[3];
end;
imageData;
end;
do
# wait for a reply
msg := Grid:-Receive( 0 );
NULL;
end;
i := Grid:-MyNode();
if ( i = 0 ) then
Server( X, Y , iter, bailout );
else
Client( i, X, Y , iter, bailout );
end;
end proc:
ModuleApply := proc ( ptsX, ptsY, iter, X1, X2, Y1, Y2, bailout )
local X, Y, ret:
end proc:
end:
> s := time[real]():
points := Mandelbrot( N, N, 100, -2.0, .7, -1.35, 1.35, 10.0 ):
time[real]()-s;
(14.29)
Using the client/server model to better distribute the work over the nodes, we get speed ups
that match our expectations, four processors leads to a four times speed up.
An algorithm implemented on top of the Grid package that ships with Maple should work
on top of the Grid Computing Toolbox. The Grid Computing Toolbox does introduce new
functions, however these functions are mostly dedicated to managing remote servers.
There are a few differences between local and remote execution. First, local nodes may start
with local Maple libraries available. These libraries will generally not be available to remote
nodes. Instead of relying on sharing the libraries via libname, explicitly pass the routines
you need using the Launch command's imports parameter.
14.11 Limitations
There are a few situations where it may be difficult to effectively take advantage of the Grid
package.
Memory Usage
With the Grid package, multiple processes run on the local machine. If the original compu-
tation requires a significant amount of memory, then each Grid node may still require a
significant amount of memory, effectively multiplying the amount of memory needed by
the number of nodes. This could consume all the memory resources on the machine, which
can make the entire computation slower in the long run.
Cost of Communication
Passing data between nodes can be slow. Algorithms where each node needs to have access
to a large amount of data may be difficult to speed up using the Grid package. Minimizing
14.12 Troubleshooting 581
the amount of data passed between nodes can be an effective way to optimize a Grid-based
computation.
Load Balancing
The Grid package currently does not have any built in load balancing. Therefore the pro-
grammer is responsible for making sure that all the nodes are kept busy. This can be difficult.
You need to balance the need to have work available for nodes to compute with the overhead
of excessive communication.
14.12 Troubleshooting
Deadlocking
Some care must be taken when using Send and Receive. A call to Receive will wait until
a message is received, so if all nodes call Receive when there are no messages to be read,
the execution will deadlock. In addition there are a few limitations on what types of expres-
sions can be used for messages. See the Grid:-Send help page for more information.
When an unhandled exception is raised on a node this will cause the node to exit prematurely.
This may cause a Send or Receive to be missed, leading to a deadlock.
With remote servers and the Grid Computing Toolbox, the value of libname in the main
engine will have no effect on the value of libname set in the remote nodes.
Missing Functions
Forgetting to send all the necessary functions to the nodes may lead to nodes exiting without
properly executing the work they have been given. This may occur without any errors being
raised.
582 14 Parallel Programming
15 Testing, Debugging, and Efficiency
New programs, whether developed in Maple or any other language, sometimes work incor-
rectly. Problems that occur when a program is run can be caused by syntax errors introduced
during implementation, logic errors in the design of the algorithm, or errors in the translation
of an algorithm's description into code. Many errors can be subtle and hard to find by
visually inspecting your program. Maple provides error detection commands and a debugger
to help you find these errors.
Maple has several commands to help you find errors in procedures. Among these are com-
mands to trace procedure execution, check assertions, raise exceptions and trap errors, and
verify procedure semantics and syntax.
Additionally, the Maple debugger lets you stop in an executing Maple procedure, inspect
and modify the values of local and global variables, and continue the execution process,
either to completion, or one statement or block at a time. You can stop the execution process
when Maple reaches a particular statement, when it assigns a value to a specified local or
global variable, or when a specified error occurs. This facility lets you investigate the inner
workings of a program.
Even when a program is working correctly, you may want to analyze its performance to try
to improve its efficiency. Maple commands are available to analyze the time and memory
consumption involved in running a program.
You can use the command-line Maple debugger or you can use the interactive Maple debug-
ger available in the standard interface.
583
584 15 Testing, Debugging, and Efficiency
In the standard interface, the interactive Maple debugger is opened automatically by Maple
when a breakpoint or watchpoint is encountered during the execution of a program. An in-
teractive debugger window is displayed, which contains the following components:
a main text box that displays a procedure name and the debugger output
a field for entering commands and an associated Execute button
buttons that perform common debugging functions
While the interactive debugger has a different user interface, it otherwise functions
identically to the command-line Maple debugger. For more information, refer to the Inter-
activeDebugger help page.
This section introduces various debugger commands. To present and describe all of the
options available for these commands, the command-line debugger will be used instead of
the interactive debugger. Note that the Common Debugger Commands buttons in the in-
teractive debugger always implement the corresponding commands with their default options.
To run a debugger command with non-default options in the interactive debugger, enter the
command and options in the Enter a debugger command: field and click the Execute
button.
Example
Consider the following procedure, sieve, which is used as a case study. It implements the
Sieve of Eratosthenes: given a parameter n, return a count of the prime numbers less than
or equal to n. To debug the sieve procedure, breakpoints and watchpoints will be used to
stop the the execution of the procedure at selected points or on selected events.
> sieve := proc(n::integer)
local i, k, flags, count,twicei;
count := 0;
15.2 The Maple Debugger: A Tutorial Example 585
for i from 2 to n do
flags[i] := true;
end do;
for i from 2 to n do
if flags[i] then
twicei := 2*i;
for k from twicei by i to n do
flags[k] = false;
end do;
count := count+l;
end if;
end do;
count;
end proc:
sieve := proc(n::integer)
local i, k, flags, count, twicei;
1 count := 0;
2 for i from 2 to n do
3 flags[i] := true
end do;
4 for i from 2 to n do
5 if flags[i] then
6 twicei := 2*i;
7 for k from twicei by i to n do
8 flags[k] = false
end do;
9 count := count+l
end if
end do;
10 count
end proc
586 15 Testing, Debugging, and Efficiency
Note: The numbers preceding each line differ from line numbers that may be displayed
in a text editor. For example, keywords that end a statement (such as end do and end if)
are not considered separate Maple commands and are therefore not numbered.
Setting a Breakpoint
Use the stopat command to set a breakpoint in the sieve procedure.
> stopat(sieve);
(15.1)
This command sets a breakpoint before the first statement in the procedure sieve. When
you subsequently execute the sieve procedure, Maple stops before executing the first state-
ment and waits for you to provide instructions on what to do next. When the execution
process stops, the debugger prompt is displayed (DBG>).
Note: If a procedure has a remember table or a cache table, you may have to run the re-
start command before running a second or subsequent stopat command. For more in-
formation about remember tables and cache tables, see The remember, cache, and system
Options (page 228) or refer to the remember or CacheCommand help pages.
DBG>
At the debugger prompt, you can evaluate Maple expressions and call debugger commands.
Maple evaluates expressions in the context of the stopped procedure. You have access to
the same procedure parameters, and local, global, and environment variables as the stopped
procedure. For example, since the sieve procedure was called with parameter value 10, the
formal parameter n has the value 10.
DBG> n
10
sieve:
1* count := 0;
The next command runs the next statement at the current nesting level. After the statement
is run, control is returned to the debugger. If the statement is a control structure (for example,
an if statement or a loop), the debugger runs any statements within the control structure that
it would normally run. It stops the execution process before the next statement after the
control structure. Similarly, if the statement contains calls to procedures, the debugger ex-
ecutes these procedure calls in their entirety before the execution process stops.
DBG> next
0
sieve:
2 for i from 2 to n do
...
end do;
DBG>
The 0 in the first line of the output represents the result of the statement that was run--that
is, the result of count := 0. A "*" does not appear next to the statement number because
there is no breakpoint set immediately before statement 2. The debugger does not show the
588 15 Testing, Debugging, and Efficiency
body of the for loop, which itself consists of statements with their own statement numbers,
unless the execution process actually stops within its body. Maple represents the body of
compound statements by ellipses (...).
The execution process now stops before statement 4. Statement 3 (the body of the previous
for loop) is at a deeper nesting level. The loop is executed n-1 times. The debugger displays
the last result computed in the loop (the assignment of the value true to flags[10]).
Tip: If you want to repeat the previous debugger command, as shown in the second next
command above, you can press Enter at the DBG> prompt. You can also view your recent
command history using the up and down arrow keys on your keyboard.
To step into a nested control structure (such as an if statement or for loop) or a procedure
call, use the step debugger command.
DBG> step
true
sieve:
5 if flags[i] then
...
end if
DBG> step
true
sieve:
6 twicei := 2*i;
DBG>
If you use the step debugger command when the next statement to run is not a deeper
structured statement or procedure call, it has the same effect as the next debugger command.
DBG> step
4
sieve:
7 for k from twicei by i to n do
...
end do;
15.2 The Maple Debugger: A Tutorial Example 589
DBG>
At any time during the debugging process, you can use the showstat debugger command
to display the current status of the debugging process.
DBG> showstat
sieve := proc(n::integer)
local i, k, flags, count, twicei;
1* count := 0;
2 for i from 2 to n do
3 flags[i] := true
end do;
4 for i from 2 to n do
5 if flags[i] then
6 twicei := 2*i;
7 ! for k from twicei by i to n do
8 flags[k] = false
end do;
9 count := count+l
end if
end do;
10 count
end proc
DBG>
Maple displays a debugger prompt to indicate that you are still working within the Maple
debugger. The asterisk (*) indicates the unconditional breakpoint. An exclamation point (!)
that follows a statement number (see line 7) indicates the statement at which the procedure
is stopped.
To continue the debugging process, run another debugger command. For example, you can
use into or step to enter the innermost loop.
The behavior of the into debugger command is between that of the next and step commands.
The execution process stops at the next statement in the current procedure independent of
whether it is at the current nesting level or in the body of a control structure (an if statement
or a loop). That is, the into command steps into nested statements, but not procedure calls.
It executes called procedures completely and then stops.
DBG> into
4
sieve:
8 flags[k] = false
590 15 Testing, Debugging, and Efficiency
DBG>
A debugger command that is related to showstat is the list command. It displays the previous
five statements, the current statement, and the next statement to indicate where the procedure
has stopped.
DBG> list
sieve := proc(n::integer)
local i, k, flags, count, twicei;
...
3 flags[i] := true
end do;
4 for i from 2 to n do
5 if flags[i] then
6 twicei := 2*i;
7 for k from twicei by i to n do
8 ! flags[k] = false
end do;
9 count := count+l
end if
end do;
...
end proc
DBG>
You can use the outfrom debugger command to finish the execution process at the current
nesting level or at a deeper level. Execution of the procedure is stopped once a statement
at a shallower nesting level is reached, that is, after a loop terminates, a branch of an if
statement executes, or the current procedure call returns.
15.2 The Maple Debugger: A Tutorial Example 591
DBG> outfrom
true = false
sieve:
9 count := count+l
DBG> outfrom
l
sieve:
5 if flags[i] then
...
end if
DBG>
The cont debugger command continues the execution process until either the procedure
stops normally or encounters another breakpoint.
DBG> cont
(15.2)
The procedure does not give the expected output. Although you may find the reason obvious
from the previous debugger command examples, in other cases, it may not be easy to find
procedure errors. Therefore, continue to use the debugger. First, use the unstopat command
to remove the breakpoint from the sieve procedure.
> unstopat(sieve);
(15.3)
Setting a Watchpoint
Use the stopwhen command to set watchpoints. In this case, the execution process will stop
whenever Maple modifies the variable count in the procedure sieve.
> stopwhen([sieve,count]);
(15.4)
592 15 Testing, Debugging, and Efficiency
The stopwhen command returns a list of all the currently watched variables (that is, the
variables that you provided to the stopwhen command).
DBG>
The execution process stops because Maple modified count and the debugger displays the
assignment statement count := 0. Similar to breakpoints, the debugger then displays the
name of the procedure and the next statement to be run in the procedure. Note that the exe-
cution process stops after Maple assigns a value to count.
This first assignment to count is correct. Use the cont debugger command to continue the
execution process.
DBG> cont
count := l
sieve:
5 if flags[i] then
...
end if
DBG>
At first glance, this may look correct. Assume that the output is correct and continue the
execution process.
DBG> cont
count := 2*l
sieve:
5 if flags[i] then
...
end if
DBG>
This output appears to be incorrect because Maple should have simplified 2*1. Note that it
printed 2*l (two times the letter l) instead. By examining the source text for the procedure,
you can see that the letter "l" was entered instead of the number "1". Since the source of
the error has been discovered, you can stop the procedure. Use the quit debugger command
15.2 The Maple Debugger: A Tutorial Example 593
to stop the debugger, and then use the unstopwhen command to remove the watchpoint
from the procedure.
DBG> quit
Interrupted
> unstopwhen();
(15.5)
After correcting the source code for sieve, run the restart command, re-execute that source
code (for example, read it into your command-line session or re-execute that code region
in your worksheet), and execute the procedure again.
> restart;
> sieve := proc(n::integer)
local i, k, flags, count,twicei;
count := 0;
for i from 2 to n do
flags[i] := true;
end do;
for i from 2 to n do
if flags[i] then
twicei := 2*i;
for k from twicei by i to n do
flags[k] = false;
end do;
count := count+1;
end if;
end do;
count;
end proc:
> sieve(10);
(15.6)
This result is still incorrect. There are four primes less than 10, namely 2, 3, 5, and 7.
Therefore, start the debugger once more, stepping into the innermost parts of the procedure
to investigate. Since you do not want to start executing the procedure from the start, set the
breakpoint at statement 6.
> stopat(sieve,6);
(15.7)
594 15 Testing, Debugging, and Efficiency
> sieve(10);
true
sieve:
6* twicei := 2*i;
DBG> step
4
sieve:
7 for k from twicei by i to n do
...
end do;
DBG> step
4
sieve:
8 flags[k] = false
DBG> step
true = false
sieve:
8 flags[k] = false
DBG>
The last step reveals the error. The previously computed result should have been false (from
the assignment of flags[k] to the value false), but instead the value true = false was returned.
An equation was used instead of an assignment. Therefore, Maple did not set flags[k] to
false.
Once again, stop the debugger and correct the source text.
DBG> quit
Interrupted
flags[k] := false;
end do;
count := count+1;
end if;
end do;
count;
end proc:
(15.8)
If showstat is called with no arguments, all procedures that contain breakpoints are displayed.
You can also use the showstat command to display a single statement or a range of statements
by using the following syntax.
showstat( procedureName, number );
showstat( procedureName, range );
In these cases, the statements that are not displayed are represented by ellipses (...). The
procedure name, its parameters, and its local and global variables are always displayed.
> f := proc(x)
if x <= 2 then
print(x);
end if;
print(-x);
end proc:
596 15 Testing, Debugging, and Efficiency
f := proc(x)
...
2 print(x)
end if;
3 print(-x)
end proc
Setting Breakpoints
The stopat command has the following syntax, where procedureName is the name of the
procedure in which to set the breakpoint, statementNumber is the line number of the
statement in the procedure before which the breakpoint is set, and condition is a Boolean
expression which must be true to stop the execution process. The statementNumber and
condition arguments are optional.
stopat( procedureName, statementNumber, condition );
The condition argument can refer to any global variable, local variable, or parameter of the
procedure. These conditional breakpoints are indicated by a question mark (?) if the showstat
command is used to display the procedure.
Since the stopat command sets the breakpoint before the specified statement, when Maple
encounters a breakpoint, the execution process stops and Maple starts the debugger before
the statement.
Note: This means that you cannot set a breakpoint after the last statement in a statement
sequence--that is, at the end of a loop body, an if statement body, or a procedure.
If two identical procedures exist, depending on how you created them, they may share
breakpoints. If you entered the procedures individually, with identical procedure bodies,
they do not share breakpoints. If you created a procedure by assigning it to the body of an-
other procedure, their breakpoints are shared.
> f := proc(x) x^2 end proc:
g := proc(x) x^2 end proc:
h := op(g):
stopat(g);
(15.9)
15.3 Maple Debugger Commands 597
> showstat();
g := proc(x)
1* x^2
end proc
h := proc(x)
1* x^2
end proc
Removing Breakpoints
The unstopat command has the following syntax, where procedureName is the name of
the procedure that contains the breakpoint, and statementNumber is the line number of
the statement where the breakpoint is set. The statementNumber parameter is optional.
unstopat( procedureName, statementNumber );
Note: The showstat command does not mark explicit breakpoints with an "*" or a "?".
> f := proc(x,y) local a;
a:=x^2;
DEBUG();
a:=y^2;
end proc:
598 15 Testing, Debugging, and Efficiency
> showstat(f);
f := proc(x, y)
local a;
1 a := x^2;
2 DEBUG();
3 a := y^2
end proc
> f(2,3);
4
f:
3 a := y^2
DBG> quit
Interrupted
If the argument of the DEBUG command is a Boolean expression, the execution process
stops only if the Boolean expression evaluates to true. If the Boolean expression evaluates
to false or FAIL, the DEBUG command is ignored.
> f := proc(x,y) local a;
a:=x^2;
DEBUG(a<1);
a:=y^2;
DEBUG(a>1);
print(a);
end proc:
> f(2,3);
9
f:
5 print(a)
DBG> quit
Interrupted
If the argument of the DEBUG command is a value other than a Boolean expression, the
debugger prints the value of the argument (instead of the last result) when the execution
process stops at the following statement.
> f := proc(x)
x^2;
DEBUG("This is my breakpoint. The current value of x is:",
x);
15.3 Maple Debugger Commands 599
x^3;
end proc:
> f(2);
"This is my breakpoint. The current value of x is:",
2
f:
3 x^3
DBG>
DBG> showstat
f := proc(x)
1 x^2;
2 DEBUG("This is my breakpoint. The current value of x is:",
x);
3 ! x^3
end proc
DBG> quit
Interrupted
Note: If you display the contents of a procedure by using the print command (or lprint)
and the procedure contains a breakpoint that was set by using stopat, the breakpoint ap-
pears as a call to DEBUG.
> f := proc(x) x^2 end proc:
> stopat(f);
(15.10)
> print(f);
(15.11)
600 15 Testing, Debugging, and Efficiency
Setting Watchpoints
The stopwhen command can take the following forms.
stopwhen( globalVariableName );
stopwhen( [procedureName, variableName] );
The first form specifies that the debugger should be started when the global variable glob-
alVariableName is changed. Maple environment variables, such as Digits, can also be
monitored by using this method.
> stopwhen(Digits);
(15.12)
The second form starts the debugger when the (local or global) variable variableName is
changed in the procedure procedureName.
When any form of stopwhen is called, Maple returns a list of the current watchpoints.
The execution process stops after Maple assigns a value to the watched variable. The debug-
ger displays an assignment statement instead of the last computed result (which would
otherwise be the right-hand side of the assignment statement).
Clearing Watchpoints
The syntax to call unstopwhen is the same as that for stopwhen. Similar to the stopwhen
command, the unstopwhen command returns a list of all (remaining) watchpoints.
If no arguments are included in the call to unstopwhen, then all watchpoints are cleared.
Error watchpoints are set by using the stoperror command. The stoperror command has
the following syntax
stoperror( "errorMessage" );
where errorMessage is a string or a symbol that represents the error message returned from
the evaluation of a Maple expression. If the argument is a string, the debugger will be started
when an error for which the given string is a prefix is encountered. A list of the current error
watchpoints is returned.
15.3 Maple Debugger Commands 601
If no argument is entered in the call to stoperror, the list of current (error) watchpoints is
returned.
> stoperror();
(15.13)
(15.14)
> stoperror();
(15.15)
If the special name `all` is used instead of a specific error message as the parameter to the
stoperror command, a procedure stops executing when any error that would not be trapped
occurs.
Errors trapped by an error trapping construct (try...catch statement) do not generate an error
message. Therefore, the stoperror command cannot be used to catch them. For more in-
formation about the try...catch structure, see Trapping Errors (page 198). If the special
name `traperror` is used instead of a specific error message as the parameter to the
stoperror command, a procedure stops executing when any error that is trapped occurs. If
the errorMessage parameter is entered in the form traperror["message"] to stoperror,
the debugger starts only if the error specified by "message" is trapped.
When a procedure stops executing because of an error which causes an exception, continued
execution is not possible. Any of the execution control commands, such as next or step
(see Controlling the Execution of a Procedure during Debugging I (page 587) and Controlling
the Execution of a Procedure during Debugging II (page 603)), process the error as if the
debugger had not intervened. For example, consider the following two procedures. The first
procedure, f, calculates 1/x. The other procedure, g, calls f but traps the "division by zero"
error that occurs when x = 0.
> f := proc(x) 1/x end proc:
g := proc(x) local r;
try
f(x);
catch:
infinity;
end try;
end proc:
> g(9);
(15.16)
(15.17)
The stoperror command stops the execution process when you call f directly.
> stoperror("numeric exception: division by zero");
(15.18)
> f(0);
Error, numeric exception: division by zero
f:
1 1/x
DBG> cont
Error, (in f) numeric exception: division by zero
The call to f from g is within a try...catch statement, so the "division by zero" error does
not start the debugger.
> g(0);
(15.19)
(15.20)
(15.21)
However, Maple starts the debugger when the trapped error occurs.
15.3 Maple Debugger Commands 603
> g(0);
Error, numeric exception: division by zero
f:
1 1/x
DBG> step
Error, numeric exception: division by zero
g:
3 infinity
DBG> step
(15.22)
In the case that a particular error message is specified in the form traperror["message"],
the debugger is started only if the error specified by "message" is trapped.
If no argument is included in the call to unstoperror, all error watchpoints are cleared.
> unstoperror();
(15.23)
The most commonly used debugger commands are into, next, step, cont, outfrom, return,
and quit.
The return debugger command causes execution of the currently active procedure call to
complete. The execution process stops at the first statement after the current procedure.
604 15 Testing, Debugging, and Efficiency
The other commands are described in the tutorial in The Maple Debugger: A Tutorial Ex-
ample (page 583). For more information on these and other debugger commands, refer to
the debugger help page.
While in the debugger mode, you can evaluate any Maple expression and perform assign-
ments to local and global variables. To evaluate an expression, enter the expression at the
debugger prompt. To perform assignments to variables, use the standard Maple assignment
statement.
> f := proc(x) x^2 end proc:
> stopat(f);
(15.24)
> f(10);
f:
1* x^2
DBG> sin(3.0);
.1411200081
f:
1* x^2
DBG> cont
(15.25)
The debugger evaluates any variable names that you use in the expression in the context of
the stopped procedure. Names of parameters or local variables evaluate to their current
values in the procedure. Names of global variables evaluate to their current values. Envir-
onment variables, such as Digits, evaluate to their values in the stopped procedure's envir-
onment.
end do;
end proc:
> stopat(f,2);
(15.26)
> f(3);
f:
2* i^2
DBG> step
1
f:
2* i^2
DBG> (step)
3
f:
2* i^2
DBG> quit
Interrupted
When the execution process is stopped, you can modify local and global variables by using
the assignment operator (:=). The following example sets a breakpoint in the loop only
when the index variable is equal to 5.
> sumn := proc(n) local i, sum;
sum := 0;
for i to n do
sum := sum + i
end do;
end proc:
> showstat(sumn);
sumn := proc(n)
local i, sum;
1 sum := 0;
2 for i to n do
3 sum := sum+i
end do
end proc
606 15 Testing, Debugging, and Efficiency
> stopat(sumn,3,i=5);
(15.27)
> sumn(10);
10
sumn:
3? sum := sum+i
DBG> cont
17
sumn:
3? sum := sum+i
DBG> cont
(15.28)
Maple has added the numbers 1, 2, 3, 4, 3, and 4 and returned 17 as the result. By continuing
the execution of the procedure, the numbers 5, 6, 7, 8, 9, and 10 are added and 62 is returned
as the result.
The list debugger command is similar to the showstat command, except that you do not
need to specify arguments. If no arguments are included in the call to list, only the five
previous statements, the current statement, and the next statement to be executed are dis-
played. This provides some context in the stopped procedure. In other words, it indicates
the static position where the execution process stopped.
The where debugger command shows you the stack of procedure activations. Starting from
the top level, it shows you the statement that is executing and the parameters it passed to
15.3 Maple Debugger Commands 607
the called procedure. The where debugger command repeats this for each level of procedure
call until it reaches the current statement in the current procedure. In other words, it indicates
the dynamic position where execution stopped. The where command has the following
syntax.
where numLevels
To illustrate these commands, consider the following example. The procedure check calls
the sumn procedure from the previous example.
> check := proc(i) local p, a, b;
p := ithprime(i);
a := sumn(p);
b := p*(p+1)/2;
evalb( a=b );
end proc:
sumn := proc(n)
local i, sum;
1 sum := 0;
2 for i to n do
3? sum := sum+i
end do
end proc
sumn:
3? sum := sum+i
DBG> cont
(15.29)
fact := proc(x)
1 if x <= 1 then
2 1
else
3 x*fact(x-1)
end if
end proc
> stopat(fact,2);
(15.30)
> fact(5);
fact:
2* 1
DBG> where
TopLevel: fact(5)
[5]
fact: x*fact(x-1)
[4]
fact: x*fact(x-1)
[3]
fact: x*fact(x-1)
[2]
fact: x*fact(x-1)
[1]
15.3 Maple Debugger Commands 609
fact:
2* 1
DBG>
If you do not want to view the entire history of the nested procedure calls, use the numLevels
parameter in the call to the where debugger command to print a specified number of levels.
DBG> where 3
fact: x*fact(x-1)
[2]
fact: x*fact(x-1)
[1]
fact:
2* 1
DBG> quit
Interrupted
The showstop command (and the showstop debugger command) displays a report of all
the currently set breakpoints, watchpoints, and error watchpoints. Outside the debugger at
the top level, the showstop command has the following syntax.
showstop();
(15.31)
> stopwhen(f,y):
> stopwhen(Digits);
(15.32)
(15.33)
Breakpoints in:
f
int
Watched variables:
Digits
y in procedure f
Watched errors:
"numeric exception: division by zero"
Except for these rules, the debugger prompt call for each command is of the same form and
takes the same arguments as the corresponding top-level command call.
15.4 Detecting Errors 611
Restrictions
At the debugger prompt, the only permissible Maple statements are debugger commands,
expressions, and assignments. The debugger does not permit statements such as if, while,
for, read, and save. However, you can use `if` to simulate an if statement and seq to
simulate a loop.
The debugger cannot set breakpoints in, or step into, built-in commands, such as diff and
has. These commands are implemented in C and compiled into the Maple kernel. Debug-
ging information about these commands is not accessible to Maple. However, if a built-
in command calls a library command, for example, the diff command calling `diff/sin`,
you can use a breakpoint to stop in the latter.
If a procedure contains two identical statements that are expressions, the debugger cannot
always determine the statement at which the execution process stopped. If this situation
occurs, you can still use the debugger and the execution process can continue. The debugger
issues a warning that the displayed statement number may be incorrect.
Note: This issue occurs because Maple stores all identical expressions as a single occur-
rence of the expression. The debugger cannot determine at which invocation the execution
process stopped.
Tracing a Procedure
The simplest tools available for error detection in Maple are the printlevel environment
variable, and the trace and tracelast commands. You can use these facilities to trace the
execution of both user-defined and Maple library procedures. However, they differ in the
type of information that is returned about a procedure.
The printlevel variable is used to control how much information is displayed when a program
is executed. By assigning a large integer value to printlevel, you can monitor the execution
of statements to selected levels of nesting within procedures. The default value of printlevel
is 1. Larger, positive integer values cause the display of more intermediate steps in a com-
putation. Negative integer values suppress the display of information.
The printlevel environment variable is set by using the following syntax, where n is the
level to which Maple commands are evaluated.
612 15 Testing, Debugging, and Efficiency
printlevel := n;
To determine what value of n to use, note that statements within a particular procedure are
recognized in levels that are determined by the nesting of conditional or repetition statements,
and by the nesting of procedures. Each loop or if condition increases the evaluation level
by 1, and each procedure call increases the evaluation level by 5. Alternatively, you can
use a sufficiently large value of n to ensure that all levels are traced. For example, printlevel
:= 1000 displays information in procedures up to 200 levels deep.
> f := proc(x) local y; y := x^2; g(y) / 4; end proc:
g := proc(x) local z; z := x^2; z * 2; end proc:
> f(3);
(15.34)
> printlevel := 5;
> f(3);
{--> enter f, args = 3
y := 9
81/2
162
The amount of information that is displayed depends on whether the call to the procedure
was terminated with a colon or a semicolon. If a colon is used, only the entry and exit points
15.4 Detecting Errors 613
of the procedure are printed. If a semicolon is used, the results of the statements are also
printed.
By assigning a large value to printlevel, the trace of all subsequent Maple procedure calls
is displayed. To display the trace of specific procedures, you can use the trace command.
The trace command has the following syntax, where arguments is one or more procedure
names.
trace(arguments);
The trace command returns an expression sequence containing the names of the traced
procedures. To begin tracing, call the procedure.
> trace(f, g);
(15.35)
> f(3):
{--> enter f, args = 3
{--> enter g, args = 9
<-- exit g (now in f) = 162}
<-- exit f (now at top level) = 81/2}
Similar to printlevel, the amount of information that is displayed during tracing when trace
is used depends on whether the call to the procedure was terminated with a colon or a
semicolon. If a colon is used, only entry and exit points of the procedure are printed. If a
semicolon is used, the results of the statements are also printed.
To turn off the tracing of specific procedures, use the untrace command.
> untrace(f, g);
(15.36)
> f(3);
(15.37)
Note: You can use debug and undebug as alternate names for trace and untrace.
If running a procedure results in the display of an error message, you can use the tracelast
command to determine the last statement executed and the values of variables at the time
of the error. The tracelast command has the following syntax.
614 15 Testing, Debugging, and Efficiency
tracelast;
After an error message is displayed, the following information is returned from a call to
tracelast.
The first line displays which procedure was called and what values were used for the
parameters.
The second line displays the # symbol, the procedure name with the line number of the
statement that was executed, and the statement that was executed.
Finally, if there are any local variables in the procedure, they are displayed with their
corresponding values.
> f := proc(x) local i, j, k;
i := x;
j = x^2;
seq(k, k=i..j);
end proc:
> f(2, 3);
Error, (in f) unable to execute seq
> tracelast;
f called with arguments: 2, 3
#(f,3): seq(k,k = i .. j)
Error, (in f) unable to execute seq
locals defined as: i = 2, j = j, k = k
You can find the error in this procedure by studying the results of the tracelast command-
-the assignment to the local variable j incorrectly uses an equal sign (=) instead of an assign-
ment operator ( := ).
The information provided by tracelast can become unavailable whenever Maple does a
garbage collection. Therefore, it is advisable to use tracelast immediately after an error
occurs. For more information about garbage collection in Maple, see Garbage Collec-
tion (page 628).
Using Assertions
An assertion is a verification of the state of Maple at the time the assertion is made. You
can include assertions in your procedure to guarantee pre- and post-conditions, and loop
invariants during execution by using the ASSERT command. You can also use assertions
to guarantee the value returned by a procedure or the value of local variables inside a pro-
cedure. The ASSERT command has the following syntax.
15.4 Detecting Errors 615
If condition evaluates to false, an error is generated and message is printed. If the first ar-
gument evaluates to true, ASSERT returns NULL.
To check assertions, turn on assertion checking before executing a procedure that contains
an ASSERT command. To query the current state of assertion checking, or turn assertion
checking on or off, use the kernelopts command.
Programming note: You should use assertions to verify that your program is working as
intended. You should not use assertions to validate computations or values which are not
completely in the control of your program, such as user input.
(15.38)
Note that when you set a kernelopts variable, such as when you turn assertion checking on
or off, kernelopts returns its previous value.
At any time during the Maple session, you can check the setting for assertion checking by
entering the following command.
> kernelopts(assertlevel);
(15.39)
(15.40)
Use the kernelopts command again to turn assertion checking off. (Again, kernelopts returns
its previous value.) When assertion checking is off, the overhead of processing an ASSERT
statement in a procedure is minimal.
> kernelopts(assertlevel=0);
(15.41)
For information on assertion checking and procedures, see Return Type (page 222)) and
Variables in Procedures (page 230).
Related to assertions are Maple warning messages. The WARNING command causes a
specified warning message to display. The warning is preceded by the string '"Warning, "'.
The WARNING command has the following syntax.
WARNING( msgString, msgParam1, msgParam2, ... );
The msgString parameter is the text of the warning message and msgParami are optional
parameters to substitute into msgString, if any. For more information on message parameters,
see Handling Exceptions (page 617).
> f := proc(x)
if x < 0 then
WARNING("sqrt(%1) is complex", x);
end if;
sqrt(x);
end proc;
15.4 Detecting Errors 617
(15.42)
> f(-2);
Warning, sqrt(-2) is complex
(15.43)
By default, warning messages are displayed. You can hide warning messages by using the
interface(warnlevel=0) command. In this case, the warning is not displayed and the call
to WARNING has no effect.
> interface(warnlevel=0);
(15.44)
> f(-2);
(15.45)
Handling Exceptions
An exception is an event that occurs during the execution of a procedure that disrupts the
normal flow of instructions. Many kinds of actions can cause exceptions, for example, at-
tempting to read from a file that does not exist. Maple has two mechanisms available when
such situations occur:
the error statement to raise an exception, and
the try...catch...finally block to handle exceptions.
Raising Exceptions
The error statement raises an exception. Execution of the current statement sequence is
interrupted, and the block and procedure call stack is popped until either an exception
handler is encountered, or execution returns to the top level (in which case the exception
becomes an error). The error statement has the following syntax.
error msgString, msgParam1, msgParam2, ...
The msgString parameter is a string that gives the text of the error message. It can contain
numbered parameters of the form %n or %-n, where n is an integer. These numbered
618 15 Testing, Debugging, and Efficiency
parameters are used as placeholders for actual values. In the event that the exception is
printed as an error message, the actual values are specified by the msgParam values.
For example,
> error "%1 has a %-2 argument, %3, which is missing", f, 4, x;
Error, f has a 4th argument, x, which is missing
A numbered parameter of the form %n displays the nth msgParam in line-printed notation
(that is, as lprint would display it). A numbered parameter of the form %-n displays the
nth msgParam, assumed to be an integer, in ordinal form. For example, the %-2 in the
previous error statement is displayed as "4th". The special parameter %0 displays all the
msgParams, separated by a comma and a space.
The error statement evaluates its arguments and then creates an exception object which is
an expression sequence with the following elements.
The name of the procedure in which the exception was raised. If the exception occurred
in a procedure local to a module, then the name of the innermost visible (non-local)
calling procedure is used. If the exception occurred at the top level (not within a proced-
ure), then the first element of the exception object will be the constant 0.
The msgString.
The msgParams, if any.
The created exception object is assigned to the global variable lastexception as an expression
sequence. For more information on lastexception, refer to the error help page.
Note: The actual arguments to the error statement are also assigned to lasterror for
compatibility with older versions of Maple.
Note: To view the value of the lastexception variable within the debugger, use the
showexception debugger command.
The error statement normally causes an immediate exit from the current procedure to the
Maple session. Maple prints an error message of the following form.
Error, (in procName) msgText
In this case, msgText is the text of the error message (which is constructed from the
msgString and optional msgParams of the error statement), and procName is the name
of the procedure in which the error occurred, or the name of the innermost non-local proced-
ure in the current call stack if the procedure is a module local. If the procedure does not
have a name, procName is displayed as unknown. If the error occurs at the top level, outside
any procedure, the (in procName) part of the message is omitted.
15.4 Detecting Errors 619
The error statement is commonly used when parameter declarations are not sufficient to
check that the actual parameters to a procedure are of the correct type. The following pairup
procedure takes a list L of the form [x_1, y_1, x_2, y_2, ..., x_n, y_n] as input, and creates
from it a list of the form [[x_1, y_1], [x_2, y_2], ..., [x_n, y_n]]. A simple type check cannot
determine if list L has an even number of elements, so you must check this explicitly by
using an error statement.
> pairup := proc(L::list)
local i, n;
n := nops(L);
if irem(n, 2) = 1 then
error "list must have an even number of "
"entries, but had %1", n;
end if;
[seq( [L[2*i-1], L[2*i]], i=1..n/2 )];
end proc:
> pairup([1, 2, 3, 4, 5]);
Error, (in pairup) list must have an even number of entries, but had
5
(15.46)
For information on trapping errors using a try...catch statement, see Trapping Er-
rors (page 198).
Checking Syntax
The Maple maplemint command generates a list of semantic errors for a specified procedure,
if any. The semantic errors for which maplemint checks include parameter name conflicts,
local and global variable name conflicts, unused variable declarations, and unreachable
code. The maplemint command has the following syntax.
maplemint( procedureName );
In the case where the specified procedure is free of semantic errors, maplemint returns
NULL.
> f := proc() local a, i; global c;
for i from 1 to 10 do
print(i);
for i from 1 to 5 do
if a = 5 then
a := 6;
620 15 Testing, Debugging, and Efficiency
return true;
print(`test`);
end if;
end do;
end do;
end proc:
> maplemint(f);
This code is unreachable:
print(test)
These global variables were declared, but never used:
c
These local variables were used before they were assigned a value:
a
These variables were used as the same loop variable for nested
loops:
i
Similar to maplemint, Maple also has an external program utility called mint. The mint
program is called from outside Maple; it is used to check both semantic and syntax errors
in an external Maple source file.
(15.47)
The option builtin phrase identifies add as a built-in command, and the identifier following
builtin is either a name or number that identifies this particular command in the kernel.
15.5 Creating Efficient Programs 621
For more information about efficiency in Maple programming, refer to the efficiency help
page.
The following statements all return the sum of the same sequence of numbers. However,
by using the time command, it is clear that the second expression, which uses the add
command, is the most efficient method with respect to time consumption.
> time( `+`(seq(2^i, i=1..10^5) ) );
(15.48)
(15.49)
Two options are available to compare these expression with the equivalent for...do statement.
The first is to wrap the statement in an anonymous function call:
> time( proc() local S, i; S:=0: for i from 1 to 10^5 do S := S +
2^i end do: end proc() );
(15.50)
Another solution is to use the other form of the time command with no arguments, which
returns the total CPU time used since the start of the Maple session. The time is reported
in seconds and the value returned is a floating-point number.
time()
To find the time used to execute a particular statement or group of statements, use the fol-
lowing statements.
st := time():
... statements to be timed ...
time() - st;
Therefore, you could use the following set of statements to calculate the amount of time (in
seconds) required to add the first 10,000 powers of 2 by using the add command.
622 15 Testing, Debugging, and Efficiency
(15.51)
CPU time is not the only important measure of efficiency. For most code, the amount of
memory used is equally important. This can be measured with the command
kernelopts(':-bytesused')
For parallel code, the real or wall clock time is also important. The time command with the
index real measures real time used:
time[':-real']()
time[':-real']( expr )
By default, CodeTools:-Usage prints the time and memory usage in evaluating the expres-
sion. If you want to save the results, you can specify an output option, which ensures that
values that can be saved are returned.
> CodeTools:-Usage( `+`(seq(sign(i)*2^abs(i), i=-10^4..10^4)),
'output'='all');
(15.52)
(15.53)
(15.54)
(15.55)
15.5 Creating Efficient Programs 623
(15.56)
For most computers, the third expression above will have the lowest cputime and bytesused
values. Depending on the parallelism available, the fourth expression, which uses Threads:-
Add, may have the lowest realtime value. The first two expressions will have the highest
bytesused values since they both create large sequences of 2*10^4 numbers before adding
them to 1.
Profiling a Procedure
The Profiling subpackage of CodeTools can be used to display run-time information about
a procedure (or procedures). The run-time information is displayed in tabular form and it
contains the number of calls to the procedures, the CPU time used, and the number of bytes
used by each call. To turn on profiling, use the Profile command.
CodeTools:-Profiling:-Profile( procedureNames )
Then, to display the run-time information collected for the profiled procedures use the
SortBy command.
CodeTools:-Profiling:-SortBy( )
To display the line-by-line profiling information for the specified procedure, use the
PrintProfiles command. If no argument is given to PrintProfiles, the run-time information
for all profiled procedures is displayed.
CodeTools:-Profiling:-PrintProfiles( procedureName )
To illustrate the use of profiling in Maple, consider the following procedures that compute
the nth Fibonacci number. Both procedures contain the same code except that Fibonacci1
uses option remember.
For more information about option remember, see The remember, cache, and system Op-
tions (page 228).
> Fibonacci1:=proc(n)
option remember;
if n<2 then
n
else
Fibonacci1(n-1)+Fibonacci1(n-2)
624 15 Testing, Debugging, and Efficiency
end if;
end proc:
> Fibonacci2:=proc(n)
if n<2 then
n
else
Fibonacci2(n-1)+Fibonacci2(n-2)
end if;
end proc:
(15.57)
> Fibonacci2(25);
(15.58)
Use the SortBy command to display the run-time information about Fibonacci1 and
Fibonacci2.
> SortBy();
function calls time time% words
words%
--------------------------------------------------------------------
-------
Fibonacci1 26 0.000 0.00 478
0.04
Fibonacci2 242785 0.570 100.00 1213923
99.96
--------------------------------------------------------------------
-------
total: 242811 0.570 100.00 1214401
100.00
> PrintProfiles(Fibonacci1);
Fibonacci1
Fibonacci1 := proc(n)
|Calls Seconds Words|
PROC | 26 0.000 478|
1 | 26 0.000 78| if n < 2 then
2 | 2 0.000 0| n
else
3 | 24 0.000 400| Fibonacci1(n-1)+Fibonacci1(n-2)
end if
end proc
> PrintProfiles(Fibonacci2);
Fibonacci2
Fibonacci2 := proc(n)
|Calls Seconds Words|
PROC |242785 0.570 1213923|
1 |242785 0.216 728355| if n < 2 then
2 |121393 0.060 0| n
else
3 |121392 0.294 485568| Fibonacci2(n-1)+Fibonacci2(n-2)
end if
end proc
By studying the run-time information, particularly the number of calls to each procedure,
you can see that it is more efficient to use option remember in a recursive procedure.
To turn off profiling, use the UnProfile command. If no argument is given to UnProfile,
all procedures currently profiled are returned to their original state.
UnProfile( procedureName )
When a procedure is unprofiled, all run-time information for that procedure is lost.
> UnProfile();
626 15 Testing, Debugging, and Efficiency
> SortBy();
Warning, total execution time is 0
Warning, total words used is 0
function calls time time% words
words%
--------------------------------------------------------------------
-------
--------------------------------------------------------------------
-------
total: 0 0.000 100.00 0
100.00
The CodeTools:-Profiling package has several other useful commands, including Load-
Profiles and SaveProfiles, which can be used to save and load profile information to and
from a file. By using these commands, you can collect profiling information from commands
run with restart commands in between. In the following code, both calls to myproc will
be profiled and the data collected as if they had been executed right after each other.
> CodeTools:-Profiling:-Profile(myproc);
> myproc( input1 );
> CodeTools:-Profiling:-SaveProfiles( "myproc.profile", 'overwrite'
);
> restart;
> CodeTools:-Profiling:-LoadProfiles( "myproc.profile" );
> myproc( input2 );
The older profile facility is also still available but it is slower and does not provide line-by-
line profiling information. It is still useful for profiling the use of built-in procedures, which
are not supported by CodeTools:-Profiling. For more information, refer to the profile help
page.
In some cases, it is useful to collect profiling information on every procedure which is in-
voked during the evaluation of a Maple expression. In this situation, use the exprofile
command with the profile kernel option. The output of exprofile can be verbose for mod-
erately complicated code.
> a:=proc(); b(100); end proc:
> b:=proc(n);
if n>0 then c(n-2); end if;
end proc:
15.6 Managing Resources 627
> c:=proc(n);
if n>0 then b(n+1); end if;
end proc:
> kernelopts(profile=true):
> writeto('output');
> a();
> kernelopts(profile=false);
> writeto(terminal);
> exprofile('output',alpha);
If the expression is successfully evaluated within the specified time, timelimit returns the
value of the expression. If the time limit is reached before the expression is evaluated,
timelimit raises an exception.
> f := proc()
local i;
for i to 100000 do
2^i
end do
end proc:
> timelimit(0.25, f());
NULL;
end try;
Multiple calls to timelimit can be nested, causing both limits to be active at once.
> g := proc(t)
try
timelimit(t, f());
catch "time expired":
error "time expired in g";
end try;
end proc:
> timelimit(10, g(0.25) );
> timelimit(0.25, g(10) );
Note that in the second of these examples, the inner call, g(10) would normally have finished
without triggering the time limit exception. The outer time limit of 0.25 cpu seconds preven-
ted the inner call from completing. Thus, the time-out event did not occur inside g and so
is not trapped by the catch clause in g. This illustrates that a try-catch construct cannot
capture a time limit exception event generated by a timelimit call in a surrounding scope.
For more information on catching time expired exceptions and nested time limits, refer to
the timelimit help page.
Garbage Collection
Garbage collection deletes all objects that are no longer in use by the program and are oc-
cupying space in memory. In Maple, garbage collection will also recover storage from the
remember tables of procedures that use an option system or option builtin by removing
entries that have no other references to them.
For more information about procedure options, see Options (page 224).
Garbage collection is also used to clear cache tables that have temporary entries when a
memory usage threshold is reached.
The Maple garbage collection command is gc. It has the following syntax.
gc()
The gc command explicitly schedules a garbage collection process and returns a value of
NULL. Otherwise, garbage collections are performed automatically by Maple every
10,000,000 words used. To change the frequency of automatic garbage collections, use the
kernelopts command to change the gcfreq option.
15.6 Managing Resources 629
> kernelopts(gcfreq);
The kernelopts command can also be used to query other garbage collection information
such as the number of bytes returned after the last garbage collection and the number of
times the garbage collection process has run.
> kernelopts( gcbytesavail );
> kernelopts( gcbytesreturned );
> kernelopts( gctimes );
The following kernelopts options can be used to limit Maple's use of system resources.
The cpulimit, datalimit, and stacklimit options can be used to set limits on the resources
available to Maple and must be used carefully. Unlike the timelimit command, once one
of these limits is reached, Maple may shut down without warning without prompting you
to save your work. This makes these limit options most useful for running in non-interactive
sessions.
On some platforms, including all Windows platforms, the detection of limit violations is
tied to garbage collection and therefore the detection of limit violations will be inaccurate
for code that rarely starts the garbage collection process. If the garbage collection process
does not occur, Maple does not detect limit violations.
These options can also be set using the -T command-line option. For more information,
refer to the maple help page.
The filelimit and processlimit limit options can similarly be used to limit the number of
open files and external processes that Maple can use at one time. Some internal Maple
commands open files or run processes and thus will fail if these limits are too low.
If the option limitjvmheap is set to true then the Java external calling virtual machine is
limited to the amount of memory given in the limit option jvmheaplimit.
The option cacheclearlimit is used to set a threshold at which Maple is allowed to clear
temporary elements from cache tables during garbage collection.
An informational kernelopts option is memusage which will display how much memory
is currently in use, listed by DAG type.
630 15 Testing, Debugging, and Efficiency
Note: There is a Maplet application that provides a graphical user interface to a subset of
the kernel options. This Maplet can be opened by calling Maplets:-Examples:-KernelOpts().
The default mode of the verify command is simple evalb equality checking.
> verify(10, 20);
> verify(10, 10.00);
The verify command called with a third argument provides numerous different structured
verifiers, many of which are similar to the structured type of the expressions being compared.
For full details, refer to the verify and verify/structured help pages.
> verify(10+x, 10.00+x, 'float(10)' );
> verify(Array(1..3,[1,2,3]), Array([1,2,3],readonly), 'Array');
> verify({0.32}, {0.320002, 0.319996},'set(float(1e5))');
For the sieve example introduced in The Maple Debugger: A Tutorial Example (page 583),
the following statements can be saved in a file called sieveTest.mpl:
verify(sieve(1), 0);
verify(sieve(2), 1);
verify(sieve(10), 4);
verify(sieve(100), 25);
verify(sieve(1223), 200);
verify(sieve(-1), 0);
verify(sieve(-1000), 0);
If the sieve function works properly, reading or running this file from the command line
maple -s -q < sieveTest.mpl
This output is easy to inspect visually for correctness. If the number of tests in one file is
large, you may want to produce errors for failures, and let successful tests proceed without
further action. The command CodeTools:-Test is a front-end to verify that provides this
functionality as well as allowing you to test expected errors and customize verifications.
The output format is quite flexible. In the following example, we use the quiet option to
suppress output for passed tests, and the label option to give each test a unique identifier,
so we can easily identify failures. Here is the new version of the test harness:
with(CodeTools):
Test(sieve(1), 0, quiet, label=10);
Test(sieve(2), 1, quiet, label=20);
Test(sieve(10), 4, quiet, label=30);
Test(sieve(100), 25, quiet, label=40);
Test(sieve(1223), 200, quiet, label=50);
Test(sieve(-1), 0, quiet, label=60);
Test(sieve(-1000), 0, quiet, label=70);
Test(sieve(sqrt(2)), "invalid input", testerror, quiet, label=80);
Test(sieve(1), -1, quiet, label=90);
632 15 Testing, Debugging, and Efficiency
This new test harness has the advantage that failures are highlighted as errors, so they stand
out visually. If you remove the quiet option, you will also get a short message for each test
that passes. That can be useful to ensure that false positive results are less likely to occur
due to tests being skipped.
For example, if a procedure takes a list as input, there should be a test case for the empty
list.
Test Coverage
Good suites of tests exercise every statement in the code that is being tested. Maple provides
a package to measure the coverage of a suite of tests in CodeTools:-Profiling:-Coverage.
To use this code, activate profiling of the procedure (or procedures) you want to test as de-
scribed in Profiling a Procedure (page 623). Then run your test suite and use the command
CodeTools:-Profiling:-Coverage:-Print to get a report on which lines in your procedures
were not run while running the test suite.
For example, we could add the following to the test file for sieve in the previous section:
with(CodeTools):
Profiling:-Profile(sieve);
...
Profiling:-Coverage:-Print();
15.8 Exercises 633
When run, in addition to the test output, this produces the message:
sieve (8): all statements covered
which informs us that the procedure was called 8 times and every statement in the procedure
was executed at least once. If statements had been missed, those missed statements would
be printed.
15.8 Exercises
1. The following procedure tries to compute .
Hint: Use the Maple debugger described in The Maple Debugger: A Tutorial Ex-
ample (page 583) and Maple Debugger Commands (page 595) to isolate the error.
2. The following recurrence relation defines the Chebyshev polynomials of the first kind,
.
This procedure has several errors. Which variables must be declared local? What happens
if is zero or negative? Identify and correct all errors, using the Maple debugger where
appropriate. Modify the procedure so that it returns unevaluated if is a symbolic value.
Appendix A Internal
Representation
The table below lists the structures that are currently implemented in Maple.
Each structure, along with the constraints on its length and contents, is described in the
sections that follow.
Evaluators
The evaluators are the main functions responsible for evaluation. There are six types of
evaluations: statements, algebraic expressions, Boolean expressions, name forming, ar-
bitrary precision floating-point arithmetic, and hardware floating-point arithmetic. The
user interface calls only the statement evaluator, but thereafter there are many interactions
between evaluators. For example, the statement
if a > 0 then b||i := 3.14/a end if;
is first analyzed by the statement evaluator, which calls the Boolean evaluator to resolve
the if condition. Once completed (for example, a true result is returned), the statement
evaluator is invoked again to perform the assignment, for which the name-forming eval-
uator is invoked with the left-hand side of the assignment, and the expression evaluator
635
636 Appendix A Internal Representation
with the right-hand side. Since the right-hand side involves floating-point values, the
expression evaluator calls the arbitrary precision floating-point evaluator.
Normally, you do not specifically call any of the evaluators. However, in some circum-
stances, when a nondefault type of evaluation is needed, you can directly call evalb (the
Boolean evaluator), evaln (the name-forming evaluator), evalf (the arbitrary precision
floating-point evaluator), or evalhf (the hardware floating-point evaluator).
Algebraic Functions
Algebraic functions are commonly called basic functions. Some examples are taking
derivatives (diff), dividing polynomials (divide), finding coefficients of polynomials
(coeff), computing series (series), mapping a function (map), expanding expressions
(expand), and finding indeterminates (indets).
Therefore, for example, the basic function diff cannot differentiate any function. All of
that functionality is included in the Maple library in procedures named 'diff/function-
Name'. This is a fundamental feature of Maple since it permits:
Flexibility (the ability to change the Maple library)
Customization (by defining your refined handling functions)
Readability (much of the Maple functionality is visible at the user level)
Maple allows the kernel to remain small by offloading nonessential functions to the library.
The header field, stored in one or more machine words, encodes the length of the structure
and its type. Additional bits are used to record simplification status, garbage collection
information, persistent store status, and various information about specific data structures
(for example, whether a for loop contains a break or next statement).
Every structure is created with its own length, and that length does not change during the
existence of the structure. Furthermore, the contents of most (but not all) data structures
are never changed during execution because it is unpredictable how many other data
structures are referring to them and relying on them not to change. The normal process
for modifying a structure is to copy it and then to modify the copy. Structures that are no
longer used are eventually reclaimed by the garbage collector.
The following sections describe each of the structures currently implemented in Maple,
along with the constraints on their lengths and contents. The 6-bit numeric value identi-
fying the type of structure is of little interest, so symbolic names will be used.
The notation ^something in the data structure depictions indicates that the value stored
in that field of the structure is a pointer to the value (something), rather than being the
something itself.
Length: 3
Length: 3
The left-hand side name entries must evaluate to assignable objects: NAME, FUNCTION,
MEMBER or TABLEREF structures, or a sequence thereof. If the left-hand side is a
sequence, the right-hand side must be a sequence of the same length.
Length: arbitrary
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 within other Maple objects (currently
only RTABLE structures). It is also sometimes used as temporary storage space during
various kernel operations.
Length: 1
Length: 3
If the name entry is one of NAME, CATENATE, LOCAL, or PARAM, and if the expr
entry evaluates to an integer, NAME, or STRING, the result is a NAME.
If the name entry is a STRING or CATENATE that resolves to a STRING, and if the
expr entry evaluates to an integer, NAME, or STRING, the result is a STRING.
Appendix A Internal Representation 639
Length: 2 or 3
Length: 2
This is an internal structure used for communication between the kernel and user interface.
Such a structure never reaches the user level, or even the mathematical parts of the kernel.
Length: 3
This structure has three interpretations depending on the context in which it is used. When
it appears in the header of a procedure definition, it is a parameter declaration that has a
type. When it appears in the local section of a procedure or on the left-hand side of an
assignment, it is a type assertion. When it appears elsewhere (specifically, in a conditional
expression), it is a type test.
DEBUG: Debug
DEBUG ^expr1 ^expr2 ...
Length: 2 or more
640 Appendix A Internal Representation
This is another structure that is only used internally. It is used by the kernel when printing
error traceback information to transmit that information up the call stack.
Length: 3
This structure has two interpretations depending on the context in which it is used. It can
be either a test for equality, or a statement of equality (not to be confused with an assign-
ment).
Length: 2
This structure represents the Maple error statement. The expr is either a single expression
(if only a message is specified in the error statement), or an expression sequence (if ar-
guments are also specified). The actual internal tag used for the ERROR structure is
MERROR to prevent a conflict with a macro defined by some C compilers.
Length: 1 or more
element groupings intact. The special value NULL is represented by an empty expression
sequence. Thus, [NULL] is equivalent to [], and f(NULL) is equivalent to f().
The integer2 field can optionally be one of the names, undefined or infinity, in which
case the FLOAT structure represents an undefined floating-point value (not-a-number,
or NaN, in IEEE terminology), or a floating-point infinity. When integer2 is undefined,
integer1 can accept different small integer values, allowing different NaN values to exist.
When integer2 is infinity, integer1 must be 1 or -1.
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 the name field of the ASSIGN structure, except that
it can also be the empty expression sequence (NULL), indicating that there is no con-
trolling variable for the loop.
642 Appendix A Internal Representation
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 therefore be replaced 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 header of the FOR
structure is used to indicate whether the stat-seq entry contains any break or next state-
ments.
Length: 1 or more
This structure is similar to the BINARY structure, except that it is for use by Maple
components outside the kernel, such as the user interface. A FOREIGN structure is exempt
from garbage collection, and the external component is responsible for freeing this
structure when it is finished using it.
FOREIGN data structures can be created and managed in external code by using the
MaplePointer API functions. For more information, refer to the OpenMaple,C,Maple-
Pointer help page.
This structure represents a function invocation (as distinct from a procedure definition
that is represented by the PROC structure). The name entry follows the same rules as in
ASSIGN, or it can be a PROC structure. The expr-seq entry gives the list of actual
parameters; this entry is always an expression sequence (possibly of length 1, which in-
dicates that no parameters are present).
GARBAGE: Garbage
GARBAGE ...
Length: 1 or more
Appendix A Internal Representation 643
This structure is used internally by the Maple garbage collector as a temporary object
type for free space.
This structure is used to store a hardware floating-point value. The one or two words
(always 8 bytes) after the header store the actual double-precision floating-point value.
HFLOAT objects can appear as the result of floating-point computations, I/O operations,
or by extracting elements from hardware floating-point RTABLE structures. They look
like and are treated as indistinguishable from software FLOAT objects.
IF: If Statement
IF ^cond-ex- ^stat-seq1 ^cond-ex- ^stat-seq2 ... ... ^stat-seqN
pr1 pr2
Maple syntax:
if condExpr1 then
statSeq1
elif condExpr2 then
statSeq2
...
else statSeqN
end if
Length: 3 or more
This structure represents the if ... then ... elif ... else ... end if statements in Maple. If the
length is even, the last entry is the body of an else clause. The remaining entries are inter-
preted in pairs, where each pair is a condition of the if or elif clause, followed by the as-
sociated body.
Length: 3
644 Appendix A Internal Representation
Length: 3
This structure has two interpretations, depending on the context in which it is used. It
can be either a test for inequality or an inequality statement.
Length: 2 or more
This data structure represents a negative integer of arbitrary precision. For a complete
description of the integer representation, including positive integers, see the following
section.
Length: 2 or more
This data structure represents a positive integer of arbitrary precision. Integers are repres-
ented internally in a base equal to the full word size of the host machine. On 32-bit archi-
tectures, this base is . On 64-bit architectures, the base is 2^64. Integers in
this range use the GNU Multiple Precision Arithmetic (GMP) library for integer arithmetic.
Small integers are not represented by data structures. Instead of a pointer to an INTPOS
or INTNEG structure, a small integer is represented by the bits of what would normally
be a pointer. The least significant bit is 1, which makes the value an invalid pointer (since
pointers must be word-aligned). Such an integer is called an immediate integer.
Length: 3
This structure has two interpretations, 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-or-
equal structure. Any input of that form is stored as a LESSEQ structure.
Length: 3
Similar to the LESSEQ structure above, this structure has two interpretations, depending
on the context. It can be interpreted as a relation (that is, an inequation), or as a compar-
ison (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.
Length: 2
LIST: List
LIST ^expr-seq ^attrib-expr
The elements of the expr-seq are the elements of the list. The list can optionally have at-
tributes.
Length: 2
This structure indicates a local variable when it appears within an expression in a procedure
or module. The integer is an index into the procedure local-seq. At procedure execution
time, it is also an index into the internal data structure storing the active locals on the
procedure activation stack, and stores private copies of the NAMEs of the local variables
(private copies in the sense that these NAMEs are not the same as the global NAMEs of
the same name).
Length: 3
This structure represents a module member access in an expression. MEMBER objects
typically do not persist when a statement is simplified. Instead, they are replaced by the
actual member that they refer to (an instance of a NAME).
Maple syntax:
module modName ( )
description d1, d2, ...;
local l1, l2, ...;
local sl1::static, sl2::static, ...;
export e1, e2, ...;
Appendix A Internal Representation 647
Length: 13
The parameter sequence (param-seq), which occurs between the parentheses after mod-
Name, points to an expression sequence describing the formal parameters of the module.
Currently, Maple does not support parameterized modules, so this field always points to
the sequence containing only an instance of the name thismodule.
The local sequence (local-seq) points to an expression sequence listing the explicitly and
implicitly declared local variables. Each entry is a NAME. The explicitly declared vari-
ables appear first. Within the module, locals are referred to by LOCAL structures, the
local variable number being the index into the local sequence. The instances of these
names appear in the MODULE structure.
The export sequence (export-seq) points to an expression sequence listing the exported
module members. Each entry is a NAME. Within the module, exports are referred to by
LOCAL structures, the local variable number being the number of elements in the local
sequence, plus the index into the export sequence. The instances of these names appear
in the MODULE structure.
The statement sequence (stat-seq) field points to a single statement or a statement sequence
(STATSEQ). If the module has an empty body, this is a pointer to NULL instead.
The global sequence (global-seq) field points to a list of the explicitly declared global
variables in the module (those that appeared in the global statement). This information
is never used at run time, but is used when simplifying nested modules and procedures
to determine the binding of lexically scoped identifiers (for example, an identifier on the
left-hand side of an assignment in a nested procedure can be global if it appears in the
global statement of a surrounding context). This information is also used at printing time,
so that the global statement contains exactly the global identifiers that were declared
originally.
648 Appendix A Internal Representation
The module name (mod-name) field points to the optional name of the module. If a
module name is specified when the module is declared, the name appears there. If no
module name is specified, this field will contain a value of NULL.
The static local-seq points to an expression sequence listing the local variables that were
explicitly declared as :static. Each entry is a NAME. Within the module, static locals are
referred to by LOCAL structures, the local variable number being the index into the
static local-seq minus the number of nonstatic locals and exports. A static local shares
its value among all instances of a class.
The static export-seq points to an expression sequence listing the exported module
members declared as static. Each entry is a NAME. Within the module, exports are re-
ferred to by LOCAL structures, the local variable number being the number of elements
in the local-seq, static local-seq, and export-seq, plus the index into the static export-seq.
The static name-seq stores the instances of the static locals and exports. It appears in the
MODDEF structure as these static variables are shared among all modules with the same
definition.
Length: 4
NAME: Identifier
NAME ^assigned-expr ^attrib-expr characters characters ...
Length: 4 or more
The assigned-expr field points to the assigned value of the name. If the name has no as-
signed value, this field is a null pointer (not a pointer to NULL). The next field points to
an expression sequence of attributes of the name. If there are no attributes, this field points
to the empty expression sequence (NULL). The remaining fields contain the characters
that form the name, stored 4 or 8 for each machine word (for 32-bit and 64-bit architectures
respectively). The last character is followed by a zero-byte. Any unused bytes in the last
machine word are also zero. The maximum length of a name is 268,435,447 characters
on 32-bit architectures and 34,359,738,351 characters on 64-bit architectures.
Length: 1
Length: 2
OR: Logical OR
OR ^expr1 ^expr2
Length: 3
Length: 2
650 Appendix A Internal Representation
This structure represents the Maple symbol _npassed (formerly nargs), the number of
arguments passed when the procedure was called.
PARAM -1
This structure represents the Maple symbol _passed (formerly args), the entire sequence
of arguments passed when the procedure was called.
PARAM -2
This structure represents the Maple symbol procname, referring to the currently active
procedure.
PARAM -3
This structure represents the Maple symbol _nresults, the number of results expected to
be returned from the procedure.
PARAM -4
This structure represents the Maple symbol _params, the sequence of declared positional
arguments passed when the procedure was called.
PARAM -5
This structure represents the Maple symbol _nparams, the number of declared positional
arguments passed when the procedure was called.
PARAM -6
This structure represents the Maple symbol _rest, the sequence of undeclared arguments
passed when the procedure was called.
PARAM -7
This structure represents the Maple symbol _nrest, the number of undeclared arguments
passed when the procedure was called.
PARAM -8
This structure represents the Maple symbol _options, the sequence of options in the
procedure.
PARAM -9
This structure represents the Maple symbol _noptions, the number of options in the
procedure.
Appendix A Internal Representation 651
PARAM -10
This structure represents the Maple symbol thisproc, referring to the instance of the
currently active procedure.
At procedure execution time, the integer (if positive) is used as an index into the internal
data structure Actvparams, which is part of the Maple procedure activation stack, and
stores pointers to the values (which are also Maple structures) of the actual parameters
passed to the procedure.
Maple syntax:
newpoly := proc(a) proc()
option builtin=sdmpoly;
end proc("create",a) end proc;
newpoly(2*y^3+4*x*y+4);
This is an internal representation for multivariate polynomials of limited degree and integer
coefficients.
Each degree word stores the total degree of the monomial and each individual degree.
For example, 5*x^2*y^3 is a two-variable polynomial with total degree 5 and degree 2
on the x term, and degree 3 on the y term. The numbers 5, 2, and 3 are packed into a
single degree word. The packing depends on the number of variables in the polynomial.
Because the packing must fit in one word of memory, not all polynomials can be repres-
ented in this way. The most common polynomials can be stored in this data structure,
which can be operated on efficiently.
The indet_seq is the sequence of indeterminates that occur in the polynomial. The inde-
terminates must be simple NAMEs.
POWER: Power
POWER ^expr1 ^expr2
Length: 3
This structure is used to represent a power when the exponent is not an integer, rational,
or floating-point value. When the exponent is numeric, the POWER structure is converted
to a length 3 PROD structure.
Maple syntax:
proc ( paramSeq ) :: returnType;
description descSeq;
local localSeq;
export exportSeq;
global globalSeq;
option optionSeq;
statSeq
end proc
The param-seq points to an expression sequence describing the formal parameters of the
procedure. Each entry is either a NAME or a DCOLON (which, in turn, contains a
NAME and an expression specifying a type). Within the procedure, parameters are referred
to by PARAM structures, the parameter number being the index into the param-seq.
The local-seq points to an expression sequence listing the explicitly and implicitly declared
local variables. Each entry is a NAME. The explicitly declared variables appear first.
Within the procedure, locals are referred to by LOCAL structures, the local variable
number being the index into the local-seq.
The option-seq field points to an expression sequence of options to the procedure (for
procedures, options are the same as attributes). Each entry is a NAME or EQUATION
specifying an option. Commonly used options are cache, operator, and `Copyright ...`.
The rem-table field points to a hash table containing remembered values of the procedure.
Entries in the table are indexed by the procedure arguments, and contain the resulting
value. If there is no remember table, this field contains a pointer to NULL, which is the
empty expression sequence.
Appendix A Internal Representation 653
The desc-seq field points to an expression sequence of NAMEs or STRINGs. These are
meant to provide a brief description of what the procedure does, and are displayed even
when the interface(verboseproc) command is less than 2.
The global-seq field points to a list of the explicitly declared global variables in the pro-
cedure (those that appeared in the global statement). This information is never used at
run time, but it is used when simplifying nested procedures to determine the binding of
lexically scoped identifiers. For example, an identifier on the left-hand side of an assign-
ment in a nested procedure can be global if it appears in the global statement of a sur-
rounding procedure. This information is also used at procedure printing time, so that the
global statement will contain exactly the same global identifiers that were declared in
the first place.
The lexical-seq field points to an expression sequence of links to identifiers in the sur-
rounding 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 second pointer is a pointer to a LOCAL, PARAM, or LEXICAL
structure which is understood to be relative to the surrounding scope. When a procedure
is evaluated (not necessarily called), the lexical-seq is updated by replacing each of the
second pointers with a pointer to the actual object represented. The name pointers are
not modified, so that the actual identifier names are still available. The lexical-seq for a
procedure contains entries for any surrounding-scope identifiers used by that procedure
or by any procedures contained within it.
The eop field is BINARY. The first entry specifies the number of positional parameters
of the procedure. The remaining entries, if any, specify the evaluation order permutation
for the procedure (that is, an evaluation order for the arguments that is consistent with
any dependencies among the parameter specifications).
The return-type field is present only if a return type has been specified for the procedure.
A return type is an assertion about the type of the value returned by the procedure; if
kernelopts(assertlevel) is set to 2, then this type is checked as the procedure returns.
Length: 2n + 1
654 Appendix A Internal Representation
This structure is interpreted as pairs of factors and their numeric exponents. Rational or
integer expressions to an integer power are expanded. If a rational constant is in the
product, this constant is moved to the first entry by the simplifier. A simple power, such
as , is represented as a PROD structure. More complex powers involving non-numeric
exponents are represented as POWER structures.
RANGE: Range
RANGE ^expr1 ^expr2
Length: 3
RATIONAL: Rational
RATIONAL ^integer ^pos-integer
Length: 3
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.
Length: 2
The Maple read statement. The expression must evaluate to either a string or symbol
(STRING or NAME structure), and specifies the name of the file to read.
Length: 2
The Maple return statement. The expression sequence is evaluated, giving the value(s)
to return.
Appendix A Internal Representation 655
The data field points to either a block of memory (for dense and NAG-sparse RTABLEs),
or to a HASHTAB structure (for Maple-sparse RTABLEs). The data block is either an
object of type BINARY, or memory allocated directly from the storage manager of the
operating system when the block is 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 contains hardware objects, the maple-
type field points to the Maple NAME anything.
The index-func field points to either an empty expression sequence (NULL), or an expres-
sion sequence containing at least one indexing function and a pointer to a copy of the
RTABLE structure. The copy of the RTABLE is identical to the original structure, except
that its index-func 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 field points to an expression sequence of zero or more arbitrary attributes,
which can be set by the setattribute command and queried by using the attributes
command.
eval - 1 bit - indicates if full evaluation should occur on lookup. For more information,
refer to the rtable_eval help page.
literal - 1 bit - optimization for internal type checking of data contained in an RTABLE.
number of dimensions - 6 bits - the number of dimensions of the RTABLE, from 0 to
63.
The num-elems field indicates the total number of elements of storage allocated for the
data. For a Maple-sparse RTABLE, num-elems is not used. For a NAG-sparse RTABLE,
and for other formats that grown in size since initial allocation, num-elems specifies the
number of elements currently allocated, some of which might not be in use.
The fields specify the upper and lower bounds of each dimension; they are stored
directly as signed machine integers. The limits on bounds are -2,147,483,648 to
2,147,483,647 for 32-bit architectures and -9,223,372,036,854,775,808 to
9,223,372,036,854,775,807 for 64-bit architectures. The total number of elements cannot
exceed the upper limit numbers either. Space is always reserved for at least 4 dimensions
in case the rtable is redimensioned.
The remaining fields refer to storage specific properties such as the number of bands
above and below the diagonal and the number of elements that are sorted in NAG-sparse
storage.
Length: 2
The Maple save statement. The expression sequence gives a list of names of objects to
save, and either a file name or Maple library archive name (.mla) in which to save them.
The file or library archive name can be specified as a NAME or STRING.
The expr entry stores the indeterminates of the polynomial (symbol for univariate cases
or expression sequence of symbols for multivariate cases).
The term_ordering is either null or a pointer to a Maple procedure that is used to compare
the exponent_vector to sort the polynomial terms. When term_ordering is null, lexico-
graphic order is used to sort the polynomial terms.
The coeff_domain is either null or a pointer to a Maple module that is used to perform
coefficient arithmetic (addition and multiplication). When coeff_domain is null, ordinary
arithmetic is used. Each of the following m terms consists of a coefficient coeff_i (i=1..m)
followed by an exponent_vector [exp_j] (j=1..n). Coefficient coeff_i is a non-zero Maple
expression. Exponent_vector [exp_j] is an array of n hardware integers. Each integer
stores the exponent of the corresponding indeterminate. By default, the polynomial terms
are sorted by lexicographic order (that is, sorted by descending powers of indeterminate).
SERIES: Series
SERIES ^expr1 ^expr2 integer ^expr3 integer ... ...
Length: 2n + 2
This is the internal representation of a series in Maple. There is no input syntax for a
series; one can only be generated from a computation. The first expression has the general
form x-a, where x denotes the variable of the series used to perform that expansion, and
a denotes the point of expansion. The remaining entries are interpreted as pairs of coeffi-
cients and exponents. The exponents are integers, not pointers to integers or immediate
integers. The exponents appear in increasing order. A coefficient O(1) (a function call
to the function O, with parameter 1) is interpreted specially by Maple as an order term.
SET: Set
SET ^expr-seq ^attrib-expr
The entries in the expression sequence of the set are sorted in a deterministic order. For
details, see the set help page.
Length: 3 or more
658 Appendix A Internal Representation
This structure represents a sequence of two or more statements, and can be used wherever
a single statement (for example, ASSIGN, IF, FOR) can appear. A statement sequence,
containing only a single statement, is replaced by that statement. A statement sequence
containing no statements is replaced by the empty expression sequence (NULL). Nested
STATSEQ structures are flattened. All of the above transformations are made by the
simplifier.
Length: 1
Length: 4 or more
Length: 2n + 1
This structure is interpreted as pairs of expressions and their numeric factors. Rational
or integer expressions with an integer factor are expanded and the factor replaced with
1. If there is a rational constant in the sum, this constant is moved to the first entry by the
simplifier. Simple products, such as a*2, are represented as SUM structures. More complex
products involving non-numeric factors are represented as PROD structures.
Appendix A Internal Representation 659
TABLE: Table
TABLE ^index-func ^array-bounds ^hash-tab
Length: 4
This is a general table type, as created by the table and array commands in Maple. The
index-func points 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.
This data structure represents a table reference, or indexed name. The name entry follows
the same rules as for ASSIGN, or it may be a TABLE or MODULE structure. (The
parser will not generate a TABLEREF with a TABLE structure for the name entry, but
this can occur internally.) The expression sequence contains the indices.
Maple syntax:
try tryStat
catch "catchStr": catchStat
...
finally finalStat;
end try
Length: 3 or more
This structure represents a try statement, and can have an arbitrary length, depending on
how many catch blocks are contained within it, and whether it has a finally block. The
catch-strs point to the catch string of the corresponding catch block. If no catch string
is specified, the catch-str points to NULL. Empty catch-stat-seqs are also represented
by pointers to NULL, as is an empty (but present) finally block.
660 Appendix A Internal Representation
The actual internal tag used for the TRY structure is MTRY to prevent collision with a
macro defined by some C exception handling libraries.
Length: 2
Maple Syntax:
use bindings in
statseq
end use
Length: 3
The use statement introduces a new binding contour and binds the names that appear on
the left-hand side of the equations in bindings. For convenience, on input, a module 'm'
can appear among the bindings, and is treated as if it were the sequence e1 = m:-e1, e2
= m:-e2, ..., where the ei are the exports of 'm'. Within the sequence statseq of statements,
the symbols appearing on the left-hand side of the equations in bindings are bound to the
corresponding right-hand sides. The previous bindings of those symbols are restored
upon exit from the use statement. Bindings are resolved during automatic simplification.
Length: 3
This is the internal representation of univariate and bivariate polynomials modulo some
integer. The modp1() and modp2() front ends provide a suite of functions to work on
this data structure operating in the domain of polynomials in one or two variables with
integer coefficients modulo n, written or Z__n[x,y], respectively. indet_seq is an
expression sequence of the indeterminates of the polynomial: (x), or (x,y). mod is the
integer modulus of the integer domain. In a univariate polynomial, the coefficients are
stored in the following order.
All coefficients are stored, including zero coefficients. The leading coefficient is always
non-zero.
Hash Table
HASHTAB ^hash-chain1 ^hash-chain2 ...
Length:
This is an internal data structure with no Maple syntax equivalent. It is used in the repres-
entation of tables within Maple. Each entry points to a hash chain (a HASH structure),
or is a null pointer if no entry has been created in that hash chain yet (that is, with that
entry location as its hash value). The size of a HASHTAB structure depends on the type
of table and the platform, but is always a power of 2 plus one.
Hash Chain
HASH key ^expr1 key ^expr2 ... ...
Length: 2n + 1
Each table element is stored as a pair of consecutive entries in a hash bucket vector. The
first entry of this pair is the hash key, and the second is a pointer to a stored value. In
some cases (for example, procedure remember tables and user-defined tables), the key
is also a pointer. In other cases, the key is a hashed value (for example, the simplification
table, the symbol table). The key cannot have the value zero (or the null pointer) since
this is used to indicate the bottom of the bucket.
Instead of using a flat, fixed-length directory, Maple dynamic hash tables use a tree
structure with contiguous bits from the hash key to select a child. A child of a directory
can be a subdirectory or a hash chain. For example, a top-level directory may use the first
10 bits to index 1024 children. One of its children may be a directory that uses, for ex-
ample, the next 8 bits of the key to index 256 children.
Appendix A Internal Representation 663
A hash chain in a dynamic table stores elements using key value pairs (in the same way
that a hash chain does in a basic hash table). The first n bits of the keys in a hash chain
are identical, where n is the number of bits required to locate the hash chain. The remaining
bits are arbitrary. Using the example in the previous paragraph, the elements of a hash
chain 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 cre-
ating 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.
Permanent entries, as designated by the way they are inserted, are stored exclusively in
the variable-sized chain, which can grow as needed.
Temporary entries are inserted in the normal way you would include a value in a basic
hash table or remember table. These are hashed to identify the bucket in which they are
to be stored. The existing entries in that bucket are pushed right by one, and the new entry
is put in the leading, ''most-recent'' spot. Reinserting an expression will cause it to be
promoted to the ''most-recent'' spot. Inserting a fifth element that hashes to the same
bucket will cause the least recently inserted temporary element to be removed from the
table.
The maximum size of the cache table can be specified at creation time. Because cache
tables have a maximum size, and because as new elements are added old ones may be
removed, the cache table does not grow continuously as values are added. When used as
a remember table, they are useful for temporarily storing elements that were recently
computed, and likely to be needed again. Over time, as more elements are inserted, the
old elements will be discarded.
Cache tables can be created by using the Cache command, or as a remember table in a
procedure with the cache option specified. The advantage of using a cache table over
standard remember tables is that a cache table has a maximum size. This means that a
cache table does not act as a memory trap, storing a large number of values that cannot
be reclaimed by the garbage collector. As cache tables allow permanent elements to be
added, they can be used in procedures that cannot use option system remember tables.
664 Appendix A Internal Representation
In Maple 13, the use of the basic and dynamic hash tables as the data structure behind
the simplification table was phased out in favor of a new structure that worked better in
a multithreaded environment. In particular, the new table guarantees atomic inserts. This
removed the need for locking, and, because the simplification table is used so often,
greatly improved performance when running many threads.
If the signature is found, then a full comparison is performed (taking into account that
additions and multiplications are commutative) to verify that it is the same expression.
If the expression is found, the one in the table is used and the searched one is discarded.
A full comparison of expressions has to be performed only when there is a collision of
signatures.
such as union or intersection, can be done in linear time by merging sorted sequences.
Sorting by machine address is also available by using the sort command.
Remember Tables
A remember table is a hash table in which the argument(s) to a procedure call are stored
as the table index, and the result of the procedure call is stored as the table value. Because
a simplified expression in Maple has a unique instance in memory, the address of the
arguments can be used as the hash function. Therefore, searching a remember table is
very fast.
Several kernel functions use remember tables including evalf, series, divide, normal,
expand, diff, readlib, and frontend. The functions evalf, series, and divide are handled
internally in a special way for the following reasons:
evalf and series need to store some additional environment information ('Digits' for evalf
and 'Order' for series). Consequently, 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, the table value is retrieved and rounded. If a result is produced with more pre-
cision than what is stored, it is stored in the table, replacing the lower precision value.
evalf remembers only function calls (this includes named constants); it does not remember
the results of arithmetic operations.
If a division operation succeeds and the divisor is a nontrivial polynomial, the divide
function stores the quotient in its remember table. Otherwise, no value is stored in the
remember table.
If option remember is specified together with option system, at garbage collection time,
the remember table entries which refer to expressions no longer in use elsewhere in the
system are removed. This provides a relatively efficient use of remembering that does
not waste storage for expressions that have disappeared from the expression space. As
garbage collection time can be unpredictable, cache remember tables provide an alternate
666 Appendix A Internal Representation
approach similar to option system, by remembering only the most recently computed
results.
Note: Unlike the array command, the Array command creates a rectangular table, which
is described in the following subsection. An array is a table for which the component in-
dices must be integers within specified bounds. Tables and arrays are implemented using
the Maple internal hash tables. Because of this, sparse arrays are equally as efficient as
dense arrays. A table object consists of the following.
Index bounds (for arrays only)
A hash table of components
An indexing function
The components of a table T are accessed using a subscript syntax (for example,
T[a,b*cos(x)]). Since a simplified expression is guaranteed to have a unique instance in
memory, the address of the simplified index is used as the hash key for a component. If
no component exists for a given index, then the indexed expression is returned.
The semantics of indexing into a table are described by its indexing function. Aside from
the default, general indexing, some indexing functions are provided by the Maple kernel.
Other indexing functions are loaded from the library or are supplied by the user.
Portability
The Maple kernel and the command-line interface are not associated with any one oper-
ating 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
Appendix A Internal Representation 667
size. Refer to the Install.html file on your product installation disc for a list of currently
supported operating system versions.
Most of the source code comprising the kernel is the same across all platforms. Extensive
use of macros and conditional compilation take care of platform dependencies, such as
word size, byte ordering, storage alignment requirements, differences in hardware floating
point support, and sometimes, C compiler bugs.
The Maple library is interpreted by the Maple kernel. Therefore, other than issues such
as maximum object size, it is completely independent of the underlying architecture.
The Standard worksheet graphical user interface is implemented in Java, which is platform-
independent. This includes custom GUI features such as embedded components and
Maplets.
668 Appendix A Internal Representation
automatic resizing
Index Arrays, 159
automatic simplification, 56
Symbols B
!, 33
backslash, 27, 30, 33
#, 33
braces
& operator, 116
forming sets, 32
.. operator, 116
breakpoints, 586
1-D output, 365
explicit, 597
2-D math, 383
removing, 599
2-D output, 365
removing, 597
:, 346
built-in commands, 8
:-, 74, 330
:: operator, 121
:= operator, 3 C
?, 33 case-sensitivity in Maple, 22
?[], 69 code generation
@ operator, 114 defining new translators, 539
@@ operator, 115 intermediate code, 538
[], 32 printing phase, 539
{}, 32 translation process, 534
~, 19 CodeTools:-Profiling:-LoadProfiles, 626
CodeTools:-Profiling:-PrintProfiles, 623
A CodeTools:-Profiling:-Profile, 623
CodeTools:-Profiling:-SaveProfiles, 626
altering plot structures, 451
CodeTools:-Profiling:-SortBy, 623
animations
colon, 2, 31, 181
3-D with viewpoint options, 476
comma
plots:-animate command, 474
forming expression sequence, 33
anyfunc type, 132
command-line interface, 519
argument
comments, 7
definition, 209
Complex constructor, 65
Arrays
complex numbers, 288
applying a function to contents, 163
evalc command, 68
automatic resizing, 159
Re and Im commands, 67
copying, 161
concatenation
creating, 155
names, 25
getting bounds, 161
strings, 25
getting number of elements, 160
connectivity
numeric, 164
CAD applications, 543
testing for equality, 162
Excel, 544
arrow notation, 265
TCP/IP sockets, 532
assignment operator, 3, 48
669
670 Index
constants debugging
special, 49 breakpoints, 586, 596
symbolic, 49, 290 explicit breakpoints, 597
constructors, 356 numbering statements, 585
copying Arrays, 161 removing watchpoints, 592
copying tables, 153 viewing the debugging process status, 589
creating watchpoints, 591, 600
Arrays, 155 definition
efficient programs, 625 argument, 209
displaying time and memory statistics, function call, 209
621 delaying evaluation, 31, 52
profiling a procedure, 623 detecting errors, 593
lists, 136 checking syntax, 619
queues, 171 handling exceptions, 617
records, 166 raising exceptions, 617
sets, 142 tracing a procedure, 611
tables, 148 using assertions, 614
creating plot structures, 450 dismantle command, 39
customizing plots DLL, 517, 531
axes and gridlines, 465 DocumentTools:-Do command, 494
colors, 456 DocumentTools:-GetProperty command, 493
controlling sampling, 453 DocumentTools:-SetProperty command, 494
coordinate systems, 467 dot character, 92
setting options, 471 double colon operator, 121
typesetting, 464 double quotes
view option, 462 displaying a text string, 2, 27
D E
DAG, 34 embedded components
data structures adding to document, 489
Arrays, 155 DocumentTools:-Do command, 494
converting, 165 DocumentTools:-GetProperty command,
filtering elements, 165 493
immutable, 145 DocumentTools:-SetProperty command,
lists, 136 494
mutable, 163 editing component properties, 490
queues, 171 programming, 493
records, 166 retrieving and updating component prop-
sets, 142 erties, 493
tables, 147 equality
types, 446 records, 167
data types, 39 error statement handling, 197
internal representation, 637 escape characters, 33
Index 671
R length, 24
mutability, 26
rational numbers, 283
parsing, 27
Re command, 67
searching, 25
read statement, 206
StringTools package, 382
records, 349
StringTools:-StringBuffer command, 26
creating, 166
structured types, 35, 130
equality, 167
sub-Array access, 158
remember tables, 665
subexpressions
restart command, 12
substituting, 127
return statement, 197
subs command, 128
right single quotes
difference between eval and subs, 128
delaying evaluation, 31
subsindets command, 130
rtables, 155
subsop command, 127
substituting subexpressions, 127
S substrings
save statement, 205 extracting, 24
scanf command, 380
selection operation, 137, 148, 331
semicolon, 2, 31, 181
T
table indexing, 148
separating statements, 31
tables
sequence, 33
applying a function to contents, 154
setattribute command, 125
checking index, 151
sets, 32, 142
copying, 153
accessing data stored in, 144
creating, 148
arithmetic, 143
evaluation rules, 151
setting time limit on computations, 627
extracting data, 152
SFloat
getting number of elements, 150
constructor, 62
removing an element from, 149
simplifying expressions, 126
testing code
sort command, 141
test coverage, 632
specfunc type, 132
verifying results, 630
special characters, 17
writing good tests, 632
sprintf command, 382
timelimit command, 627
square brackets, 32
tracing a procedure, 611
and braces, 32
try statement, 198
sscanf command, 382
type checking, 35
stacks
type command, 51
creating, 169
typeset math, 383
popping, 169
typesetting package, 384
pushing, 169
strings, 2, 27
concatenation, 25
Index 677
U
undefined, 287
unevaluation quotes, 31, 52
union operator, 111
UnProfile command, 625
use statement, 355
V
Vector
creating, 33
verify command, 630
viewing help pages, 2
W
web services, 506
white space characters, 29
worksheet files, 379
worksheet input and output
interactive input, 370
interactive output, 368
interfaces, 367
678 Index