Physics 210 Maple Programming Labs

Table of Contents

      
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

1 Maple source code files covered in these labs

2 Maple programming documentation

3 Maple programming setup

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 &

4 The read command

The 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

5 Working through these notes

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;

6 Displaying procedure definitions: the op command

Use 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!)

7 Type checking

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!

  1. 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).

  2. 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

  1. algebraic
  2. numeric
  3. integer
  4. float
  5. list

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?)

8 Syntax errors

(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!)

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 ...

8.1 EXERCISE: Write a simple procedure.

Using your text editor, create the file myprocs1 that contains a definition for the following procedure

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

NOTE

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!

8.2 EXERCISE: Write another simple procedure.

Add another procedure definition to the file myprocs1 as follows (I'll subsequently use the term Function instead of What the procedure does.

Test/debug as before.

IMPORTANT

Each time you modify myprocs1, be sure to save it, then reload it into Maple using

> read myprocs1;

9 EXERCISE: Debug the definition of 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

  1. If you are using kate, you can turn on line-numbering via
  2. If you are using gedit, you can turn on line-numbering via

10 The print statement

Use 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:

  1. Printing the value of a variable from within a procedure.
  2. Returning a value from the procedure.

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:

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.

11 Selection: the if statement

Here 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.

11.1 The "else-less" if statement

Among 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

12 Boolean operators and expressions

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

12.1 Relational Operators

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

12.2 Logical Operators

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"

12.3 Boolean-valued (logical) expressions

First, the following illustrates two other Maple features

  1. Multiple statements can be typed on a single line
  2. 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

12.3.1 Some trickier examples of Boolean expressions

> 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}

13 Sample procedures using if statements

13.1 myif: Simple if-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

13.2 myif1: Simple 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

13.3 myif2: 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

13.4 myif3: Simple 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

14 EXERCISE: Writing basic Maple procedures

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)

  1. myaddA

  2. myaddB

  3. myaddC

  4. myifA

  5. myifB

  6. myifC

15 EXERCISE: Locating and repairing syntax errors in if statements

Examine 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

16 Sample solutions to exercises from previous lab

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

17 Testing Maple procedures

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.

18 Important: Change of setup

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

HERE

and then click on the appropriate links.

Start xmaple from the command line (in ~/maplepgm)

% xmaple &

19 Global and local variables

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:

  1. Global variables
  2. Local variables

19.1 Global variables

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.

19.2 Local variables: the local statement

In 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)

19.3 Implicit declaration of global and local variables

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 the proc 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:

  1. If the procedure wasn't previously defined, no definition is made, and the name of the procedure remains unassigned.
  2. If the procedure was previously defined, the definition remains unchanged.

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

20 Important: Change of setup

Input the file procs2 that contains procedure definitions that we will be considering for the remainder of the lab

> read procs2;

21 Iteration (looping): the for statement

21.1 Simple form

We 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

21.2 General form

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

21.3 Sample procedures that use for statements

Note

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

21.3.1 myfor1: Most basic form of 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 ...

21.3.2 myfor2: Uses negative loop increment

> 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)

21.3.3 myfor3: Uses loop increment of 3

> 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:

21.3.4 myfactorial: Computes n!

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

22 The error statement

Syntax

  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 even NULL).

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.

22.1 error_demo: Example procedure that uses the error statement

This 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.

23 Maple arrays I: One dimensional arrays

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.

23.1 Defining 1D arrays: The 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:

  1. A 1D array with 10 elements was created and assigned to the variable (name) a1.

  2. Maple initialized all elements to the value 0. This is the default mode of operation for all array creation.

  3. The collection of elements that Maple echoes, i.e. the contents of the array are enclosed in (square) brackets and

    1. Separated with commas in command-line Maple.
    2. Separated with whitespace in GUI Maple.

    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.

23.2 Accessing elements of a 1D 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]

23.3 Initializing 1D arrays with arbitrary values.

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;

23.4 Initializing 1D arrays with a constant value.

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]

