CPSUPsolns CC Linked
CPSUPsolns CC Linked
EXERCISE SOLUTIONS
for
COMPUTATION AND PROBLEM SOLVING
IN UNDERGRADUATE PHYSICS
DAVID M. COOK
with assistance from
This document contains solutions to selected exercises from Computation and Problem Solving in
Undergraduate Physics (CPSUP), a book that has grown from small beginnings in the 1990s to a
flexible volume that provides an orientation to a subset of tools chosen from
• the general purpose programs IDL, MATLAB, OCTAVE, PYTHON, MAXIMA, MAPLE, and
MATHEMATICA,
• the programming languages FORTRAN and C,
In addition, chapters on ordinary differential equations, integration, and root finding provide ex-
amples of the use of the selected subset of tools for solving a wide variety of problems in physics.
Problems from mechanics, electromagnetic theory, quantum mechanics, thermodynamics, statistical
mechanics, relativity, and other subarea of physics are included.
This document admits the same flexibility in composition that characterizes CPSUP. Both CP-
SUP and this document can be configured to include all of the possibilities or only a selected subset
of the options. Because there are 13 different components, each of which can be included or not,
there are technically 213 = 8012 versions of these items. To be sure, the vast majority of these
possibilities makes no sense. Still, the number of versions is staggering. Creating documents with
this degree of flexibility would be impossible without exploiting the elegant features of the ifthen
package in LATEX, and I owe an immense debt to Donald Knuth, Leslie Lamport, and numerous
others who have contributed to the development of that publishing system.
David M. Cook
Appleton, Wisconsin
19 February 2023
iii
iv PREFACE
Table of Contents
Preface iii
Table of Contents v
9 Introduction to Programming 1
in Psuedocode
9.2 Relationship between IF-THEN-ELSE and CASE . . . . . . . . . . . . . . . . . . . . . . 1
9.7 Tracking Both Extremes and their Positions . . . . . . . . . . . . . . . . . . . . . . . . . 4
9.9 Mystery Procedure 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
in C
9.12 Conversion from Celsius to Fahrenheit (C) . . . . . . . . . . . . . . . . . . . . . . . . . . 8
9.14 Laplace’s Equation with Different Grids (C) . . . . . . . . . . . . . . . . . . . . . . . . . 10
9.16 Exploring Trajectories in 3D (C) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
9.22 Great Circle Distances (C) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
11 Solving ODEs 45
numerically with C
11.26 Radioactive Decay (C) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
numerically with Numerical Recipes in C
11.33 Logistic Growth (Numerical Recipes-C) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
11.36 Lorenz system (Numerical Recipes-C) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
13 Evaluating Integrals 61
numerically with C
13.34 Simpson’s Rule (C) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
13.35 Gaussian Integration (C) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
numerically with Numerical Recipes in C
13.44 Numerical Integration (Numerical Recipes–C) . . . . . . . . . . . . . . . . . . . . . . . . 69
13.47 Maxwell-Boltzmann Distribution (Numerical Recipes–C) . . . . . . . . . . . . . . . . . . 71
14 Finding Roots 75
numerically with C
14.12 Square Root by Newton’s Method (C) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
14.26 Rootfinding with Newton’s Method (C) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
numerically with Numerical Recipes in C
14.14 Natural Frequencies of Bar (Numerical Recipes-C) . . . . . . . . . . . . . . . . . . . . . . 83
14.18 Single-Slit Diffraction (Numerical Recipes-C) . . . . . . . . . . . . . . . . . . . . . . . . . 89
v
vi PREFACE
Introduction to Programming
Exercise: Figure E9.1 shows three different alternative structures. Express each structure
using (a) only CASE structures and (b) only IF-THEN-ELSE structures. In these figures, T, F, C, and
B stand for true, false, condition, and block of statements, respectively. Use proper indentation as
illustrated in the examples.
Solution: (a) In the pseudocode of Chapter 9, this figure translates directly into the nested
IF-THEN-ELSE control structure
IF C1 THEN B1
ELSE IF C2 THEN B2
ELSE B3
END_IF
END_IF
CASE
OF C1 DO B1
OF C2 DO B2
OF OTHERS DO B3
END_CASE
(b) In the pseudocode of Chapter 9, this figure translates directly into the CASE structure
CASE
OF C1 DO B1
OF C2 DO B2
OF C3 DO B3
OF OTHERS DO B4
END_CASE
1
2 Exercise 9.2
(a) (b)
T F T
C1 C1 B1
F
T F
C2 T
C2 B2
F
B1 B2 B3 T
C3 B3
F
B4
(c)
T F
C1
T F T F
C2 C3
B1 B2 B3 B4
IF C1 THEN B1
ELSE IF C2 THEN B2
ELSE IF C3 THEN B3
ELSE B4
END_IF
END_IF
END_IF
(c) In the pseudocode of Chapter 9, this figure translates directly into the IF-THEN-ELSE structure
IF C1 THEN IF C2 THEN B1
ELSE B2
END_IF
ELSE IF C3 THEN B3
ELSE B4
END_IF
END_IF
Exercise 9.2 3
(a) (b)
T F
T C1
C1 B1
F T C2 F
T
C2 B2 F
T C3
B3
B1 B2 B3 B4
(c)
T T
C1 C2 B1
F F
B2
T
C3 B3
B4
CASE
OF C1 DO CASE
OF C2 DO B1
OF OTHERS DO B2
END_CASE
OF OTHERS DO CASE
OF C3 DO B3
OF OTHERS DO B4
END_CASE
END_CASE
In Fig. E9.2, we redraw each of the diagrams in the statement of the exercise in the alternative
form that leads more directly to a casting of the coding in the other of the two possible structures.
Recognize that the two diagrams for each part of this exercise are simply topological rearrangements
of each other.
4 Exercise 9.7
write an algorithm that will obtain words one at a time and ultimately report (1) the word that
would appear last if the list were alphabetized, (2) the word that would appear first if the list were
alphabetized, (3) the total number of words given, and (4) the position of each extreme word in the
original list. Only one pass through the list is permitted.
Solution: Algorithm (6) in Section 9.2 involves scanning through a list of values, keeping track
of the extreme value in the list and ultimately displaying that extreme value. This exercise asks
that, in scanning the list, we keep track of the earliest value, its position in the list, the latest value,
and its position in the list, and that we count the entries in the list as we go so we can report the
total number of values entered. For the last item, we will need to count values as they are entered,
not only to have the total count at the end but also to have the information along the way that
will permit us to note the position of items in the list. In broad outline, we need to execute the
statements shown in Table 9.1.
While this program will do the job, note that
• the algorithm will not recognize it if the ultimate earliest or latest entry occurs more than once
in the list. If, say the earliest item occurs twice, which occurrence will this algorithm report?
How would you modify the algorithm to report the other occurrence?
• the algorithm does not recognize that, if a particular item is earlier than the current earliest
item, it is certainly not later than the current latest item. Thus, the separate IF-THEN
structures in the loop could be combined into a nested structure in which the algorithm didn’t
bother to make the second test if the first test happens to be satisfied.
• the incrementation of CNT% is positioned in the loop so that the sentinel is not counted as
a valid entry. In broad terms, one must pay careful attention to the point in the sequence
of instructions at which a counter is incremented; off-by-one errors are extremely common in
counting operations. Generally speaking, counters should be incremented immediately after
the task being counted has been completed and only if the completion represents a bona fide
countable occurrence.
Exercise 9.7 5
PROCEDURE ???????
SCANEND% ←− N%
LOOP
CARD% ←− 1%
Obtain word on card CARD% and store in WORD$
LATEST WORD$ ←− WORD$
LATEST CARD% ←− CARD%
LOOP
CARD% ←− CARD% + 1%
Obtain word on card CARD% and store in WORD$
IF WORD$ occurs after LATEST WORD$
THEN BEGIN BLOCK
LATEST WORD$ ←− WORD$
LATEST CARD% ←− CARD%
END BLOCK
END IF
EXIT LOOP WHEN CARD% = SCANEND%
END LOOP
Exchange card LATEST CARD% with card SCANEND%
SCANEND% ←− SCANEND% - 1%
EXIT LOOP WHEN SCANEND% = 1%
END LOOP
END PROCEDURE
Solution: To determine the function of the procedure in Table 9.2, we begin by looking at
what happens in the outermost loop, which begins by
1. setting the counter CARD% to 1, i.e., by setting our attention on the first card on the table,
2. fetching the word on that card and storing it in memory location WORD$,
3. copying that word into a memory location labeled LATEST WORD$, and
4. recording the card number in a memory location labeled LATEST CARD%.
These several actions prime the inner loop, in each pass through which we
The loop continues until we have examined the SCANEND%-th card on the table, at which point
LATEST WORD$ contains the particular word on the cards from the first through the SCANEND%-th
Exercise 9.9 7
that occurs latest in the alphabet and LATEST CARD% contains the position on the table of the card
containing that word.
Upon exit from the inner loop, we exchange the SCANEND%-th card with the LATEST CARD%-th
card, thereby moving to the SCANEND%-th position the card in the subgroup of cards from 1 thru
SCANEND%, i.e., we move the card in that subgroup that will be latest in the alphabet to the last
position in the subgroup!
Then, we decrement SCANEND% and go back to the beginning of the outermost loop.
Note that the outer loop starts with SCANEND% set to the number of cards on the table. Thus,
in the first pass through that outer loop, we move the card in the entire stack that is latest in
the alphabet to the last position in the stack. Then we ignore that last card by decrementing
the counter on the outer loop. At the end of the next pass through the outer loop, we move the
“latest-in-the-alphabet” card in the first N%-1% cards to the bottom of that stack. Then, we move
the “latest-in-the-alphabet” card in the first N%-2% cards to the bottom of that stack, . . .. Every
time we move a card, we move the card displaced by the “latest-in-the-alphabet” card to a position
earlier in the stack—so it will continue to be examined in each pass through the loops until such
time as it, itself, becomes the “latest-in-the-alphabet” card, moves to the bottom, and ceases to be
further examined.
By the time this mystery procedure has completed execution, the cards on the table have been
arranged in alphabetic order.
8 Exercise 9.12 (C)
convert the value to Celsius by invoking the above equation with a statement like
F = 9.0*C/5.0 + 32.0
This exercise, however, asks that the conversion be placed in a loop such that the program asks
repeatedly for Celsius temperatures and effects the conversion of each, stopping only when the
flagging value 9999 is entered in place of a legitimate Celsius temperature. To achieve that end with
the loop structures that are available in C, we must prime the loop with the entry of the first value
outside of the loop. At that point, we will have available the first Celsius temperature for use in
the condition that determines whether the loop will be executed or not. Within the loop and after
the temperature has been converted, we then must ask for the entry of the next Celsius temperature.
That way, when the loop bounces back to the beginning and the controlling condition is tested, that
condition will be able to detect the flag 9999 and terminate the loop when the time comes. We
must, of course, also include whatever libraries are necessary and declare all variables appropriately.
A complete program, including the loop and its control, might involve the statements
/* PROGRAM cent_to_fahr.c */
#include <stdio.h>
main()
{
float C, F; /* For temperatures */
while (C != 9999)
{ F = 9.0*C/5.0 + 32.0;
printf( "Temperature in Fahrenheit = %10.2f", F );
printf( "\nEnter temperature in Celsius: ");
scanf( "%f", &C );
}
}
Exercise 9.12 (C) 9
Once this program has been stored in the file named cent to fahr.c, we can compile and execute
it with the statements
cc -o cent_to_fahr.xc cent_to_fahr.c
./cent_to_fahr.xc
Here, we have tested the program with three known values: 0 ◦ C = 32 ◦ F; 100 ◦ C = 212 ◦ F; −40 ◦ C
= −40 ◦ F.
10 Exercise 9.14 (C)
(a) Copy laplace file.c from $HEAD/cc to your directory, naming it laplace15 file.c. Then
compile, link, and run laplace15 file.c to generate the file laplace c.dat, which you should
rename laplace15 c.dat.
(b) Produce the program laplace29 file.c by copying laplace15 file.c to the new file and
editing the new file so that the program, when run, generates a solution on a 29 × 29 grid.
(c) Compile, link, and run laplace29 file.c and rename the output file to laplace30 c.dat.
(d) Import both laplace15 c.dat and laplace30 c.dat into an available program for graphical
visualization and then
i. generate a graphical display of each solution, either a contour map in the xy plane or a
surface plot over the xy plane (or perhaps both). In either case (or both cases), make
sure the axes are labeled correctly with the proper coordinate values. Warning: This latter
requirement is a bit subtle. Beware.
ii. develop a way to compare the two solutions at those grid points that are common to the
two and display the differences graphically.
(e) Copy the file laplace29 file.c to a new name—your choice—and then edit that file so that
the program it conveys monitors the change from one iteration to the next and displays on
the screen the maximum absolute value of the change that occurs during the course of each
iteration.
(f) Edit the last program again so that iteration is stopped when the maximum change falls below
a tolerance that is specified as input when the program is run (or—to prevent infinite loops—
when the number of iterations exceeds some maximum value). Arrange for the program to
display the number of iterations carried out when the solution finally converges. Compile,
link, and run this last program and explore the way the number of iterations varies with the
tolerance specified. Was 300 iterations as a trial in the original programs vast overkill?
Solution:
cp /usr/share/CPSUP/cc/laplace_file.c laplace15_file.c
cc -o laplace15_file.xc laplace15_file.c
./laplace15_file.xc
mv laplace_c.dat laplace15_c.dat
will copy, then compile, link, and run the program, and rename the output file to the specified
name.
(b) The statement
cp laplace15_file.c laplace29_file.c
will prepare for the editing that will generate a solution on a more refined grid. To change the
grid to 29 × 29, however, we simply edit the define statements to change xdim and ydim to
29. No other changes are necessary.
(c) Then, we compile, link, and run laplace29 file.c the program and rename the output file
with the statements
Exercise 9.14 (C) 11
cc -o laplace29_file.xc laplace29_file.c
./laplace29_file.xc
mv laplace_c.dat laplace29_c.dat
(d) To import these files into IDL, we start IDL and then execute the IDL statements
We do not, in the present case, have to worry about the association of positions in the array
with the proper values of the coordinates. Largely by accident, it has turned out to be correct.
In the creation of the file in the first place by the C program, we wrote for each j in turn
all values of i in the order 1, 2, 3, . . . across a row in the physical space of the problem.
Further, we wrote j = 1 first, then j = 2, etc. Thus, direct printing of the file would have
the x coordinate running correctly across the page from left to right but would have the y
coordinate upside down, running from the top to the bottom of the printed array. When we
read that file into IDL, however, we read the first row of the file into the first row of the internal
array (corresponding to the smallest value of y, the second row of the file into the second row
of the internal array (corresponding to the next value of y in the grid), etc. In other words,
the “upside-downness” of the array in the file has been exactly what we needed to have the
array in IDL be right side up.
Now, with the files properly read into IDL, the surface and contour plots are simply created
with the statements
The resulting graphs are shown in Figs. E9.3, E9.4, E9.5, and E9.6.
12 Exercise 9.14 (C)
Figure E9.3: IDL surface plot of solution gen- Figure E9.4: IDL surface plot of solution gen-
erated on 15 × 15 grid. erated on 29 × 29 grid.
Figure E9.5: IDL contour plot of solution gen- Figure E9.6: IDL contour plot of solution gen-
erated on 15 × 15 grid. erated on 29 × 29 grid.
It is, however, difficult to compare these two solutions by looking at these graphs. To effect a
more revealing comparison, we will first have to extract a 15 × 15 grid from the 29 × 29 grid
so we can compare the solutions at identical points in the physical space of the problem. We
do so with the statements
Then, to show the differences, we might subtract one from the other and ask about the range
of values in the difference with the statements
Figure E9.7: IDL surface plot of the difference between the solution generated on a 15 × 15 grid and
the solution generated on a 29 × 29 grid.
The resulting graph is shown in Fig. E9.7. We note that the largest differences occur in the
middle of the physical space and near the corners at which an edge at u = 100 meets an edge
at u = 0—both places where we might expect convergence to be slowest. Remember also that
the largest value of the solution in the entire domain is 100, so a difference of 1.000 is only a
difference of 1%. Still, there is a hint here that, despite likely convergence (see parts (e) and
(f)), discretization error in the solution on a 15 × 15 grid may still be significant, at least in
some portions of the domain of the problem.
(d) To import these files into MATLAB, we start MATLAB and then execute the MATLAB
statements1
We do not, in the present case, have to worry about the association of positions in the array
with the proper values of the coordinates. Largely by accident, it has turned out to be correct.
In the creation of the file in the first place by the FORTRAN program, we wrote for each j in
turn all values of i in the order 1, 2, 3, . . . across a row in the physical space of the problem.
Further, we wrote j = 1 first, then j = 2, etc. Thus, direct printing of the file would have
the x coordinate running correctly across the page from left to right but would have the y
coordinate upside down, running from the top to the bottom of the printed array. When we
read that file into MATLAB, however, we read the first row of the file into the first row of
the internal array (corresponding to the smallest value of y, the second row of the file into the
second row of the internal array (corresponding to the next value of y in the grid), etc. In
other words, the “upside-downness” of the array in the file has been exactly what we needed
to have the array in MATLAB be right side up.
1 Remember that transposition of the array read in from laplace*.dat is necessary so that the x and y coordinates
Now, with the files properly read into MATLAB, the surface and contour plots are simply
created with the statements
>> dx = 1.0/14.0;
>> [x,y] = meshgrid( 0.0:dx:1.0, 0.0:dx:1.0 );
>> mesh( x, y, u15, ’edgecolor’, ’black’, ’linewidth’, 2 );
>> set( gca, ’fontsize’, 14 )
>> xlabel( ’x’, ’fontsize’, 16 )
>> ylabel( ’y’, ’fontsize’, 16 )
>> zlabel( ’u15’, ’fontsize’, 16 )
>> print -dpdf ’program-ex14-cMAT-fig1.pdf’
>> print -deps2 ’program-ex14-cMAT-fig1.eps’
>> labs = [0, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90];
>> [c,h] = contour( x, y, u15, labs, ’k’, ’linewidth’, 2 );
>> set( gca, ’fontsize’, 14 )
>> xlabel( ’x’, ’fontsize’, 16 )
>> ylabel( ’y’, ’fontsize’, 16 )
>> axis square
>> clabel( c, h, [5,10,20,30,40,60], ’fontsize’, 16 );
>> dx = 1.0/28.0;
>> [x,y] = meshgrid( 0.0:dx:1.0, 0.0:dx:1.0 );
>> mesh( x, y, u29, ’edgecolor’, ’black’, ’linewidth’, 2 );
>> set( gca, ’fontsize’, 14 )
>> xlabel( ’x’, ’fontsize’, 16 )
>> ylabel( ’y’, ’fontsize’, 16 )
>> zlabel( ’u29’, ’fontsize’, 16 )
>> print -dpdf ’program-ex14-cMAT-fig2.pdf’
>> print -deps2 ’program-ex14-cMAT-fig2.eps’
>> [c,h] = contour( x, y, u29, labs, ’k’, ’linewidth’, 2 );
>> set( gca, ’fontsize’, 14 )
>> xlabel( ’x’, ’fontsize’, 16 )
>> ylabel( ’y’, ’fontsize’, 16 )
>> axis square
>> clabel( c, h, [5,10,20,30,40,60], ’fontsize’, 16 );
The resulting graphs are shown in Figs. E9.8, E9.9, E9.10, and E9.11.
It is, however, difficult to compare these two solutions by looking at these graphs. To effect a
more revealing comparison, we will first have to extract a 15 × 15 grid from the 29 × 29 grid
so we can compare the solutions at identical points in the physical space of the problem. We
do so with the statements
Then, to show the differences, we might subtract one from the other and ask about the range
of values in the difference with the statements
Figure E9.8: MATLAB surface plot of solu- Figure E9.9: MATLAB surface plot of solu-
tion generated on 15 × 15 grid. tion generated on 29 × 29 grid.
100 100
80 80
60 60
u15
u29
40 40
20 20
0 0
1 1
1 1
0.8 0.8
0.5 0.6 0.5 0.6
0.4 0.4
0.2 0.2
y 0 0 y 0 0
x x
Figure E9.10: MATLAB contour plot of solu- Figure E9.11: MATLAB contour map of solu-
tion generated on 15 × 15 grid. tion generated on 29 × 29 grid.
1 5 1 5
10
2040 10
20 0
5 5 4
0.9 10 30 0.9 10 30
5 5
20 20
60
60
0.8 0.8
0.7 0.7
40
40
10
10
30
30
0.6 0.6
0.5 0.5
y
y
20
5
20
5
60
60
0.4 0.4
0.3 0.3
40
40
10
10
30
30
0.2 0.2
20 20
5 5
0.1 10 0.1 10 6
320400
6040
5 320 5
0 1050 0 10 50
0 0.2 0.4 0.6 0.8 1 0 0.2 0.4 0.6 0.8 1
x x
>> dx = 1.0/14.0;
>> [x,y] = meshgrid( 0.0:dx:1.0, 0.0:dx:1.0 );
>> mesh( x, y, diff, ’edgecolor’, ’black’, ’linewidth’, 2 );
>> set( gca, ’fontsize’, 14 )
>> xlabel( ’x’, ’fontsize’, 16 )
>> ylabel( ’y’, ’fontsize’, 16 )
>> zlabel( ’difference’, ’fontsize’, 16 )
The resulting graph is shown in Fig. E9.12. We note that the largest differences occur in the
middle of the physical space and near the corners at which an edge at u = 100 meets an edge
at u = 0—both places where we might expect convergence to be slowest. Remember also that
the largest value of the solution in the entire domain is 100, so a difference of 1.000 is only a
difference of 1%. Still, there is a hint here that, despite likely convergence (see parts (e) and
(f)), discretization error in the solution on a 15 × 15 grid may still be significant, at least in
16 Exercise 9.14 (C)
Figure E9.12: MATLAB surface plot of the difference between the solution generated on a 15 × 15
grid and the solution generated on a 29 × 29 grid.
0.5
difference
−0.5
−1
1
1
0.8
0.5 0.6
0.4
0.2
y 0 0
x
(d) To import these files into OCTAVE, we start OCTAVE and then execute the OCTAVE
statements2
We do not, in the present case, have to worry about the association of positions in the array
with the proper values of the coordinates. Largely by accident, it has turned out to be correct.
In the creation of the file in the first place by the FORTRAN program, we wrote for each j in
turn all values of i in the order 1, 2, 3, . . . across a row in the physical space of the problem.
Further, we wrote j = 1 first, then j = 2, etc. Thus, direct printing of the file would have
the x coordinate running correctly across the page from left to right but would have the y
coordinate upside down, running from the top to the bottom of the printed array. When we
read that file into OCTAVE, however, we read the first row of the file into the first row of
the internal array (corresponding to the smallest value of y, the second row of the file into the
second row of the internal array (corresponding to the next value of y in the grid), etc. In
other words, the “upside-downness” of the array in the file has been exactly what we needed
to have the array in OCTAVE be right side up.
Now, with the files properly read into OCTAVE, the surface and contour plots are simply
created with the statements
>> dx = 1.0/14.0;
>> [x,y] = meshgrid( 0.0:dx:1.0, 0.0:dx:1.0 );
>> mesh( x, y, u15, ’edgecolor’, ’black’, ’linewidth’, 2 );
>> set( gca, ’fontsize’, 14 )
2 Remember that transposition of the array read in from laplace*.dat is necessary so that the x and y coordinates
Figure E9.13: OCTAVE surface plot of solu- Figure E9.14: OCTAVE surface plot of solu-
tion generated on 15 × 15 grid. tion generated on 29 × 29 grid.
100 100
80 80
60 60
u15
u29
40 40
20 20
0 0
1 1
0.8 1 0.8 1
0.6 0.8 0.6 0.8
0.4 0.6 0.4 0.6
y 0.4 y 0.4
0.2 0.2 x 0.2 0.2 x
0 0 0 0
>> dx = 1.0/28.0;
>> [x,y] = meshgrid( 0.0:dx:1.0, 0.0:dx:1.0 );
>> mesh( x, y, u29, ’edgecolor’, ’black’, ’linewidth’, 2 );
>> set( gca, ’fontsize’, 14 )
>> xlabel( ’x’, ’fontsize’, 16 )
>> ylabel( ’y’, ’fontsize’, 16 )
>> zlabel( ’u29’, ’fontsize’, 16 )
>> print -dpdf ’program-ex14-cOCT-fig2.pdf’
>> print -deps2 ’program-ex14-cOCT-fig2.eps’
>> [c,h] = contour( x, y, u29, labs, ’k’, ’linewidth’, 2 );
>> set( gca, ’fontsize’, 14 )
>> xlabel( ’x’, ’fontsize’, 16 )
>> ylabel( ’y’, ’fontsize’, 16 )
>> axis square
>> clabel( c, h, [5,10,20,30,40,60], ’fontsize’, 16 );
The resulting graphs are shown in Figs. E9.13, E9.14, E9.15, and E9.16.
It is, however, difficult to compare these two solutions by looking at these graphs. To effect a
more revealing comparison, we will first have to extract a 15 × 15 grid from the 29 × 29 grid
so we can compare the solutions at identical points in the physical space of the problem. We
do so with the statements
Figure E9.15: OCTAVE contour plot of solu- Figure E9.16: OCTAVE contour map of solu-
tion generated on 15 × 15 grid. tion generated on 29 × 29 grid.
1 1
5 10 5 10
60 60
30 40 20 30 40
0.8 20 0.8
0.6 0.6
y
10
5
5
10
0.4 0.4
0.2 0.2
20 20 30 40
30 40
60
5 10 60 5 10
0 0
0 0.2 0.4 0.6 0.8 1 0 0.2 0.4 0.6 0.8 1
x x
for j=1:15
u15from29(i,j)=u29(2*i-1,2*j-1);
end
end
Then, to show the differences, we might subtract one from the other and ask about the range
of values in the difference with the statements
>> dx = 1.0/14.0;
>> [x,y] = meshgrid( 0.0:dx:1.0, 0.0:dx:1.0 );
>> mesh( x, y, diff, ’edgecolor’, ’black’, ’linewidth’, 2 );
>> set( gca, ’fontsize’, 14 )
>> xlabel( ’x’, ’fontsize’, 16 )
>> ylabel( ’y’, ’fontsize’, 16 )
>> zlabel( ’difference’, ’fontsize’, 16 )
The resulting graph is shown in Fig. E9.17. We note that the largest differences occur in the
middle of the physical space and near the corners at which an edge at u = 100 meets an edge
at u = 0—both places where we might expect convergence to be slowest. Remember also that
the largest value of the solution in the entire domain is 100, so a difference of 1.000 is only a
difference of 1%. Still, there is a hint here that, despite likely convergence (see parts (e) and
(f)), discretization error in the solution on a 15 × 15 grid may still be significant, at least in
some portions of the domain of the problem.
(d) To import these files into PYTHON, we start PYTHON and then execute the PYTHON
statements3
3 Remember that transposition of the array read in from laplace*.dat is necessary so that the x and y coordinates
Figure E9.17: OCTAVE surface plot of the difference between the solution generated on a 15 × 15
grid and the solution generated on a 29 × 29 grid.
0.5
difference
0
-0.5
-1
1
0.8 1
0.6 0.8
0.4 0.6
y 0.4
0.2 0.2 x
0 0
We do not, in the present case, have to worry about the association of positions in the array
with the proper values of the coordinates. Largely by accident, it has turned out to be correct.
In the creation of the file in the first place by the FORTRAN program, we wrote for each j in
turn all values of i in the order 1, 2, 3, . . . across a row in the physical space of the problem.
Further, we wrote j = 1 first, then j = 2, etc. Thus, direct printing of the file would have
the x coordinate running correctly across the page from left to right but would have the y
coordinate upside down, running from the top to the bottom of the printed array. When we
read that file into PYTHON, however, we read the first row of the file into the first row of
the internal array (corresponding to the smallest value of y, the second row of the file into the
second row of the internal array (corresponding to the next value of y in the grid), etc. In
other words, the “upside-downness” of the array in the file has been exactly what we needed
to have the array in PYTHON be right side up.
Now, with the files properly read into PYTHON, the surface and contour plots are simply
created with the statements
Figure E9.18: PYTHON surface plot of solu- Figure E9.19: PYTHON surface plot of solu-
tion generated on 15 × 15 grid. tion generated on 29 × 29 grid.
100 100
80 80
60 60
u29
u15
40 40
20 20
0 0
1.0 1.0
0.8 1.0 0.8 1.0
0.6 0.8 0.6 0.8
0.6 0.6
y 0.4
0.2 0.2
0.4 x y 0.4
0.2 0.2
0.4 x
0.0 0.0 0.0 0.0
>>> lns = [5, 10, 20, 30, 40, 50, 60, 70, 80, 90]
>>> gr = plt.contour( x,y,u15, lns, colors=’black’ )
>>> plt.xlabel( ’$x$’, fontsize=14 )
>>> plt.ylabel( ’$y$’, fontsize=14 )
>>> plt.clabel( gr, lns )
>>> plt.axis( ’square’ )
>>> plt.show()
>>> lns = [5, 10, 20, 30, 40, 50, 60, 70, 80, 90]
>>> gr = plt.contour( x1,y1,u29, lns, colors=’black’ )
>>> plt.xlabel( ’$x$’, fontsize=14 )
>>> plt.ylabel( ’$y$’, fontsize=14 )
>>> plt.clabel( gr, lns )
>>> plt.axis( ’square’ )
>>> plt.show()
The resulting graphs are shown in Figs. E9.18, E9.19, E9.20, and E9.21.
Exercise 9.14 (C) 21
Figure E9.20: PYTHON contour plot of solu- Figure E9.21: PYTHON contour map of solu-
tion generated on 15 × 15 grid. tion generated on 29 × 29 grid.
1.0 1.0
10.000
5.000
0.8 0.8
0
20.00
0.6 0.6
30.000
10.000
20.000
30.000
40.000
50.000
60.000
70.000
80.000
90.000
40.000
50.000
60.000
70.000
80.000
90.000
5.000
y
y
0.4 0.4
0.2 0.2
0.0 0.0
0.0 0.2 0.4 0.6 0.8 1.0 0.0 0.2 0.4 0.6 0.8 1.0
x x
It is, however, difficult to compare these two solutions by looking at these graphs. To effect a
more revealing comparison, we will first have to extract a 15 × 15 grid from the 29 × 29 grid
so we can compare the solutions at identical points in the physical space of the problem. We
do so with the statements
Then, to show the differences, we might subtract one from the other and ask about the range
of values in the difference with the statements
The resulting graph is shown in Fig. E9.22. We note that the largest differences occur in the
middle of the physical space and near the corners at which an edge at u = 100 meets an edge
at u = 0—both places where we might expect convergence to be slowest. Remember also that
the largest value of the solution in the entire domain is 100, so a difference of 1.000 is only a
difference of 1%. Still, there is a hint here that, despite likely convergence (see parts (e) and
(f)), discretization error in the solution on a 15 × 15 grid may still be significant, at least in
some portions of the domain of the problem.
22 Exercise 9.14 (C)
Figure E9.22: PYTHON surface plot of the difference between the solution generated on a 15 × 15
grid and the solution generated on a 29 × 29 grid.
0.4
0.2
Difference
0.0
0.2
0.4
0.6
0.8
1.0
1.0
0.8 1.0
0.6 0.8
0.6
y 0.4
0.2 0.2
0.4 x
0.0 0.0
(e) The general strategy in this part of the exercise is, in each iteration, to calculate the new value
each node and store it initially in a temporary location so that, once the value is available, it
can be compared with the previous value at the node. Then, on the basis of that comparison,
we adjust what we think to be the largest change in the solution appropriately. We begin
by copying laplace29 file.c to laplace29 monitor.c. Then we edit this new file in the
following ways:
◦ Supposing that we will keep track of the largest change in a variable called bigchg, we
edit the double loop that calculates U[I][J] to evaluate the change at each point, adjust
the value of bigchg appropriately at each step, and only then store the newly calculated
value in its proper place in the array U. The loop then will read
for(i=1; i<xdim-1; i++) /* Conduct one iteration */
{ for(j=1; j<ydim-1; j++)
{tmp = 0.25 * ( U[i+1][j] + U[i-1][j] + U[i][j+1] + U[i][j-1] );
test = fabs(tmp-U[i][j]);
if (test > bigchg)
{bigchg = test; isave=i; jsave=j;}
U[i][j] = tmp;
}
where tmp stores the new value temporarily and test avoids double evaluation of the
absolute value of the difference between the newly evaluated value of U[I][J] and the
one from the previous iteration. Note that the program also records the position in U at
which the largest change occurs.
◦ At the beginning of each iteration, which means at the beginning of the loop on itcnt,
we must initialize bigchg to a value that is smaller than any value we will encounter with
the statement
bigchg = 0.0
(Remember that all values to be encountered will be positive.)
◦ Then, to comply with the request in the exercise, we must display at the end of the loop
on itcnt not only the value of bigchg but also, to label it, the position (isave, jsave)
in the array at which the maximum change occurs and the value of itcnt. We therefore
insert the statement
printf( "%d %d %d %e\n", itcnt, isave, jsave, bigchg);
Exercise 9.14 (C) 23
cc -o laplace29_monitor.xv laplace29_monitor.c
./laplace29_monitor.xc
and, in addition to the ultimate file laplace f.dat, produces the output
1 27 12 3.333333e+001
2 27 14 1.481482e+001
3 26 15 9.876543e+000
4 26 16 7.681757e+000
5 26 17 5.974697e+000
6 26 18 4.755371e+000
7 25 19 4.044098e+000
8 25 19 3.583462e+000
9 25 20 3.178917e+000
10 25 20 2.832386e+000
. .
. .
101 18 14 1.749020e-001
102 18 14 1.720600e-001
103 18 14 1.692696e-001
104 18 14 1.665344e-001
105 18 14 1.638489e-001
106 18 14 1.612148e-001
107 17 14 1.586399e-001
108 17 14 1.562538e-001
109 17 14 1.539078e-001
110 17 14 1.516018e-001
. .
. .
201 14 14 4.430771e-002
202 14 14 4.375267e-002
203 14 14 4.320717e-002
204 14 14 4.266167e-002
205 14 14 4.212952e-002
206 14 14 4.159927e-002
207 14 14 4.108047e-002
208 14 14 4.056549e-002
209 14 14 4.005623e-002
210 14 14 3.955460e-002
. .
. .
291 14 13 1.423836e-002
292 14 14 1.406097e-002
293 14 14 1.388550e-002
294 14 13 1.371002e-002
295 14 13 1.353836e-002
296 14 14 1.336861e-002
297 14 14 1.320076e-002
298 14 13 1.303673e-002
299 14 14 1.287079e-002
24 Exercise 9.14 (C)
300 14 14 1.271057e-002
Note that the beyond the first steps, convergence is rather slow. Note also that the problem
point is in the middle of the array U[I][J]. Once we get beyond the first 75 or 100 iterations,
the middle of the array is consistently the region that records the largest change with each
iteration.
(f) To address this last recasting, we will need to edit laplace29 monitor.c to request the desired
tolerance and then modify the test that terminates iteration to include a test on bigchg. Thus,
we copy laplace29 monitor.c to laplace29 tol.c and make the following changes:
◦ We add at the beginning a request for the desired (absolute) tolerance, storing that value
in the variable TOL, with the statement
◦ We also recast the program so that the value of maxit is supplied at execution time by
removing maxit from the define statement and adding at the beginning the statements
where we have assumed that this test is positioned in the loop after the iteration has been
fully completed and also after itcnt has been incremented to count the iteration.
Essentially, we must replace the outermost for loop with a while loop that initializes
itcnt and initiates the loop with the statements
itcnt = 1;
while (bigchg > tol and itcnt < maxit)
itcnt = itcnt + 1;
and setting bigchg to a value outside the loop that will prevent the loop from terminating
right away. Thus, the loop on itcnt becomes
bigchg = 2.0*tol;
itcnt = 0;
while (bigchg > tol and itcnt < maxit)
.
.
itcnt = itcnt+1;
Then we compile, link, and run the program with the statements
cc -o laplace29_tol.xc laplace29_tol.c
./laplace29_tol.x
Desired tolerance = 0.01
Maximum number of iterations = 300
Convergence to tolerance 0.010000 not achieved
in 300 iterations.
to learn what we already know from part (e) that the maximum change at the 300-th iteration
is bigger than 0.01, so we expected the program to terminate without achieving convergence.
We can, however, increase the allowed number of iterations to test the other stopping criterion
by providing the input
laplace29_tol.xc
Desired tolerance = 0.01
Maximum number of iterations = 1000
Tolerance 0.010000 achieved in 320 iterations.
With this program, we are in a position to explore the number of iterations required to achieve
convergence to a variety of tolerances. Repeated running with a maximum of 1000 iterations
yields the data for the table
It appears that, with 300 iterations and a 29×29 grid, we almost achieved an absolute tolerance
of 0.01 in the values generated and plotted. Changes of that magnitude will not be noticed to
the resolution of the graphs produced from the solution.
26 Exercise 9.14 (C)
/* PROGRAM laplace_file.c */
#include <stdio.h>
#include <math.h>
#define xdim 29
#define ydim 29
#define maxit 300
void main()
{
/***** Declare variables *****/
{ bigchg = 0.0;
for(i=1; i<xdim-1; i++) /* Conduct one iteration */
{ for(j=1; j<ydim-1; j++)
{tmp = 0.25 * ( U[i+1][j] + U[i-1][j] + U[i][j+1] + U[i][j-1] );
test = fabs(tmp-U[i][j]);
if (test > bigchg)
{bigchg = test; isave=i; jsave=j;}
U[i][j] = tmp;
}
}
printf( "%d %d %d %e\n", itcnt, isave, jsave, bigchg);
}
fclose( fptr );
/* PROGRAM laplace_file.c */
#include <stdio.h>
#include <math.h>
#define xdim 29
#define ydim 29
void main()
{
/***** Declare variables *****/
itcnt = 0;
bigchg = 2.0*tol;
28 Exercise 9.14 (C)
fclose( fptr );
}
Exercise 9.16 (C) 29
• five lines of text describing the contents of the file and its origin,
• one line containing the number of points N on the trajectory included in the file, and
• N lines, each of which contains four floating point values separated by commas, those values
being in order t, x(t), y(t), and z(t) for a point on the trajectory. (The N lines are ordered by
increasing value of t.)
Describe a general procedure to create this file and then implement that procedure in at least one
language of your choice, testing your program(s) with the trajectory given by
Note the position at which ncnt% is incremented. Further, to minimize problems from cumulative
roundoff, note that the program calculates each new time by adding the appropriate multiple of dt
to tstart rather than simply adding dt repeatedly to t. Since we are writing the values to the file
as they are generated, we have in the context of this program no reason to introduce a dimensioned
array to store all the values; we can afford to forget each value after it has been written to the file.
While conceptually fairly straightforward, the above program makes use of a loop structure
that is not available in C. Before developing more explicit coding, we must recast the loop so that
the EXIT_LOOP statement appears at the beginning of the loop. To do so, we must “prime” the
loop, i.e., we must execute the statements in the above loop but preceding the EXIT LOOP statement
before entering the loop and then execute them again at the end of the loop. To prime the loop, the
program above must be embellished to become
Now, we are ready to recast this program into an explicit program in C, though to create a compilable
and runnable program, we must assume a specific trajectory, which we take to be the trajectory
defined by the parametric equations
Further, to facilitate exploration, we cast the actual program so that (1) the time step and the
number of steps to be taken will be entered at execution time and (2) the descriptive lines reflect
the specific trajectory adopted as the example. We thus arrive at the program
/* PROGRAM trajectory.c */
#include <stdio.h>
#include <math.h>
Exercise 9.16 (C) 31
void main()
{
/***** Declare variables *****/
/***** Initialize variables, prime loop, write initial values to file *****/
fclose( fptr );
cc -o trajectory.xc trajectory.c
./trajectory.xc
Trajectory when
x(t) = cos(t)
y(t) = sin(t)
z(t) = 0.1 t
11
0.000 1.000 0.000 0.000
0.100 0.995 0.100 0.010
0.200 0.980 0.199 0.020
0.300 0.955 0.296 0.030
0.400 0.921 0.389 0.040
0.500 0.878 0.479 0.050
0.600 0.825 0.565 0.060
0.700 0.765 0.644 0.070
0.800 0.697 0.717 0.080
0.900 0.622 0.783 0.090
1.000 0.540 0.841 0.100
To produce the desired graphical display of this trajectory, we need to input this file into a
suitable graphics program. In IDL, we would execute the statements
to open the file, read past the descriptive lines, read the data contained in the file, close the file, and
(for convenience in reference) extract each column into a more mnemonically named variable. Then,
to display the trajectory in a graph, we execute the statements (see Section 2.16.2 of CPSUP )
The scaling was determined by noting that −1.0 ≤ x, y ≤ 1.0 (since those are the limits on the sine
and cosine functions) and 0 ≤ z ≤ 0.1.
The sample above, of course, did not trace the trajectory very far away from its origin. To find
a more suitable total time interval, we note that x and y each take time 2π to complete one cycle. If
we want to plot the trajectory through, say, 4 full cycles, we should allow t to run from 0 to about
8π ≈ 25.0, whence z will run from 0 to 2.5. If we rerun the program with the input
Figure E9.23: Trajectory when x(t) = cos t, y(t) = sin t, z(t) = 0.1t. Graph produced by IDL.
and then use identically the same IDL statements to read the resulting file into IDL and then use
the same statements with, however, the extension of zrange to 2.5, to plot the graph, we will find
the display shown in Fig. E9.23.
To produce the desired graphical display of this trajectory, we need to input this file into a
suitable graphics program. In MATLAB, we would execute the statements5
to open the file, read past the descriptive lines, read the data contained in the file, close the file, and
(for convenience in reference) extract each column into a more mnemonically named variable. Then,
to display the trajectory in a graph, we execute the statements (see Section 2.16.2 of CPSUP )
The scaling was determined by noting that −1.0 ≤ x, y ≤ 1.0 (since those are the limits on the sine
and cosine functions) and 0 ≤ z ≤ 0.1.
The sample above, of course, did not trace the trajectory very far away from its origin. To find
a more suitable total time interval, we note that x and y each take time 2π to complete one cycle. If
we want to plot the trajectory through, say, 4 full cycles, we should allow t to run from 0 to about
8π ≈ 25.0, whence z will run from 0 to 2.5. If we rerun the program with the input
and then use identically the same MATLAB statements to read the resulting file into MATLAB and
then use the same statements with, however, the extension of zrange to 2.5, to plot the graph, we
will find the display shown in Fig. E9.24.
5 For some reason, reading nlines creates a two-dimensional array, only the first element of which is the one we
want.
34 Exercise 9.16 (C)
Figure E9.24: Trajectory when x(t) = cos t, y(t) = sin t, z(t) = 0.1t. Graph produced by MATLAB.
2.5
1.5
0.5
0
1
0.5 1
0.5
0
0
−0.5
−0.5
−1 −1
To produce the desired graphical display of this trajectory, we need to input this file into a
suitable graphics program. In OCTAVE, we would execute the statements
to open the file, read past the descriptive lines, read the data contained in the file, close the file, and
(for convenience in reference) extract each column into a more mnemonically named variable. Then,
to display the trajectory in a graph, we execute the statements (see Section 2.16.2 of CPSUP )
The scaling was determined by noting that −1.0 ≤ x, y ≤ 1.0 (since those are the limits on the sine
and cosine functions) and 0 ≤ z ≤ 0.1.
The sample above, of course, did not trace the trajectory very far away from its origin. To find
a more suitable total time interval, we note that x and y each take time 2π to complete one cycle. If
we want to plot the trajectory through, say, 4 full cycles, we should allow t to run from 0 to about
8π ≈ 25.0, whence z will run from 0 to 2.5. If we rerun the program with the input
and then use identically the same OCTAVE statements to read the resulting file into OCTAVE and
then use the same statements with, however, the extension of zrange to 2.5, to plot the graph, we
will find the display shown in Fig. E9.25.
To produce the desired graphical display of this trajectory, we need to input this file into a
suitable graphics program. In PYTHON, we would execute the statements
Figure E9.25: Trajectory when x(t) = cos t, y(t) = sin t, z(t) = 0.1t. Graph produced by OCTAVE.
2.5
1.5
0.5
0
1
0.5 1
0 0.5
0
-0.5
-0.5
-1 -1
to open the file, read past the descriptive lines, read the data contained in the file, close the file, and
(for convenience in reference) extract each row into a more mnemonically named variable. Then, to
display the trajectory in a graph, we execute the statements (see Section 5.15.2 of CPSUP )
>>> fig=plt.figure()
>>> ax=plt.axes(projection=’3d’)
>>> ax.plot(x,y,z, linewidth=3,color=’black’)
The scaling was determined by noting that −1.0 ≤ x, y ≤ 1.0 (since those are the limits on the sine
and cosine functions) and 0 ≤ z ≤ 0.1.
The sample above, of course, did not trace the trajectory very far away from its origin. To find
a more suitable total time interval, we note that x and y each take time 2π to complete one cycle. If
we want to plot the trajectory through, say, 4 full cycles, we should allow t to run from 0 to about
8π ≈ 25.0, whence z will run from 0 to 2.5. If we rerun the program with the input
and then use identically the same PYTHON statements to read the resulting file into PYTHON and
then use the same statements with, however, the extension of zrange to 2.5, to plot the graph, we
will find the display shown in Fig. E9.26.
Alternative Program using a Subroutine: Because the priming of the loop in this exercise involves
several statements, we could simplify that operation by defining a FORTRAN subroutine that ac-
cepted the time as input and returned values of x, y, and z as output so as to be able to invoke that
subroutine in the two places where those values are needed. The subroutine would have the form
36 Exercise 9.16 (C)
Figure E9.26: Trajectory when x(t) = cos t, y(t) = sin t, z(t) = 0.1t. Graph produced by PYTHON.
2.5
2.0
1.5
1.0
0.5
0.0
1.00
0.75
0.50
0.25
1.000.75 0.00
0.500.25 0.25
0.50
0.000.25 0.75
0.500.75 1.00
1.00
Then, with this subroutine placed at the end of the file, the program would have the form
/* PROGRAM trajectorysub.c */
#include <stdio.h>
#include <math.h>
void main()
{
/***** Declare variables *****/
/***** Initialize variables, prime loop, write initial values to file *****/
fclose( fptr );
cc -o trajectoryseb.xc trajectoryseb.c
./trajectorysub.xc
the resulting file trajectory c.dat would be the same as the corresponding file displayed earlier.
38 Exercise 9.22 (C)
and
r2 = R sin θ2 cos φ2 î + sin θ2 sin φ2 ĵ + cos θ2 k̂
Remembering that the dot product of two vectors A and B is defined by
A•B
A • B = |A| |B| cos θAB =⇒ cos θAB =
|A| |B|
we conclude that the angle subtended at the center of the earth by the two vectors identifying the
points of interest on the earth’s surface is given by
−1 r1 • r2 r1 r2
θ12 = cos = •
r1 r2 R R
and then that the required distance is given by
d = R θ12
A listing of a program to accomplish the desired task is presented at the end of this solution. The
task is complicated a bit because the input information is to be given in terms of the normal latitude-
longitude designation of the points of interest, e.g., Albany, NY, at 42◦ 400 N latitude, 73◦ 450 W
longitude. Thus, the first task of the program before calculating the distance between two points
so specified is to obtain the input coordinates in the conventional terminology and then convert
that specification for each into the polar and azimuthal angles involved in the above expressions
for the radius vectors of the two points. We will use implicit variable typing, except that we need
to declare explicitly four character variables to contain single characters N or S for latitude, E or
W longitude. Using S as the initial character of a variable pertaining to the starting point and E
as the initial character for the ending point, LAT and LON to convey latitude and longitude, and
DEG and MIN in a variable name to convey degrees and minutes, we begin—Section 1—by declaring
four character variables that will ultimately contain the latitude and longitude of each point of
interest and dimensioning two vectors that will ultimately contain the three components of the two
vectors r1 /R and r2 /R. Then—Section 2—we set important constants. Next—Section 3—we print
instructions to clarify the way in which input data will be provided. In Section 4, we actually obtain
the input data, storing the three values for each latitude and longitude in three separate variables.
Exercise 9.22 (C) 39
The actual calculation begins in Section 5, where each specification of a latitude or longitude
is converted into the corresponding polar or azimuthal angle in the standard coordinate system and
ultimately expressed in radians. Note that, for south latitudes and west longitudes, the input angle
must have its sign changed and that the polar angles must be expressed as angles down from the
north pole rather than up or down from the equator.
Once the angles have been converted into the conventional coordinate system, we can calculate—
Section 6—the three components of the vectors r1 /R and r2 /R, storing them in the arrays prepared
in Section 1, and then the angle of interest.
Finally—Section 7—we calculate and display the required distance.
After storing this program in a file named greatcircle.c in the default directory, we compile
link and run the program for sample input with the statements6
cc -o greatcircle.xc greatcircle.c
./greatcircle.xc}
Starting latitude: 0 0 N
Starting longitude: 0 0 E
Ending latitude: 0 0 N
Ending longitude: 90 0 E
The distance is 6225.00 miles.
As a test, we have specified two points that are on the equator and 90◦ apart, in which case the
distance should be one-quarter of the earth’s circumference (i.e., 24900/4 = 6225 miles), which the
program has calculated as well. With a point at the pole and a second point on the equator, the
result is
Starting latitude: 90 0 N
Starting longitude: 0 0 E
Ending latitude: 0 0 N
Ending longitude: 0 0 E
The distance is 6225.00 miles.
6 To save space here, input data are displayed on the same line as the prompting message. In the actual run, these
We again confirm the expected value of one-quarter of the circumference of the earth. More in-
terestingly, the distance between Albany, NY (latitude and longitude above) and Los Angeles, CA
(34◦ 30 N, 118◦ 150 W) is
Starting latitude: 42 40 N
Starting longitude: 73 45 W
Ending latitude: 34 3 N
Ending longitude: 118 15 W
The distance is 2456.11 miles.
We make two further tests. For two points that are on the equator but more than 90◦ apart,
say, 120◦ we find that
Starting latitude: 0 0 S
Starting longitude: 90 0 E
Ending latitude: 0 0 N
Ending longitude: 45 0 W
The distance is 9337.50 miles.
which is, as expected, three-eighths of the circumference of the earth ((3/8) × 24900 = 9337.5 miles).
For two points that are on the equator and 270◦ apart, we find that
Starting latitude: 0 0 S
Starting longitude: 90 0 E
Ending latitude: 0 0 N
Ending longitude: 180 0 W
The distance is 6225.00 miles.
/* PROGRAM test.c */
#include <stdio.h>
#include <math.h>
void main()
{
PI = 3.14159265;
}
Exercise 9.22 (C) 41
Then we compile, link, and run this program with the statements
cc -o test.xf test.f
./test.xf
-1. 180.
-0.800000012 143.130096
-0.600000024 126.869896
-0.400000006 113.578178
-0.200000003 101.536957
0. 90.
0.200000003 78.4630432
0.400000006 66.4218216
0.600000024 53.1301003
0.800000012 36.8698959
1. 0.
(The output in this display was rearranged a bit to align columns.) Evidently the C inverse cosine
function does what we had expected. Thus, the program designed to address this exercise will
apparently work properly, always returning an angular separation of the two points of interest in the
range from 0◦ to 180◦ , i.e., the program will always return the shorter great circle distance between
the two points even if the angle between the associated two vectors exceeds 180◦ .
/* PROGRAM greatcircle.c */
#include <stdio.h>
#include <math.h>
void main()
/* 2. Set constants */
CIRCUM = 24900.0;
PI = 3.1415926535;
RADIUS = CIRCUM/(2.0*PI);
/* 3. Provide instructions */
X1[1] = sin(SLAT)*cos(SLON);
X1[2] = sin(SLAT)*sin(SLON);
X1[3] = cos(SLAT);
X2[1] = sin(ELAT)*cos(ELON);
X2[2] = sin(ELAT)*sin(ELON);
X2[3] = cos(ELAT);
Exercise 9.22 (C) 43
}
Chapter 11
Solving ODEs
To avoid conflict, we also change the name of the output file to decayie c.dat. The resulting
program is named decayie.f and is listed later in this solution. It is compiled and run with the
statements
cc -o decayie.xc decayie.c
./decayie.xc
Decay constant for A: 0.1
Decay constant for B: 0.1
Number of steps : 200
Time step : 0.25
Initial A : 1000.0
45
46 Exercise 11.26 (C)
Initial B : 0.0
Initial C : 0.0
where we choose the same inputs that we used in the example at the end of Section 9.12.1. We find
the first and last few lines of the output file to be
t A B C
0.00 1000.000 0.000 0.000
0.25 975.312 24.375 0.312
0.50 951.234 47.546 1.219
0.75 927.751 69.559 2.690
. . . .
49.50 7.087 35.070 957.843
49.75 6.912 34.377 958.711
50.00 6.742 33.697 959.562
These values, of course, differ from those obtained (see text) with the same parameters but using
Euler’s—as opposed to the improved Euler—method.
(b) Recasting decay.c so that it invokes the second-order Runge-Kutta algorithm also involves
changing how we calculate the new values. For this method, we replace the statements in the one
for loop with the statements
To avoid conflict, we also change the name of the output file to decayrk c.dat. The resulting
program is named decayrk.f and is listed later in this solution. It is compiled and run with the
statements
cc -o decayrk.xc decayrk.c
./decayrk.xc
where we choose the same inputs that we used in the example at the end of Section 9.12.1. We find
the first and last few lines of the output file to be
t A B C
0.00 1000.000 0.000 0.000
0.25 975.312 24.375 0.312
0.50 951.234 47.546 1.219
0.75 927.751 69.559 2.690
. . . .
Exercise 11.26 (C) 47
t ∆t A B C
Euler’s Method
6.000 1.0000 531.441 354.294 114.265
0.5000 540.360 341.280 118.360
0.2500 544.641 335.164 120.194
0.1250 546.740 332.197 121.063
0.0625 547.779 330.735 121.486
20.000 1.0000 121.577 270.170 608.253
0.5000 128.512 270.552 600.936
0.2500 131.938 270.642 597.421
0.1250 133.640 270.663 595.696
0.0625 134.489 270.669 594.843
Improved Euler’s Method
6.000 1.0000 549.404 327.821 122.776
0.5000 548.954 328.940 122.106
0.2500 548.846 329.202 121.951
0.1250 548.820 329.266 121.914
0.0625 548.814 329.282 121.904
20.000 1.0000 135.822 270.144 594.033
0.5000 135.452 270.549 593.999
0.2500 135.364 270.641 593.995
0.1250 135.342 270.663 593.994
0.0625 135.337 270.669 593.994
Second-Order Runge-Kutta Method
Results same as Improved Euler Method
Table 11.1: Representative values for solution at t = 6.0 and t = 20.0 for different time steps and
different methods.
These values, of course, differ from those obtained (see text) with the same parameters but using
Euler’s—as opposed to the improved Euler—method, but they agree with those obtained for the same
parameters with the improved Euler method. Of course, this latter result should not be surprising,
since the improved Euler method and the second-order Runge-Kutta method are basically the same,
though the calculation is set it up slightly differently, so as to facilitate setting up higher-order
Runge-Kutta methods.
To examine accuracy, we will solve the problem with several different time steps, recording for
comparison the solution obtained at t = 6.00 and t = 20.00. We find the results shown in Table 11.1.
Even with the smallest illustrated time step, Euler’s method appears to give results only to about
three digits while the improved Euler method and the second-order Runge-Kutta method appear to
give results to five digits or more.
48 Exercise 11.26 (C)
Listing of decayie.c
/* PROGRAM decayie.c */
#include <stdio.h>
#include <math.h>
main()
{
fclose( fptr );
}
Listing of decayrk.c
/* PROGRAM decayie.c */
#include <stdio.h>
#include <math.h>
main()
{
Bk1 = dBdt*dt;
Ck1 = dCdt*dt;
Ak2 = (-kA*(A + Ak1))*dt; /* Calculate k2 values */
Bk2 = (kA*(A + Ak1) - kB*(B + Bk1))*dt;
Ck2 = (kB*(B + Bk1))*dt;
A = A + .5*(Ak1 + Ak2); /* Calculate corrected values */
B = B + .5*(Bk1 + Bk2);
C = C + .5*(Ck1 + Ck2);
t = t + dt;
fprintf( fptr, "%6.2f %10.3f %10.3f %10.3f\n", t, A, B, C );
}
fclose( fptr );
}
Exercise 11.33 (Numerical Recipes-C) 51
to return the derivative dN/dT as implied by the dynamic equation for the system, hard coding
values of the parameters in the routine itself. In the main program, we (1) declare and initialize
several variables with the statements
(2) open a suitably named file and write a label and the initial values into that file with the statements
and (3) step the solution forward the selected number of time steps, writing the solution at each
time into the file, with the statements
/* Program growrk4.c */
#include <stdio.h>
#include <math.h>
#include "nr.h"
#include "nrutil.h"
Our choices for dt and N step the solution from time zero to time 100.0 in steps of 0.25. With these
segments (and appropriate opening and closing lines), the full program, which we name growrk4.c,
has the form shown in Table 11.2.
After copying the Numerical Recipes rk4.c and nrutil.c and the files nr.h and nrutil.h
from the Numerical Recipes library to the default directory, we compile, link, and run this program
with the statements
The output consists of the file growrk4 c.dat, whose first and last few lines are
Exercise 11.33 (Numerical Recipes-C) 53
T N
0.0000 15.0000
0.2500 15.3681
0.5000 15.7448
0.7500 16.1306
1.0000 16.5254
1.2500 16.9296
1.5000 17.3433
1.7500 17.7668
2.0000 18.2002
.
.
98.0000 499.1051
98.2500 499.1272
98.5000 499.1487
98.7500 499.1697
99.0000 499.1902
99.2500 499.2101
99.5000 499.2296
99.7500 499.2486
100.0000 499.2671
Since rk4.c is a non-adaptive routine, we need to assess the accuracy of this solution (i.e., the
adequacy of the step size dt = 0.25). To do so, let us repeat the solution with dt = 1.0, which
entails editing the program to change dt and N[1] appropriately. The first and last several lines in
the output this time are
T N
0.0000 15.0000
1.0000 16.5254
2.0000 18.2002
.
.
98.0000 499.1051
99.0000 499.1902
100.0000 499.2671
These values are in agreement with those first obtained to the fourth place after the decimal point.
Given that the method we are using is a fourth-order method, reducing the time step by a factor of
four should change the accuracy by a factor of about (1/4)4 = 1/256, and we should therefore be
able to believe the first results to several digits after the decimal place. A graph of these results—
you need to use your own graphics program to plot the data in the file produced by growrk4.c—is
shown in Fig. E11.1.
As an alternative approach, we choose to use the Numerical Recipe rkqs.c, which is a stepper
routine that controls the use of the algorithm routine rk4.c—actually rkck.c, which is a variant
of rk4.c—and implements an adaptive approach that gives us the opportunity to specify a desired
accuracy. The calling sequence for rkqs.c is
where dydt is the current value of the derivative, y and t on input are the current solution and time
and on output are the next solution and time, n is the number of first-order equations being solved,
dttry is the initial step size for the next step, eps is the desired accuracy in the solution, dtact
54 Exercise 11.33 (Numerical Recipes-C)
Figure E11.1: Growth of a Population with k = 0.1, initial population equal to 15 and capacity
equal to 500, solved with rk4.c
is the actual step size used in the step once completed, dtnext is the recommended step size for
the next step, and derivs is the name of the subroutine returning the derivatives of the dependent
variables. The variable yscale is more complicated. In words, it provides the value against which
the error is scaled. If we set yscale to y, then eps becomes a fractional accuracy because rkqs will
then strive to determine the solution so that, at each step the accuracy is eps*y; if, on the other
hand, we set yscale to a constant, then rkqs will seek to obtain a solution so that, at each step the
accuracy is eps times that constant and the solution will be sought to an absolute accuracy.
To invoke this routine, we of course need first a procedure to return the derivative of the
dependent variable. We have already coded the necessary subroutine with the statements
Then, we open a suitably named file and write a label and the initial values into that file with the
statements
Finally, we step the solution forward until the time first exceeds 100.0, writing the solution at each
time into the file, with the statements
With these segments (and appropriate opening and closing lines, the full program, which we
name growrkqs.c, has the form shown in Table 11.3.
After copying the Numerical Recipe rkqs.c and rkck.c from the Numerical Recipes library to
the default directory, we run this program with the statements
The output consists of the file growrkqs f.dat, whose first and last few lines are
T N
0.0000 15.0000
0.2500 15.3681
0.8849 16.3426
2.1484 18.4623
3.8794 21.7991
5.6241 25.7406
7.3357 30.2551
8.9894 35.3115
10.5924 40.9480
.
.
81.0057 495.1432
82.9330 495.9877
85.1481 496.7798
87.4485 497.4381
89.7705 497.9668
92.3678 498.4305
95.0694 498.8011
97.6773 499.0758
100.9184 499.3313
Believing that this adaptive method achieves the specified absolute accuracy of 10−6 , we presume
these values to be correct to close to the number of digits shown (except for roundoff problems).
The graph of these values is in complete accord with the graph presented earlier, though we elect
not to include this second graph.
56 Exercise 11.33 (Numerical Recipes-C)
/* Program growrkqs.c */
#include <stdio.h>
#include <math.h>
#include "nr.h"
#include "nrutil.h"
main()
{
float dttry, t; /* For time step, time */
float dtact, dtnext; /* For additional time steps */
float N[2], dNdt[2]; /* For N and dN/dt */
float eps, cscale[2]; /* For desired accuracy, scaling */
FILE *fptr; /* For output file */
where we have hard-coded the values of the three parameters in the system.
With this procedure, we can then create the program to track the evolution of the Lorenz
system. First, we declare and dimension necessary variables, and set several parameters—many of
them by entry at execution time—with the statements
Then, we open a suitable file and write a header and the initial values into the file with the statements
Finally, we invoke rk4.c in a loop to advance the solution from the initial values, writing the output
at each new step into the file and, in the end, closing that file. The necessary FORTRAN statements
are
With these segments (and appropriate opening and closing lines), the full program, which we name
lorenz.c, has the form shown in Table 11.4.
After the Numerical Recipes rk4.c and nrutil.c and the header files nr.h and nrutil.h have
been copied from the Numerical Recipes library to the default directory, we compile, link, and run
this program with the statements
Here, we have chosen a time step and number of steps so that the solution will be tracked for a total
time of 50.00 units. The first and last few lines in the resulting file lorenz c.dat are
T X Y Z
0.00 1.00000 0.00000 0.00000
0.01 0.91793 0.26634 0.00126
0.02 0.86792 0.51173 0.00466
0.03 0.84537 0.74465 0.00984
0.04 0.84681 0.97232 0.01673
0.05 0.86979 1.20112 0.02549
0.06 0.91265 1.43676 0.03640
0.07 0.97439 1.68451 0.04996
0.08 1.05463 1.94940 0.06682
0.09 1.15346 2.23633 0.08784
0.10 1.27146 2.55023 0.11414
0.11 1.40961 2.89615 0.14713
0.12 1.56929 3.27934 0.18861
0.13 1.75227 3.70535 0.24084
0.14 1.96069 4.18010 0.30669
0.15 2.19706 4.70988 0.38974
0.16 2.46428 5.30144 0.49454
.
.
49.84 -0.15248 0.94243 20.19732
49.85 -0.04919 0.92509 19.66492
49.86 0.04301 0.91574 19.14744
49.87 0.12593 0.91438 18.64436
49.88 0.20122 0.92100 18.15523
49.89 0.27036 0.93558 17.67966
49.90 0.33469 0.95810 17.21726
49.91 0.39543 0.98861 16.76771
49.92 0.45368 1.02720 16.33071
Exercise 11.36 (Numerical Recipes-C) 59
/* PROGRAM lorenz */
#include <stdio.h>
#include "nr.h"
#include "nrutil.h"
void main(void)
{
int N, i; /* Number of steps, loop index */
float dt, t; /* Time step, time */
float y[4], dydt[4]; /* Dependent variable, derivs */
FILE *fptr; /* Pointer for output file */
As a way of assessing the error in this calculation, we run the program again, this time stipulating
a time step of 0.04 and setting the number of steps to 1250 (so as to generate a solution over the
same total time interval). The program is run with the statement and input
./lorenz.xc
Initial x : 1.0
Initial y : 0.0
Initial z : 0.0
Time step : 0.04
Number of steps : 1250
and the first and last few lines in the resulting file are
T X Y Z
0.00 1.00000 0.00000 0.00000
0.04 0.84961 0.96847 0.01647
0.08 1.05670 1.94589 0.06651
0.12 1.57011 3.27626 0.18835
0.16 2.46382 5.29777 0.49426
.
.
49.84 -4.99980 -8.50150 14.05649
49.88 -6.62267 -11.25993 14.81306
49.92 -8.70811 -14.45579 17.06980
49.96 -11.13495 -17.32971 21.39102
50.00 -13.42790 -18.32227 27.69478
The chaotic nature of this system makes it difficult to know when a particular accuracy has been
achieved, since small differences early on will balloon to large differences when the solution is tracked
for long times into the future.
Chapter 13
Evaluating Integrals
Solution: We want to write and test a C program that will use Simpson’s rule
∆x
I= (f0 + 4f1 + 2f2 + · · · + 2fn−2 + 4fn−1 + f n)
3
to evaluate the integral
Z b
2 2
I=√ e−x dx
π a
for limits and number of divisions entered at the time of execution. The program trapezoidal.c
can serve as a modelbasis for the program to be created. We need, of course, to have a function
subprogram to return the value of the integrand. An appropriate segment of the code would then
involve the statements
The limits and number of divisions would be obtained from the keyboard with statements identical
to those in trapezoidal.c, namely
We would then have to set the increment that expresses the width of each strip in the division of
the interval a ≤ x ≤ b into n segments with the statement
61
62 Exercise 13.34 (C)
Then, the evaluation of the integral would be achieved by adding up the several terms in the above
equation. To achieve that end, we would use the statements
A full listing of this program appears on the last page of this solution. That listing includes the
necessary include statements and appropriate declaration of all variables.
As a futher wrinkle, that listing also includes explicit recognize that Simpson’s rule requires n
to be even and incorporates a segment of coding that rejects inadvertently entered odd values of n.
We simply test n as it is entered and return to ask for it again ifagain the entered value happens to
be odd. To that end, we would modify the statements requesting the C variable n to have the form
Once saved in the default directory with the name simpson.c, this program can be compiled
and run using the statements
cc -o simpson.xc simpson.c
./simpson.xc
Lower limit : 0
Upper limit : 1
Number of divisions: 8
Integral = 0.842703
We test the program’s capacity to reject odd values of n with the statements
./simpson.xc
Lower limit : 0
Upper limit : 1
Number of segments: 15
Value of n must be even.
Number of segments: 17
Value of n must be even.
Number of segments: 16
Integral = 0.842701
Exercise 13.34 (C) 63
Finally, we test the convergence of this evaluation by executing similar statements with other values
of n, obtaining the results
n Integral
2 0.843103
4 0.842736
8 0.842703
16 0.842701
32 0.842701
64 0.842701
128 0.842701
By the time 16 divisions have been used, the value of the integral has converged to 0.842701. As
illustrated in the text, the program trapezoidal.c required on the order of 256 or even 512 divisions
before the results it generated had converged to six digits after the decimal place.
64 Exercise 13.34 (C)
/* PROGRAM simpson.c
main()
{
float a, b, value; /* For limits, sum */
float dx; /* For step size */
int n, i; /* For number of segments, loop index */
Solution: We want to write and test a C program that will evaluate the integral
Z b
2 2
I=√ e−t dt
π a
using the five–point Gaussian formula,
Z b m 5
X ∆ti X ∆ti
g(t) dt = wj g tmid
i + xj
a i=1
2 j=1
2
(See the antepenultimate equation in Section 13.5.5.) The limits and the number of divisions are to
be entered at execution time.
We need, of course, to have a function to return the value of the integrand. An appropriate
segment of the code would then involve the statements
float func(float x)
{
return (2.0/sqrt(3.14159265))*exp(-pow(x,2));
}
The limits and number of divisions would be obtained from the keyboard with statements identical
to those in trapezoidal.c, namely
We would then have to set the increment that expresses the width of each strip in the division of
the interval a ≤ x ≤ b into n segments with the statement
dt = (b-a)/m;
We also need to define the basic constants that are important to the five-point Gaussian quadrature
formula. To that end, we include the statements
w[1]=0.23692689;
w[2]=0.47862867;
w[3]=0.56888889;
w[4]=0.47862867;
w[5]=0.23692689;
x[1]=-0.90617985;
x[2]=-0.53846931;
x[3]=0.0;
x[4]=0.53846931;
x[5]=0.90617985;
66 Exercise 13.35 (C)
where the values are copied from the table at the end of Section 11.5.5. Further, the variables x and
w need to be dimensioned. We add the statements1
to declare, type, and dimension all needed variables. Finally, we write the code
ans = 0.0;
for( j=1; j<m+1; j++ ) /* Outer sum */
{
sum = 0.0;
tmid = dt/2.0 + a + dt*(j-1);
for( i=1; i<=5; i++ ) /* Inner sum */
{
g[i] = func( tmid + dt*x[i]/2.0);
sum = sum + g[i]*w[i];
}
ans = ans + sum;
}
ans = (dt/2.0)*ans;
to evaluate the double sum in the general formula quoted above and the code
to display the final result. The complete listing of the final program, which has been named gauss.c,
appears on the next page.
Once the file gauss.c has been created and stored in the default directory, we compile and run
it with the statements
Then, choosing the interval a = 0.0 and b = 1.0 as a sample, we would run this program and generate
its output with statements like
./gauss.xc
Enter lower Limit: 0.0
Enter upper Limit: 1.0
Enter number of steps: 1
Integral= 0.84270078
./gauss.xc
Enter lower Limit: 0.0
Enter upper Limit: 1.0
Enter number of steps: 2
Integral = 0.84270084
Even treating the interval as a single strip, the five-point Gaussian approach appears to yield a result
that is accurate to six or seven digits after the decimal point. This result should be compared with
that obtained via Simpson’s rule in Exercise 13.34.
1 Remember that indices in C start at zero, so our desire to use indices running from 1 to 5 requires that we
/* PROGRAM gauss.c
#include <stdio.h>
#include <math.h>
float func(float x)
{
return (2.0/sqrt(3.14159265))*exp(-pow(x,2));
}
main()
{
/* Declare variables; set constant parameters */
w[1]=0.23692689;
w[2]=0.47862867;
w[3]=0.56888889;
w[4]=0.47862867;
w[5]=0.23692689;
x[1]=-0.90617985;
x[2]=-0.53846931;
x[3]=0.0;
x[4]=0.53846931;
x[5]=0.90617985;
/* Evaluate integral */
dt = (b-a)/m;
ans = 0.0;
for( j=1; j<m+1; j++ ) /* Outer sum */
{
sum = 0.0;
tmid = dt/2.0 + a + dt*(j-1);
for( i=1; i<=5; i++ ) /* Inner sum */
{
g[i] = func( tmid + dt*x[i]/2.0);
68 Exercise 13.35 (C)
/* Display result */
/* PROGRAM errqsimp */
#include <stdio.h>
#include <math.h>
#include "nr.h"
main()
{
float a, b, s; /* For limits, sum */
Note that only one invocation of qsimp is necessary and that func(x) describes the error function
specifically. Note also that we have accepted the default value of the (editable) parameter EPS within
qsimp, a parameter that sets the fractional tolerance to which the result is computed to the value
10−6 .
Once the above file has been stored in the default directory with the name errqsimp.c, we
compile and run the program with the statements
We must, of course, make sure that not only errqsimp.c but also qsimp.c, trapzd.c, nrutil.c,
and the header file nr.h are in the default directory. The output from this execution is
a value that agrees with all other evaluations we have made of this same integral.
70 Exercise 13.44 (Numerical Recipes–C)
To create the driver errqromb.c, we follow the same procedure, exchanging qromb.c for
qsimp.c. The resulting program contains the statements
/* PROGRAM errqromb */
#include <stdio.h>
#include <math.h>
#include "nr.h"
main()
{
float a, b, s; /* For limits, sum */
Note that only one invocation of qromb is necessary and that func(x) describes the error function
specifically. Note also that we have accepted the default value of the (editable) parameter eps within
qromb, a parameter that sets the fractional tolerance to which the result is computed to the value
10−6 .
Once the above file has been stored in the default directory with the name errqromb.c, we
compile and run the program with the statements
We must, of course, make sure that not only errqromb.f but also qromb.f, trapzd.f, and polint.f
are in the default directory. The output from this execution is
a value that agrees with all other evaluations we have made of this same integral.
Exercise 13.47 (Numerical Recipes–C) 71
as a function of the upper limit x, printing a table of values of x and F (x) for values of x ranging
in steps ∆x from x = a to x = b, where g(s) is defined by a function subprogram and ∆x, a, and b
are to be entered at execution time. Use the error function erf(x) as defined by
Z x
2 2
erf(x) = √ e−s ds
π 0
as a test integrand for your program.
Solution: To evaluate the quantity
Z v
02
f (v) = v 02 e−v /2
dv
0
a) for v = 1.0, we modify the driving program errtrapzd.c mentioned earlier in the chapter so that
the function reads
Then we can compile, link and run ertrapzd.xc (since all the other subroutines are presumably
still in the default directory from the exercises in the chapter) with the statements
and we can conclude that the value of the integral is 0.249094 (to six digits).
We modify the functions in errqtrap.c, errqsimp.c and errqromb.c in the same way, then
compile, link and run them with the statements
72 Exercise 13.47 (Numerical Recipes–C)
/* PROGRAM errtrapzd */
main()
{
float a, b, s; /* For limits, sum */
int numit, i, j; /* For number of iterations */
FILE *fptr; /* For file pointer */
We then compile, link, and run this program with the statements
Exercise 13.47 (Numerical Recipes–C) 73
/* PROGRAM errqtrap */
main()
{
float a, b, s; /* For limits, sum */
int i; /* For iterations */
FILE *fptr; /* For file pointer */
for(i=1;i<=1000;i++)
{
b = i*0.003;
s = qtrap( func, a, b ); /* Evaluate integral */
/* Write the upper limit and */
/* value of the integral to file */
fprintf(fptr, "%20.6f %20.6f\n",b,s);
}
fclose( fptr ); /* Close the file */
}
which we compile, link and execute as above to evaluate the integral using qtrap.c. We do similar
things to use qsimp.c and qromb.c. We can use the data in errtrapzd c.dat, errqtrap c.dat,
errqsimp c.dat, and errromb c.dat to create graphs, etc. if we like, such as those in Figs. E13.1
and E13.2. Note, in Fig. E13.2, that we must look very closely before we can see any differences in
the answers given by the various recipes, since they are in such close agreement.
74 Exercise 13.47 (Numerical Recipes–C)
Figure E13.1: The value of the integral as a function of its upper limit
1.4
1.2
1
Value of the integral
0.8
0.6
0.4
0.2
0
0 0.5 1 1.5 2 2.5 3
Value of the upper limit
Figure E13.2: Extreme close up showing different values given for the integral by the various recipes
1.17
1.17
trapzd.f
1.17 qtrap.f
qsimp.f
Value of the integral
1.17 qromb.f
1.17
1.17
1.17
1.17
Finding Roots
(b) With a pocket calculator, if we start with a = 2 and the initial guess x0 = 2, we then find that
1 2 1 2
x1 = x0 + = 2+ = 0.5 × 3 = 1.5
2 x0 2 2
Then, the next iteration gives
1 2 1 2
x2 = x1 + = 1.5 + = 1.4166667
2 x1 2 1.5
and the next iteration gives
1 2 1 2
x3 = x2 + = 1.4166667 + = 1.4142157
2 x2 2 1.416667
and the next gives
1 2 1 2
x4 = x3 + = 1.4142157 + = 1.4142136
2 x3 2 1.4142157
The square root of 2 obtained with the square root key on this pocket calculator gives 1.4142136, so
four iterations have apparently given us the square root of 2 to seven digits after the decimal place.
75
76 Exercise 14.12 (C)
/* Program squareroot.c */
#include <stdio.h>
#include <math.h>
float a, val, t;
int main(void){
float diff, temp;
diff = t + 1.0;
printf( "\nIterative Results:\n" );
while(diff > t){
temp = ( val + a/val )/ 2.0;
diff = fabs( temp - val );
val = temp;
printf("%14.6f\n", val);
}
printf( "Square root = %14.6f \n", val);
}
(c) Using basic commands, it is not difficult to construct a program that utilizes Newton’s method
to find the square root of a number. A sample C program is shown at the top of the next page.
The statement diff = toler + 1.0 is included before the loop to make sure that diff is indeed
greater than toler when the loop is started. Note also that the tolerance is an absolute tolerance
and that each iterate is printed so the user can see how quickly the algorithm converges.
Once this program has been stored in the default directory with the name squareroot.c, we
would compile and run the program with the statements
cc -o squareroot.xc squareroot.c
./squareroot.xc
Number whose root is to be found: 2.0
Initial guess: 1.0
Absolute tolerance: 0.0001
Iterative Results:
1.500000
1.416667
1.414216
1.414214
Square root = 1.414214
./squareroot.xc
Number whose root is to be found: 2.0
Exercise 14.12 (C) 77
./squareroot.xc
Number whose root is to be found: 55.0
Initial guess: 7.0
Absolute tolerance: 0.0001
Iterative Results:
7.428572
7.416209
7.416199
Square root = 7.416199
In all these examples, convergence to five digits after the decimal point occurs in no more than half
a dozen iterations.
78 Exercise 14.26 (C)
• use the functions FUNC and FUNCD to return f (x) and df (x)/dx, respectively.
• request a tolerance, an initial guess, a maximum number of iterations, and a flag—0 or 1—to
be entered when run.
• find the root, terminating iteration either when successive iterates differ by less than the
specified tolerance or when the specified maximum number of iterations is exceeded.
• print the final iterate only (flag = 0) or all iterates along the way (flag = 1).
• print a warning if iteration is terminated because the maximum number of iterates was ex-
ceeded.
• print the root and the value of the function at that root.
Solution: For the sake of a definite (and testable) program, we elect to use the function
x3 x2 x 1
V (x) = + − −
10000 200 500 2
used also in Section 12.1.1 as ab example. From the work in that section, we already know that this
cubic polynomial has three real roots and that those roots lie in the intervals
−50.00 < x1 < −47.50 ; −12.50 < x2 < −10.00 ; 7.50 < x3 < 10.00
To use Newton’s method, we must define both the function and its derivatives as procedures
with statements like
float func(float x)
{
return pow(x,3)/10000.0 + pow(x,2)/200.0 - x/500.0 - 0.5;
}
float funcd(float x)
{
return 3.0*pow(x,2)/10000.0 + 2.0*x/200.0 - 1/500.0;
}
Then, according to the stipulations for the program, we must request several controlling parameters
at execution time with the statements
The statements
Exercise 14.26 (C) 79
provide a counter for the number of iterations and define a variable to be used for storing the current
difference between two consecutive iterates, giving it a sufficiently large value to prevent termination
after only one iterate has been computed.
The heart of the computation now involves a loop in which the next iterate is computed using
Newton’s method, with the loop executing as long as the desired tolerance remains unattained and
the iteration count remains less than the maximum number of iterations to be allowed. We precede
the computation by outputing the initial guess. Thus, appropriate coding is the statements
Finally, we print the requested warning (if appropriate) and output the final value with the state-
ments
Then, with necessary variable declarations, compiler directives and a few more comments, the full
program is
/* PROGRAM newton.c */
#include <stdio.h>
#include <math.h>
80 Exercise 14.26 (C)
float func(float x)
{
return pow(x,3)/10000.0 + pow(x,2)/200.0 - x/500.0 - 0.5;
}
float funcd(float x)
{
return 3.0*pow(x,2)/10000.0 + 2.0*x/200.0 - 1/500.0;
}
main()
{
With the program in hand, we then compile, link and run it with test input with the statements
Tolerance = 0.00001
Max number of iterations = 15
Print all iterates? (1 for yes, 0 for no)= 0
Initial guess = -50.0
Root = -48.268269
Attained in 4 iterates
Value at root = 0.000000
./newton.xc
Tolerance = 0.00001
Max number of iterations = 15
Print all iterates? (1 for yes, 0 for no)= 0
Initial guess = -10.0
Root = -11.080437
Attained in 4 iterates
Value at root = 0.000000
./newton.xc
Tolerance = 0.00001
Max number of iterations = 15
Print all iterates? (1 for yes, 0 for no)= 0
Initial guess = 10.0
Root = 9.348704
Attained in 4 iterates
Value at root = 0.000000
which values are in essential agreement with those obtained by other means.
82 Exercise 14.26 (C)
To test the termination when the maximum number of iterates is exceeded, we use the input
./newton.xc
Tolerance = 0.00001
Max number of iterations = 2
Print all iterates? (1 for yes, 0 for no)= 0
Initial guess = -50.0
./newton.xc
Tolerance = 0.00001
Max number of iterations = 15
Print all iterates? (1 for yes, 0 for no)= 1
Initial guess = -50.0
-50.000000
-48.387096
-48.268887
-48.268269
-48.268269
Root = -48.268269
Attained in 4 iterates
Value at root = 0.000000
Exercise 14.14 (Numerical Recipes-C) 83
where K is the radius of gyration of the cross section of the bar, E is Young’s modulus for the
material of the bar, ρ is the density (mass/unit volume) of the material of the bar, and αn is a
solution to the equation
tan α = ± tanh α
Find the lowest half dozen natural frequencies for this bar. [A detailed discussion of this exercise can
be found in N. H. Fletcher and T. D. Rossing, The Physics of Musical Instruments, Second Edition
(Springer-Verlag, New York, 1991), pp. 57ff.]
Solution: The natural frequencies for the transverse vibrations of a bar of uniform cross section
are given by the equation s
4K E 2
ωn = α
L2 ρ n
where L is the length of the bar, K is the radius of gyration of the cross-section of the bar, E is
Young’s modulus for the material of the bar, ρ is the density of the material of the bar, and αn is a
solution to the equation
tan(α) = ± tanh(α)
Since K, L, E, and ρ are fixed for a given bar, the relationships among the natural frequencies of
that bar are determined by αn . The previous equation can be rewritten as
tan(α) ∓ tanh(α) = 0
Therefore, we can find the first six values of αn2 , and thus the lowest half dozen natural frequencies
of the bar, by finding the roots of this equation and then squaring them.
As a start, we must have some idea where the roots we seek lie. Thus, graphs of the functions
y = tan(α) − tanh(α) and y = tan(α) + tanh(α) should be made. A graph of these functions is
presented in Fig. E14.1.1 From this graph we conclude that the first six roots lie in the intervals
tan(α) = − tanh(α) 2.0 < x < 3.0 5.2 < x < 6.0 8.0 < x < 9.0
tan(α) = tanh(α) 3.5 < x < 4.5 6.5 < x < 7.5 10.0 < x < 10.7
You should use a program with which you are familiar to generate this—or an equivalent—graph.
Note in particular that the divergences of the tangent function (1) complicate the production of
a graph that does not have extraneous and erroneous (nearly) vertical lines at α equal to an odd
multiple of π/2 and (2) make it critical to avoid embracing any of these special values of α in any
of the intervals in which we seek a root.
To find the roots of this equation, we choose first to use the Numerical Recipe rtbis.c, which
we must copy to the default directory from the directory $NRHEAD/recipes_c-ansi/recipes. As
a start on a suitable driving program, we also copy the demonstration program xrtbis.c from
$NRHEAD/recipes_c-ansi/demo/src, saving it under the name natfreq bisect.c. Then, we mod-
ify the demonstration program in the following ways:
1 C by itself does not offer the possibility of making such graphs. The graph shown was produced by IDL. The
coding used to create an equivalent graph in the program(s) incorporated in this document is recorded at the end of
this solution.
84 Exercise 14.14 (Numerical Recipes-C)
1. We change every x in the program to an a. While this does not change the program at all,
it makes the program easier to correlate with the theoretical description of the exercise by
associating a in the program with α in the description.
2. Since we seek to find roots of the two functions shown above, we declare the variable j to be
global by placing the declaration
float j;
at the beginning of the program and outside of both the main program and the function
definition. Then, we replace the function definition in xrtbis.f with the definition
so, by assigning either the value −1.0 or 1.0 to j, we can select the specific function we wish to
examine. We must then remember to assign suitable values to j. Since there are two different
values, we anticipate that the main program will need two separate loops, one for finding the
roots of each function.
4. We add statements that calculate and save the squares of the roots. Again, saving six roots
with indices from 1 to 6 requires dimensions the vector to store them at 7.
5. Beyond each root and the associated value of the function (so we can confirm that a root has
been found), we arrange for the output to include the square of each root and the ratio of that
Figure E14.1: Graph of the equations with the upper sign, i.e., of y = tan(α) − tanh(α) (solid line)
and the lower sign, i.e., y = tan(α) + tanh(α) (dashed line).
Exercise 14.14 (Numerical Recipes-C) 85
square to the square of the lowest root (so we can easily compare the natural frequencies with
one another).
/* Program natfreq_bisect.c */
#include <stdio.h>
#include <math.h>
#include "nr.h"
#include "nrutil.h"
int main(void)
{
int i; /* Loop index */
float aacc, root, tmp;
float ab1[4], ab2[4], sqr[7]; /* Bounds, square of root */
Next, we must copy the files nr.h and nrutil.h from $NRHEAD/recipes_c-ansi/include and
nrutil.c from $NRHEAD/recipes_c-ansi/recipes into our default directory. We now compile and
link natfreq bisect.c, rtbis.c, and nrutil.c with the statement
./natfreq_bisect.xc
We conclude that the values of αn2 that correspond to the lowest six natural frequencies are 5.593318,
15.418199, 30.225845, 49.964859, 74.638878, and 104.247681, though not all the digits reported are
significant. Thus, we determine that the natural frequencies themselves are given by
s s
4K E 4K E
ω1 = 5.593318 2 ω2 = 15.418199 2 = 2.756539ω1
L ρ L ρ
s s
4K E 4K E
ω3 = 30.225845 2 = 5.403920ω1 ω4 = 49.964859 2 = 8.932955ω1
L ρ L ρ
s s
4K E 4K E
ω5 = 74.638878 2 = 13.344293ω1 ω6 = 104.247681 2 = 18.637896ω1
L ρ L ρ
Similar edits to the demo program xrtnewt.c, though adding the procedure funcd that returns
both the function and its first derivative, produces the program natfreq newt.f:
Exercise 14.14 (Numerical Recipes-C) 87
/* Program natfreq_newt.c */
#include <stdio.h>
#include <math.h>
#include "nr.h"
#include "nrutil.h"
int main(void)
{
int i; /* Loop index */
float aacc, root, tmp;
float ab1[4], ab2[4], sqr[7]; /* Bounds, square of root */
return 0;
}
This program uses the alternative recipe rtnewt.c and can be compiled, linked, and run with the
statements
Within roundoff error, this output is entirely consistent with that produced by the first program.
Exercise 14.18 (Numerical Recipes-C) 89
Exercise: The intensity I(x) in the diffraction pattern produced by a single slit is given by
I(x) sin2 x
=
I0 x2
where I0 is the intensity in the center and x is related to the position of the observation point away
from the central maximum. The zeroes in this pattern are easy to locate (they occur at x = nπ,
n = 0, ±1, ±2, . . .). Careful location of the maxima, however, is more complicated. They don’t occur
where sin2 x = 1 because of the influence of the denominator that steadily increases as x increases.
Locate the positions of the first half dozen maxima in this pattern, which—basically—is a request
to find the roots of the derivative of the function (though note that not all roots correspond to
maxima). Use at least three different methods and at least two different computational tools, and
compare the results. Do your results confirm that the roots approach odd multiples of 12 π as they
become large? Optional : You might also find it interesting to approximate the function with a power
series expansion for sin x, keeping quite a few terms but converting the root finding problem into
that of finding the roots of a polynomial. Then, use methods for finding roots of polynomials and
see if you can come to understand how accuracy depends on how many terms you keep and which
root you seek.
Solution: The intensity I(x) in the diffraction pattern produced by a single slit is given by
I(x) sin2 x
=
Io x2
A graph of this function is given in Fig. E14.2.2 We seek the points at which this function has its
2 C by itself does not offer the possibility of making such graphs. The graph shown was produced by IDL. The
coding used to create an equivalent graph in the program(s) incorporated in this document is recorded at the end of
this solution.
/* ex18bin.c */
#include <stdio.h>
#include <math.h>
#include "nr.h"
#include "nrutil.h"
#define N 150
#define NBMAX 20
#define X1 -0.5
#define X2 20.0
int main(void)
{
int i, nb=NBMAX;
float xacc, root, xb1[NBMAX+1], xb2[NBMAX+1];
This program prints the roots in the designated range. It also determines the value of the
function at the point, so we can see how close it actually is to 0. After copying rtbis.c,
zbrak.c, and nrutil.c from $NRHEAD/recipes_c-ansi/recipes and nr.h and nrutil.h from
$NRHEAD/recipes_c-ansi/include, we can compile, link, and execute the program by invoking the
statements
x f(x)
root 1 0.000000 -2.0210e-07
root 2 3.141593 -3.0598e-08
root 3 4.493410 -1.6585e-08
root 4 6.283185 -1.5299e-08
root 5 7.725252 -1.0368e-08
root 6 9.424777 -2.0936e-08
root 7 10.904122 -1.1563e-08
root 8 12.566370 -7.6495e-09
root 9 14.066195 -6.2504e-09
root 10 15.707963 -2.2545e-09
root 11 17.220757 -8.4612e-09
root 12 18.849554 -1.0468e-08
By comparing our results to the graph, we can see that the first root corresponds to a maximum,
the second to a minimum and so forth. Thus, the maxima are roots 1, 3, 5, etc.
To use rtnewt.c, we follow essentially the same procedure. Assuming that we already have
nr.h, nrutil.h, zbrak.c, and nrutil.c in our default directory, we only have to copy two more
files into our defualt directory. We must copy xrtnewt.c from $NRHEAD/recipes_c-ansi/demo/src
(saving it under the name ex18newt.c) and rtnewt.c from $NRHEAD/recipes_c-ansi/recipes. We
modify rtnewt.c in much the same way we did rtbis.c, only we also have to find the derivative
of the function whose roots we wish to locate, the second derivative of the intensity equation. The
modified program is
/* Program ex18newt.c */
#include <stdio.h>
#include <math.h>
#include "nr.h"
#include "nrutil.h"
#define N 150
#define NBMAX 20
#define X1 -0.5
#define X2 20.0
x f(x)
root 1 0.000000 3.8147e-06
root 2 3.141593 1.7716e-08
root 3 4.493410 -1.6585e-08
root 4 6.283185 8.8578e-09
root 5 7.725252 5.3489e-09
root 6 9.424778 5.3700e-10
root 7 10.904121 4.3453e-09
root 8 12.566371 4.4289e-09
root 9 14.066194 3.3411e-09
root 10 15.707963 -2.2545e-09
root 11 17.220755 4.3590e-09
root 12 18.849556 2.6850e-10
for the roots. A comparison to the graph shows us just as before that the odd numbered roots
correspond to the maxima. Notice that the roots found by rtbis.c are very similar to those found
by rtnewt.c.
Exercise 14.19 (Numerical Recipes-C) 93
Roots of bessj0:
x f(x)
root 1 2.404827 -0.000001
root 2 5.520077 0.000000
root 3 8.653729 0.000000
root 4 11.791533 0.000000
root 5 14.930930 -0.000003
root 6 18.071054 -0.000002
root 7 21.211639 0.000000
root 8 24.352465 -0.000001
root 9 27.493486 -0.000001
root 10 30.634581 -0.000004
root 11 33.775833 -0.000002
root 12 36.917088 -0.000001
root 13 40.058434 -0.000001
root 14 43.199783 -0.000001
root 15 46.341213 -0.000003
root 16 49.482594 -0.000002
To use rtnewt, we follow essentially the same procedure. Assuming that we already
have nr.h, nrutil.h, zbrak.c, nrutil.c, and bessj0.c in our default directory, we only
have to copy three more files into our default directory. We must copy xrtnewt.c from
$NRHEAD/recipes_c-ansi/demo/src (saving it under the name bessrtnewt.c) and rtnewt.c and
bessj1.c from $NRHEAD/recipes_c-ansi/recipes. Now, we need only compile and run the pro-
gram since the Bessel function is again the demo function. We compile and run the program with
the statements
3 M. Abramowitz and I. A. Stegun, Handbook of Mathematical Functions, U. S. Department of Commerce, Applied
Roots of bessj0:
x f(x)
root 1 2.404825 0.000000
root 2 5.520078 0.000000
root 3 8.653728 0.000000
root 4 11.791534 0.000000
root 5 14.930918 0.000000
root 6 18.071064 0.000000
root 7 21.211637 0.000000
root 8 24.352472 0.000000
root 9 27.493479 0.000000
root 10 30.634607 0.000000
root 11 33.775822 0.000000
root 12 36.917099 0.000000
root 13 40.058426 0.000000
root 14 43.199791 0.000000
root 15 46.341187 0.000000
root 16 49.482609 0.000000
Exercise 14.20 (Numerical Recipes-C) 95
1 + X2 1 + X2
V1 = 0.1 − and V2 = 0.2 −
8 + X4 8 + X4
respectively. (At the roots, the kinetic energy K will be K = 0.) We can find the roots of
this equations using the Numerical Recipes rtbis.c and rtnewt.c. To use rtbis.c, we must
copy several files into our default directory. First, we copy the driving program xrtbis.c from
$NRHEAD/recipes_c-ansi/demo/src into our default directory and rename the program with an
appropriate name (here we use the name potenergy.c). After copying the file we modify it to fit
our specific problem. Knowing that the roots lie in the range −4.0 ≤ x ≤ 4.0, we modify the range in
the program. We need also modify the definition of the function fx to conform with V1 . Further, we
must add the header file math.h file so that we can use the pow() function. The resulting program
lis
/* Program potenergy.c */
#include <stdio.h>
#include <math.h>
#define NRANSI
#include "nr.h"
#include "nrutil.h"
#define N 100
#define NBMAX 20
#define X1 -4.0
#define X2 4.0
int main(void)
{
int i,nb=NBMAX;
float xacc,root,*xb1,*xb2;
96 Exercise 14.20 (Numerical Recipes-C)
xb1=vector(1,NBMAX);
xb2=vector(1,NBMAX);
zbrak(fx,X1,X2,N,xb1,xb2,&nb);
printf("\nRoots of fx:\n");
printf("%21s %15s\n","x","f(x)");
for (i=1;i<=nb;i++) {
xacc=(1.0e-6);
root=rtbis(fx,xb1[i],xb2[i],xacc);
printf("root %3d %14.6f %14.6f\n",i,root,fx(root));
}
free_vector(xb2,1,NBMAX);
free_vector(xb1,1,NBMAX);
return 0;
}
#undef NRANSI
Next, we must copy the files nr.h and nrutil.h from $NRHEAD/recipes_c-ansi/include into
our defualt directory. Finally, we must copy the files rtbis.c, nrutil.c, and zbrak.c from
$NRHEAD/recipes_c-ansi/recipes into our default directory. We now compile potenergy.c (along
with rtbis.c, nrutil.c, and zbrak.c) with the statement
./potenergy.xc
Roots of fx:
x f(x)
root 1 -3.193141 0.000000
root 2 3.193141 0.000000
To find the roots of V2 , we need only adjust the function fx to read return 0.2- .... The result
of running the program in this form is
Roots of fx:
x f(x)
root 1 -2.074313 0.000000
root 2 -0.835000 0.000000
root 3 0.835000 0.000000
root 4 2.074313 0.000000
If we wish to use rtnewt.c instead, we must know our function and its derivative. For both
total energies, we have
2X 5 + 4X 3 − 16X
V10 = V20 =
X 8 + 16X 4 + 64
After copying xrtnewt.c from $NRHEAD/recipes_c-ansi/demo/src into the default directory,
we rename it (here the name is potnewt.c) and then modify it by changing the range (as for
potenergy.c) and entering the function and its derivative. The modified program is
Exercise 14.20 (Numerical Recipes-C) 97
/* Program potnewt.c */
#include <stdio.h>
#include <math.h>
#define NRANSI
#include "nr.h"
#include "nrutil.h"
#define N 100
#define NBMAX 20
#define X1 -4.0
#define X2 4.0
int main(void)
{
int i,nb=NBMAX;
float xacc,root,*xb1,*xb2;
xb1=vector(1,NBMAX);
xb2=vector(1,NBMAX);
zbrak(fx,X1,X2,N,xb1,xb2,&nb);
printf("\nRoots of fx:\n");
printf("%21s %15s\n","x","f(x)");
for (i=1;i<=nb;i++) {
xacc=(1.0e-6);
root=rtnewt(funcd,xb1[i],xb2[i],xacc);
printf("root %3d %14.6f %14.6f\n",i,root,fx(root));
}
free_vector(xb2,1,NBMAX);
free_vector(xb1,1,NBMAX);
return 0;
}
#undef NRANSI
Next, we must copy the file rtnewt.c from $NRHEAD/recipes_c-ansi/recipes into our default
directory. The other necessary files can be copied into the default directory as before if they are not
already there. To compile and run this program, we execute the statements
Roots of fx:
x f(x)
root 1 -3.193141 0.000000
root 2 3.193141 0.000000
To find the roots of V2 , we make the same single adjustment as we did for rtbis.c (both under fx
and funcd), recalling that the derivatives of the two functions are the same. The result is
Roots of fx:
x f(x)
root 1 -2.074313 0.000000
root 2 -0.835000 0.000000
root 3 0.835000 0.000000
root 4 2.074313 0.000000
Exercise 14.24 (Numerical Recipes-C) 99
12x4 − 12x3 + 4x − 1
one over an interval on x that is too broad to be useful and the second over an interval that more
clearly reveals the approximate location of the two real roots, are presented in Fig. E14.3 and
Fig. E14.4.4 Using C to find the roots of the equation is quite simple. We can find the roots either
with the Numerical Recipe rtbis.c or with the Numerical Recipe rtnewt.c. To use rtbis.c, we
copy xrtbis.c from $NRHEAD/recipes_c-ansi/demo/src into the default directory under a new
name (here fluidflow.c). After copying the file we modify it to fit our specific problem. First, we
must specify the range in which the program searches for roots. It appears from the graphs that if
we modify our parameters to take us through the range −1.0 ≤ x ≤ 1.0 or so, we will be able to
find all of the (real) roots. Further, we must also modify the the external function fx to return the
given polynomial, simplify xacc, and change the output comments to fit our problem. The resulting
program is
/* Program fluidflow.c */
#include <stdio.h>
#include <math.h>
#include "nr.h"
#include "nrutil.h"
#define N 100
#define NBMAX 20
4 C by itself does not offer the possibility of making such graphs. The graph shown was produced by IDL. The
coding used to create an equivalent graph in the program(s) incorporated in this document is recorded at the end of
this solution.
Figure E14.3: Graph of the polynomial Figure E14.4: Graph of the polynomial
with a wide domain. around the origin.
3
120000
100000
2
80000
1
60000
40000
–0.6 –0.4 –0.2 0 0.2 0.4 0.6
x
20000
–1
–10 –8 –6 –4 –2 2 4 6 8 10
x
100 Exercise 14.24 (Numerical Recipes-C)
#define X1 -1.0
#define X2 1.0
int main(void)
{
int i, nb=NBMAX;
float xacc, root, xb1[NBMAX+1], xb2[NBMAX+1];
To use rtnewt.c, we follow essentially the same procedure. Assuming that we already have
nr.h, nrutil.h, zbrak.c, and nrutil.c in our default directory, we only have to copy two more
files into our defualt directory. We must copy xrtnewt.c from $NRHEAD/recipes_c-ansi/demo/src
(saving it under the name fluidnewt.c) and rtnewt.c from $NRHEAD/recipes_c-ansi/recipes.
We modify fluidnewt.c in much the same way we modified the original version of fluidflow.c,
first, changing the parameters to scan through the range −1.0 ≤ x ≤ 1.0, then modifying the external
function fx to return the given polynomial, simplifying xacc, and changing the comment labeling
the output and including the math.h package. In addition, we enter the given polynomial and its
derivative into the subroutine funcd. The resulting program is
/* Program fluidnewt.c */
#include <stdio.h>
#include <math.h>
#include "nr.h"
#include "nrutil.h"
#define N 100
#define NBMAX 20
#define X1 -1.0
Exercise 14.24 (Numerical Recipes-C) 101
#define X2 1.0
int main(void)
{
int i, nb = NBMAX;
float xacc, root, xb1[NBMAX+1], xb2[NBMAX+1];
zbrak(fx,X1,X2,N,xb1,xb2,&nb);
printf("\nRoots of fluid flow problem:\n");
printf("%21s %15s\n","x","f(x)");
for (i=1;i<=nb;i++) {
xacc=(1.0e-6);
root=rtnewt(funcd,xb1[i],xb2[i],xacc);
printf("root %3d %14.6f %14.6f\n",i,root,fx(root));
}
}
Having already copied the necessary files to our default directory, we compile, link and execute the
program with the statements
From both programs we see that the two roots of the given fourth-order polynomial are
−0.556951 and 0.313410.
102 Exercise 14.30 (Numerical Recipes-C)
for a finite depth well. Write your program so that the value of c and the boundaries of the region in
which roots are to be sought are entered from the terminal at the time the program is run. Finally,
test your program with c = 25, comparing your results with those obtained in the text, and then
examine other values of c, seeking ultimately to obtain a graph showing each root as a function of c.
Solution: We examine the way that the energy levels of a quantum well change as the depth
and width of the well (specified by c) change. As described in CPSUP Section 12.1.5, the value of
s for an allowed energy level must satisfy the equation
p
s cot(s) = − c2 − s2
We can simplify this equation, as is done in Section 12.1.5, by squaring it, finding that the values of
s for allowed energy levels are the roots of the expression
s
sin(s) ∓ =0
c
Note however that, by simplifying the expression, we have possibly introduced spurious roots, so we
must ultimately check the roots in the original equation.
We can find the roots of our equation in FORTRAN using either the Numerical Recipe rtbis.c
and a corresponding driving program or the Numerical Recipe rtnewt.c and a corresponding driving
program.
Solution using rtbis.c:
To use rtbis.c, we must copy several files into our default directory. First, we copy the
demonstration program xrtbis.c from $NRHEAD/recipes_c-ansi/demo/src and save it under an
appropriate name (here qwell.c). Then, we modify this file to fit our specific problem by
• changing all variables whose names begin with x to variables with names beginning with s (so
as to facilitate correlation of the program with the statement of the problem).
• defining the function whose roots are to be found with the statements
where we have introduced the variable aj, which will be given the value +1 or the value −1
as a means to combine both signs in the above equation into a single program, and we define
the variables aj and c as global variables with the statement
float c, j;
placed outside the main program so values set in the main program can be communicated to
the function without placing them as explicit arguments to the function.Further, we add the
statements
Exercise 14.30 (Numerical Recipes-C) 103
to the main program so that values of c and aj can be entered at execution time.
• deleting the arguments s1 and s2 of the #define directives that specify the range of the search
and adding the statements
so that the range will be specified by the user at execution time. (Note that a search started
at the value s = 0 will generate an error message. We seek only positive roots, but we will,
when the time comes, start each search at s = −0.5 to avoid the problem just mentioned.)
• arranging for writing the results into a file for transfer to a graphing program by defining the
character variable filename and reading its value at execution time with the statements
char filename[20];
printf("\n Enter an appropriate file name (must be less than 20
characters and end in .dat): " );
scanf( "%s", filename );
fptr = fopen( filename, "w" );
We must, of course, also modify the output statement to write to this file rather than to the
screen and then, after all output has been produced, we must close the file.
These provisions allow the user to run the program multiple times without having to recompile it.
The resulting program is
/* Program qwell.c */
#include <stdio.h>
#include "nr.h"
#include "nrutil.h"
#include <math.h>
#include <string.h>
#define N 100
#define NBMAX 30
float c, aj;
int main(void)
{
104 Exercise 14.30 (Numerical Recipes-C)
zbrak(fs,s1,s2,N,sb1,sb2,&nb);
fptr = fopen( filename, "w" );
for (i=1;i<=nb;i++) {
sacc=(1.0e-6);
root=rtbis(fs,sb1[i],sb2[i],sacc);
fprintf(fptr, "%14.6f\n",root);
}
fclose(fptr);
return 0;
}
Next, we must copy the files nr.h and nrutil.h from $NRHEAD/recipes_c-ansi/include into
our default directory. Finally, we must copy the files rtbis.c, nrutil.c, and zbrak.c from
$NRHEAD/recipes_c-ansi/recipes into our default directory and compile qwell.c (along with
rtbis.c and zbrak.c) with the statement
Once the executable file qwell.xc is available, we run the program many times with different
values of c and different filenames. For example, with c = 2.0, we would create files containing the
roots for each sign with the input
./qwell.xc
Enter a value for the lower bound: -0.5
Enter a value for the upper bound: 60.0
Enter a value for c: 2.0
Enter -1 for an upper solution and 1 for a lower solution: -1
Enter an appropriate file name(ends in .dat
and is less than 20 characters): qwell02up_c.dat
./qwell.xc
Enter a value for the lower bound: -0.5
Enter a value for the upper bound: 60.0
Enter a value for c: 2.0
Enter -1 for an upper solution and 1 for a lower solution: 1
Enter an appropriate file name(ends in .dat
and is less than 20 characters): qwell02lo_c.dat
Exercise 14.30 (Numerical Recipes-C) 105
In similar fashion, We run the program many times to obtain the results for several values of c,
including c = 5, 10, 25, and 50. Each time we run the program we enter a different filename as the
output location for the data, changing in particular the sixth and seventh characters to reflect the
value of c to which the file applies. In particular, by displaying these files, we find that the roots
(some of which are spurious) for c = 2 are
• changing all variables whose names begin with x to variables with names beginning with s.
• defining the function whose roots are to be found exactly as above, using global variables to
communicate the values of aj and c from the main program.
• deleting the arguments s1 and s2 of the #define directives and adding statements to read
that range at execution time.
• arranging for writing the results into a file for transfer to a graphing program by defining
the character variable filename, reading its value at execution time, modifying the output
statement to write to this file rather than to the screen and,after all output has been produced,
closing the file.
• defining the function to return a derivative by adding the function funcd defined by static
void funcd(float s,float *fn, float *df) *fn= sin(s)+aj*s/c; *df = cos(s) + aj/c;
These provisions allow the user to run the program multiple times without having to recompile it.
The resulting program is
/* Program qwellnewt.c */
#include <stdio.h>
#include "nr.h"
#include "nrutil.h"
#include <math.h>
#include <string.h>
106 Exercise 14.30 (Numerical Recipes-C)
#define N 100
#define NBMAX 30
float c, aj;
int main(void)
{
float s1, s2;
char filename[20];
int i, nb=NBMAX;
float sacc, root, sb1[NBMAX+1], sb2[NBMAX+1];
FILE *fptr;
zbrak(fs,s1,s2,N,sb1,sb2,&nb);
fptr = fopen(filename, "w");
for (i=1;i<=nb;i++) {
sacc=(1.0e-6);
root=rtnewt(funcd,sb1[i],sb2[i],sacc);
fprintf(fptr, "%14.6f\n",root);
}
fclose(fptr);
return 0;
}
Finally, we must copy the files rtnewt.c from the directory $NRHEAD/recipes_c-ansi/recipes
and, if they are not already the several supporting files copied for use with qwell.c. Then, we
compile qwellnewt.c (along with several other files) with the statement
This program can then be run in the same way as qwellnewt.xc. One modification in the input is,
however, necessary. Newton’s method is more unstable than the method of bisection. In particular,
Exercise 14.30 (Numerical Recipes-C) 107
searching in the interval −0.5 < s < 60.0 apparently hits that instablilty, since, when run, the
program yields the output
./qwellnewt.xc
more temp.dat
0.000000
3.272885
6.039204
9.828836
12.062848
16.424784
18.043257
suggesting that the calculation of one of the roots has encountered a derivative that is close to
zero somewhere along the line. If, however, we search over a different range, that instability is not
encountered. For example, the input
./qwellnewt.xc
more temp.dat
0.000000
3.272885
6.039204
9.828836
12.062848
16.424784
18.043257
23.177778
23.864494
Once we find ways to avoid this instability, the output is the same in all cases as that obtained by
the method of bisection, and we suppress any further discussion or processing of that output.
Chapter 15
where ρ(x) is the mass per unit length of the string. (b) Taking ρ to be constant, show that the
tension τ (x), which is simply equal to the weight of the string below x, is given by τ (x) = ρg(l − x).
With this restriction, the equation of motion then becomes
∂2u
∂ ∂u
= (l − x) g
∂t2 ∂x ∂x
p
(c) Recast this equation in dimensionless form by introducing the variables x = x/l and t = t g/l.
(d) Suppose you seek a sinusoidal solution for which u(x, t) = f (x) cos ωt. Find the ODE satisfied
by f (x) and then introduce the new independent variable y defined by y 2 = 1 − x to find that,
expressed as a function of y, f satisfies the Bessel equation
d2 f df
y2 +y − 4ω 2 y 2 f = 0
dy 2 dy
Note: The variable transformation y 2 = 1 − x in effect recognizes that the vertical string is more
appropriately treated by locating points on the string relative to the bottom rather than the top of
the string!
Solution: (a) In order to deduce the equation of motion for a string that hangs vertically and
is acted on by gravity, we first determine the forces that act on the string. A force diagram is shown
in Fig. E15.1. Then, using Newton’s second law, F = ma, we write the equations of motion. We
take the transverse displacement to be u(x, t), and the longitudinal displacement to be v(x, t). Also
109
110 Exercise 15.2
τ(x,t)
θ(x,t)
x
x u
v
ρ∆xg
x+ ∆ x
τ(x+ ∆ x,t)
θ(x+ ∆ x,t)
Figure E15.1: Force diagram for a vertically hanging string acted upon by gravity.
note that we have chosen the positive x direction to be down, and we have chosen to measure u
positive to the right and v positive down. Then, with the mass of the element shown in the figure
given by ρ(x) ∆x, we write Newton’s second law for that element to be
∂2u
ρ(x) ∆x = τ (x + ∆x, t) sin θ(x + ∆x, t) − τ (x, t) sin θ(x, t)
∂t2
for the transverse motion of the string and
∂2v
ρ(x) ∆x = ρ(x) ∆x g + τ (x + ∆x, t) cos θ(x + ∆x, t) − τ (x, t) cos θ(x, t)
∂t2
for the longitudinal motion of the string. Now, we divide by ∆x, and let ∆x go to zero, reducing
the equations of motion to
∂2u ∂
ρ(x) 2 = τ (x, t) sin θ(x, t)
∂t ∂x
and
∂2v ∂
ρ(x) 2 = ρ(x)g + τ (x, t) cos θ(x, t)
∂t ∂x
We have too many unknowns in the above equations, so we must eliminate θ(x, t). Using
Fig. E15.2, we can see that
∂u
∆u ∆u ∂x
sin θ = = ="
∆s [∆u2 + (∆x + ∆v)2 ]1/2 2 2 #1/2
∂u ∂v
+ 1+
∂x ∂x
and that
∂v
∆u + ∆v 1+
cos θ = =" ∂x
∆s 2 2 #1/2
∂u ∂v
+ 1+
∂x ∂x
We can now substitute sin θ and cos θ into the two equations from which we need to eliminate
θ to find that
∂u
∂2u ∂ τ (x, t)
ρ(x) 2 =
∂x
∂t ∂x
"
2 2 #1/2
∂u ∂v
+ 1+
∂x ∂x
Exercise 15.2 111
∆s
∆x+ ∆v θ
∆u
Figure E15.2: Geometry for determining θ(x, t) from u(x, t) and v(x, t).
∂v
τ (x, t) 1 +
∂2v ∂ ∂x
ρ(x) 2 = ρ(x)g +
∂t ∂x
"
2 2 1/2
#
∂u ∂v
+ 1+
∂x ∂x
At this point, we have only the unknowns that we are looking for, u(x, t) and v(x, t). In order
to be able to solve these equations, we must make a few approximations. If (1) the amplitude of the
motion is small so that
∂u
∆u ∆x =⇒ 1
∂x
and (2) the motion is transverse so that v = 0 everywhere and always, then the equation for v
reduces to Z x
∂τ
0 = ρ(x) g + =⇒ τ (x) = − ρ(x0 ) g dx0
∂x l
where we find that, in this limit, τ cannot depend on t. (The limits are chosen so that τ will be zero
at the bottom of the string where x = l and grow to the total weight of the string at the top of the
string, where x = 0.) In this same limit, the equation for u becomes
∂2u
∂ ∂u
ρ(x) 2 = τ (x)
∂t ∂x ∂x
In the special case that ρ(x) is, in fact, constant, then the integral giving τ (x) can be quickly
evaluated to give that
τ (x) = ρ g (l − x)
and, in consequence, the equation for u becomes
∂2u
∂ ∂u
= g (l − x)
∂t2 ∂x ∂x
We
p next cast this result in a dimensionless form by introducing the variables x = x/l and
t = t g/l to find that
∂2u
∂ ∂u g ∂ ∂u
= gl (1 − x) = (1 − x)
∂t2 l ∂x l ∂x l ∂x ∂x
Suppose, now, that u(x, t) = f (x) cos ωt. Substitution of this form into the equation we have
deduced leads to the result
d df
ω2 f = (1 − x)
dx dx
Finally, let us substitute the independent variable y defined by y 2 = 1 − x, in terms of which
dy dy 1
2y = −1 =⇒ =−
dx dx 2y
With that change, the equation becomes
2
2 1 d 2 1 df 1 d df 1 d f df
ω f =− y − = y = y 2 +
2y dy 2y dy 4y dy dy 4y dy dy
Multiplying by 4y 2 bringing all terms to the same side of the equation, we finally find that
d2 f df
y2 +y − 4ω 2 y 2 f = 0
dy 2 dy
which is, in fact, the zeroth-order Bessel equation.
Exercise 15.2 113
d2 ϕ dϕ
a(x) + b(x) + c(x) ϕ = g(x)
dx2 dx
can be recast in self-adjoint form as defined in Section 15.1.7. Hint: Multiply the equation by an
undetermined integrating factor h(x), find a (first-order) differential equation for h(x), and solve
that equation—at least formally.
Solution: We want to show that the general second-order ODE
d2 ϕ dϕ
a(x) + b(x) + c(x) ϕ(x) = g(x)
dx2 dx
can be recast in self-adjoint form, specifically
d2 ϕ(x) dϕ(x)
−α(x) − α0 (x) + β(x) ϕ(x) = f (x)
dx2 dx
We’ll begin by multiplying the general equation by an undetermined function h(x), which we will
later find as an integrating factor, obtaining that
d2 ϕ dϕ
h(x) a(x) + h(x) b(x) + h(x) c(x) ϕ(x) = h(x) g(x)
dx2 dx
We can easily see that, for this equation to be recast in self-adjoint form, we must choose h(x) so
that
h(x) a(x) = −α(x) and h(x) b(x) = −α0 (x)
or
d
h(x) a(x) = h(x) b(x) =⇒ h0 (x) a(x) + h(x) a0 (x) = h(x) b(x)
dx
Equivalently, we have that
b(x) − a0 (x)
Z Z Z
dh b(x)
= dx =⇒ ln h = dx − ln a + C 0
h a(x) a(x)
which is exactly the coefficient of the derivative dϕ/dx in the first equation in this paragraph. To
finish, we recognize by comparing the ODE just derived with the second equation in this solution
that
c
R
β(x) = e (b/a) dx
a
and
g
R
f (x) = e (b/a) dx
a
Exercise 15.7 115
d2 u
+ k2 u = 0 ; u(0) = u(L) = 0
dx2
(a) Find the analytic solution to this problem and identify the special values of k that permit non-
trivial solutions. (b) Show that the finite difference approach to the problem leads to the matrix
eigenvalue problem Au = −k 2 L2 u/N 2 where N is the number of equal-length segments into which
the length L of the domain is divided and
−2 1 0 ··· 0 0 0 u1
1 −2 1 · · · 0 0 0 u2
0
1 −2 · · · 0 0 0
u3
A = ... .. .. .. .. .. ; u = ..
. . . . .
.
0
0 0 · · · −2 1 0
uN −3
0 0 0 · · · 1 −2 1 uN −2
0 0 0 ··· 0 1 −2 uN −1
Note that, when the domain 0 ≤ x ≤ L is divided into N segments, there will be N +1 nodes ranging
from x0 = 0 to xN +1 = L. Because, along the way to a solution, the boundary conditions result in
the rows and columns associated with these two nodes being deleted, these matrices will have only
N − 1 rows and columns. (c) Taking N = 100, use an available numeric processing program like
IDL, MATLAB, OCTAVE, or PYTHON to find the first several eigenvalues kn and compare your
results with the values found in part (a).
Solution: (a) The general solution to the differential equation is well known to be
At the two ends of the domain (i = 0 and i = N ), however, this equation involves quantities—u−1
and uN +1 —that are outside the domain, so we don’t know them. But the boundary conditions allow
us to ignore the original equations,
k 2 L2 k 2 L2
u1 − 2u0 + u−1 = − u0 ; uN +1 − 2uN + uN −1 = − uN
N2 N2
replacing them with the equations
u0 = 0 ; uN = 0
respectively. In total, then, the equations we seek to solve are
u0 = 0
k 2 L2
ui+1 − 2ui + ui−1 = − ui 1≤i≤N −1
N2
uN = 0
To recast these equations in a matrix form, we would leave out the first and last (for which we
already know a solution) and introduce the vector
u1 , u2 , u3 , . . . , uN −2 , uN −1
(though we should probably write it as a column rather than a row). Further, we then note that we
need to single out the equations for i = 1 and i = N − 1 for special treatment since, without that
treatment the first of these refers to u0 and the second refers to uN , which we have omitted from
the equations to be solved. For i = 1, for example, the equation becomes
k 2 L2 k 2 L2
u2 − 2u1 + u0 = − 2
u1 =⇒ u2 − 2u1 = − 2 u1
N N
(Remember that u0 = 0). Similarly, the equation for i = N − 1 becomes
k 2 L2 k 2 L2
uN −2 − 2uN −1 + uN = − uN −1 =⇒ u N −2 − 2uN −1 = − uN −1
N2 N2
(Remember that uN = 0.) Finally, we conclude that the equations to be solved are
k 2 L2
−2u1 + u2 = − u1
N2
k 2 L2
ui−1 − 2ui + ui+1 = − ui 2≤i≤N −2
N2
k 2 L2
uN −2 − 2uN −1 = −uN −1
N2
Here, we have reversed the order of the terms on the left-hand side so that the indices increase
across the equation rather than decrease, thus rendering more evident the equivalent—and more
compact—matrix presentation
−2 1 0 0 ... 0 0 0 u1
u1
1 −2 1 0 ... 0 0 0
u2 u2
0 1 −2 1 . . . 0 0 0
u3
u3
0 0 1 −2 . . . 0 0 0
u4 2 2 u4
k L
=− 2
.. .. .. .. .. .. .. N
. . . . . . . . . . ...
...
uN −3 uN −3
0 0 0 0 . . . −2 1 0
uN −2 uN −2
0 0 0 0 . . . 1 −2 1
0 0 0 0 ... 0 1 −2 uN −1 uN −1
Exercise 15.7 117
In terms of these quantities, the problem we must solve is the simple eigenvalue problem
√
k 2 L2 p N kn −λn
Au = λu = − 2 u =⇒ kn = −λn =⇒ = √
N L k1 −λ1
where A is the coefficient matrix, u is a vector of the unknown values at the nodes 1 to N − 1, and
λ = −k 2 L2 /N 2 is the eigenvalue to be found. At this point, we hope that the values of λn turn out
to be negative. Note that these matrices have dimension (N − 1) × (N − 1), even though there are
N + 1 nodes in the discretization on this domain.
118 Exercise 15.8
d2 u
+ k2 u = 0 ; u(0) = u(L) = 0
dx2
(a) Find the analytic solution to this problem and identify the special values of k that permit non-
trivial solutions. (b) Show that the finite element approach to the problem leads to the generalized
eigenvalue problem Au = −k 2 L2 Bu/6N 2 where N is the number of equal-length elements into which
the length L of the domain is divided and
−2 1 0 ··· 0 0 0 4 1 0 ··· 0 0 0
1 −2 1 · · · 0 0 0 1 4 1 ··· 0 0 0
0
1 −2 · · · 0 0 0
0 1 4 ··· 0 0 0
A = ... .. .. .. .. .. ; B = .. .. .. .. .. ..
. . . . .
. . .
. . .
0
0 0 · · · −2 1 0
0 0 0 ··· 4 1 0
0 0 0 · · · 1 −2 1 0 0 0 ··· 1 4 1
0 0 0 ··· 0 1 −2 0 0 0 ··· 0 1 4
and
u1
u2
u3
u=
..
.
uN −3
uN −2
uN −1
Note that, when the domain 0 ≤ x ≤ L is divided into N segments, there will be N +1 nodes ranging
from x0 = 0 to xN +1 = L. Because, along the way to a solution, the boundary conditions result in
the rows and columns associated with these two nodes being deleted, these matrices will have only
N − 1 rows and columns. (c) Taking N = 100, use an available numeric processing program like
IDL, MATLAB, OCTAVE, or PYTHON to find the first several eigenvalues kn and compare your
results with the values found in part (a).
Solution: (a) The general solution to the differential equation is well known to be
Following the pattern laid out in Section 15.9, we begin by discretizing the domain 0 ≤ x ≤ L
into equally-length elements whose end points lie at
L
xi = i , i = 0, 1, 2, . . . , N
N
so that, for the e-th element,
(e) (e)
x1 = xe ; x2 = xe+1 = xe + L/N , e = 0, 1, 2, . . . , N − 1
(e)
where the ũj s for j = 1, 2 are constants to be determined. The residual r(x) as defined in Sec-
tion 15.9.3 is for this exercise given by
d2 u
r(x) = + k2 u
dx2
The weighted averages of this residual over the e-th element when the weights are the interpolation
functions then are defined by
(e)
x2
d2 ũ(e)
Z
(e) (e)
Ri = Ni + k 2 ũ(e) dx
x1
(e) dx2
(e) (e)
x2
!
x2 (e)
dNi dũ(e) (e)
Z
(e) (e) dũ
= − − k 2 Ni ũ(e) dx + Ni ; i = 1, 2
x1
(e) dx dx dx (e)
x1
Here, the second form is calculated by evaluating the integral of the second derivative in the first
form by parts. Substituting from the above equation for u(e) (x), we next learn that
!
2 Z x(e) (e) (e) (e)
x(e)
(e)
X (e)
2 dN dNj (e) (e) (e) dũ 2
Ri = − ũj i
− k 2 Ni Nj dx + Ni
j=1
(e)
x1 dx dx dx x
(e)
1
(e) (e)
We have several integrals to evaluate. Because dN1 /dx = −L/N and dN2 /dx = L/N or,
(e)
more compactly dNk = (−1)k L/N , we conclude quickly that
(e) (e) (e)
x2 (e) x2
dNj L2 (e) L3
Z Z
dNi L L (e)
dx = (−1)i (−1)j dx = (−1)i+j 2 (x2 − x1 ) = (−1)i+j 3
x1
(e) dx dx x1
(e) N N N N
L3 /N 3 −L3 /N 3
" 5
L /3N 5 L5 /6N 5
" # #
h i
(e) 2
K = − +k
−L3 /N 3 L3 /N 3 L5 /6N 5 L5 /3N 5
" # " #
L3 −1 1 k 2 L5 2 1
= +
N3 1 −1 6N 5 1 2
and
(e) (e) dũ (e) dũ (e)
g2 = N2 − N2 = ũ0 (x2 ) = ũ0 (xe+1 )
dx (e)
x=x2 dx (e)
x=x1
so
−ũ0 (xe )
( )
(e)
g =
ũ0 (xe+1 )
Setting the average residual on the e-th element to zero, we conclude that the elemental equation
for the e-th element is
−ũ0 (xe )
" # " #! ( ) ( ) ( )
(e) L3 −1 1 k 2 L5 2 1 ũe 0
{R } = 3
+ 5
+ =
N 1 −1 6N 1 2 ũe+1 0
ũ (xe+1 ) 0
or
−ũ0 (xe )
" # " #! ( ) ( )
−1 1 k 2 L2 2 1 ũe N3
+ = 3
1 −1 6N 2 1 2 ũe+1 L ũ0 (xe+1 )
or, with β = k 2 L2 /6N 2 ,
−ũ0 (xe )
" #( ) ( )
−1 + 2β 1+β ũe N3
= 3
1+β −1 + 2β ũe+1 L ũ0 (xe+1 )
1 These integrals are simple enough to be evaluated by hand. Alternatively, they can be evaluated with an available
symbolic manipulating program like MAXIMA, MAPLE, or Mathematica. The details of those evaluations are
omitted.
2 Remember that N (e) (x(e) ) = 1, N (e) (x(e) ) = 0, N (e) (x(e) ) = 0, and N (e) (x(e) ) = 1.
1 1 1 2 2 1 2 2
Exercise 15.8 121
As guidance for inferring the general case, we write out these equation for a system in which
there are six nodes (N = 6, i = 0, 1, 2, 3, 4, 5) and five elements e = 0, 1, 2, 3, 4. For e = 0, the
equation, expanded to involve 6 × 6 matrices and six-element vectors, is3
ũ0 −ũ0 (x0 )
−1 + 2β 1+β 0 0 0 0
ũ1 ũ0 (x1 )
1+β −1 + 2β 0 0 0 0
ũ2
0 0 0 0 0 0 N3 0
= 3
ũ3 L
0 0 0 0 0 0
0
ũ4
0 0 0 0 0 0 0
ũ5
0 0 0 0 0 0 0
For e = 1,
ũ0
0 0 0 0 0 0
0
ũ1 −ũ0 (x1 )
0 −1 + 2β 1+β 0 0 0
ũ2 0
0 1+β −1 + 2β 0 0 0 N 3 ũ (x2 )
= 3
ũ3 L
0 0 0 0 0 0
0
ũ4
0 0 0 0 0 0 0
ũ5
0 0 0 0 0 0 0
For e = 2,
ũ0
0 0 0 0 0 0
0
ũ1
0 0 0 0 0 0 0
ũ2 0
0 0 −1 + 2β 1+β 0 0 N 3 −ũ (x2 )
= 3
ũ3 L ũ0 (x3 )
0 0 1+β −1 + 2β 0 0
ũ4
0 0 0 0 0 0 0
ũ5
0 0 0 0 0 0 0
For e = 3
ũ0
0 0 0 0 0 0
0
ũ1
0 0 0 0 0 0 0
ũ2
0 0 0 0 0 0 N3 0
= 3
ũ3 L 0
0 0 0 −1 + 2β 1+β 0
−ũ (x3 )
ũ4 ũ0 (x4 )
0 0 0 1+β −1 + 2β 0
ũ5
0 0 0 0 0 0 0
3 We have also rearranged the terms a bit to reduce the ”width” of the equation.
122 Exercise 15.8
For e = 4
ũ0
0 0 0 0 0 0
0
ũ1
0 0 0 0 0 0 0
ũ2
0 0 0 0 0 0 N3 0
= 3
ũ3 L
0 0 0 0 0 0
0
ũ4 −ũ0 (x4 )
0 0 0 0 −1 + 2β 1 + β
ũ5 0
0 0 0 0 1+β −1 + 2β ũ (x5 )
The next step is to add these equations to find the single matrix equation
ũ0 −ũ0 (x0 )
−1 + 2β 1+β 0 0 0 0
ũ1
1+β −2 + 4β 1+β 0 0 0 0
ũ2
0 1+β −2 + 4β 1+β 0 0 N3 0
= 3
ũ3 L
0 0 1+β −2 + 4β 1+β 0
0
ũ4
0 0 0 1+β −2 + 4β 1+β 0
ũ5 0
0 0 0 0 1+β −1 + 2β ũ (x5 )
Finally, we have to incorporate the boundary conditions, which essentially require that ũ0 =
ũ5 = 0. We change the first row and last rows in the coefficient matrix to reflect these conditions:
ũ0
1 0 0 0 0 0
0
ũ1
1+β −2 + 4β 1+β 0 0 0 0
ũ2
0 1+β −2 + 4β 1+β 0 0 3 0
N
= 3
ũ3 L
0
0 1+β −2 + 4β 1+β 0
0
ũ4
0 0 0 1+β −2 + 4β 1+β 0
ũ5
0 0 0 0 0 1 0
Further, since the second element in the first column of the coefficient matrix is multiplied by ũ0 = 0
and the next to last element in the last column of the coefficient matrix is multiplied by ũ5 = 0,
we can replace each of those elements by 0. Further, since the right hand side of this equation is
entirely zero, we can remove the factor N 3 /L3 . We find that
ũ0
1 0 0 0 0 0
0
ũ1
0 −2 + 4β 1+β 0 0 0 0
ũ2
0 1+β −2 + 4β 1+β 0 0 0
=
ũ3
0 0 1+β −2 + 4β 1+β 0
0
ũ4
0 0 0 1+β −2 + 4β 0 0
ũ5
0 0 0 0 0 1 0
Exercise 15.8 123
we conclude that the problem is actually a bit simpler, since we already know ũ0 and ũ5 . We can
therefore delete the first and last rows and columns to find the essential problem we must solve to
be
ũ1 ũ1
2 −1 0 0
4 1 0 0
ũ ũ
−1 2 −1 0 2 1 4 1 0 2
= −β
ũ3 ũ3
0 −1 2 −1 0 1 4 1
ũ ũ
0 0 −1 2 4 0 0 1 4 4
We can now readily extend this result to apply to an arbitrary number of elements by introducing
the quantities4
−2 1 0 ··· 0 0 0 4 1 0 ··· 0 0 0
1 −2 1 · · · 0 0 0 1 4 1 ··· 0 0 0
0
1 −2 · · · 0 0 0
0 1
4 ··· 0 0 0
.. .
.. .
.. .
.. .
.. .
.. ; B = . . .. .. .. ..
A= . .. ..
. . . .
0
0 0 · · · 2 −1 0
0 0
0 ··· 4 1 0
0 0 0 · · · −1 2 −1 0 0 0 ··· 1 4 1
0 0 0 · · · 0 −1 2 0 0 0 ··· 0 1 4
and
u1
u2
u3
u=
..
.
uN −3
uN −2
uN −1
In terms of these quantities, the problem we must solve is the generalized eigenvalue problem
√
k 2 L2 p N kn βn
Au = −βB u = − B u =⇒ kn = 6βn =⇒ = √
6N 2 L k1 β1
where A and B are matrices, u is a vector of the unknown values at the nodes 1 to N − 1, and
β = k 2 L2 /6N 2 is the eigenvalue to be found. At this point, we hope that the values of βn are
positive. Note that these matrices have dimension (N − 1) × (N − 1), even though there are N + 1
nodes in the discretization on this domain.
4 We now drop each tilde, understanding that u now stands for the approximate solution.
124 Exercise 15.9
d2 ϕ(x) f
+ λ2 ϕ(x) = − (E15.9.4)
dx2 α
which is an inhomogeneous Helmholtz equation in one dimension.
We know that the general solution to this inhomogeneous equation consists of the sum of
a general solution to the homogeneous equation (f = 0) and a particular solution which, when
substituted into the equation, yields the inhomogeneity. While the independent variable is a space
variable rather than time, the homogeneous equation is basically the equation for simple harmonic
oscillation and has the general solution
Now we are left with the task of using the boundary conditions to solve for the constants A and
B. We first use Eq. (E15.9.2) to find that
f f
ϕ(0) = B + =p or B = p − (E15.9.8)
β β
Exercise 15.9 125
Then we use Eq. (E15.9.3) to find A. We substitute the solution for ϕ(x) from Eq. (E15.9.7) to find
that
f
α Aλ cos(λx) − Bλ sin(λx) + γ A sin(λx) + B cos(λx) + =q (E15.9.9)
β x=L
If we substitute x = L, rearrange the equation to solve for A, and substitute the result for B from
Eq. (E15.9.8), we find that
Optional Part
To address the optional part of this exercise, we look next at what happens when β/α = 0. The
differential equation then becomes
d2 ϕ(x) f
=− (E15.9.12)
dx2 α
and the general solution, which we can obtain simply by integrating the equation twice and inserting
appropriate integration constants, is
f x2
ϕ(x) = − + Ax + B (E15.9.13)
2α
Once again we use the boundary conditions to solve for A and B.
ϕ(0) = B = p (E15.9.14)
f L2
dϕ(x) fL
α + γϕ(x) =α − +A +γ − + AL + B = q (E15.9.15)
dx x=L α 2α
f L2 γ
q − pγ + f L +
A= 2α (E15.9.16)
α + γL
So the final solution for the case β/α = 0 is
f L2 γ
f x2 q − pγ + f L +
ϕ(x) = − + 2α x + p (E15.9.17)
2α α + γL
Finally, we deduce the solution when β/α > 0. In this case, we would set ξ 2 = β/α and write
the differential equation in the form
d2 ϕ(x) f
− ξ 2 ϕ(x) = − (E15.9.18)
dx2 α
The solution to the homogeneous equation this time is a linear combination of the exponentials eξx
and e−ξx and a suitable particular solution is still φp = f /β. Thus, the general solution to this
equation is
f
ϕ(x) = Aeξx + Be−ξx + (E15.9.19)
β
Once again we use the boundary conditions to find that the constants A and B must satisfy both
f f
ϕ(0) = A + B + =p =⇒ A+B =p− (E15.9.20)
β β
and
ξx −ξx ξx −ξx f
α(Aξe − Bξe ) + γ(Ae + Be + ) =q
β x=L
γf
=⇒ (γ + αξ)eξL A + (γ − αξ)e−ξL B = q − (E15.9.21)
β
Solved for A and B, these two equations, yield the results
q − γf /β + e−ξL (αξ − γ)(p − f /β)
A= (E15.9.22)
(αξ + γ)eξL + (αξ − γ)e−ξL
and
−(q − γf /β) + eξL (αξ + γ)(p − f /β)
B= (E15.9.23)
(αξ + γ)eξL + (αξ − γ)e−ξL
Exercise 15.9 127
With these values, the final solution for the case β/α > 0 is
q − γf /β + e−ξL (αξ − γ)(p − f /β)
ϕ(x) = eξx
(αξ + γ)eξL + (αξ − γ)e−ξL
−(q − γf /β) + eξL (αξ + γ)(p − f /β) f
+ e−ξx + (E15.9.24)
(αξ + γ)eξL + (αξ − γ)e−ξL β
128 Exercise 15.12
Exercise: Continuing with the circumstances of Exercises 15.10 and 15.11, suppose the region
of interest is divided into three three-noded elements, e = 1, 2, 3 with global nodes 1, 2, 3, 4, 5, 6, 7,
nodes 1, 2, and 3 in element 1, nodes 3, 4, and 5 in element 2, and nodes 5, 6, and 7 in element 3.
Suppose node 2 is midway between nodes 1 and 3, node 4 is midway between nodes 3 and 5, and
node 6 is midway between nodes 5 and 7, but do not suppose the lengths of the three elements are
the same. Following the pattern in Sections 15.9.4 and 15.9.5, assemble the elemental equations
for these three elements into an equation for the whole system analogous to the lasst equation in
Section (15.9.5) if the solution is required to satisfy the boundary conditions of Eqs. (15.68) and
(15.69).
Solution: The specific geometry of the system in this exercise is clarified in Fig. E15.3. The
system of interest has three elements and seven distinct nodes.
In Exercise 15.11, we found that the elemental equation applicable to a three-noded linear
element (e) is
(e) (e) (e)
(e) (e) (e)
K11 K12 K13 ϕ̃1
b1
g
1
(e) (e) (e) (e) (e)
K21 K22 K23 ϕ̃2 = b2 + 0
(e) (e) (e) (e)
(e) (e)
K31 K32 K33 ϕ̃3 b3
g3
For the system shown in Fig. E15.3, we want to assemble the elemental equations for each element
into an equation for the whole system if the solution is required to satisfy the boundary conditions
dϕ(x)
ϕ(x)|x=0 = p ; α(x) + γϕ(x) =q
dx x=L
or equivalently,
(1) (1) (1) (1) (1) (1) (1) (1)
K11 ϕ̃1 + K12 ϕ̃2 + K13 ϕ̃3 = b1 + g1
(1) (1) (1) (1) (1) (1) (1)
K21 ϕ̃1 + K22 ϕ̃2 + K23 ϕ̃3 = b2
(1) (1) (1) (1) (1) (1) (1) (1)
K31 ϕ̃1 + K32 ϕ̃2 + K33 ϕ̃3 = b3 + g3
Similarly, we can write out the equations for the remaining two elements, finding that
(2) (2) (2) (2) (2) (2) (2) (2)
K11 ϕ̃1 + K12 ϕ̃2 + K13 ϕ̃3 = b1 + g1
(2) (2) (2) (2) (2) (2) (2)
K21 ϕ̃1 + K22 ϕ̃2 + K23 ϕ̃3 = b2
(2) (2) (2) (2) (2) (2) (2) (2)
K31 ϕ̃1 + K32 ϕ̃2 + K33 ϕ̃3 = b3 + g3
and
(3) (3) (3) (3) (3) (3) (3) (3)
K11 ϕ̃1 + K12 ϕ̃2 + K13 ϕ̃3 = b1 + g1
(3) (3) (3) (3) (3) (3) (3)
K21 ϕ̃1 + K22 ϕ̃2 + K23 ϕ̃3 = b2
(3) (3) (3) (3) (3) (3) (3) (3)
K31 ϕ̃1 + K32 ϕ̃2 + K33 ϕ̃3 = b3 + g3
For this region, the relationship between the local and global numbering systems is defined as
(e) (e) (e)
x1 = x2e−1 ; x2 = x2e ; x3 = x2e+1 ; e = 1, 2, 3
(1) (2) (2) (3)
The condition of continuity at nodes 2 and 3 then requires that ϕ̃3 = ϕ̃1 and ϕ̃3 = ϕ̃1 . Further,
as evidenced in Fig. E15.3,
(1) (1) (1) (2) (2)
ϕ̃1 = ϕ̃1 ; ϕ̃2 = ϕ̃2 ; ϕ̃3 = ϕ̃1 = ϕ̃3 ; ϕ̃2 = ϕ̃4
(2) (3) (3) (3)
ϕ̃3 = ϕ̃1 = ϕ̃5 ; ϕ̃2 = ϕ̃6 ; ϕ̃3 = ϕ̃7
Thus, we rewrite the individual elemental equations in the form
(1) (1) (1) (1) (1)
K11 ϕ̃1 + K12 ϕ̃2 + K13 ϕ̃3 = b1 + g1
(1) (1) (1) (1)
K21 ϕ̃1 + K22 ϕ̃2 + K23 ϕ̃3 = b2
(1) (1) (1) (1) (1)
K31 ϕ̃1 + K32 ϕ̃2 + K33 ϕ̃3 = b3 + g3
(2) (2) (2) (2) (2)
K11 ϕ̃3 + K12 ϕ̃4 + K13 ϕ̃5 = b1 + g1
(2) (2) (2) (2)
K21 ϕ̃3 + K22 ϕ̃4 + K23 ϕ̃5 = b2
(2) (2) (2) (2) (2)
K31 ϕ̃3 + K32 ϕ̃4 + K33 ϕ̃5 = b3 + g3
(3) (3) (3) (3) (3)
K11 ϕ̃5 + K12 ϕ̃6 + K13 ϕ̃7 = b1 + g1
(3) (3) (3) (3)
K21 ϕ̃5 + K22 ϕ̃6 + K23 ϕ̃7 = b2
(3) (3) (3) (3) (3)
K31 ϕ̃5 + K32 ϕ̃6 + K33 ϕ̃7 = b3 + g3
Unfortunately, we now have nine equations but only seven unknowns. We know that some of
these equations must be redundant, and we therefore need to reduce the number of equations. In
order to do this, we can replace the third and fourth equations by their sum, as well as the sixth
and seventh equations by their sum. We then find that
(1) (1) (1) (1)
(1)
K11 K12 K13 0 0 0 0
ϕ̃
b1 g1
1
(1)
(1) (1) (1) (1)
K21 K22 K23 0 0 0 0
ϕ̃2
b2
g2
(1) (1) (1) (2) (2) (2)
(1)
(2) (1) (2)
K31 K32 K33 + K11 K12 K13 0 0
ϕ̃3 b3 + b 1 g3 + g1
(2) (2) (2)
(2) (2)
ϕ̃4 = +
0 0 K21 K22 K23 0 0 b2 g2
(2) (2) (2) (3) (3) (3)
ϕ̃
(2)
(3)
(2) (3)
0 0 K31 K32 K33 + K11 K12 K13 5 b3 + b 1 g3 + g1
(3) (3) (3)
ϕ̃6 (3) (3)
0
0 0 0 K21 K22 K23
b2
g2
ϕ̃7
(3) (3) (3) (3) (3)
0 0 0 0 K31 K32 K33 b3 g3
130 Exercise 15.12
(e)
g2 = 0
and
(2) (3) (2) dϕ̃(2) (3) dϕ̃(3)
g3 + g1 = α(x3 ) −α(x1 ) =0
dx x3
(2) dx (3)
x1
More explicit values for all entries in this equation except the unknowns in the vector {ϕ̃} can
be inferred from the results in Exercise 15.11, though those values apply only if the interior nodes
in each element are halfway between the end nodes of that element.
Exercise 15.8 131
dϕ(x, y) dϕ(x, y)
−
2
dϕ (x, y) dx xi +∆x/2,yj dx xi −∆x/2,yj
≈
dx2 xi ,yj ∆x
evaluate
ϕ̃(x + ∆x) − ϕ̃(x)
to show that
ϕ̃(x + ∆x) − ϕ̃(x)
ϕ̃0 (x) = + O(∆x)
∆x
That is, derive the forward difference formula in Eq. (15.74). (b) Deduce the more accurate forward
difference formula
−ϕ̃(x + 2 ∆x) + 4ϕ̃(x + ∆x) − 3ϕ̃(x)
ϕ̃0 (x) ≈ + O(∆x2 )
2 ∆x
Hint: Start by using the Taylor expansion
1 2 00
f (x + h) = f (x) + h f 0 (x) + h f (x) + O(h3 )
2
to expand ϕ̃(x + ∆x) and ϕ̃(x + 2 ∆x).
Solution: (a) This first part involves little more than a rearrangement of the terms in the
Taylor expansion provided in part (a), first to
and then to
ϕ̃(x + ∆x) − ϕ̃(x) O(∆x2 )
= ϕ̃0 (x) +
∆x ∆x
and finally to5
ϕ̃(x + ∆x) − ϕ̃(x)
ϕ̃0 (x) = + O(∆x)
∆x
Introducing x = xi and x + ∆x = xi+1 in the above Taylor expansion to find that
(b) Based on the Taylor series provided in part (b), we note that
1
ϕ̃(x + ∆x) = ϕ̃(x) + ∆x ϕ̃0 (x) + ∆x2 ϕ̃00 (x) + O(∆x3 )
2
and that
1
ϕ̃(x + 2 ∆x) = ϕ̃(x) + 2 ∆x ϕ̃0 (x) + (2 ∆x)2 ϕ̃00 (x) + O(∆x3 )
2
Solving the first of these equations for ϕ̃00 (x) ∆x2 yields
Substituting this result into the second equation then yields that
ϕ̃(x + 2 ∆x) = ϕ̃(x) + 2 ∆x ϕ̃0 (x) + 2 2 ϕ̃(x + ∆x) − 2 ϕ̃(x) − 2 ∆x ϕ̃0 (x) + O(∆x3 )
5 We can retain the plus sign before O(∆x) because it is of no consequence in specifying the magnitude of the
potential error.
Exercise 15.25 133
We require that this function yield the known values at the ends of the interval, i.e., that
(e) (e)
ϕ̃1 = a(e) + b(e) x1
(e) (e)
ϕ̃2 = a(e) + b(e) x2
Subtracting the first of these equations from the second yields that
(e) (e) (e) (e)
(e) (e) (e) (e) ϕ̃2 − ϕ̃1 ϕ̃2 − ϕ̃1
ϕ̃2 − ϕ̃1 = b(e) (x2 − x1 ) =⇒ b(e) = (e) (e)
=
x2 − x1 l(e)
• Reading in a desired tolerance (i.e., maximum amount by which values in successive iterates can
change before terminating iteration) and declaring the variable tol by adding the statements
float tol;
printf( "Tolerance: "); scanf("%f", &tol);
• Changing the outermost for loop to a while loop so that the second stopping criterion can
be incorporated more easily. Leaving that second criterion aside for a moment, we replace the
outermost loop with the loop
itcnt=0
while( itcnt < maxits )
{ .
.
itcnt=itcnt+1;
}
Here, we have recognized that the while loop requires us also to initialize the iteration counter
outside the loop and then increment it at the appropriate point inside the loop. Note also that
this counter is incremented after the counted iteration has been completed so, when we return
to start the next path through the loop, itcnt contains the number of iterations completed.
Thus, the while loop should stop when itcnt has the value maxits. If we expressed the
criterion as itcnt <= maxits, the loop would be executed one too many times.
• (to keep track of the change from one iteration to the next) Saving the old value every time
a new value of u[i][j] is calculated and then compare the old value with the new value
and adjust a record—we suppose that record to be kept in a variable change, which must
be initialized appropriately—of the largest value if the new difference is larger than the old
difference, i.e., we must replace every calculation of a new entry for u[i][j] with
Every time we start a new iteration, we must initialize change to a value smaller than any
likely actual change; we choose 0.0, so the statement
change=0.0;
must be inserted immediately after the while loop is entered. Further, we must declare the
new variables tmp, change, and chg with the statement
• Adding the second criterion to the condition in the while statement by editing that statement
to read
This addition means that, before the loop is entered, change needs to be initialized to a value
that is larger than tol so that the loop will not be terminated immediately. We therefore add
the statement
change=2.0*tol;
• Finally, when the coding exits from the while loop, we need to display a message conveying
whether convergence was achieved or not and indicating the number of iterations completed.
Further, since convergence may be achieved at an iteration that is otherwise not printed, we
need to print the final iterate outside the loop to be sure we see it is printed no matter how
many iterations are completed. Thus, we add the statements
The command file is named pde ex23.c and is listed at the end of the solution to this exercise.
A full test of this program will make sure that it stops properly both when convergence is
achieved and when convergence fails. As a start, we compile the program in C and run it with the
statements
cc -o pde_ex23.xc pde_ex23.c
./pde_ex23.xc
Exercise 15.23 (C) 137
Number of segments: 5
Length of side: 10
Maximum number of iterations: 10
Display frequency: 5
Tolerance: 2
Iterate 0
100.0000 100.0000 100.0000 100.0000 100.0000 100.0000
80.0000 0.0000 0.0000 0.0000 0.0000 0.0000
60.0000 0.0000 0.0000 0.0000 0.0000 0.0000
40.0000 0.0000 0.0000 0.0000 0.0000 0.0000
20.0000 0.0000 0.0000 0.0000 0.0000 0.0000
0.0000 0.0000 0.0000 0.0000 0.0000 0.0000
Iterate 5
100.0000 100.0000 100.0000 100.0000 100.0000 100.0000
80.0000 73.9017 70.1803 68.4650 68.2962 69.2642
60.0000 51.7524 46.5277 43.9893 43.6158 44.8891
40.0000 33.0073 28.4468 26.1477 25.7376 26.7981
20.0000 16.2646 13.7882 12.5156 12.2704 12.8347
0.0000 0.0000 0.0000 0.0000 0.0000 0.0000
We have deliberately picked a large tolerance, expecting that convergence would be achieved in a
very few iterations. Note that (1) the program has worked correctly to display the final solution
even though it didn’t occur at a multiple of 5 iterations, (2) the actual tolerance achieved is indeed
smaller than the specified desired tolerance, and (3) the proper message regarding the achievement
of tolerance has been output.
As a second test, let us suppress all intermediate output by making the display frequency larger
than the maximum number of iterations and run the program with a smaller tolerance, e.g., with
the input
./pde_ex23.xc
Number of segments: 5
Length of side: 10
Maximum number of iterations: 10
Display frequency: 20
Tolerance: 0.1
Iterate 0
100.0000 100.0000 100.0000 100.0000 100.0000 100.0000
80.0000 0.0000 0.0000 0.0000 0.0000 0.0000
60.0000 0.0000 0.0000 0.0000 0.0000 0.0000
40.0000 0.0000 0.0000 0.0000 0.0000 0.0000
20.0000 0.0000 0.0000 0.0000 0.0000 0.0000
0.0000 0.0000 0.0000 0.0000 0.0000 0.0000
which shows only the initial guess and the final solution, indicates that the full ten iterations were
used and that the specified tolerance was not achieved in those ten iterations.
Next, we set the maximum number of iterations quite high, specify a still smaller tolerance,
and—with luck—will achieve convergence to a more accurate final solution. We use the input
./pde_ex23.xc
Number of segments: 5
Length of side: 10
Maximum number of iterations: 100
Display frequency: 200
Tolerance: 0.01
Iterate 0
100.0000 100.0000 100.0000 100.0000 100.0000 100.0000
80.0000 0.0000 0.0000 0.0000 0.0000 0.0000
60.0000 0.0000 0.0000 0.0000 0.0000 0.0000
40.0000 0.0000 0.0000 0.0000 0.0000 0.0000
20.0000 0.0000 0.0000 0.0000 0.0000 0.0000
0.0000 0.0000 0.0000 0.0000 0.0000 0.0000
Finally, seeking a solution that coincides with the fully converged solution obtained in the text,
we set the tolerance to 0.00001 with the input
./pde_ex23xc
Number of segments: 5
Length of side: 10
Maximum number of iterations: 100
Display frequency: 200
Tolerance: 0.00001
Iterate 0
100.0000 100.0000 100.0000 100.0000 100.0000 100.0000
80.0000 0.0000 0.0000 0.0000 0.0000 0.0000
60.0000 0.0000 0.0000 0.0000 0.0000 0.0000
40.0000 0.0000 0.0000 0.0000 0.0000 0.0000
20.0000 0.0000 0.0000 0.0000 0.0000 0.0000
0.0000 0.0000 0.0000 0.0000 0.0000 0.0000
Convergence to what is, in fact, the exact solution for this problem, is achieved in 58 iterations.
The same embellishments described in Section 15.17.3 of the text could now be invoked to write
a more refined solution to a file and then to create graphs of that solution. Those embellishments
were not requested in this exercise.
140 Exercise 15.23 (C)
itcnt = 0;
dx = l/n;
for(i=0; i<=n; i++)
{ x[i]=dx*i; y[i]=dx*i;
for(j=0; j<=n; j++)
u[i][j] = 0.0;
}
change = 2.0*tol;
while( (itcnt < maxits) & (change > tol) )
{ change = 0.0;
for(i=1; i<n; i++)
{ for(j=1; j<n; j++)
{ tmp = u[i][j]; /* Save current value */
u[i][j]= 0.25*(u[i+1][j]+u[i-1][j]+u[i][j+1]+u[i][j-1]);
chg = fabs(tmp-u[i][j]); /* Calculate absolute value of change */
if (chg > change) change = chg;
}
tmp = u[i][n]; /* Save current value */
u[i][n]= 0.25*(2.0*u[i][n-1]+u[i-1][n]+u[i+1][n]);
chg = fabs(tmp-u[i][n]); /* Calculate absolute value of change */
if (chg > change) change = chg;
}
itcnt = itcnt+1;
if( nf*(itcnt/nf) == itcnt )
{ printf(" Iterate %5d\n", itcnt );
for(i=0; i<=n; i++)
{ for(j=0; j<=n; j++)
printf( "%10.4f", u[i][j] );
printf( "\n" );
}
}
}
printf(" Number of iterations completed = %5d\n", itcnt );
printf(" Tolerance achieved = %12.6f\n", change );
if (change > tol)
printf(" Specified tolerance %12.6f not achieved\n", tol);
else
printf(" Specified tolerance %12.6f achieved\n", tol);
printf(" Final solution = \n");
for(i=0; i<=n; i++)
{ for(j=0; j<=n; j++)
printf( "%10.4f", u[i][j] );
printf( "\n" );
}
}