Chapter 8: Programming -- logic, loops, and procedures
> f:=x-> if x<0 then -1 elif x=0 then 0 else 1 end if;
Here's a test to see if it works
> f(-.5);f(0);f(.5);
(Note: this is the same as Maple's signum function.)
>
(Note: as you will see in a minute, this i f-then-elif-else-end if way of building functions with special cases doesn't work well in Maple. The piecewise syntax introduced below is the best syntax to use. I am only using this more awkward form to introduce you to logic statements.)
The statement defining the function f almost reads like English if you just translate elif as "else if" and end if as "we're done". As you can see there are three elements that go into this statement.
The first are the logical syntax commands if ... then, elif ... then, else, and end if . They have to be in this order.
< less than
<= less than or equal
> greater than
>= greater than or equal
= equal
<> not equal
> evalb(7>5);evalb(3=4);evalb(3<>4);
Stare at the results of executing these commands until they make sense.
Note: and , or , and not can also be used to combine logic statements, like this
-2<=x and x<=-1 .
There are other conjuctions as well; see ?if and ?boolean for more details.
The third element of the if statement is the set of statements that it executes. In the example above these are just the numbers -1, 0, and 1, but any valid expression could go in these positions. For instance, we could use this construct to define a function which is one thing for negative numbers and another for positive numbers, like this:
> g:=x-> if x<0 then 1/(1+x^2) else cos(x) end if;
Test it to make sure there are no errors
> g(-1.5);g(2.5);
Ok, we seem to be getting numbers out. But watch what happens if you do this
> g(Pi);
Error, (in g) cannot evaluate boolean: Pi < 0
You need to able to interpret this error message because you will be getting it occasionally. It tells you that it tried to evaluate g, but that when it did, at least one of the Boolean expressions couldn't be evaluated. This happened because Pi is not really a number in Maple, so we just asked for a comparison between the number 0 and the non-number Pi. (Pi is more of an idea than a number in Maple.) You get the same error if you try to evaluate g(a), where a hasn't been given a number yet
> g(a);
Error, (in g) cannot evaluate boolean: a < 0
The problem with Pi can be fixed by remembering that Pi is tricky and always using evalf(Pi)
when you want a number:
> g(evalf(Pi));
Now let's plot our function and see what it looks like
> plot(g(x),x=-10..10);
Error, (in g) cannot evaluate boolean: x < 0
Whoa, bad booleans again. Now what went wrong? Well, with the if statement in g(x) Maple can't plot with this syntax. When x goes from the plot command into g, x is apparently not yet a number. A clue to solving this problem is found by looking at what Maple did to our statement g:=x->... above; it turned it into a procedure (more about these later). And Maple plots procedures using "operator notation", meaning that you don't say g(x),x=-10..10 in the plot command, you just leave x out, like this
> plot(g,-10..10);
But if you try to integrate the function or differentiate it, you are at a dead end:
> int(g(x),x=-2..2);
Error, (in g) cannot evaluate boolean: x < 0
> diff(g(x),x);
Error, (in g) cannot evaluate boolean: x < 0
This difficulty is the reason that the i f-then-else-end if syntax is not very convenient for defining functions in Maple. But piecewise functions like this are pretty important, so it would be good if there were some clean way to handle them. Fortunately, there is. Maple has a piecewise command that is used for exactly this purpose. To redefine the function g(x) using piecewise we would do this
> g:=x->piecewise(x<0, 1/(1+x^2),x>=0, cos(x));
Now it really is a function and you can plot it like a function
> plot(g(x),x=-5..5);
integrate it like a function
> int(g(x),x=-5..5);
and differentiate it like a function
> diff(g(x),x);
>
So this is the best way to do piecewise functions; if-then-elif-end if is mostly used inside loops and procedures, which we will get to shortly.
Problem 8.1
_____________________________________________________________
if
is in the range -2..-1
______________________________________________________________
if
is in the range -1..0
B(t) = ______________________________________________________________
if
is in the range 0..1
______________________________________________________________
-
if
is in the range 1..2
______________________________________________________________
For
outside of the range -2..2 ,
.
You will discover that when you need to specify a range with a beginning and an end, like -1 to 0, you will need to combine two inequalities, i.e. b<a<c. Maple does this by using two different inequality conditions and combining them with and, like this:
b<a and a<c . When you get the function defined, plot it between -4 and 4.
----------------------------------------------------------------------------
Problem 8.2
Integrate B(t) from -3..3. Also plot its first three derivatives on the interval -3..3.
----------------------------------------------------------------------------
Problem 8.3
Here's an example of a problem where piecewise won't work cleanly and it is better to use the if...end if syntax.
Use this syntax to
make a plot of the sine function from
, but with the peaks and valleys all chopped off flat at height 0.9 or depth -0.9. The Maple command
signum
will be useful in doing this, as will your old friend
evalf
. Note that instead of doing logic horizontally (in x), as we do when using
piecewise
, you will be doing logic vertically (in y) instead.
Loops: for, do, end do, while, and break
Much of computing is done in loops: Maple's sum command has a loop at the bottom, as does numerical integration, fsolve , numerical differential equation solving, dsolve(...type=numeric) , and many more. You have seen an example of a homemade loop in the three-dimensional integration procedure in the calculus section[ Triple integrals] and in the differential equation section on Shooting. When Maple won't do what you want it to do, you will often find yourself writing your own loop. So that we have something concrete to talk about, let's write our own loop to sum the squares of the integers from 1 to 100. Maple's sum command does it this way
> sum(i^2,i=1..100);
Error, (in sum) summation variable previously assigned, second argument evaluates to 101 = 1 .. 100
Somewhere down in Maple's depths, what is actually being done by this command is a loop. A handmade one would go like this.
Sum Loop:
First you decide where you want the answer to be stored and set that variable to zero:
> answer:=0;
> for i from 1 to 100 do
> answer:=answer + i^2;
> end do;
Well, that was more information than you wanted. To fix this, end the end do that terminates the loop with a colon instead of a semicolon then look at answer at the end.
> answer:=0;
> for i from 1 to 100 do
> answer:=answer + i^2;
> end do:
> answer;
And what if you want to sum the odd numbers from 1 to 99? Well, you just want to start at 1, end at 99, and step up by 2. A loop that does this using Maple's by syntax is
> answer:=0;
> for i from 1 by 2 to 99 do
> answer:=answer + i^2;
> end do:
> answer;
Successive Substitution Loop:
If you know how far you want to count this is all you have to do, but sometimes you don't know how many times you want to loop. Instead, you want to loop until some condition is met. For instance, here is an interesting little trick that some of you may have noticed on your hand calculators. What happens if you just take the cosine of a number over and over and over? Let's let Maple do it 20 times and show us what it's doing as it goes along.
> x:=5.;
> for i from 1 to 20 do
Take the cosine of x and store the result in x so we can do it again and again
> x:=cos(x);
> end do;
Choose a starting value of x and an initial fake value of xold, the "previous value of x"
> x:=5.;xold:=0;
Keep looping as long as |x-xold|>1e-8 (this is why we need the initial fake value of xold)
> for i from 1 while abs(x-xold)>1e-8 do
Put the new x from taking the cosine into a temporary variable t
> t:=cos(x):
Put the current x into xold so we will be able to tell when to quit
> xold:=x:
Put the new x (called t right now) into x
> x:=t;
Go back up and do it again
> end do;
>
This is called successive iteration, and it is used in computing a lot. It would be a good idea for you to look at the structure of this loop carefully to see how it is built. In particular, pay attention to when each variable is computed and where it is stored. You may also notice that I tried in the loop to have only x print by using colons on the t and xold lines; this didn't work and I don't know how to fix it.
Well, those are the two basic kinds of loops. For additional information see ?for.
Problem 8.4
Write a loop that evaluates the power series for the cosine function,
for
even . Give
a value outside the loop, use the
by
syntax to get even numbers, and keep looping until the next term added to the sum is less than 1e-8. To do this you will need to know how to make a loop that adds. There is one at the beginning of this section, right here:
Adding loop
--------------------------------------------------------------------------------
Problem 8.5
Write a loop that evaluates the Fourier series
. Give
a value outside the loop and keep looping until the magnitude of the terms being added drop below 1e-4 in magnitude. Since the sine function is never greater than 1 in magnitude, this means that you only need to apply the
while
test to
.
(It is bad to test on the whole term with the sine included because the sine might just happen to be really small and cause the loop to terminate too soon.)
--------------------------------------------------------------------------------
Problem 8.6
Write a loop to solve the equation
by successive substitution. Get the answer to 8 significant figures. To see how to build a successive substitution loop go here (just above):
Successive substitution loop
--------------------------------------------------------------------------------
Problem 8.7 (Secant Method)
Here's a loop that computational physicists use all the time. It's called the secant method and it's a way of solving hard equations in one variable. In fact, Maple almost certainly uses a fancy varition of this technique inside the fsolve command. I also used it in the shooting procedure for differential equations in Chapter 7. Here's the idea.
Suppose you want to solve a hard equation of the form
, for
. The secant method starts by asking you for two reasonably close guesses
and
. Then you evaluate the function at each value of
to get
and
. You learned in high school algebra what to do with two ordered pairs (
) and (
) like this: use the two-point line formula to put a straight line through them:
. But what we have just built is a straight-line approximation to the function
, so to get an approximation to the value of
that solves our hard equation, we just solve the line formula above for the value of
that gives
and call it
, our next best guess at where the solution is.
> restart;
> Eqline:=0-f2=(f2-f1)/(x2-x1)*(x-x2);
> x3:=solve(Eqline,x);
>
This is a good algebraic expression, but not a good numerical one because as we get close to the root when both x1 and x2, and f1 and f2, are close to each other we get x3 by almost dividing zero by zero. This can cause floating point troubles, so it is better to rearrange this formula so that it looks like this:
.
With this form we get the next best guess by adding something small to the previous best guess and we get in less numerical trouble. It is true that both (
) and (
) are small, so we are dividing zero by zero again, but the values of
and
in the numerator are extra small because we are close to
, so the sins we commit in dividing zero by zero don't affect the answer as much.
Ok, here is finally the problem:
Write a loop that uses the secant method to solve the equation
for a root near
. (Make your loop similar to a
Successive substitution loop.) In the loop you will find that it is desirable to jump out of the loop if
to as many significant digits as Maple is keeping (typically 10; see online help about
Digits
) because this will keep you from dividing by zero. This is hard to do with
while
, but fortunately there is a way to jump out using the
break
command.
Here is a stupid example: suppose you were looping from 1 to 100 but you wanted to jump out when i was 20. You would use
> for i from 1 to 100 do
> if i=20 then break end if;
> end do;
Notice that after the jump, i has the value it had when the jump occurred.
> i;
>
You will want to use this in the secant method procedure you are developing here. To test your use of it in your loop, keep repeating the calculation until the value of f2 is less than 1e-10 by using while ; this should make division by zero happen so you will need the break command to protect your loop.
You may discover, however, that getting Maple to divide by zero in this problem is difficult. If it won't cooperate, don't worry about it. The break statement is still a good idea in general, though, so I want you to practice putting it in.
Procedures: putting loops and logic to work
Assign the procedure to a name and define an argument list for it
> f:=proc(x,y)
Declare the names of local variables, those only used within the procedure
> local a,b,z;
Declare the names of global variables, those from outside to be used inside
> global d;
Now come the Maple statements that get the job done. In this case we use local a and b, global d, and passed in x and y to compute function value z, which is local. So how does Maple know what value to give us back? The returned value is always the last thing computed in the procedure.
> a:=3.;b:=17.;
> z:=a*b*x*y*d; # last value
> end;
> d:=31.;
> f(1,2);
>
To illustrate how a procedure works, here is one that simply evaluates the cosine of an angle in degrees.
> restart;
> cosdeg:=proc(theta)
> local phi,answer;
> phi:=theta*Pi/180.;
> answer:=cos(phi);
> end;
Now test it
> yy:=cosdeg(45.);
> trace(cosdeg);
then execute it like this
> cosdeg(45);
{--> enter cosdeg, args = 45
<-- exit cosdeg (now at top level) = cos(.2500000000*Pi)}
And then when it is working right and you don't want to see all of this stuff every time you use it type
> untrace(cosdeg);
>
Problem 8.8
Notice also that you didn't get what you probably expected out of this cosdeg procedure. Fix it so that it gives a floating point number back. Make sure that your fix takes place inside the procedure and not in the call you make to it.
--------------------------------------------------------------------------------
If you look at how cosdeg is used you can see that a procedure is used just like a function. But because it is a self-contained little program it can be a really complicated function. And this is, in fact, the usual reason for writing a procedure. You have to be careful, though, because functions defined this way may not be compatible with other Maple commands like plot , int , sum , dsolve , etc.. In addition, procedures are usually slower than the built-in Maple commands.
Problem 8.9
Write a procedure that computes the cubic B-spline function described in Problem 8.1. Plot your procedure (function) from -3..3. Note: if you are going to plot the result of a procedure the answer has to be returned as the last result, and the only arguments that can be passed in are the appropriate independent variables, i.e. for a one-dimensional function you would use B:=proc(x) , for 2-d use C:=proc(x,y) , etc.. When you plot output from a procedure, you can treat it like a function using the plot(f(x),x=a..b) command, or you can use Maple's operator notation with the name of x left out: plot(B,-3..3) .
--------------------------------------------------------------------------------
Problem 8.10
You can also use procedures to perform a task that you want to do a whole bunch of times while you change some parameter. Write a procedure that takes as inputs the parameter Nterms and the angle
and returns the value of the Fourier series
given in Problem 8.5. Use Maple's
sum
command inside the procedure and keep terms up to n=Nterms. Then make three plots of
vs.
with Nterms=10, Nterms=100, and Nterms=400. This will give you an instructive way of seeing how the Fourier representation improves as you keep more terms. Because your procedure has more than one input variable, you will need to use a plot command of form
plot(f(Nterms,theta),theta=0..Pi)
.
--------------------------------------------------------------------------------
Problem 8.11
Write a procedure that takes as input the parameter
, then solves the differential equation
with
and
. Let the procedure return
as a single number (you will likely need to use trace to get this right). Then try different values of
and see if you can find at least 2 values of
(other than 0) for which
. Find these 2 values of
accurate to 5 significant figures.
This is a pretty hard problem, so here are some hints. You will want to put dsolve(...type=numeric) into the procedure and have it return a number. But the output from the numerical version of dsolve is a set of equations, like this
> restart;
> f:=dsolve({diff(y(t),t$2)=-y(t),y(0)=2,D(y)(0)=0},{y(t)},type=numeric);
> g:=f(1);
When we encountered this before we used the assign statement to get the number assigned to y(t), like this
> assign(g);
Error, (in assign) invalid left hand side in assignment
> op(1,g);
Error, wrong number (or type) of parameters in function diff
The number is really close now; all we have to do is get it out of the equation, and that's just what rhs does: it extracts the right-hand side of an equation. So see what happens when you do this
> rhs(op(2,g));
Error, wrong number (or type) of parameters in function diff
You will need to use this construction inside your procedure so that it can return a number.
>
--------------------------------------------------------------------------------
Problem 8.12
Write a procedure that changes the Pnm(x) Associated Legendre Function discussed in Problem 3.4 into a real function that returns a number for any valid choice of n and m, including m=0. This means that you have to solve the "differentiate zero times" problem discussed in Chapter 3. Associated Legendre functions Test your procedure sufficiently that you know it is working correctly. (a) First write the procedure so that it returns an expression in x, then (b) change it so it returns a floating point number.
Example: plotting results from fsolve
Suppose you have a hard equation to solve that has a variable parameter
in it, like this
where
varies from 0 up to 20. I want to build a function
that returns the solution
of this equation for any
that I give it, and then I want to plot this function
. The only way I know to do this is to write a procedure. After the Maple commands inside the procedure below have executed, F is assigned the last result calculated in the procedure, in this case the value of x from the
fsolve
, which is just what we want F to be.) Study the procedure below, then execute the plot.
Declare F to be a procedure
> F:=proc(k)
Declare variables that will only be used inside the procedure
> local s,x;
Use fsolve to solve the equation using the value of k passed in through proc(k)
> s:=fsolve(cos(x)-k*x,x,0..2);
Return
> end;
Try a few values
> F(1);F(2);
Make a plot (use operator notation--the form plot(F(k),k=0..20) doesn't work)
> plot(F,0..20);
>
Problem 8.13
Consider the polynomial
with
a variable parameter. You can give this polynomial to
fsolve
and it will find all four roots as long as
has a value.
> a:=2.5;
> P:=x^4+a*x^3+x^2+x+1;
> s:=fsolve(P,x,complex);
If you want to select one of the roots, say the second one, you just do this
> s[2];
>
Copy the procedure at the start of this section and modify it to plot the second root of this polynomial as
varies between 2 and 10. (Just put the Maple code in this problem into the procedure.) Note: procedures return the last value calculated, so in your new procedure, right after the
s:=fsolve(....)
you will need the command
s[2];
to return the value of the second root.
---------------------------------------------------------------------
Problem 8.14
(a) Write a procedure that returns the function
defined as the solution of the implicit equation
.
In the procedure use Maple's fsolve command to do the solve and when you have built it, plot t(x) on the range x=0..10.
(b) Do the same thing as in (a), but this time use the function notation to define
, i.e.,
t:=x->fsolve(...).
Are the two ways of doing this equivalent?
(c) Write either a function or a procedure that will give you the derivative of this implicitly defined function, i.e., get a Maple function or procedure for
. Warning: do not just take the
-derivative of the equation above treating
like a constant--
is really
on
both sides of the equation
. So rewrite the equation with
replaced by
, then use
diff
to differentiate the whole equation with respect to
. Then have Maple solve for
and evaluate the resulting expression by modifying your procedure for
. When you have built this procedure, use it to plot
on the range 0..10 and verify visually that it looks like the derivative of the function you plotted in part (a).
--------------------------------------------------------------------------------