23.5 Out-of-range indexes.

If you use index a 1D array with an expression that is ...

  1. Not an integer
  2. Not in the index range

... 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

23.6 A one-dimensional array is not a list and vice versa.

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.

24 myifC revisited

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);

25 EXERCISES: Using for statements

25.1 EXERCISE 1: mysum

In the file ~/maplepgm/myprocs3 create the the procedure mysum as follows:

mysum := proc( ... you fill in the rest ... )
  1. Takes a single argument, n which must be an integer (use type-checking).

  2. Uses a for loop to compute and return the sum of the integers from 1 to n.

  3. 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:

  1. The first line should be read myprocs3;
  2. Subsequent lines should call 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.
  3. Use read tmysum to read the testing commands you define in the file into Maple, and thus do the testing per se.
  4. Continue modifying myprocs3 and or tmysum until you are satisfied that your implementation is correct.
  5. If possible, show your results to one of us.

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!

25.2 EXERCISE 2: 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:

  1. It reads the definitions of the procedures that you wrote in the last lab: recall that those were to be defined in the file procs2.
  2. It calls (and thus tests) myifC, rather than myifCbad, within the triply-nested for loop.
  3. The 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.

26 Free time to work on homework, discuss term projects

IMPORTANT NOTES

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.

27 Sample solution to the mysum exercise from previous lab

Can 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)

28 Important: change of setup

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;

29 The trace statement: tracing execution of a procedure

Maple 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>);

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 #.

29.1 Remark: Commenting-out code

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.

30 Sample procedures that manipulate lists.

30.1 The NULL sequence and empty list.

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:

  1. tladd: The testing script (our scaffolding)
  2. ladd: The source file defining the procedure (our gleaming condo tower)

30.2 GROUP EXERCISE: Implementing 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 or tladd?

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;

30.3 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 

31 Maple arrays 2: Two dimensional arrays

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:

  1. <index1> and <index2> are both of type integer
  2. <index1> and <index2> are within the ranges of their respective dimensions.

31.1 Defining 2D arrays: The 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. From 1 through <m> inclusive in the first dimension.
  2. From 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.

31.2 Initializing 2D arrays with arbitrary values

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:

  1. It has the right number of sublists: 2, the number of rows.
  2. Each sublist has the correct length: 4, the number of columns.

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

31.3 Accessing elements of a 2D array

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

31.4 Initializing 2D arrays with a constant value

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]

31.5 Out-of-range indexes.

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.

31.6 A two-dimensional array is not a list-of-lists and vice versa.

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. Make a copy of the original list [1, 2, 3, 4] that was constructed in-place and assigned to the name l1.
  2. Change the 2nd value of the copy to 0.
  3. "Finalize" the creation of the new list, [1, 0, 3, 4].
  4. Assign the new list to the name 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

32 Examples of procedures that create and manipulate arrays

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.

32.1 V_squares: Procedure that creates and manipulates a 1D array

First, 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

32.2 A_ij_sum: Procedure that creates and manipulates a 2D array

Here'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

33 The printf statement

The 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.

33.1 printf: Syntax

printf(<format string>, <arg1>, <arg2>, ...);

33.2 printf: Semantics

This 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.

33.3 printf: Example usage

Enter 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!)

33.4 printf: Example of procedure that uses printf

Here'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>

33.5 printf: Concluding remarks

Note 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.

33.6 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. 

34 Increasing the number of array elements that Maple will display inline

34.1 The interface command

We 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.

35 The Maple initialization file: ~/.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:

If 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.

36 Introduction to Maple's plotting facilities

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.

36.1 EXERCISE: 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

  1. It's best if you start your coding from scratch, rather than copying/modifying ladd

  2. As usual, start with the test script file, tmyprocs4, and try to use enough logically distinct invocations of lprod to test your implementation thoroughly.

  3. As usual, make sure that the first command in your test script reads the procedure definition file, i.e.

    > read myprocs4;
    

36.2 EXERCISE (Out of lab): 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.