Maple 7 Programming Guide (large PDF file, 642 pages)
Maple Programming Guide (Even larger PDF file, 702 pages)
Open a terminal window. Open a browser and point it to these notes.
Create the subdirectory maplepgm
in your home directory, and cd to it.
% cd
% mkdir maplepgm
% cd maplepgm
% pwd
/home/phys210f/maplepgm
Copy all of the files in the directory ~phys210/maplepgm
.
Recall (or note!) that
the *
(star/asterisk) "globs", or "expands" to the names of all of the
files in ~phys210/maplepgm
, and that .
means the working directory, as usual
% cp ~phys210/maplepgm/* .
Get a listing of the files:
% ls
err1 errif index.html procs tprocs
NOTE
The contents of these and other source files that we will cover in these notes are browseable HERE.
You can also use cat or more from the terminal window, e.g.
% cat procs
% more procs
Remember that for more - Space bar moves forward a page - b moves back a page - q quits
Look at the contents of procs
using your browser or from the
command line with more
Ensure that the working directory ~/maplepgm
, and start xmaple in the
background
% pwd
/home/phys210f/maplepgm
% xmaple &
read
commandThe Maple read
command is completely analogous to the
gnuplot load
command: i.e. it redirects Maple's input from the
interactive prompt to the specified file.
Read in the procedure definitions in procs
.
> read procs;
Because I have ended each procedure definition with a semi-colon, Maple will
echo the definitions in procs
as they are read (and thus defined):
myadd := proc(x, y) x + y end proc
.
.
.
myif3 := proc(x::numeric)
if 0 < x then 1 elif x < 0 then -1 else 0 end if
end proc
The Maple output below is produced by command-line Maple: the output from xmaple will appear different ("prettier") but has the same content.
In the following, only type in what follows the Maple prompt, >
If something happens to your xmaple
session, or you accidentally
exit it, restart using
% xmaple &
from a terminal with working directory ~/maplepgm
, then reexecute
% read procs;
op
commandUse the op
command to see the definition of a procedure
> op(myadd);
proc(x, y) x + y end proc
Give myadd a whirl and note the output in each case
> myadd(2, 3);
5
> myadd(w, z);
w + z
> myadd(sin(x)^2, cos(x)^2);
2 2
sin(x) + cos(x)
Try
> myadd("a", "b");
What happens?
Does this make any sense? I.e. is there a situation where
we would want to compute/represent the sum of two strings? (For those
of you in the know, ignore that fact that some languages use +
to
concatenate strings!)
Maple has a notion of the type
of expressions (best illustrated
through example, as below).
Maple has a simple and powerful facility to state what type each argument is expected to be. When a procedure is invoked, the arguments will be checked from first to last (as they appear in the argument list), and if any is of incorrect type, an error message is output and the procedure immediately returns
Only the first invalid argument encountered generates an error: if more that one arg is of the wrong type you may/will have to correct them one after another.
Look at the definition of myadd1
> op(myadd1);
proc(x::algebraic, y::algebraic) x + y end proc
IMPORTANT!
To use Maple's automatic type-checking of arguments, follow each and every argument with a double colon (no space between the colons), and then the expected type (which can differ from argument to argument).
We will generally want to have type checking for all arguments. In
this case we are requiring that both of myadd1
's arguments are of
type algebraic
:
myadd1
in action:
> myadd1(x, 2*y);
x + 2 y
> myadd1("a", "b");
Error, invalid input: myadd1 expects its 1st argument, x,
to be of type algebraic, but received a
> myadd1(diff(sin(x),x), diff(x^4, x));
3
cos(x) + 4 x
Note that in the second invocation Maple doesn't tell us
what type a
is, only that it isn't algebraic
.
is because there are many, many different types in the language
and virtually every expression (including a string like "a"
) is a
member of more than one type. However,
it can always decide whether or not a given expression is or
is not an instance of a specific type.
Here's an example where we supply a list, which is not of type
algebraic, as the first argument to myadd1
> myadd1([a, b], 2);
Error, invalid input: myadd1 expects its 1st argument, x,
to be of type algebraic, but received [a, b]
Here's a version of myadd
which requires numeric
arguments
> op(myadd2);
proc(x::numeric, y::numeric) x + y end proc
> myadd2(2, 3);
5
> myadd2(1.5, 3/2);
3.000000000
> myadd2(x, y);
Error, invalid input: myadd2 expects its 1st argument, x,
to be of type numeric, but received x
Again, note how Maple doesn't tell us that both of the supplied arguments are of invalid type: as soon as it detects one bad argument, it bails out.
Here's a version of myadd
which requires integer
arguments
> op(myadd3);
proc(x::integer, y::integer) x + y end proc
> myadd3(3, 5);
8
> myadd3(3.0, 5);
Error, invalid input: myadd3 expects its 1st argument, x,
to be of type integer, but received 3.0
IMPORTANT!!
KEY TYPES FOR US
algebraic
type/algebraic
),
loosely something that "makes sense" as an algebraic expression.
sense.numeric
integer
float
list
[ <sequence-of-maple-expressions> ]
QUESTION
Why wouldn't (isn't) a Maple sequence, e.g.
1, 2, 3
be a valid type? (Hint: How would/could a procedure check that an argument was of this type?)
(The most basic type of bug encountered in programming)
Syntax error -> (grammatically) improper construction of code.
You will be informed of the first error encountered, as well as some information about the location of the error, but it will not always be obvious exactly what the error is.
Finding and removing syntax errors is a key talent that must be honed for programming in any language: experience is the primary means to develop the skill (in addition to the acceptance that you have made an error!)
Correcting syntax errors: Easiest type of debugging and, except for novices, really isn't considered true debugging at all (since the programming-language-interpreter will keep telling you that something's wrong until you get it right!)
Finding and fixing
are more difficult tasks in general.
Look at contents of err1
(using cat
or browser)
######################################################
# PHYSICS 210
#
# Illustrates syntax error in definition of procedure
######################################################
err1 := proc(x,y)
x + y
end porc;
Read err1
into your xmaple session
> read err1;
on line 8, syntax error, missing operator or `;`:
end porc;
^
Error, while reading `err1`
Notice the reference to a line number, the error message, and the caret '^', which indicates where Maple thinks the error is (approximately).
WHAT IS THE ERROR?
We'll fix it later ...
Using your text editor, create the file myprocs1
that contains
a definition for the following procedure
mysub1
a, b
(no restrictions on the type of arguments)a
and b
Make sure that the file myprocs1
lives in ~/maplepgm
Read myprocs1
into your xmaple session via
> read myprocs1;
and test it with various input.
If you have one or more syntax errors, try to fix them by
myprocs1
myprocs1
myprocs1
into MapleNOTE
To expedite this process, manipulate, resize and configure windows for (at least) your editor session and xmaple so that you can see both on the screen simultaneously, without having to iconify or, worse yet, exit one to see what's going on in the other.
If you haven't yet adjusted the font size in your editor so that it is about as small as is comfortable for you to read, now's the time to do so. That way you'll be able to see more text (program) using less screen real estate; ditto your xmaple window (use, e.g., View -> Zoom), and terminal application (use Settings -> Edit Current Profile -> Appearance -> Text Size).
ASK FOR HELP AS NECESSARY!
Add another procedure definition to the file myprocs1
as follows (I'll
subsequently use the term Function instead of What the
procedure does.
mysub2
a, b
(a
must be numeric, b
must be integer)a
and b
Test/debug as before.
IMPORTANT
Each time you modify myprocs1
, be sure to save it, then
reload it into Maple using
> read myprocs1;
err1
Fix the error in err1
using your text editor, save the file
(overwrite previous err1
), reread and verify that err1
works
NOTE
When err1
is read, Maple reports that there is a syntax
error on line 8
: it is thus useful to see line numbers when you
are editing Maple source files
print
statementUse print
to display (print) one or more values. Can be useful, for
example, to output "intermediate" values in procedures, often to
trace/debug execution, since only the last result evaluated in a
procedure is returned.
print
can take an arbitrary number of arguments; string arguments especially
useful for producing output fit for human consumption:
> a := 2;
a := 2
> print("The value of a is", a);
"The value of a is", 2
> print("Here are three numbers", 1, 17, 42);
"Here are three numbers", 1, 17, 42
IMPORTANT!!
When programming procedures, do not confuse:
Even though these may seem to be related/equivalent in terms of what you see on the screen, they are distinct concepts/operations. In particular, in contrast to a returned value, a printed value can not be assigned to another variable or used in another Maple statement.
To emphasize this point, consider the following sequence of statements:
> 2;
2
> print(2);
2
From what (literally) appears on the screen there is absolutely no way to tell that what Maple is actually outputting is completely different in the two instances. To see the distinction let's assign the value returned by each statement to different variables. (Recall that I told you that every Maple statement does return a value---we'll actually encounter a single exception, but let's ignore that for now.)
> res1 := 2;
res1 := 2
> res2 := print(2);
2
res2 :=
Observe the following about the second assignment:
Maple produces two lines of output:
2
, which is produced by the print
command.The echoing of the assignment of "nothing" (literally) to
res2
res2 :=
That is, print(2)
causes 2
to appear on the terminal
("prints" 2), but returns nothing.
Verify this by asking Maple to display the values of res1
and res2
(here
I'm being careful to copy-and-paste precisely what Maple does, i.e. not
adding any empty lines to improve readability):
> res1;
2:
> res2;
>
This may be confusing, and if it is to you, feel free to ask for clarification.
if
statementHere and below recall that constructs of the form something
mean that something
(including the "meta-parentheses" <>) is to be
replaced with a specific instance of a "something"
General Form
<Bexpr> = Boolean expression
<ss> = statement sequence
if <Bexpr1> then
<ss1>
elif <Bexpr2> then
<ss2>
elif <Bexpr3> then
<ss3>
.
.
.
else
<ssn>
end if;
Semantics (i.e. what happens)
If <Bexpr1> is true, execute statement sequence <ss1>,
then continue execution with the first statement after end if
Otherwise, if <Bexpr2> is true, execute <ss2>,
then continue execution with the first statement after end if
Otherwise, if <Bexpr3> is true, execute <ss3>,
then continue execution with the first statement after end if
.
.
.
If all elif tests are false, execute <ssn>,
then continue execution with the first statement after end if
Note
In this most general form of the if
statement:
Exactly one of the statement sequences <ss1>, <ss2> .., <ssn> is executed.
if
statementAmong all the clauses/statement-sequences in the general form, only
the first is required. Thus there is a particularly simple form
of if
statement which one encounters frequently in programming:
if <Bexpr> then
<ss>
end if
i.e.
If <Bexpr> evaluates to true, execute <ss>,
then continue execution with the first statement after end if
Otherwise continue execution with the first statement after end if
As with any programming language, it is important to be able to construct
logical expressions whose typical used is in if
statements.
Maple has a special Boolean type, that uses the special values true
and
false
for logical truth and logical falsity (don't blame me for the
word: try ?Boolean
at the Maple prompt)
> true;
true
> false;
false
Non-trivial Boolean expressions---expressions that evaluate to
true
or false
can be constructed using
Here are the most commonly used relational operators, and their meanings:
= -> equal
<> -> not equal
< -> less than
> -> greater than
<= -> less than or equal
>= -> greater than or equal
We can also use the following logical operators in Boolean expressions:
and -> true if both operands are true, false otherwise
or -> true if either or both operands is/are true, false otherwise
not -> negates sense of expression
Refer to the programming guides or online help for operator precedence: safest simply to use parentheses to ensure evaluation order is what we want.
Examples
> not true;
> not not true;
true;
> true and false;
false;
> true and true;
true;
> true or false;
true;
> if 3 > 2 then print("foo") end if;
"foo"
> if 2 <> (1 + 1) then print("foo") else print("bar") end if;
"bar"
First, the following illustrates two other Maple features
A colon (:) can also be used to terminate a statement, and will cause the output from the statement to be suppressed.
> a := 3: b := 2: c := 5:
Note that there is no output because we terminated the assignment statements with colons.
Example using and
... boolean expression is true
if both
a <= b
and b <= c
are true:
> if (a <= b) and (b <= c) then b else a + c end if;
8
Example using or
... boolean expression is true if either/both
of a < b
and a <> c
is/are true:
> if (a < b) or (a <> c) then b else c end if;
2
Example using not
... not
"reverses" value of Boolean expression:
> not true;
false
> not false;
true
> if not (a > b) then a else b end if;
2
We can evaluate general Bexpr
to determine its truth value using the
evalb
procedure. This is analogous to evalf
for floating point
evaluation:
> evalb(3 > 2);
true
> evalb(2 > 3);
false
> a; b;
3
2
> evalb(a <> b);
true
> evalb(a <= b);
false
> xx; yy;
xx
yy
> evalb(xx = yy);
false
So, Maple considers that two distinct names are not equal to one another with respect to Boolean evaluation.
How about this:
> evalb(xx <= yy);
xx <= yy
Why?
What do you think this will evaluate to?
> evalb(100! > exp(100));
Try it!
Or this?
> evalb(100! > exp(100.));
Go ahead, make Maple's day ...
Try to explain Maple's behaviour in the last 3 examples to your neighbor(s).
BEGIN ASIDE
Maple uses "McCarthy" evaluation rules:
This can have important consequences:
> mylogical := proc(x) print("I've been invoked! Ouch!!"); end proc:
> mylogical(y);
"I've been invoked! Ouch!!"
> a := 10:
> if (a > 2) or mylogical(b) then a else b end if;
10
The procedure call mylogical(b)
does not get executed in this case!
END ASIDE
NOTE
Logical operators can also be used in algebraic expressions, where they often "placeholders" for constructing equations, inequalities, etc. Here are some examples:
> eqn := x = 3 * y + 16;
eqn := x = y
> solve(eqn, y);
- 16/3 + x/3
> ineq := 2 * z > 12 * q - 24;
ineq := 12 q - 24 < 2 z
> solve(ineq, q);
{q < z/6 + 2}
if
statementsif-then-else
with no type checking> op(myif);
proc(a, b) if b < a then a else b end if end proc
> myif(3, 2);
3
> myif(3, 10.0);
10.0
Try an invocation that doesn't seem to make sense ...
> myif(3, x);
Error, (in myif) cannot determine if this expression is true or false: x < 3
... and Maple returns an appropriate error message
if-then-else
with type checking> op(myif1);
proc(a::numeric, b::numeric) if b < a then a else b end if end proc
Note how Maple has transformed a > b
in the code in the file procs
to b < a
. The two forms are algebraically equivalent of course.
> myif1(10, 3);
10
> myif1(x, 2);
Error, invalid input: myif1 expects its 1st argument, a, to be of type numeric,
but received x
if-then-else
with more complicated test> op(myif2);
proc(a::numeric, b::numeric, c::numeric)
if 5 < a + b and 10 < a + c then true else false end if
end proc
> myif2(1,5,12);
true
> myif2(1,5,6);
false
if-then-elif-else
> op(myif3);
myif3 :=
proc(x::numeric) if 0 < x then 1 elif x < 0 then -1 else 0 end if end proc
> myif3(2);
1
> myif3(-3);
-1
> myif3(0);
0
Using your text editor, create a file named ~/maplepgm/myprocs2
that contains definitions of procedures that do the following
(recall that Maple is case-sensitive)
Do as many as you can!
Test your procedures interactively---particularly for
myifC
try to make your testing exhaustive (i.e. all
possible permutations of x1, x2, x3 with respect to
"largest" property, including cases where two or all three numbers
have the maximum value)
myaddA
myaddB
myaddC
myifA
myifB
myifC
if
statementsExamine the contents of the file errif
.
Read the file into Maple:
> read errif;
In line 9, syntax error, missing operator or `;`:
a;
^
Error, while reading `errif`
Find and correct the syntax error(s) in the three procedures
defined in errif
Can be found in
~phys210/maplesolns/myprocs2soln
% cd ~phys210/maplesolns
% ls
max3 myifCbad myprocs2soln tmax3 tmyifCbad
or by browsing HERE.
Look at
myprocs2soln
.
Also look at
myifCbad
.
We will return to this example (procedure that returns largest of maximum of three values) later in lab
Whenever you create a file that contains definitions of Maple procedures, it is an excellent idea (i.e. recommended practice)! to create another file which tests the procedures, both with valid and invalid input.
In a terminal window, change to the maplepgm
directory we
created/used in the last lab, and get a directory listing
% cd
% cd maplepgm
Examine the file tprocs
(or use more
or cat
in the terminal window)
Start xmaple in the background from the same terminal, and read in tprocs
% xmaple &
> read tprocs;
You should see output like the following:
"Testing of myadd begins"
5
w + z
2 2
sin(x) + cos(x)
"a" + "b"
"Testing of myadd ends"
"Testing of myadd with more info begins"
"The value of myadd(2, 3) is", 5
"The value of myadd(w, z) is", w + z
2 2
"The value of myadd(sin(x)^2, cos(x)^2) is", sin(x) + cos(x)
"The value of myadd("a"+"b") is", "a" + "b"
"Testing of myadd with more info ends"
Preparing a testing file for each file of procedures will ultimately save time when testing procedures, versus doing the testing interactively.
Exit the xmaple session (don't save the worksheet), ensure that you are still in your ~/maplepgm directory
% cd
% cd maplepgm
% pwd
/home2/phys210f/maplepgm
then execute the following CP command (use the upper case version since there are files that we want to overwrite):
% CP ~phys210/maplepgm/* .
Get a listing of the directory, and you should see the following
% ls
err1 glob imp loc locbad myprocs2 procs2 tprocs2
errif glob1 index.html loc1 myprocs1 procs tprocs
You can look at any of the files using cat
or more
in a terminal, or,
again, point your browser
and then click on the appropriate links.
Start xmaple from the command line (in ~/maplepgm
)
% xmaple &
We have seen the use of variables in Maple, which (as in most programming languages), are used as locations in which to store values that will generally change as a procedure/program executes.
Example
myfor := proc(n::integer)
discussed in class
myfor1 := proc(n::integer)
for i from 1 to n do
print(i);
end do;
end proc;
Here i
is a variable.
There are two types of variables in Maple that will be of interest to us:
For our purposes, every Maple variable is given by a Maple name.
A global variable is a variable such that when the name is used outside all procedures (e.g. at the Maple prompt), or inside any procedure (with an exception that we will see shortly), the same value will be returned. (++)
Again, this concept is best illustrated by example, so let's
first look at the file glob
.
Now read glob
into your Maple session, and note the output
> read glob;
globalvar := 100
"The value of globalvar outside, and before the procedure call is", 100
"The value of globarvar inside the procedure is", 100
"The value of globalvar outside, and after the procedure call is", 100
Note how, as the output indicates, the value of globalvar
inside
and outside of the procedure is the same
Now, modify the value of globalvar
, and invoke myglobal()
again:
Be sure to type the () (empty argument list), or myglobal
will
NOT be executed.
> globalvar := 200;
> myglobal();
"The value of globarvar inside the procedure is", 200
... so the change of the value of globalvar
outside of the procedure
affects the value inside.
local
statementIn contrast to a global variable, a local variable is one which is defined within a procedure and whose value is local to the procedure.
If different procedures define a variable with the same name to be local, then the value of the variable in one procedure will not affect the value of that name in another procedure, or the value outside all procedures (i.e. at the Maple command line).
IMPORTANT!!
Within a procedure, we explicitly define variables to be local using the
local
statement.
Example
Examine the file
loc
note the use of the local
statements
Read loc
, and note the output
> read loc;
localvar := 10
"The value of localvar outside, and before the procedure calls is", 10
"The value of localvar inside 'mylocal1' is", 3.141592654
"The value of localvar inside 'mylocal2' is", 2.718281828
"The value of localvar outside, and after the procedure calls is", 10
... so we see that the values of localvar
at the Maple command prompt,
and within the two procedures do not "conflict" with one another
Verify this: first set localvar
at the interactive prompt to
a different value:
> localvar := 60;
Then execute mylocal1()
and mylocal2()
again
> mylocal1(); mylocal2();
"The value of localvar inside 'mylocal1' is", 3.141592654
"The value of localvar inside 'mylocal2' is", 2.718281828
Note again that when a vbl is declared local to a procedure, it "overrides" any value of a vbl with the same name defined at the command line (i.e. this is the exception to the rule (++) above)
If a Maple name has been assigned a value before a procedure definition is made, and that procedure uses the name as a variable, then the variable is implicitly global.
If a Maple name has not been assigned a value before a procedure
definition is made, and that procedure uses the name as a variable,
without an explicit local
declaration, then the variable is
implicitly declared to be local, and a warning message is issued.
Example
Examine the contents of the file
loc1
and then read it in
> read loc1;
Warning, `mylocal3var` is implicitly declared local to procedure `mylocal3`
.
.
.
> mylocal3();
"The value of mylocal3var inside 'mylocal3' is", 3.141592654
You should always declare local variables to be such ... I will do so in all of the examples that follow (at least I will try to!), and you should as well in the lab exercises and especially in the homework.
So ... if you see one of more warnings such as the above, be sure to modify your procedure definition to explicitly declare all local variables.
Note
If a procedure uses more than one local vbl, they can be defined either with a single statement, or multiple statements, e.g.
local var1, var2, var3;
and
local var1;
local var2;
local var3;
are equivalent.
VERY IMPORTANT!
local
statements must be placed immediately after theproc
statement (the procedure header).They cannot come after any assignment statements,
if
statements,for
statements etc.
The file
locbad
contains a (defective) procedure definition that
suffers from this misordering problem.
% cat locbad
locbad := proc(x)
print(x);
local lx;
lx := x;
x^2;
end proc;
> read locbad;
Error, unexpected local declaration in procedure body
Technically, local statements are part of the procedure header and all statements in the header must precede any statement in the body.
This is also a good place to point out that if Maple encounters an error when reading the definition of a procedure then:
In the current case there was no previous definition for locbad
, so
if we ask to see its definition using op
, we find
> op(locbad);
locbad
Input the file
procs2
that contains procedure definitions that we
will be considering for the remainder of the lab
> read procs2;
for
statementWe saw this form in the lecture:
<nexpr1> -> numeric expression (best to make it integer)
<nexpr2> -> numeric expression (best to make it integer)
<name> -> Loop variable (changes at every iteration of loop)
<ss> -> Statement sequence (body of loop)
for <name> from <nexpr1> to <nexpr2> do
<ss>
end do;
The loop body (sequence of statements between for
and end do
is
executed repeatedly, with the loop variable changing at each iteration
as follows:
First iteration: <name> := <nexpr1>
Second iteration: <name> := <nexpr1> + 1
Third iteration: <name> := <nexpr1> + 2
.
.
.
Last iteration: <name> := <nexpr2>
Again, it is best to keep <nexpr1>
and <nexpr2>
integer-valued.
Note: Assuming that <nexpr1>
and <nexpr2>
are integer-valued,
then the total number of times the loop executes is
<nexpr2> - <nexpr1> + 1
for <name> from <nexpr> by <nexpr> to <nexpr> do
<ss>
end do;
`by` specifies loop increment (step), which can be positive
or negative
Begin Aside
The loop increment can also be 0, and Maple won't complain, but this will result in the notorious "infinite loop" whereby the iteration doesn't stop until the execution is (externally) interrupted in some manner (using the Interrupt the current operation icon, for example).
End Aside
for
statementsNote
These 3 examples use the print
command so that the operation
of the loop is clearer, but remember that you can not use print
to return a value from a procedure. Also observe the declaration
of the loop variable i
to be local
in all 3 cases
Note
As we work through the examples, it is best to also look at the
full definitions in the file
procs2
so that the structures
of the procedures are clearer, and so that you can read the
comments
for
> op(myfor1);
proc(n::integer) local i; for i to n do print(i) end do end proc
> myfor1(5);
1
2
3
4
5
Important
I've already mentioned this, but I can't stress it enough:
In these examples, we use
print
statements to explicitly illustrate the execution of the loops.
none of the values that are displayed by these statements are returned
by the procedures so, again, when coding your own procedures that use
loops do not attempt to use print
to return a value.
In fact, every print
statement returns a special value, NULL
,
(all upper case, and a Maple reserved word), which essentially means
"nothing". Try the following:
> NULL;
... and after you press Enter, you should see that Maple replies with literally nothing
> res := print("hello");
"hello"
res :=
Explicitly demonstrate that print
returned NULL
using the
evalb
(evaluate in Boolean domain) command
> evalb(res = NULL);
true
End aside
Continuing with our for
examples ...
> op(myfor2);
proc(n::integer)
local i;
for i from n by -1 to 1 do print(i) end do
end proc
> myfor2(5);
5
4
3
2
1
Try to predict what the following will do before you execute it
> myfor2(-10);
So "if there is nothing for the loop to do" then the loop does not execute at all (i.e. 0 times)
> op(myfor3);
proc(n1::integer, n2::integer)
local i;
for i from n1 by 3 to n2 do print(i) end do
end proc
> myfor3(10, 20);
10
13
16
19
Note that the value of the loop index in a for
loop never exceeds
the upper bound if the loop increment/step is positive, or the
lower bound if the step is negative.
Here's a more interesting example of a procedure that uses a
for
loop:
Here's the definition from the source file:
########################################################################
# Computes n! (Doesn't check that n >= 0, left as exercise)
########################################################################
myfactorial := proc(n::integer)
# local variables
#
# i -> loop variable
# val -> used to "build up" value to be returned
local i, val;
# Initialize return value
val := 1;
# Can start loop from 2 (why?)
for i from 2 to n do
# Note the structure of the following statement, which is
# common is programming: the right hand side of the assignment
# is evaluated first, the result of that evaluation is
# assigned to `val`: i.e. the statement CHANGES the value
# of `val`
val := val * i;
end do;
# Return the computed value
val;
end proc;
Display the definition in the Maple session ... observe how compact Maple's representation of it is (but then again, most of the characters in my initial definition are in comment lines!)
> op(myfactorial);
proc(n::integer)
local i, val;
val := 1; for i from 2 to n do val := val*i end do; val
end proc
Try a couple of sample invocations:
> myfactorial(10);
3628800
> myfactorial(100);
933262154439441526816992388562667004907159682643816214685929638952175999932\
29915608941463976156518286253697920827223758251185210916864000000000000\
000000000000
Test to see whether the implementation is correct, at least for some "typical" invocation, by comparing with the result returned from Maple's built in factorial function:
> myfactorial(100) - 100!;
0
error
statementSyntax
error <string>;
or
error(<string>);
i.e. error
can be used with or without parentheses.
Semantics
A call to
error
causes the invoking procedure to display an error message containing<string>
, then exit immediately, and the procedure does not return a value (not evenNULL
).
This command is useful for stopping execution of a procedure when an unexpected condition arises, so that it makes no sense for the procedure to continue.
Our typical application will be for checking validity of procedure arguments beyond that which can be done with the built in type-checking facility.
error
statementThis procedure returns an error message and exits if its second argument is not strictly positive
Definition of error_demo in
procs2
(with comments removed):
error_demo := proc(y::numeric, z::numeric)
if y <= 0 then
error "First argument must be positive";
end if;
y + z;
end proc;
Note that the error checking involves the briefest form of
the if
statement, discussed in the previous lab, where there is no
else
clause.
Display the definition of error_demo
in the Maple session, and try
some sample invocations:
> op(error_demo);
proc(y::numeric, z::numeric)
if y <= 0 then error "First argument must be positive" end if; y + z
end proc
> error_demo(3, 7);
10
> error_demo(-10, 6);
Error, (in error_demo) First argument must be positive
Observe
When an error
statement/command is encountered
in a procedure, the string argument supplied to error
is prepended
with 'Error, (in proc-name) ', the resulting string is output,
and the procedure automatically/immediately exits/return.
Again, when an error
statement is executed, the procedure does not
return a value, as can be seen from the following sequence:
> vbl1;
vbl1
> vbl1 := error_demo(1, 2);
vbl1 := 3
> vbl1;
3
> vbl2;
vbl2
> vbl2 := error_demo(-1, 2);
Error, (in error_demo) First argument must be positive
> vbl2;
vbl2
Note that the value of vbl2
(i.e. its name) was unaffected by the
apparent assignment statement vbl2 := error_demo(-1, 2);
Important
You will need to use the
error
statement when implementing some of the procedures in the Maple homework.
As with many other programming languages, one of the most basic and important data structures in Maple is the array.
An array is a structured collection of elements which is organized in a "rectangular" or "Cartesian" fashion: this notion will become clearer in the next lab when we consider two-dimensional arrays.
A one-dimensional (1D) array can be viewed simply as an ordered set of Maple expressions. There are essentially no restrictions on what can be stored in any given element of an array, but for simplicity, we will focus on the case where each element is a numeric value. This is precisely the same type of array that we will encounter in our study of Matlab and, if it helps, think of a 1D array as a row or column of values in a spreadsheet.
Array
command.Maple arrays are created using the Array
command, which for 1D arrays has one required argument that defines the
range of the array's indexes. Array indexes in Maple can
be arbitrary integers but again for simplicity, as well as
to parallel what we will encounter in Matlab, we will always
use ranges that start from 1.
Recall that a range in Maple is specified with the syntax
<integer1> .. <integer2>
and is effectively shorthand for the sequence of consecutive
integers from <integer1>
to <integer2>
inclusive.
Thus ...
1 .. 5
... for example, means
1, 2, 3, 4, 5
The general syntax for defining (creating) a one-dimensional array of length
<n>
, with indexes that range from 1
through <n>
inclusive,
and assigning it to a variable <arrayvar>
is
<arrayvar> := Array(1 .. <n>);
Begin important note
Note that the array-creation command that we will use is Array
(upper-case A), not array
(lower case a). The latter is also
a valid Maple command that creates arrays, but of a somewhat different
sort, and its use is a "deprecated" feature meaning that it will
only be maintained so that legacy code doesn't "break".
Any new
code, such as that which you will write, should use Array
.
End important note
Here's a specific array-creation example:
> a1 := Array(1 .. 10);
a1 := [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Observe the following about the Maple output from the Array
command:
A 1D array with 10 elements was created and assigned to the variable
(name) a1
.
Maple initialized all elements to the value 0
. This is the default
mode of operation for all array creation.
The collection of elements that Maple echoes, i.e. the contents of the array are enclosed in (square) brackets and
This is the same form that is used to display lists, and we need to take care not to confuse the two types of data structures. We will return to this point shortly.
Because defining a very large array could result in a very large
amount of output, by default Maple will refuse to display in-line the actual
elements for 1D arrays with more than 10 elements (25 elements
for command line Maple, and we can
change this behaviour with the interface
command, but for the
time being will accept it as a fact of life)
Thus if we define an array with a length > 10, Maple produces a different type of output:
> a2 := Array(1 .. 100);
[ 1..100 1-D Array ]
a2 := [ Data Type: anything ]
[ Storage: rectangular ]
[ Order: Fortran_order ]
If you are executing this within an xmaple
session you can
double-click on the text within the brackets to open up a data browser
that will allow you to inspect (and modify) the contents of the
array.
Individual elements of a 1D array are accessed with an indexing syntax that uses the square brackets.
For example, to refer to the 4-th element of a1
, we use
a1[4]
If we type ...
> a1[4];
0
... Maple will echo the value of the 4-th element, which like all
the other elements is 0
, due to the default initialization process.
We can set the values of specific array elements using assignment. For example
> a1[1] := 1;
a1[1] := 1
Note that within xmaple, Maple "pretty prints" the output using a subscript notation for the array index. Let's make assignments to a couple more elements:
> a1[3] := 10.2;
a1[3] := 10.2
> a1[10] := evalf(Pi);
a1[10] := 3.141592654
We can see the net effect of these three assignments by evaluating the array name itself.
> a1;
[1, 0, 10.2, 0, 0, 0, 0, 0, 0, 3.141592654]
Note that what appears on the right hand side of an assignment to an array element can be an arbitrary Maple expression. For example we could set ...
> a1[5] := a1[5] + (a1[1] + a1[10]) / 10.0;
a1[5] := 0.4141592654
... so that our array now has contents ...
> a1;
[1, 0, 10.2, 0, 0.4141592654, 0, 0, 0, 0, 3.141592654]
Similarly, what appears within the [ ]
on the left hand side
is also arbitrary, so long as it evaluates to an integer which
is within the range of the arrays indexes. For example ...
> ii := 3;
ii := 3
> a1[ii + 1] := evalf(sqrt(2));
a1[4] := 1.414213562
> a1;
[1, 8, 27, 1.414213562, 125, 216, 343, 512, 729, 1000]
1D arrays and simple for
loops are natural compatriots. Let's
use a for
loop to set the values of a
to the cubes of the
integers from 1 to 10:
> for i from 1 to 10 do a1[i] := i^3: end do:
Note how I have typed the for
loop on one line, which is not
good programming style, but is OK for demo purposes.
Begin aside
Also note
that I've ended the for
statement with a colon to suppress any output
from the assignment statements.
In particular, if we use a semi-colon after the end do
then the
output from all the assignments in the body of the for
loop are
echoed even if the assignment statement per se still is terminated
with a colon (try it!). This behaviour carries over to the other Maple control
structures, such as the if
statement.
End aside
Examine the contents of a1
:
> a1;
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
If a second argument is supplied to Array
when a 1D array
is created, it should be a list of values with which
to initialize the elements of the array.
For example:
> a_few_primes := Array(1 .. 7, [2, 3, 5, 7, 11, 13, 17]);
a_few_primes := [2, 3, 5, 7, 11, 13, 17]
Select a few elements of this array using indexing:
> a_few_primes[3];
5;
> a_few_primes[7];
17;
If we want to initialize all array elements to some specific non-zero
value, we can use the fill
option of the Array
command. Here's
an example.
> six_49 := Array(1..6, fill=49);
six_49 := [49, 49, 49, 49, 49, 49]
If you use index a 1D array with an expression that is ...
... Maple will complain with an error message.
Examples:
> a2 := Array(1..5);
> a2[0];
Error, Array index out of range
> a2[6];
Error, Array index out of range
> a2[0.5];
Error, bad index into Array
> a2[foo];
Error, bad index into Array
Begin aside
There is a twist here, however. For 1D arrays with index ranges
from 1
to n
, Maple maps negative indexes from -1
to
-n
as follows:
a[-1] -> a[n]
a[-2] -> a[n-1]
a[-3] -> a[n-2]
.
.
.
a[-n] -> a[1]
End aside
Consider the following two Maple statements:
The first defines a 5-element 1D array:
array1 := Array(1..5, [2, 3, 5, 7, 11]);
array1 := [2, 3, 5, 7, 11]
The second defines a 5-element list:
list1 := [2, 3, 5, 7, 11];
list1 := [2, 3, 5, 7, 11]
In terms of what Maple outputs when we type in these
two statements, the only way to distinguish the array array1
from the list list1
is that the list elements are separated
by commas. However, when using command-line Maple, so are array
elements. Also, since elements from both structures
are referenced using [ ]
, the indexing mechanism won't help us
differentiate between the two either:
> array1[3];
5
> list1[3];
5
However, we can always use the type
command to sort out any confusion:
> type(array1, Array);
true
> type(array1, list);
false
> type(list1, list);
true
> type(list1, Array);
false
Again, note that we must be careful to use the type name
Array
rather than array
:
> type(array1, array);
false
Wrapping up ...
In the next lab we will look at a few basic procedures which create and manipulate arrays (both 1D and 2D), and you will do some programming with them in the Maple homework assignment.
Recall that the purpose of myifC
(from last lab's exercise set) is
to determine the largest (maximum) of its three arguments
Let's consider a version of this procedure, now called
max3
,
which
Here is the definition of max3
:
############################################################
# Implementation of myifC, renamed max3, that illustrates
# use of code "factoring" via definition of "helper"
# procedure, max2, which returns maximum of its TWO
# arguments.
#
# Coded this way, the correctness of the implementation
# is more manifest and the "gotcha" vis a vis the use
# of >= rather than > does not arise.
############################################################
############################################################
# max2: returns maximum of two numeric values
############################################################
max2 := proc(x1::numeric, x2:: numeric)
if x1 > x2 then
x1;
else
x2;
end if;
end proc;
############################################################
# max2: using max2, returns maximum of three numeric values
############################################################
max3 := proc(x1::numeric, x2::numeric, x3::numeric)
max2(max2(x1, x2), x3);
end proc;
Examine the testing code for max3
that is defined in the
file
tmax3
(it uses the printf
command---which is
a slightly advanced topic, see ?printf, or use the help menu for
more information).
############################################################
# "Exhaustive" testing of max3, using all permutations
# of x1, x2, x3 chosen from 1, 2, 3
#
# Checks routine using Maple's max command which will
# accept an arbitrary number of arguments and return
# the maximum value among them.
###########################################################
read max3;
# Initialize a variable that will be used to count
# the number of incorrect calculations.
n_incorrect := 0:
# Note the use of a triply nested for loop.
for x1 from 1 to 3 do
for x2 from 1 to 3 do
for x3 from 1 to 3 do
max3_result := max3(x1, x2, x3);
max_result := max(x1, x2, x3);
if max3_result <> max_result then
n_incorrect := n_incorrect + 1;
end if;
printf("max3(%a,%a,%a)=%a max(%a,%a,%a)=%a\n",
x1, x2, x3, max3_result,
x1, x2, x3, max_result);
end do;
end do;
end do;
printf("\nNumber of incorrect calculations: %a\n", n_incorrect);
From within the directory ~phys210/maplesolns
, start a Maple session
(command-line Maple) and read tmax3
% cd ~phys210/maplesolns
% maple
> read tmax3;
What does the output tell you?
Now read
tmyifCbad
,
and examine the output carefully, comparing to
that from tmax3
... What's the difference?
> read tmyifCbad;
Now, look at the definition of
myifCbad
,
and, referring to the
output from tmyifCbad
, isolate the conceptual (logical) error in
myifCbad
. Recall that you can display the definition of myifCbad
from within your Maple session using
> op(myifCbad);
for
statementsmysum
In the file ~/maplepgm/myprocs3
create the the procedure mysum
as follows:
mysum := proc( ... you fill in the rest ... )
Takes a single argument, n which must be an integer (use type-checking).
Uses a for
loop to compute and return the sum of the integers from
1 to n.
The procedure should check that n
is larger than or equal to 1; if
it isn't, it should use the error
statement to output an appropriate
message and exit (note that calling error
will cause the procedure
to exit, i.e. there's nothing else that you need to do to have the
procedure immediately quit but call error
).
Create a testing file for mysum called tmysum
as follows:
read myprocs3;
mysum
with various arguments, valid
as well as invalid: if you wish, use print
or printf
statements to document
what is being supplied as the input.read tmysum
to read the testing commands you define in the
file into Maple, and thus do the testing per se.myprocs3
and or tmysum
until you are
satisfied that your implementation is correct. You can use myfactorial
as a guide, but you should implement (i.e. type
without copy and paste) the whole procedure yourself.
Finally,
please code according to the instructions given above, even if you know of
or find a more direct way to code the procedure than using a
for
loop! The point of this exercise is to provide practice using
for
loops!
tmyifC
To complete this exercise you must have finished your implementation
of myifC
from the previous lab. If you haven't yet done this, do
so now, and try to do it without looking at the solution that I
have provided.
Execute the following at the bash command-line:
% cd
% cd maplepgm
% cp ~phys210/maplesolns/tmyifCbad tmyifC
Now, edit the file
tmyifC
and modify it so that:
procs2
.myifC
, rather than myifCbad
, within the
triply-nested for
loop.printf
statements reflect the fact that you are testing
myifC
and not myifCbad
.As usual when creating any file containing source code, be sure to
save tmyifC
every time you have made a set of modifications.
Start xmaple from ~/maplepgm
, read tmyifC
, and verify that
your implementation appears to be correct. If the testing fails (and
assuming that you haven't messed up the testing procedure), try
to debug your implementation, asking for help as necessary.
IMPORTANT NOTES
I have updated the reference list at the top of these notes.
In particular, I have added a link to the Maple 7 Programming Guide (2001): the first two chapters of this reference provide a better introduction to Maple programming, relative to the more recent guide, especially for novices.
We will not have sufficient time in the labs to go through all of this material in detail, and some sections may have to be omitted altogether. Nonetheless, you should not consider what we skip to be irrelevant, especially for completion of the current homework.
You are thus strongly advised to go through what we don't cover together by yourself, ideally while running a Maple session so that you try the examples and variants for themselves.
mysum
exercise from previous labCan be found in
~phys210/maplesolns/myprocs3soln
~phys210/maplesolns/tmysumsoln
Also available HERE (myprocs3soln
)
and HERE (tmysumsoln
).
Try my testing script & solution using command-line Maple:
% cd ~phys210/maplesolns
% maple
> read tmysumsoln;
tmysumsoln: Checking mysum with valid input ...
mysum(1)=1 sum(1)=1 difference=0
mysum(2)=3 sum(2)=3 difference=0
mysum(3)=6 sum(3)=6 difference=0
mysum(10)=55 sum(10)=55 difference=0
tmysumsoln: Checking mysum with invalid input ...
tmysumsoln: Note that since the error command is activated in all of
these instances, the value of 'val_mysum', displayed as
'mysum(...)=<val_mysum>', remains unchanged.
Error, (in mysum) input argument 'n' must be >= 1
mysum(0)=55
Error, (in mysum) input argument 'n' must be >= 1
mysum(-1)=55
Error, invalid input: mysum expects its 1st argument, n, to be
of type integer, but received x
mysum(x)=55
Error, invalid input: mysum expects its 1st argument, n, to be
of type integer, but received 1.0
mysum(1.0)=55
Exit the command-line Maple session.
> quit;
or
> Ctrl-d
(not Ctrl-z
or Ctrl-c
)
Change your working directory to ~/maplepgm
and, as we did last day, recopy
the files from ~phys210/maplepgm, not worrying about overwriting existing
files:
% cd
% cd maplepgm
% pwd
/home2/phys210f/maplepgm
% CP ~phys210/maplepgm/* .
Get a listing, and you should see all of the following files (and more if you have done the exercises):
% ls
err1 glob imp loc locbad procs tladd tprocs
errif glob1 index.html loc1 printf_demo procs2 tprintf_demo tprocs2
Then start xmaple
in the background from the command line:
% cd ~/maplepgm
% xmaple &
Point your browser (preferably a separate tab) HERE so that you can see the contents of the various source files that we will be using.
Read the file procs2
into your xmaple
session
> read procs2;
trace
statement: tracing execution of a procedureMaple provides a simple yet powerful "tracing" facility that allows one to see the input value of arguments to procedures, the values returned by any of the statements executed in the procedure, and the return value from the procedure.
This is a very useful feature that allows us to see what is happening "inside" a procedure and thus for debugging the procedure, once syntax errors have been removed.
IMPORTANT
In order to get full tracing information, ensure that
every statement uses the ;
as a terminator, if :
is used (recall
that :
suppresses output), then the tracing info for that
statement will NOT appear.
Thus, recommended coding practice is to use semi-colons to terminate each statement while you are developing and debugging code. You can then convert any/all of them to colons to suppress any unwanted output as necessary. This need not be done manually: your text editor has a facility to do global search and replace. Ask for help if you can't figure out how to do it.
Syntax
trace(<procedure name>);
untrace(<procedure name>);
trace
turns on tracing of <procedure name>
untrace
turns off tracing of <procedure name>
Example:
> trace(myfactorial);
myfactorial
Note how trace
echoes the name of the procedure ... if you mistype
the procedure name, or otherwise pass it an argument that is NOT the
name of the procedure, it will not echo anything. For example:
> trace(myfctorial);
>
Now let's see how the tracing works ...
> myfactorial(4);
{--> enter myfactorial, args = 4
val := 1
val := 2
val := 6
val := 24
24
<-- exit myfactorial (now at top level) = 24}
24
o Now turn off tracing, and execute the same call to myfactorial
:
> untrace(myfactorial);
myfactorial
> myfactorial(4);
24
IMPORTANT
You can enable tracing for any procedure that you write, but the facility is only useful once all syntax errors have been eradicated.
IMPORTANT
Here is the recommended practice for use of trace
when
implementing procedures in a source file.
For the sake of concreteness assume that we are coding a procedure
myproc
in a source file also called myproc
. Then include the
trace
statement in the file and FOLLOWING the definition of the
procedure:
myproc := proc( ... )
.
.
.
end proc;
trace(myproc);
This ordering is crucial since every time a procedure is redefined---which,
assuming that we haven't introduced any syntax errors into the
procedure code, happens every time we re-read the source file---Maple
will effectively execute untrace
for that procedure (i.e. turn tracing off).
Thus, placing
the trace
command after the end of the procedure definition ensures
that tracing remains enabled.
Also note that you can then disable tracing by "commenting out"
the trace
command; i.e. insert # before the command:
#trace(myproc);
and re-enable as necessary by deleting the #.
Here I should point out that if you look at a lot of code that various programmers write---experts and non-experts alike---you will frequently see code that has been "commented out" (disabled).
In general, this is not a good habit to develop. It tends to make the code difficult to read and understand, and eventually you will forget to comment out a line that you meant to, or comment out one that you didn't want to and will waste a lot of time trying to figure out what went wrong.
However, in instances like this, where we are simply enabling/disabling a debugging feature with a single character, it's difficult to argue that it's bad practice.
Indeed, any self-respecting programmer will be able to deduce what's going on if she/he encounters a line like
#trace(myproc)
in a source file.
Maple has a unique special value that denotes "nothing", and which we have
already encountered in the context of the print
statement, which
returns it. The protected name NULL
is set to this value, and
can be used in various contexts where one needs an "empty" expression.
One such instance that will be useful for us occurs when we wish to initialize a variable that will be used to "build up a sequence".
For example, suppose we want to construct the sequence
1, 8, tan(y), Pi
step by step, by adding one element at each step, and maintain
it in the variable, s
. If s
already has a value that is
part of the sequence, e.g ...
> s;
1, 8
... then it is easy to add the next element with the statement
> s := s , tan(y);
However, if we try to start this process with the statement
> s := s , 1;
when s
does not yet have a value, then Maple will complain:
Error, recursive assignment
The solution is to initialize s
with NULL, which we can thus
view as the null sequence or empty sequence.
> s := NULL;
s :=
> s := s , 1
s := 1
> s := s , 8
s := 1 , 8
Similarly, there is a unique special list, denoted []
which has no elements, and which is known as the empty list.
If you enter this expression at the command line, Maple will
echo it:
> [];
[]
We can verify that it has no elements using the
nops
command:
> nops( [] );
0
Since NULL
is the unique empty sequence and []
is the
unique empty list, []
and [NULL]
must be the same
entity. We can verify this assertion with evalb
:
> evalb([] = [NULL]);
true
IMPORTANT
First, exit your current xmaple
session.
Now, let's implement the following procedure ...
Header:
ladd := proc(l1::list(algebraic), l2::list(algebraic))
Function:
Given two equal-length lists of algebraic quantities,
returns a list whose elements are the sums of the corresponding
elements of the input lists
Sample usage:
> ladd( [10, 20, 30, 40], [-1, -2, -3, -4] );
[9, 18, 27, 36]
> ladd( [1, 2, 3, 4], [a, b, c, d] );
[1 + a, 2 + b, 3 + c, 4 + d]
In a terminal window, cd
to ~/maplepgm
and restart xmaple
from the
bash command line:
% cd
% cd maplepgm
% pwd
/home2/phys210f/maplepgm
% xmaple &
Using your text editor, and also working in the directory ~/maplepgm
,
we will create two text files:
tladd
: The testing script (our scaffolding)ladd
: The source file defining the procedure (our gleaming condo
tower)tladd
and ladd
We will create tladd
and ladd
together. Open
two editing sessions, both with working directory
~/maplepgm
create and save the two files
tladd
ladd
and let's start typing. Once we get going, the only thing that we will be executing at the Maple command line is
> read tladd;
REPEATING
Make sure you have two editor windows open, one editing the file
~/maplepgm/tladd
and the other editing
~/maplepgm/tladd
As usual, ask for help immediately should you be experiencing difficulties getting things set up properly.
NOT A TRICK QUESTION
Which file should we start with:
ladd
ortladd
?
INTERLUDE WHILE WE WORK THE PROBLEM TOGETHER
IMPORTANT
Exit xmaple, then restart it, ensuring that the working
directory is still ~/maplepgm
.
% cd ~/maplepgm
% pwd
/home2/phys210f/maplepgm
% xmaple &
Input the procedure definitions from procs2
> read procs2;
lpair
and ljoin
Let's look at the definitions of another 2 procedures that manipulate lists, the first of which illustrates the technique of building up a list by building up the corresponding sequence, and the converting the sequence to a list simply by enclosing it in square brackets.
Procedure:
lpair:
Header:
lpair := proc(l1::list, l2::list)
Function:
Returns list whose elements are 2-element lists containing
corresponding elements of list arguments `l1` and `l2`.
Sample Invocations
> lpair( [1, 2], [a, b] );
[[1, a], [2, b>>
> lpair( [u, v, w, [x, y>>, [[a, b], c, d, e] );
[[u, [a, b>>, [v, c], [w, d], [[x, y], e>>
> lpair( [1, 2, 3], [a, b] );
Invalid, since input lists are not of the same length
Here's my definition ...
########################################################################
# Returns list whose elements are 2-element lists containing
# corresponding elements of list arguments.
#
# Again uses the trick that sequences are easier to build up than
# lists.
########################################################################
lpair := proc(l1::list, l2::list)
local s, i;
if nops(l1) <> nops(l2) then
error "Input lists are not of equal length";
end if;
# Initialize the sequence
s := NULL;
# Build up a sequence of two element lists
for i from 1 to nops(l1) do
s := s , [l1[i],l2[i>>;
end do;
# Convert the sequence to a list, and return the list.
[s];
end proc;
Test procedure, with both valid and invalid input
> lpair( [1, 2], [a, b] );
[[1, a], [2, b>>
> lpair( [ [1, 2], 3, 4], [a, [b, c], d] );
[[[1, 2], a], [3, [b, c>>, [4, d>>
> lpair( [], [] );
[]
> lpair( [1, 2, 3], [a, b] );
Error, (in lpair) Input lists are not of equal length
> lpair(1, [a, b, c] );
Error, invalid input: lpair expects its 1st argument, l1, to be of type list,
but received 1
Try a call with tracing enabled:
> trace(lpair);
lpair
> lpair( [1, 2, 3], [a, b, c] );
{--> enter lpair, args = [1, 2, 3], [a, b, c]
s :=
s := [1, a]
s := [1, a], [2, b]
s := [1, a], [2, b], [3, c]
[[1, a], [2, b], [3, c>>
<-- exit lpair (now at top level) = [[1, a], [2, b], [3, c>>}
[[1, a], [2, b], [3, c>>
Turn off the tracing, and execute the same call.
> untrace(lpair);
> lpair( [1, 2, 3], [a, b, c] );
[[1, a], [2, b], [3, c>>
Let's move on to the last example of a list-manipulating procedure:
Procedure:
ljoin:
Header:
ljoin := proc(l1::list, l2::list)
Function:
Returns list whose elements are those of `l1` followed
by those of `l2`.
Sample Invocations
> ljoin( [1, 2, 3, 4], [a, b, c, d] );
> ljoin( [1, 2, 3, 4], [a, b, [d, e>> );
> ljoin( [], [w, x, y] );
> ljoin( [w, x, y], [] );
> ljoin( [], [] );
Here's my implementation ...
########################################################################
# Returns list whose elements are those of 'l1' followed by those
# of 'l2'.
#
# Can be written in a very compact form using the fact that
# l[] converts the list 'l' into a sequence with the same elements
########################################################################
ljoin := proc(l1::list, l2::list)
[ l1[], l2[] ];
end proc;
Try a couple of calls ...
> ljoin( [1, 2, 3, 4], [a, b, c, d] );
[1, 2, 3, 4, a, b, c, d]
> ljoin( [], [w, x, y] );
[w, x, y]
> ljoin( [foo], bar );
Error, invalid input: ljoin expects its 2nd argument, l2,
to be of type list, but received bar
In the previous lab I introduced you to one-dimensional (1D) Maple arrays. Here we will take a similarly quick look at two-dimensional (2D) arrays, and then examine a couple of example procedures that create and manipulate arrays: one for each dimensionality. You will need to code similar procedures for the current homework.
As mentioned previously, when thinking about arrays of scalar values---which is what we are restricting attention to here---in Maple, or Matlab, or many other programming languages, it can be useful to relate to things that you may be more familiar with:
1D array -> A row or column of cells in a spreadsheet.
1D array -> A vector of values, as often encountered in
mathematics or physics.
2D array -> A rectangular collection of cells in a spreadsheet.
2D array -> A matrix of values, as often encountered in
mathematics or physics.
The key distinction between 1D and 2D arrays, and arbitrary-dimensional arrays for that matter, is the number of indexes that are required to specify (select) a particular element:
Dimensionality Number of Indices Maple Syntax
1 1 d1[i];
2 2 d2[i, j];
3 3 d3[i, j, k];
.
.
.
BEGIN ASIDE
Note that in Maple we can in fact use one of two distinct syntaxes to index a 2D array:
d2[i, j]
or
d2[i][j]
I will use the former here and in my sample code, and I recommend that you do the same not least since that's the style that must be used in Matlab.
END ASIDE
As in the 1D case, when we refer to an element of a 2D array:
d2[<index1>, <index2>]
we need to ensure that:
<index1>
and <index2>
are both of type integer
<index1>
and <index2>
are within the ranges of their
respective dimensions.Array
command.The general syntax for defining (creating) a 2D array of size
<m>
by <n>
(think <m>
by <n>
matrix),
with indexes that range
1
through <m>
inclusive in the first dimension.1
through <n>
inclusive in the second dimension.and assigning it to a variable <arrayvar>
is
<arrayvar> := Array(1..<m>, 1..<n>);
Note that we use the same command, Array
, to create both 1D and
2D arrays (and, in general, 3D, 4D, ... ones), but for the 2D
case we need to provide a minimum of two arguments: a range for
each dimension.
Here's a specific example:
> m1 := Array(1..3, 1..5);
[0 0 0 0 0]
[ ]
m1 := [0 0 0 0 0]
[ ]
[0 0 0 0 0]
This statement creates a 3 x 5 array (3 rows, 5 columns) with all values initialized to 0. Again, the initialization with 0 is the default mode for creation of an array in any dimension.
BEGIN ASIDE
Confusingly, one often encounters the terminology "the dimensions of an array", which in the language we are using actually means "the number of integers in the ranges associated with each dimension of the array".
The key hint that the confusing terminology is in play is the use of the plural form of "dimensions".
For example, adopting the confusing terminology, I would say that
"m1
has dimensions 3, 5" or "m1
has dimensions 3 x 5".
I will do my best to stick to the term size, rather than dimensions, which has the benefit of conforming to Matlab lingo. It is quite possible, however, that I will slip up from time to time!
END ASIDE
As in the 1D case, Maple will display the values of an array in-line if all of the array's index ranges span 10 integers or fewer (25 if we're using command-line Maple). Thus, we see all the elements for
> m10 := Array(1..10, 1..10);
(I won't display the here), but anything bigger gets displayed in the summary format we saw in the 1D discussion:
> m11 := Array(1..11, 1..11);
[ 1..11 x 1..11 2-D Array ]
m26 := [ Data Type: anything ]
[ Storage: rectangular ]
[ Order: Fortran_order ]
Again, if we are using xmaple we can then double-click on the summary (within the square brackets) to pop up an array-browsing window.
I'll consider this topic before moving to the more basic concept of accessing individual elements of a 2D array so that we can actually have some non-zero array elements to play with.
Just as we can initialize a 1D array by supplying a list of values as the argument following the single range specification, we can initialize a 2D array by providing a list of lists as the argument after the two range specifications.
For example:
> A := Array( 1..2, 1..4, [ [2, 3, 5, 7], [11, 13, 17, 19] ] );
[ 2 3 5 7]
A := [ ]
[11 13 17 19]
Notice that A
is a 2 x 4 array: i.e. it has 2 rows and 4
columns. The list of lists with values:
[ [2, 3, 5, 7], [11, 13, 17, 19] ]
does the initialization row by row and I have to be very careful to construct the list of lists so that:
BEGIN ASIDE
In particular, you might think I could get away with dropping the inner brackets. However, if I do this, something quite unexpected happens.
A_bad1 := Array( 1..2, 1..4, [ 2, 3, 5, 7, 11, 13, 17, 19] );
[2 0 0 0]
A_bad1 := [ ]
[3 0 0 0]
This doesn't work either ...
A_bad2 := Array( 1..2, 1..4, [ [2, 3], [5, 7], [11, 13], [17, 19] ] );
[2 3 0 0]
A_bad2 := [ ]
[5 7 0 0]
You will note that in both cases the first few values in the list, or
lists of lists, are inserted "column-wise" rather than "row-wise".
This is a reflection of Fortran storage order which we will also
encounter, at least tangentially, in our study of Matlab.
END ASIDE
Now that we have our 2D array, A
, nicely initialized we can use
indexing to reference particular elements. Again, we must use
two indices, the first specifies the row, the second the column.
Thus, we have, for example:
> A[1,1];
2
> A[2,4];
19
We can use an in-line nested for
loop structure to display all the
elements one by one. Here I'm going to "cheat" and take advantage
of the fact that for i to <n>
is Maple shorthand for i from to <n>
.
> for i to 2 do for j to 4 do print(A[i,j]); end do; end do;
2
3
5
7
11
13
17
19
This enumerates the elements row by row, so called row major order. We can
just as easily display the values in column major order by switching
the order of the for ... do
statements:
> for j to 4 do for i to 2 do print(A[i,j]); end do; end do;
2
11
3
13
5
17
7
19
The array indexes don't have to be constant: as long as the indexing expressions evaluate to integers within the appropriate ranges the element selection is valid:
> ii := 1: jj := 2:
> A[jj-ii, jj+ii];
5
We can assign values to individual array elements in the expected manner:
> A[2, 1] := 100;
A[2, 1] := 100
> A[1, 2] := -100;
A[1, 2] := -100
Again, the output in these notes was generated using command-line Maple. Within xmaple we see that the output is "pretty printed" using the "double subscript" form of the indexes labelling the element.
What does the entire array look like now?
> A;
[ 2 -100 5 7]
[ ]
[100 13 17 19]
And once more, what appears on the right hand side of the assignment of a single array element can be an arbitrary Maple expression:
> A[2,3] := A[1,2] + A[2,1] + A[2,2]*[2,4];
A[2, 3] := 247
Here we supply the fill
option to the Array
command. For example:
> ones4 := Array(1..4, 1..4, fill=1);
[1 1 1 1]
[ ]
[1 1 1 1]
ones4 := [ ]
[1 1 1 1]
[ ]
[1 1 1 1]
Again, the rules for indexing are the same as they were for the 1D case, and thus the error messages that we will see when we violate those rules are also the same.
> ones4[0, 1];
Error, Array index out of range
> ones4[5, 1];
Error, Array index out of range
> ones4[1, 5];
Error, Array index out of range
> ones4[0.5, 4];
Error, bad index into Array
> ones4[x, y];
Error, bad index into Array
Note, in particular, that Maple doesn't tell us which index/indexes is/are flawed, just that at least one of them is.
Consider the following (again, the output here is from command-line Maple, we see a different form in xmaple):
> array2d := Array(1..2, 1..3, [[11, 12, 13], [21, 22, 23>>);
[11 12 13]
array2d := [ ]
[21 22 23]
> list2d := [[11, 12, 13], [21, 22, 23>>;
list2d := [[11, 12, 13], [21, 22, 23>>
Although these two objects may seem to be the same kind of beast,
they are not. Once more, we can use type
to definitively determine
whether the expression in question is an array or a list.
> type(array2d, Array);
true
> type(array2d, list);
false
> type(list2d, list);
true
> type(list2d, listlist);
true
> type(list2d, Array);
false
BEGIN ASIDE
If we want to do "serious" work in Maple with a structured, rectangular collection of elements---especially if the index ranges are large (i.e. so that there is a large number of elements)---then an array is probably the best choice.
A main reason for this is that lists are immutable objects in Maple. That is once created, the elements of a list can not be changed. When I execute a statement sequence such as ...
> l1 := [1, 2, 3, 4];
l1 := [1, 2, 3, 4]
> l1;
[1, 2, 3, 4]
> l1[2] := 0;
l1[2] := 0
> l1;
[1, 0, 3, 4]
... the assignment l1[2] := 0
forces Maple to
[1, 2, 3, 4]
that
was constructed in-place and assigned to the name l1
.0
.[1, 0, 3, 4]
.l1
, thus disassociating l1
from the first list.As you might suspect, the fact that a new list must be created every time we want to modify the value of a specific element leads to serious inefficiencies for large numbers of elements.
On the other hand, when we create an Array
in Maple and
assign it to a name:
> a1 := Array(1..5, [1949, 1951, 1955, 1959, 1961]);
a1 := [1949, 1951, 1955, 1959, 1961]
then what is assigned to the name--a1
in this case---is what
is known as a pointer to the array, i.e. (and loosely
speaking) a reference to the location in computer memory
where the storage for the array starts.
Thus, if I now "copy" the array to the name a2
...
> a2 := a1;
a2 := [1949, 1951, 1955, 1959, 1961]
... the only thing I have copied is the pointer, not any of the elements themselves.
So now a1
and a2
"point" to the same place in memory, and
if I change an element of a1
...
> a1[5] := 1995;
a1[5] := 1995
... then the corresponding element of a2
changes as well ...
> a2[5];
1995
... because it's the same element! (the same location in
memory). I.e. once the assignment a2
and a1
is made,
the names are aliases for one another, and the contents
of what superficially appear to be two separate arrays
will always be identical:
> a1;
[1949, 1951, 1955, 1959, 1995]
> a2;
[1949, 1951, 1955, 1959, 1995]
This semantics for array assignment is also seen in other
computer languages, notably Python
. It means
that Array
operations involving element assignment are much
more efficient than those for lists
, but at the same time
it introduces subtleties---like the fact that what looks like
copying really isn't---that one must be aware of.
Finally, lists are still useful in Maple, particularly since we can construct them "on the fly", without needing to call a special command. Their role in providing non-0 initial values for arrays is just one example of the utility of this feature.
END ASIDE
Refer to the Homework 2 handout, section Notation and translation of mathematical expressions that use indexed objects into Maple for motivation for my particular choice of procedure and variable names in what follows.
V_squares
: Procedure that creates and manipulates a 1D arrayFirst, we consider a 1D example.
########################################################################
# Creates and returns a 1D array (vector) of length n whose elements
# are the squares of the integers 1 through n.
#
# Illustrates:
#
# 1) Technique of declaring local variable (name) which is assigned
# the array that is created, and which is subsequently returned,
# in this case using an explicit return statement.
#
# 2) Basic technique of using for loop to iterate through
# all elements of a 1D array.
#
# Note: type posint -> non-negative integer, which is the appropriate
# type for the upper bound of a range that starts from 1.
########################################################################
V_squares := proc(n::posint)
# Define local variables for array and loop index.
#
# The use of the name 'the' for something generic that one would
# otherwise need to invent an (arbitrary) name for is an oft-used
# convention in computer programming.
local V, i;
# Create the 1D (one-dimensional) array and assign it to the local
# variable V. The bounds of the array are specified using a range.
# In this case the array indexes will run from 1 to n inclusive.
#
# Also note that the Array command will initialize all elements
# to 0 by default.
V := Array(1..n);
# This for loop "naturally" generates all of the values of the
# array index that we need.
for i from 1 to n do
# Define the i-th element of the array to be the square of i.
V[i] := i^2;
end do;
# Return the array.
V;
end proc;
This procedure is defined in procs2
, so provided you have
previously read this file you should be able to invoke it as follows:
> V_squares(11);
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121]
> V_squares(5);
[1, 4, 9, 16, 25]
> V_squares(11);
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121]
> V_squares(1);
[1]
> V_squares(0);
Error, invalid input: V_squares expects its 1st argument,
n, to be of type posint, but received 0
> V_squares(-10);
Error, invalid input: V_squares expects its 1st argument,
n, to be of type posint, but received -10
> V_squares(foo);
Error, invalid input: V_squares expects its 1st argument,
n, to be of type posint, but received foo
A_ij_sum
: Procedure that creates and manipulates a 2D arrayHere's a 2D example.
########################################################################
# Creates and returns a 2D array (matrix) of size n_r x n_c whose
# elements satisfy
#
# A[i, j] = i + j for all i, j with i = 1 .. n_r, j = 1 .. n_c
#
# Illustrates:
#
# 1) Creation and assignment of 2D array.
#
# 2) Basic technique of using NESTED for loops to iterate through
# elements of an array.
########################################################################
A_ij_sum := proc(n_r::posint, n_c::posint)
# Define local variables for array and the loop indexes.
local A, i, j;
# Create the 2D (two-dimensional) array and assign it to the local
# variable. The bounds of the array are specified using separate
# ranges for the first and second dimension.
#
# Again, the Array command will initialize all elements
# to 0 by default.
A := Array(1..n_r, 1..n_c);
# Loop over all elements of 2D array and define elements
# per the specification.
for i from 1 to n_r do
for j from 1 to n_c do
# Define the [i,j]-th element to be i + j.
A[i,j] := i + j;
end do;
end do;
# Return the array.
A;
end proc;
Try it out:
> A_ij_sum(4, 3);
[2 3 4]
[ ]
[3 4 5]
[ ]
[4 5 6]
[ ]
[5 6 7]
> A_ij_sum(3, 4);
[2 3 4 5]
[ ]
[3 4 5 6]
[ ]
[4 5 6 7]
> A_ij_sum(1, 3);
[2]
[ ]
[3]
[ ]
[4]
> A_ij_sum(3, 1);
[2 3 4]
> A_ij_sum(1, 1);
[2]
> A_ij_sum(0, 1);
Error, invalid input: A_ij_sum expects its 1st argument,
n, to be of type posint, but received 0
> A_ij_sum(1, -1);
Error, invalid input: A_ij_sum expects its 2nd argument,
m, to be of type posint, but received -1
> A_ij_sum(foo, bar);
Error, invalid input: A_ij_sum expects its 1st argument,
n, to be of type posint, but received foo
printf
statementThe printf
command provides a facility to control the appearance
of output that is "printed" in Maple; in computing parlance, this
is known as formatting the output. The command is based on
a family of similarly-named functions in the C programming language,
so anyone familiar with C or its derivatives (C++ e.g.) or languages
whose formatting capabilities are inspired by C's (too many to
enumerate), will be familiar with the syntax.
As is often the case, I will refer you to online help ...
> ?printf
... for instance (remember, get instant help for a command/procedure in
Maple simply by prefacing the command name with a question mark and hitting
Enter
), or google "maple printf examples", for full details.
Fortunately, for the purposes of this course (and for most Maple work for that matter), there isn't much syntax to master.
printf
: Syntaxprintf(<format string>, <arg1>, <arg2>, ...);
printf
: SemanticsThis is definitely a case where it is much easier to describe how a command works through example, rather than from a formal definition. So I will take the easy way out.
printf
: Example usageEnter the following ...
> printf("This is an integer: %a\n", 210);
This is an integer: 210
> printf("This is a float: %a\n", evalf(Pi));
This is a float: 3.141592654
> printf("This is an expression: %a\n", expand((x + 1)^5));
This is an expression: x^5+5*x^4+10*x^3+10*x^2+5*x+1
> printf("Here are two integers and a float: %a %a %a\n",
2014, 1995, 33.33);
Here are two integers and a float: 2014 1995 33.33
You can probably guess what's going on.
printf
simply echoes
its first argument, known as the format string, except
that every time it encounters %<something>
in that string,
it converts the corresponding additional argument (starting from
the second) to a string and replaces the %<something>
in the format string with that conversion.
See, I told you that it wouldn't be easy translating the semantics into English!!
The %<something>
is called a format specification and in
other programming languages, such as C, you generally have
to use different format specifications for different types of
variables that you wish to print.
For example, in C
%d -> formats an integer
%f -> formats a float using "normal" floating point notation
%e -> formats a float using scientific notation
%s -> formats a string
.
.
.
In Maple, the %a
can be used to format just about everything, which
makes life easy.
The one other little piece of syntax is that the \n
(backslash-lower-case-n) that appears
in each of the printf
statements that we typed generates a new line.
If we accidentally forget these "new line characters" then the output from
multiple printf
's is likely to get "jammed together" on a single line.
For example:
> printf("%a", 100); printf("%a", 200);
100200
(Something even stranger happens in command-line Maple!)
printf
: Example of procedure that uses printfHere's the definition of a procedure contained in printf_demo
that
illustrates the utility of printf
and the %a
format
specification.
For the sake of presentation, I've stripped
almost all the comments from the definition, so refer to the
source file: printf_demo
to see the real deal and the
script source file: tprint_fdemo
for the associated "testing" code.
########################################################################
# printf_demo takes several arguments of various types and prints
# their values using printf and the %a format specification.
########################################################################
printf_demo := proc(n::integer, x::float, z::complex, s::string,
expr::algebraic, l::list, V::Array, A::Array)
printf("printf_demo: In routine.\n\n");
#---------------------------------------------------------------------
# Print all of the arguments, one per printf statement.
#---------------------------------------------------------------------
printf("printf_demo: n::integer=<%a>\n", n);
printf("printf_demo: x::float=<%a>\n", x);
printf("printf_demo: z::complex=<%a>\n", z);
printf("printf_demo: s::string=<%a>\n", s);
printf("printf_demo: expr::algebraic=<%a>\n", expr);
printf("printf_demo: l::list=<%a>\n", l);
printf("printf_demo: V::1D array=<%a>\n", V);
printf("printf_demo: A::2D array=<%a>\n", A);
#---------------------------------------------------------------------
# Print multiple arguments per printf statement.
#---------------------------------------------------------------------
printf("printf_demo: Some scalar values: <%a> <%a> <%a>\n", n, x, z);
printf("printf_demo: The same string displayed twice: <%a> <%a>\n", s, s);
printf("printf_demo: An expression and then a list: <%a> <%a>\n",
expr, l);
#---------------------------------------------------------------------
# Print the definition a procedure using the eval command.
#---------------------------------------------------------------------
printf("printf_demo: The definition of ljoin: <%a>\n", eval(ljoin));
end proc;
Here's the testing script, tfprint_demo
, again with the commenting
stripped out:
read printf_demo;
read procs2;
printf("Demonstrating printf_demo.\n\n");
printf_demo(666, evalf(Pi), evalc(exp(I*Pi/3)), "Greetings!",
(-b + sqrt(b^2 - 4*a*c)) / (2*a),
[I, am, a, list],
Array(1..5, [2, 3, 5, 7, 11]),
Array(1..3, 1..3, [[1, 0, 0], [0, 1, 0], [0, 0, 1>>));
And here's the output from execution of the script (extra lines added for readability)
> read tprintf_demo;
tprintf_demo: Demonstrating printf_demo.
printf_demo: In routine.
printf_demo: n::integer=<666>
printf_demo: x::float=<3.141592654>
printf_demo: z::complex=<1/2+1/2*I*3^(1/2)>
printf_demo: s::string=<"Greetings!">
printf_demo: expr::algebraic=<1/2*(-b+(-4*a*c+b^2)^(1/2))/a>
printf_demo: l::list=<[I, am, a, list]>
printf_demo: V::1D array=<Array(1..5, [2,3,5,7,11])>
printf_demo: A::2D array=<Array(1..3, 1..3, [[1,0,0],[0,1,0],[0,0,1>>)>
printf_demo: Some scalar values: <666> <3.141592654> <1/2+1/2*I*3^(1/2)>
printf_demo: The same string displayed twice: <"Greetings!"> <"Greetings!">
printf_demo: An expression and then a list:
<1/2*(-b+(-4*a*c+b^2)^(1/2))/a> <[I, am, a, list]>
printf_demo: The definition of ljoin:
<proc (l1::list, l2::list) [l1[], l2[>> end proc>
printf
: Concluding remarksNote that, fundamentally, from the point of view of being computational scientists, where the correctness of the results that we compute is of paramount importance, we shouldn't spend too much time trying to make the output from our computer programs "look pretty".
Nonetheless, it should be clear to you (or become clear to you as
you gain more experience with programming), that it is good to
know a little bit about output formatting and because the printf
-esque
syntax is so ubiquitous, it's worth your time to put in a little
effort so that you can use it, if only in the straightforward manner
demonstrated here.
printf
: The Unix command-line version (aside!)printf
is also the name of a Unix command, as you can see by
typing the following at the bash command line:
% which printf
/usr/bin/printf
% man printf
PRINTF(1) ...
NAME
printf - format and print data
SYNOPSIS
printf FORMAT [ARGUMENT]...
printf OPTION
.
.
.
Here's a sample usage (note that the \ tells the shell that the command line continues after the Enter key is struck)
% printf '%-12s %-12s\n' 'Line' 'words'; \
printf '%-12s %-12s\n' 'up' 'in' ; \
printf '%-12s\n' 'columns.'
Line words
up in
columns.
interface
commandWe do this using the rtablesize
of the interface
command. For example,
as noted above, the default behaviour of xmaple is to suppress inline
output once there are more than 10 elements in any dimension:
> Array(1..10);
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
> Array(1..11);
[ 1..11 1-D Array ]
[ Data Type: anything ]
[ Storage: rectangular ]
[ Order: Fortran_order ]
To increase the value to 25, say, matching what command-line Maple does by default, we execute
> interface(rtablesize=25);
10
(The value returned from interface is the previous setting of
rtablesize
.)
Now try the following:
> Array(1..11, fill=11);
[11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11]
> Array(1..25, fill=25);
[25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25]
> Array(1..26, fill=26);
[ 1..26 1-D Array ]
[ Data Type: anything ]
[ Storage: rectangular ]
[ Order: Fortran_order ]
For the second homework, and for Problem 5 in particular,
you may want to use this command to increase the number
of inline-displayed elements so that you can more easily inspect
what your procedures are returning. However, recall that within
xmaple
you will always be able to use the data browser to inspect
the contents of an array.
Also, be aware that if you set rtablesize
to a very large value then
the maximal output that Maple will produce when evaluating an array
will be correspondingly voluminous. You may find yourself
with a worksheet session that needs to be interrupted using the
stop icon or even terminated with extreme prejudice using xkill
.
Note, if you get help with interface
using ...
> ?interface
... you will find that there are many features/behaviours of Maple that can be controlled with it.
~/.mapleinit
As with many other programs and applications running under Unix/Linux (and other OSes), Maple will read start-up commands from a special file that is configurable by the user.
Under Unix/Linux, this file:
.mapleinit
, so it is a hidden file, as is
typical for startup/configuration fileIf this file exists, and contains a sequence of valid Maple commands, then any time Maple starts (both xmaple and command-line Maple), the effect is precisely as if you executed
> read "~/.mapleinit";
(Note that if we want to read a file whose name or
pathname contains any special characters, such as ~
, .
, and /
in
this case, we need to enclose the name in double quotes (single
quotes won't work).
Let's see how that works.
Exit your Maple session, cd
to ~/maplepgm
, and start
xmaple
again
% cd
% cd maplepgm
% xmaple
Enter the following
> Array(1..20);
[ 1..20 1-D Array ]
[ Data Type: anything ]
[ Storage: rectangular ]
[ Order: Fortran_order ]
As is described above, Maple suppresses the display of the array
elements since rtablesize
is set to 10 by default.
Exit the xmaple session, and within your home directory, use
your editor to create the file .mapleinit
% cd
% kate .mapleinit &
or
% cd
% gedit .mapleinit &
or other, should you be using a different text editor.
Again, be sure that this file is created/saved in your home directory, or Maple won't find it.
Enter the following line in the file
interface(rtablesize=100):
and save the file, but do not quit your editor session yet!
Make sure that you end the line with a colon, not a semi-colon,
or every time you start Maple a 10
will appear in your worksheet
since the interface
command will echo what the old value of
rtablesize
was.
Now, start a new xmaple session (it doesn't matter what the working directory is) and type
> Array(1..20);
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
So now you have customized Maple so that, by default, it will display up to 100 elements along any and every dimension of an array.
I recommend that you leave your ~/.mapleinit
file this way
since, as noted elsewhere in the notes and in the homework
handout, it may be useful to be able to display more than 10 elements
of an array while you are developing your code.
NOTE
If the above example isn't working for you, then you haven't
created ~/.mapleinit
properly, so ask for help.
Once you've verified that it is working, you can safely exit the editing session that you used to create the startup file.
FINAL NOTE
Again, the startup file can contain any sequence of valid
Maple commands, so if you happen to find that there's some
setting that you often make, or some packages that you often
use that aren't loaded into Maple by default, put the
corresponding commands into your ~/.mapleinit
, always
being careful to back up the file before you begin editing
it.
Maple has powerful plotting capabilities, both for 2D (x-y) and 3D (x-y-z) plots, and three of the problems on the current homework require you to make use of these.
As with gnuplot
, I will ask that you invest some time and
effort in learning about Maple's plotting facilities---the homework
should provide some prodding in this regard.
Will only take a brief look at 2D plots here, use the online help facility via
> ?plot
and
> ?plot/options
for full details on making 2D plots.
You might also be interested in making 3D plots. See
> ?plot3d
for more details
The most useful basic forms for us are ..
Form 1
plot( f, x = x0..x1 );
where
f -> expression in independent variable x
x -> independent variable
x0 .. x1 -> range of plot
x0 -> left endpoint of range
x1 -> right endpoint of range
Form 2
plot( [ f1, f2, ..., fn ], x = x0..x1 );
where
[f1, ..., fn] -> list of expressions in independent variable x
x -> independent variable
x0 .. x1 -> range of plot
x0 -> left endpoint of range
x1 -> right endpoint of range
This type of invocation will produce a single plot; i.e. the
n
expressions f1(x), f2(x), ... f(n)
will be graphed
simultaneously.
Try the following:
> plot( 6*sin(2*x)*sin(3*x) - 1, x = 0 .. 4 );
> plot( [sin(x), cos(x), tan(x) ], x = -2*Pi .. 2*Pi );
Try some examples of your own, here or on your own time.
NOTE
Maple can also be used to plot numeric data that is defined in a file.
However, gnuplot
and Matlab
are both designed to make that
task very straightforward, so one of those is generally a better
option.
Finally, I'll leave you with a couple of optional out-of-lab exercises that will provide additional practice in writing procedures, and which may serve as good warm-ups for the homework.
lprod
Create two Maple source files
~/maplepgm/tmyprocs4 -> contains testing code for lprod
~/maplepgm/myprocs4 -> contains definition of lprod
lprod
is defined as follows
Procedure:
`lprod`:
Header:
lprod := proc(l1::list(algebraic), l2::list(algebraic))
Function:
Returns a list whose elements are the products of the
corresponding elements of the arguments `l1` and `l2`,
both of which are lists.
Error checking
Uses the error statement to exit with the message
Input lists l1 and l2 are not of equal length
if the input lists are not of the same length.
Sample invocations and output.
> lprod( [2, 4, 6], [3, 4, 9] );
[6, 16, 54];
> lprod( [2, 4, 6], [3, 4] );
Error, (in lprod) Input lists l1 and l2 are not of equal length
> lprod( [], [] );
[]
NOTE
It's best if you start your coding from scratch,
rather than copying/modifying ladd
As usual, start with the test script file, tmyprocs4
, and try to
use enough logically distinct invocations of lprod
to test
your implementation thoroughly.
As usual, make sure that the first command in your test script reads the procedure definition file, i.e.
> read myprocs4;
myplot3
In ~/maplepgm/myprocs4
add a definition for a procedure
myplot3
:
Procedure:
myplot3
Header:
myplot3 := proc(f::procedure, x::name, xmin::algebraic, xmax::algebraic)
Function:
Makes a single plot of f(x) and its first two derivatives
(i.e. three curves on the same graph) on the domain xmin .. xmax.
Sample invocation:
myplot3(x -> cos(2*x), 'x', 0, 3*Pi);
NOTE
Observe that first argument to myplot3
must be something of type procedure
and that an expression such as cos(2*x)
is not of that type:
type(cos(2*x), procedure);
false
but x -> cos(2*x)
is
type(x -> cos(2*x), procedure);
true
So, if you solve this problem correctly
myplot3(cos(2*x), x, -Pi, 2*Pi);
will not work, but should return the error message
Error, invalid input: myplot3 expects its 1st argument, f, to be of type
procedure, but received cos(2*x)
However, if you define a procedure mycos2x
as follows:
mycos2x := proc(x) cos(2*x) end proc;
then
> myplot3(mycos2x, x, -Pi, 2*Pi);
should do the job.
Also note that
> myplot3(mycos2x, y, -Pi, 2*Pi);
should produce an identical plot, except that the horizontal
axis will be labelled y
rather than x
;
i.e. there's no need to pass the name x
to the procedure, any
(unassigned) name will do.