0% found this document useful (0 votes)
8 views

CPSUPsolns CC Linked

Uploaded by

assiadakiche74
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
8 views

CPSUPsolns CC Linked

Uploaded by

assiadakiche74
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 147

19 February 2023

EXERCISE SOLUTIONS
for
COMPUTATION AND PROBLEM SOLVING
IN UNDERGRADUATE PHYSICS

IDL MATLAB OCTAVE


PYTHON MAXIMA MAPLE
MATHEMATICA FORTRAN • C
• NUMERICAL RECIPES LSODE MUDPACK
LATEX TGIF

DAVID M. COOK
with assistance from

DANICA DRALUS, LU ’02 RYAN PETERSON, LU ’03


SCOTT KAMINSKI, LU ’04 MICHELLE MILNE, LU ’04
LAUREN KOST, LU ’05 CLAIRE WEISS, LU ’07
ERIK GARBACIK, LU ’08
Department of Physics
Lawrence University
711 E Boldt Way SPC 24
Appleton, Wisconsin 54911

Copyright c 2000–23 by David M. Cook

This work is licensed under a Creative Commons Attribution-NonCommercial-


ShareAlike 4.0 International License (creativecommons.org/licenses/by-nc-sa/4.0/).
Any use not permitted by this license requires authorization in writing from David
M. Cook.
ii
Preface

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,

• FORTRAN NUMERICAL RECIPES,


• C NUMERICAL RECIPES,
• the FORTRAN procedure library LSODE,

• the UNIX drawing program TGIF, and


• the tool LATEX for preparation of technical documents.

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

14.19 Roots of J0 (x) (Numerical Recipes-C) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93


14.20 Double-Welled Potential (Numerical Recipes-C) . . . . . . . . . . . . . . . . . . . . . . . 95
14.24 A Fluid Mechanics Problem (Numerical Recipes-C) . . . . . . . . . . . . . . . . . . . . . 99
14.30 Finite Depth Square Well (Numerical Recipes-C) . . . . . . . . . . . . . . . . . . . . . . 102

15 Introduction to Partial Differential Equations 109


15.2 A Vertically Hanging String . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
15.3 Second-Order ODEs Made Self-Adjoint . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
15.7 FDM 1D Homogeneous Helmholtz Equation . . . . . . . . . . . . . . . . . . . . . . . . . 115
15.8 FEM 1D Homogeneous Helmholtz Equation . . . . . . . . . . . . . . . . . . . . . . . . . . 118
15.9 1-D Inhomogeneous Helmholtz Equation . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
15.12 Assembly for Three-Element Linear System . . . . . . . . . . . . . . . . . . . . . . . . . . 128
15.20 FDM 2D Inhomogeneous Helmholtz Equation . . . . . . . . . . . . . . . . . . . . . . . . 131
15.25 Finite Difference Formulas via Taylor Series . . . . . . . . . . . . . . . . . . . . . . . . . . 132
15.26 Shape Functions for Two-Node Linear Element . . . . . . . . . . . . . . . . . . . . . . . . 134
numerically with C
15.23 Laplace Equation: Alternate Convergence Test (C) . . . . . . . . . . . . . . . . . . . . . 135
Chapter 9

Introduction to Programming

9.2 Relationship between IF-THEN-ELSE and CASE

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

Equivalently, using the CASE structure, we would write

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

Equivalently, using the IF-THEN-ELSE structure, we would write

1
2 Exercise 9.2

Figure E9.1: Figure for 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

Figure E9.2: Figure for Exercise 9.2.

(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

Equivalently, using the CASE structure, we would write

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

9.7 Tracking Both Extremes and their Positions


Exercise: Basing your work on Algorithm (6) of Section 9.2,specifically

SENTINEL@ ←− hagreed-upon special valuei


READ (first) ITEM@ from list
EXTREME@ ←− ITEM@
LOOP
READ (next) ITEM@ from list
EXIT LOOP WHEN ITEM@ = SENTINEL@
IF ITEM@ and EXTREME@ are out of order
THEN EXTREME@ ←− ITEM@
END IF
END LOOP
WRITE "The extreme value is "; EXTREME@

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

Table 9.1: Algorithm for Part (a) of Exercise 9.7.

SENTINEL$ <-- ??? ! Set sentinel to mark end of list


CNT% <-- 0 ! Initialize counter to count entries
READ ITEM$ from list ! Get first item
CNT% <-- CNT% + 1% ! Count item entered
EARLIEST$ <-- ITEM$ ! First item is earliest item
LATEST$ <-- ITEM$ ! First item is also latest item
EARLCNT% <-- CNT% ! Note position of current earliest item
LATCNT% <-- CNT% ! Note position of current latest item
LOOP
READ ITEM$ from list ! Get next item
EXIT_LOOP WHEN ITEM$ = SENTINEL$
CNT% <-- CNT% + 1% ! Count newly entered item
IF ITEM$ < EARLIEST$ THEN BEGIN_BLOCK ! Adjust earliest
EARLIEST$ <-- ITEM$ ! if necessary
EARLCNT% <-- CNT%
END_BLOCK
END_IF
IF ITEM$ > LATEST$ THEN BEGIN_BLOCK ! Adjust latest
LATEST$ <-- ITEM$ ! if necessary
LATCNT% <-- CNT%
END_BLOCK
END_IF
END_LOOP
WRITE "The earliest item is"; EARLIEST$; "in position"; EARLCNT%
WRITE "The latest item is"; LATEST$; "in position"; LATCNT%
WRITE "The total number of items is"; CNT%
6 Exercise 9.9

9.9 Mystery Procedure 1


Exercise: Suppose you have N% cards laid out in a row on a table. On each card is a single
word. Determine the end result of applying the mystery procedure laid out in Table 9.2 to that
array of cards and choose a suitable name for the procedure.

Table 9.2: Procedure for Exercise 9.9.

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

1. move our sights to the next card on the table,


2. copy the word on that card into WORD$,
3. adjust the value of LATEST WORD$ and LATEST CARD% to be the content and position of the
newly examined card, but only if its contents WORD$ occurs later in the alphabet than the
current contents of LATEST WORD$.

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)

9.12 Conversion from Celsius to Fahrenheit (C)


Exercise: Write, compile, and test a program that asks for the input of a temperature in
Celsius and prints out the corresponding temperature in Fahrenheit. To make it a bit more of a
challenge, write the program in such a way that it asks repeatedly for Celsius temperatures until
the temperature 9999 is entered, at which point the program terminates smoothly.
Solution: The conversion from a temperature C in Celsius to the corresponding temperature
F in Fahrenheit is given by the equation
9
F = C + 32
5
At base, a C program to achieve this conversion must ask for the input of a Celsius temperature
with statements like

printf( "\nEnter temperature in Celsius: ");


scanf( "%f", &C );

convert the value to Celsius by invoking the above equation with a statement like

F = 9.0*C/5.0 + 32.0

and then output the result with a statement like

printf( "Temperature in Fahrenheit = %10.2", F );

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 */

printf( "\nEnter temperature in Celsius: ");


scanf( "%f", &C );

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

The resulting “conversation” with the computer might be something like

Enter temperature in Celsius: 0.0


Temperature in Fahrenheit = 32.00
Enter temperature in Celsius: 100.0
Temperature in Fahrenheit = 212.00
Enter temperature in Celsius: -40.0
Temperature in Fahrenheit = -40.00
Enter temperature in Celsius: 9999

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)

9.14 Laplace’s Equation with Different Grids (C)


Exercise:

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

(a) The statements

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

IDL> openr, 1, ’laplace15_c.dat’


IDL> u15 = fltarr(15,15)
IDL> readf, 1, u15
IDL> close, 1
IDL> openr, 1, ’laplace29_c.dat’
IDL> u29 = fltarr(29,29)
IDL> readf, 1, u29
IDL> close, 1

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

IDL> x15 = findgen(15)/14.0


IDL> surface, u15, x15, x15, thick=3.0, title=’15 x 15 grid’
IDL> x29 = findgen(29)/28.0
IDL> surface, u29, x29, x29, thick=3.0, title=’29 x 29 grid’
IDL> contour, u15, x15, x15, thick=3.0, title=’15 x 15 grid’, $
IDL> levels=[0, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90], $
IDL> c_labels=[0,1, 1, 1, 1, 1, 1, 1, 1, 1, 1], /isotropic
IDL> contour, u29, x29, x29, thick=3.0, title=’29 x 29 grid’, $
IDL> levels=[0, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90], $
IDL> c_labels=[0,1, 1, 1, 1, 1, 1, 1, 1, 1, 1], /isotropic

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

IDL> u15from29 = fltarr(15,15)


IDL> for i=0,14 do for j=0,14 do u15from29[i,j]=u29[2*i,2*j]

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

IDL> diff = u15from29-u15


IDL> print, max(diff), min(diff)
0.409996 -1.00000

or we could make a surface plot of the differences with the statement


Exercise 9.14 (C) 13

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.

IDL> surface, diff, x15, x15, thick=3.0, title=’Difference’

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

>> id = fopen(’laplace15_c.dat’, ’r’ );


>> u15 = fscanf( id, ’%f’, [15,15] );
>> status = fclose( id );
>> u15 = u15’;
>> id = fopen(’laplace29_c.dat’, ’r’ );
>> u29 = fscanf( id, ’%f’, [29,29] );
>> status = fclose( id );
>> u29 = u29’;

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

will be properly associated with the values in the array.


14 Exercise 9.14 (C)

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

>> u15from29 = zeros(15,15);


>> for i=1:15
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

>> diff = u15from29-u15;


>> [max(max(diff)), min(min(diff))]
0.4100 -1.0000
Exercise 9.14 (C) 15

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

or we could make a surface plot of the differences 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.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

some portions of the domain of the problem.

(d) To import these files into OCTAVE, we start OCTAVE and then execute the OCTAVE
statements2

>> id = fopen(’laplace15_c.dat’, ’r’ );


>> u15 = fscanf( id, ’%f’, [15,15] );
>> status = fclose( id );
>> u15 = u15’;
>> id = fopen(’laplace29_c.dat’, ’r’ );
>> u29 = fscanf( id, ’%f’, [29,29] );
>> status = fclose( id );
>> u29 = u29’;

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

will be properly associated with the values in the array.


Exercise 9.14 (C) 17

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

>> xlabel( ’x’, ’fontsize’, 16 )


>> ylabel( ’y’, ’fontsize’, 16 )
>> zlabel( ’u15’, ’fontsize’, 16 )
>> print -dpdf ’program-ex14-cOCT-fig1.pdf’
>> print -deps2 ’program-ex14-cOCT-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-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

>> u15from29 = zeros(15,15);


>> for i=1:15
18 Exercise 9.14 (C)

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

>> diff = u15from29-u15;


>> [max(max(diff)), min(min(diff))]
0.41000 -1.00000

or we could make a surface plot of the differences 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

will be properly associated with the values in the array.


Exercise 9.14 (C) 19

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

>>> import matplotlib.pyplot as plt


>>> import numpy as np
>>> from mpl_toolkits.mplot3d import Axes3D
>>> f = open(’laplace15_c.dat’, ’r’ )
>>> u15 = []
>>> for line in f:
u15.append( [float(x) for x in line.split()] )
>>> f.close()
>>> u15 = np.array( u15 )

>>> f = open(’laplace29_c.dat’, ’r’ )


>>> u29 = []
>>> for line in f:
u29.append( [float(x) for x in line.split()] )
>>> f.close()
>>> u29 = np.array( u29 )

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

>>> xx=np.linspace(0.0,1.0,15); yy=np.linspace(0.0,1.0,15)


>>> x, y = np.meshgrid( xx, yy )
>>> fig1 = plt.figure(1)
20 Exercise 9.14 (C)

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

>>> ax1 = plt.axes( projection=’3d’ )


>>> ax1.plot_surface(x,y,u15, color=’white’, shade=False, edgecolor=’black’)
>>> ax1.set_xlabel( ’$x$’, fontsize=14 )
>>> ax1.set_ylabel( ’$y$’, fontsize=14 )
>>> ax1.set_zlabel( ’$u15$’, fontsize=14 )
>>> ax1.view_init(30,-130)
>>> plt.show()

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

>>> xx=np.linspace(0.0,1.0,29); yy=np.linspace(0.0,1.0,29)


>>> x1, y1 = np.meshgrid( xx, yy )
>>> fig2 = plt.figure(2)
>>> ax2 = plt.axes( projection=’3d’ )
>>> ax2.plot_surface(x1,y1,u29, color=’white’, shade=False, edgecolor=’black’)
>>> ax2.set_xlabel( ’$x$’, fontsize=14 )
>>> ax2.set_ylabel( ’$y$’, fontsize=14 )
>>> ax2.set_zlabel( ’$u29$’, fontsize=14 )
>>> ax2.view_init(30,-130)
>>> 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

>>> u15from29 = np.zeros( [15,15] )


>>> for i in range(15):
for j in range(15):
u15from29[i,j]=u29[2*i,2*j]

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

>>> diff = u15from29-u15


>>> [np.amax(diff), np.amin(diff)]
[0.4099999999999966, -1.0]

or we could make a surface plot of the differences with the statements

>>> fig3 = plt.figure(3)


>>> ax3 = plt.axes( projection=’3d’ )
>>> ax3.plot_surface(x,y,diff, color=’white’, shade=False, edgecolor=’black’)
>>> ax3.set_xlabel( ’$x$’, fontsize=14 )
>>> ax3.set_ylabel( ’$y$’, fontsize=14 )
>>> ax3.set_zlabel( ’Difference’, fontsize=14 )
>>> ax3.view_init(30,-130)
>>> plt.show(3)

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

at the end of the loop counting iterations.


The program is listed at the end of this solution. It is compiled, linked, and run with the
statements

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

printf("\nDesired tolerance: " );


scanf( "%f", &tol );

◦ 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

printf("\nMaximum number of iterations: " );


scanf( "%d", maxit );

◦ We remove the statement

printf( "%d %d %d %e\n", itcnt, isave, jsave, bigchg);

that displays the maximum change at each iteration.


◦ Most importantly, we recast the outermost loop in the main solution so that it terminates
either when bigchg is reduced below tol or when the maximum number of iterations has
been reached. Achieving this recasting is a bit tricky because the loop will be terminated
on either of two conditions, i.e., under the compound condition

EXIT_LOOP WHEN ( (bigchg .LT. tol) .OR. (itcnt .GE. maxit) )

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)

increments itcnt after each iteration with the statement

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;

◦ We add the statement


Exercise 9.14 (C) 25

if (bigchg > tol)


{ printf("Convergence to tolerance &f not achieved\n", tol );
printf( "in %d iterations.\n", maxit );
}
else
{ printf("Tolerance %f achieved in %d iterations.\n", tol, itcnt );
}
just before the file is written to report on whether tolerance was achieved or not.

Then we compile, link, and run the program with the statements

cc -o laplace29_tol.xc laplace29_tol.c

As a test, we run the program with the input

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

Tolerance Required Number of Iterations


1.000 27
0.500 49
0.200 93
0.100 139
0.050 192
0.020 265
0.010 320
0.005 374
0.002 447
0.001 502

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)

Listing of laplace29 monitor.c

/* PROGRAM laplace_file.c */

/* This program solves Laplace’s equation in a square when


three sides of the square are maintained at zero potential
and the fourth side is maintained at a potential of 100 V.
The solution on a 15 x 15 grid is stored in the array U. */

#include <stdio.h>
#include <math.h>
#define xdim 29
#define ydim 29
#define maxit 300

void main()
{
/***** Declare variables *****/

FILE *fptr; /* For file pointer */


float U[xdim][ydim]; /* For solution */
int i, j, itcnt; /* For loop control */
int isave, jsave;
float bigchg, test, tmp;

/***** Initialize U(I,J); set boundary conditions *****/

for(i=0; i<xdim; i++)


for(j=0; j<ydim; j++)
U[i][j] = 0.0;

for(j=0; j<ydim; j++)


U[xdim-1][j] = 100.0;

/***** Iterate to solution *****/

for(itcnt=1; itcnt<=maxit; itcnt++)

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

/***** Display solution *****/

fptr=fopen( "laplace_c.dat", "w" );


Exercise 9.14 (C) 27

for(j=0; j<ydim; j++)


{
for(i=0; i<xdim; i++)
fprintf( fptr, "%7.2f", U[i][j] );
fprintf( fptr, "\n" );
}

fclose( fptr );

Listing of laplace29 tol.c

/* PROGRAM laplace_file.c */

/* This program solves Laplace’s equation in a square when


three sides of the square are maintained at zero potential
and the fourth side is maintained at a potential of 100 V.
The solution on a 15 x 15 grid is stored in the array U. */

#include <stdio.h>
#include <math.h>
#define xdim 29
#define ydim 29

void main()
{
/***** Declare variables *****/

FILE *fptr; /* For file pointer */


float U[xdim][ydim]; /* For solution */
int i, j, itcnt, maxit; /* For loop control */
int isave, jsave;
float bigchg, test, tmp, tol;

printf("\nDesired tolerance: " ); scanf( "%f", &tol );


printf("Maximum number of iterations: " ); scanf( "%d", &maxit );

/***** Initialize U(I,J); set boundary conditions *****/

for(i=0; i<xdim; i++)


for(j=0; j<ydim; j++)
U[i][j] = 0.0;

for(j=0; j<ydim; j++)


U[xdim-1][j] = 100.0;

/***** Iterate to solution *****/

itcnt = 0;
bigchg = 2.0*tol;
28 Exercise 9.14 (C)

while (bigchg > tol && itcnt < maxit)


{ 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;
}
}
itcnt = itcnt+1;
}

if (bigchg > tol)


{ printf("Convergence to tolerance %f not achieved\n", tol );
printf( "in %d iterations.\n", maxit );
}
else
{ printf("Tolerance %f achieved in %d iterations.\n", tol, itcnt );
}

/***** Display solution *****/

fptr=fopen( "laplace_c.dat", "w" );

for(j=0; j<ydim; j++)


{
for(i=0; i<xdim; i++)
fprintf( fptr, "%7.2f", U[i][j] );
fprintf( fptr, "\n" );
}

fclose( fptr );

}
Exercise 9.16 (C) 29

9.16 Exploring Trajectories in 3D (C)


Exercise: The trajectory of a particle in three-dimensional space is given parametrically as a
function of time t by the position vector
r = x(t) î + y(t) ĵ + z(t) k̂
You desire to fathom out the general character of this trajectory by using a graphical visualization
tool that does not have much computational capability. Thus, you must generate the data using
one tool but will visualize the trajectory with another tool. You elect to use an ASCII file to
communicate the data from the first tool to the second. Suppose that the ASCII file produced by
the first tool is to be structured as follows:

• 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

r = cos t î + sin t ĵ + 0.1t k̂


which describes the path followed by a charged particle in a constant magnetic field along the z axis.
Solution: In broad outline, the task we need to perform in solving this exercise is to initialize
several variables and then, time instant by time instant, systematically move from the starting time
through the desired number of steps, writing the results at each step to the desired file. Somewhere
prior to the loop in which the coordinates are evaluated at each step, we would have to open the
intended file and write the desired labeling lines to the file, and then after the loop we would have
to close the file. In outline (and in pseudocode), the program might have the general structure4

ATTACH NEW FILE filename ON CHANNEL 1


WRITE TO CHANNEL 1%, "Descriptive line 1"
WRITE TO CHANNEL 1%, "Descriptive line 2"
WRITE TO CHANNEL 1%, "Descriptive line 3"
WRITE TO CHANNEL 1%, "Descriptive line 4"
WRITE TO CHANNEL 1%, "Descriptive line 5"
nsteps% <-- ?? ! Set number of time steps
WRITE TO CHANNEL 1%, nsteps%+1% ! Write number of lines to file
tstart <-- ?? ! Set starting time
dt <-- ?? ! Set time increment between steps
ncnt% <-- 0 ! Initialize step counter
LOOP
t = tstart + dt*float(ncnt%) ! Set current time (tstart on first pass)
x <-- x(t) ! Evaluate x, y, z at current time
y <-- y(t)
z <-- z(t)
WRITE TO CHANNEL 1%, t, x, y, z ! Output values to file
EXIT_LOOP WHEN ncnt% = nsteps%
ncnt% <-- ncnt% + 1% ! Increment counter for next step
END_LOOP
CLOSE FILE ON CHANNEL 1%
4 The function float used at the end of the loop forces a conversion of the integer ncnt% to floating point form for

the floating-point computation in which it appears.


30 Exercise 9.16 (C)

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

ATTACH NEW FILE filename ON CHANNEL 1%


WRITE TO CHANNEL 1%, "Descriptive line 1"
WRITE TO CHANNEL 1%, "Descriptive line 2"
WRITE TO CHANNEL 1%, "Descriptive line 3"
WRITE TO CHANNEL 1%, "Descriptive line 4"
WRITE TO CHANNEL 1%, "Descriptive line 5"
nsteps% <-- ?? ! Set number of time steps to take
WRITE TO CHANNEL 1%, nsteps%+1% ! Write number of lines to file
tstart <-- ?? ! Set starting time
dt <-- ?? ! Set time increment between steps
ncnt% <-- 0 ! Initialize step counter
t = tstart ! Initialize current time
x <-- x(t) ! Evaluate x, y, z at current time
y <-- y(t)
z <-- z(t)
WRITE TO CHANNEL 1%, t, x, y, z ! Output initial values to file
LOOP
EXIT_LOOP WHEN ncnt% = nsteps%
ncnt% <-- ncnt% + 1% ! Count step about to be taken
t = tstart + dt*float(ncnt%) ! Set current time
x <-- x(t) ! Evaluate x, y, z at current time
y <-- y(t)
z <-- z(t)
WRITE TO CHANNEL 1%, t, x, y, z ! Output current values to file
END_LOOP
CLOSE FILE ON CHANNEL 1%

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

x(t) = cos t ; y(t) = sin t ; z(t) = 0.1t (E9.16.1)

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 *****/

FILE *fptr; /* For file pointer */


int nsteps, ncnt;
float tstart, dt, t, x, y, z;

/* ***** Open file, set number of steps, and write descriptive


lines to file */

fptr = fopen( "trajectory_c.dat", "w" );


fprintf( fptr, "Trajectory when " );
fprintf( fptr, "\n x(t) = cos(t)" );
fprintf( fptr, "\n y(t) = sin(t)" );
fprintf( fptr, "\n z(t) = 0.1 t\n" );
fprintf( fptr, "\n" );

printf( "\nEnter number of steps: "); /* Set number of time steps */


scanf( "%d", &nsteps );
fprintf( fptr, "%d\n", nsteps+1 ); /* Write number of lines to file */

/***** Initialize variables, prime loop, write initial values to file *****/

tstart = 0.0; /* Set starting time */


printf( "\nEnter time between points: "); /* Set time increment */
scanf( "%f", &dt );
ncnt=0; /* Initialize step counter */
t = tstart; /* Initialize current time */
x = cos(t); y=sin(t); z = 0.1*t; /* Evaluate x, y, z at current time */
fprintf( fptr, "%6.3f %6.3f %6.3f %6.3f\n", t,x,y,z );
/* Output initial values to file */

/***** Evaluate desired number of steps, writing values at each step


to file *****/

for( ncnt=1; ncnt<=nsteps; ncnt++ )


{ t = tstart + dt*ncnt; /* Set current time */
x = cos(t); y=sin(t); z= 0.1*t; /* Evaluate x, y, z at current time */
fprintf( fptr, "%6.3f %6.3f %6.3f %6.3f\n", t,x,y,z );
}

/***** Close file *****/

fclose( fptr );

This program would be compiled and executed with the statements

cc -o trajectory.xc trajectory.c
./trajectory.xc

When run, with the input


32 Exercise 9.16 (C)

Enter number of steps: 10


Enter time between points: 0.1

the resulting file trajectory c.dat contains the lines

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

ln = "" ; Set string variable for reading lines


openr, 1, ’trajectory_c.dat’ ; Open file
for i=1,5 do readf, 1, ln ; Read past and ignore first 5 lines
readf, 1, nlines ; Read number of lines
traj = fltarr(4,nlines) ; Dimension array for data
readf, 1, traj ; Read data
close, 1 ; Close the file
t = traj[0,*] & x = traj[1,*] & y = traj[2,*] & z = traj[3,*]

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 )

scale3, xrange=[-1.0,1.0], yrange=[-1.0,1.0], zrange=[0.0,0.1]


plots, x, y, z, /t3d

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

Enter number of steps: 250


Enter time between points: 0.1
Exercise 9.16 (C) 33

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

id = fopen( ’trajectory_c.dat’, ’r’ ); % Open file


for i=1:5 fgetl( id ); end % Read past and ignore first 5 lines
nlines = fscanf( id, ’%d’ ); % Read number of lines
traj = fscanf( id, ’%f’, [4,nlines(1)] );
status = fclose(id);
t = traj(1,:); x = traj(2,:); y = traj(3,:); z = traj(4,:);

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 )

plot3(x,y,z, ’color’,’black’, ’linewidth’,4 )


grid on

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

Enter number of steps: 250


Enter time between points: 0.1

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

id = fopen( ’trajectory_c.dat’, ’r’ ); # Open file


for i=1:5 fgetl( id ); end # Read past and ignore first 5 lines
nlines = fscanf( id, ’%d’ )(1); # Read number of lines
traj = fscanf( id, ’%f’, [4,nlines] )
status = fclose(id);
t = traj(1,:); x = traj(2,:); y = traj(3,:); z = traj(4,:);

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 )

plot3(x,y,z, ’color’,’black’, ’linewidth’,4 )

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

Enter number of steps: 250


Enter time between points: 0.1

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

>>> import numpy as np


>>> import matplotlib.pyplot as plt
Exercise 9.16 (C) 35

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

>>> from mpl_toolkits.mplot3d import Axes3D


>>> f = open( ’trajectory_f.dat’, ’r’ )
>>> for i in range(6):
ln = f.readline()
>>> traj = []
>>> for line in f:
traj.append( [float(x) for x in line.split()] )
>>> f.close()
>>> traj = np.array(traj)
>>> t = traj[:,0]; x = traj[:,1]; y = traj[:,2]; z = traj[:,3]

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

Enter number of steps: 250


Enter time between points: 0.1

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

void coords( float t, float x, float y, float z )


{ x = cos(t); y=sin(t); z = 0.1*t;
}

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 coords( float t, float x, float y, float z )


{ x = cos(t); y=sin(t); z = 0.1*t;
}

void main()
{
/***** Declare variables *****/

FILE *fptr; /* For file pointer */


int nsteps, ncnt;
float tstart, dt, t, x, y, z;

/* ***** Open file, set number of steps, and write descriptive


lines to file */

fptr = fopen( "trajectory_c.dat", "w" );


fprintf( fptr, "Trajectory when " );
fprintf( fptr, "\n x(t) = cos(t)" );
fprintf( fptr, "\n y(t) = sin(t)" );
fprintf( fptr, "\n z(t) = 0.1 t\n" );
fprintf( fptr, "\n" );

printf( "\nEnter number of steps: "); /* Set number of time steps */


scanf( "%d", &nsteps );
fprintf( fptr, "%d\n", nsteps+1 ); /* Write number of lines to file */
Exercise 9.16 (C) 37

/***** Initialize variables, prime loop, write initial values to file *****/

tstart = 0.0; /* Set starting time */


printf( "\nEnter time between points: "); /* Set time increment */
scanf( "%f", &dt );
ncnt=0; /* Initialize step counter */
t = tstart; /* Initialize current time */
coords( t, x, y, z); /* Evaluate x, y, z at current time */
fprintf( fptr, "%6.3f %6.3f %6.3f %6.3f\n", t,x,y,z );
/* Output initial values to file */

/***** Evaluate desired number of steps, writing values at each step


to file *****/

for( ncnt=1; ncnt<=nsteps; ncnt++ )


{ t = tstart + dt*ncnt; /* Set current time */
coords( t, x, y, z); /* Evaluate x, y, z at current time */
fprintf( fptr, "%6.3f %6.3f %6.3f %6.3f\n", t,x,y,z );
}

/***** Close file *****/

fclose( fptr );

This program would be compiled and executed with the statements

cc -o trajectoryseb.xc trajectoryseb.c
./trajectorysub.xc

When run, with the input

Enter number of steps: 10


Enter time between points: 0.1

the resulting file trajectory c.dat would be the same as the corresponding file displayed earlier.
38 Exercise 9.22 (C)

9.22 Great Circle Distances (C)


Exercise: Write and test a program to ask for the latitude and longitude of both a point of
departure D and a point of arrival A on the surface of the earth and then calculate and print out
the “crow-flies” distance along a great circle route from D to A. Make sure your program prints the
shorter of the two distances, regardless of the location of the points, and make sure your program
doesn’t run into difficulties if the two points happen to be at opposite ends of a diameter. Take the
earth to be a perfect sphere with a circumference of 24900 miles (radius 3963 miles). For purposes
of testing, note that Albany, NY, is at [43◦ 400 N, 73◦ 450 W]; Grand Junction, CO, is at [39◦ 50 N,
108◦ 330 W]; Los Angeles, CA, is at [34◦ 30 N, 118◦ 150 W]; Appleton, WI, is at [44◦ 160 N, 88◦ 250 W];
Calcutta India, is at [22◦ 320 N, 88◦ 200 E]; Sydney, Australia, is at [33◦ 520 S, 151◦ 120 E]; Paris,
France, is at [48◦ 490 N, 2◦ 290 E]; and Stockholm, Sweden, is at [59◦ 210 N, 18◦ 40 E].
Solution: Introduce a coordinate system in which the axis of the earth defines the z axis, a line
from the center of the earth to the equator at a point at longitude of 0◦ defines the x axis, and a line
from the center of the earth to the equation at a point at longitude 90◦ E. In that system, let the
polar and azimuthal angles (not the latitude and longitude) of one point be θ1 , φ1 and the polar and
azimuthal angles of the other point be θ2 , φ2 , where 0◦ ≤ θ1 , θ2 ≤ 90◦ and −180◦ ≤ φ1 , φ2 ≤ 180◦ .
Further, let the radius of the earth, considered to be a sphere, be R. Then vector r1 and r2 from
the center of the earth to each of these points are given by

r1 = R sin θ1 cos φ1 î + sin θ1 sin φ1 ĵ + cos θ1 k̂

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}

Latitudes should be given as three entries


in, for example, the form 15,30,N for
15 degrees, 30 minutes north latitude.
Degrees will range from 0 to 90 and minutes
from 0 to 60; compass direction will be
either N or S. Entries should be separated
by spaces.

Longitudes should also be given as three


entries in, for example, the form 89,44,W
for 89 degrees, 44 minutes west longitude.
Degrees will range from 0 to 180 and minutes
from 0 to 60; compass direction will be
either W or E. Entries should be separated
by spaces.

Note that each triplet is entered using


spaces but NO commas to separate values.

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

values will be typed on the line after the prompt.


40 Exercise 9.22 (C)

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.

which is correct (one-quarter the circumference of the earth).


These results appear to be correct. The remaining source of concern reflects the fact that the
inverse cosine function has some ambiguities. For the correctness of the above analysis, we have
assumed that the inverse cosine returns an angle between zero and π (0◦ and 180◦ ). To check that
assumption, we write the quick program

/* PROGRAM test.c */

#include <stdio.h>
#include <math.h>

float PI, arccos[11], angle[11];


int I;

void main()
{
PI = 3.14159265;

for( I = 0; I<11; I++)


{ arccos[I] = 0.2*(I-5); /* Values from -1.0 to 1.0 */
angle[I] = acos(arccos[I]); /* Find inverse cosine */
angle[I] = 180.0*angle[I]/PI; /* Convert to degrees */
printf( "%f %f\n", arccos[I], angle[I]); /* Display results */
}

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

Final Program for Exercise 9.22

/* PROGRAM greatcircle.c */

#include <stdio.h>
#include <math.h>

void main()

/* 1. Declare those variables needing declaring */

float SLATDEG, SLATMIN, SLAT; /* For starting point latitude */


float ELATDEG, ELATMIN, ELAT; /* For ending point latitude */
float SLONDEG, SLONMIN, SLON; /* For starting point longitude */
float ELONDEG, ELONMIN, ELON; /* For ending point longitude */
float X1[3], X2[3]; /* For intermediate values */
char SLATDIR, ELATDIR, SLONDIR, ELONDIR; /* For compass directions */
float CIRCUM, PI, RADIUS, ANGLE, DIST;

/* 2. Set constants */

CIRCUM = 24900.0;
PI = 3.1415926535;
RADIUS = CIRCUM/(2.0*PI);

/* 3. Provide instructions */

printf( "\nLatitudes should be given as three entries\n" );


printf( "in, for example, the form 15,30,N for\n");
42 Exercise 9.22 (C)

printf( "15 degrees, 30 minutes north latitude.\n" );


printf( "Degrees will range from 0 to 90 and minutes\n" );
printf( "from 0 to 60; compass direction will be\n" );
printf( "either N or S. Entries should be separated\n" );
printf( "by spaces.\n" );

printf( "\nLongitudes should also be given as three\n" );


printf( "entries in, for example, the form 89,44,W\n" );
printf( "for 89 degrees, 44 minutes west longitude.\n" );
printf( "Degrees will range from 0 to 180 and minutes\n" );
printf( "from 0 to 60; compass direction will be\n" );
printf( "either W or E. Entries should be separated\n" );
printf( "by spaces.\n" );
printf( "\nNote that each triplet is entered using\n" );
printf( "spaces but NO commas to separate values.\n" );

/* 4. Get input values */

printf( "Starting latitude: " );


scanf( "%f %f %c", &SLATDEG, &SLATMIN, &SLATDIR );
printf( "Starting longitude: " );
scanf( "%f %f %c", &SLONDEG, &SLONMIN, &SLONDIR );
printf( "Ending latitude: " );
scanf( "%f %f %c", &ELATDEG, &ELATMIN, &ELATDIR );
printf( "Ending longitude: " );
scanf( "%f %f %c", &ELONDEG, &ELONMIN, &ELONDIR );

/* 5. Convert to angles in radians */

SLAT = SLATDEG + SLATMIN/60.0;


if (SLATDIR == ’S’) {SLAT = -SLAT;}
SLAT = PI * (90.0 - SLAT) / 180.0;

SLON = SLONDEG + SLONMIN/60.0;


if (SLONDIR == ’W’) {SLON = -SLON;}
SLON = PI * SLON / 180.0;

ELAT = ELATDEG + ELATMIN/60.0;


if (ELATDIR == ’S’) {ELAT = -ELAT;}
ELAT = PI * (90.0 - ELAT) / 180.0;

ELON = ELONDEG + ELONMIN/60.0;


if (ELONDIR == ’W’) {ELON = -ELON;}
ELON = PI * ELON / 180.0;

/* 6. Calculate angle subtended from center of great circle */

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

ANGLE = X1[1]*X2[1] + X1[2]*X2[2] + X1[3]*X2[3];


ANGLE = acos( ANGLE );

/* 7. Calculate and display distance */

DIST = ANGLE * RADIUS;


printf( "The distance is %7.2f miles.", DIST);

}
Chapter 11

Solving ODEs

11.26 Radioactive Decay (C)


Exercise: Recast the program decay.c so that it invokes (a) the improved Euler method
and (b) the second-order Runge-Kutta method rather than Euler’s method to solve the problem of
three-species decay. Then, compile and test your programs. Give particular attention to exploring
the accuracy of the solution by using several different time steps. Further, compare the accuracy
obtained for various time steps with that obtained for the same time steps using Euler’s method.
Solution: (a) Recasting decay.c so that it invokes the improved Euler method involves chang-
ing how we calculate the new values. We need merely to replace the statements in the one for loop
with the statements

dAdt = -kA*A; /* Calculate old derivatives */


dBdt = kA*A - kB*B;
dCdt = kB*B;
Apre = A + dAdt*dt; /* Calculate predicted values */
Bpre = B + dBdt*dt;
dApre = -kA*Apre; /* Calculate predicted derivatives */
dBpre = kA*Apre - kB*Bpre;
dCpre = kB*Bpre;
A = A + .5*(dAdt + dApre)*dt; /* Calculate corrected values */
B = B + .5*(dBdt + dBpre)*dt;
C = C + .5*(dCdt + dCpre)*dt;
t = t + dt;
fprintf( fptr, "%6.2f %10.3f %10.3f %10.3f\n", t, A, B, C );

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

dAdt = -kA*A; /* Calculate old derivatives */


dBdt = kA*A - kB*B;
dCdt = kB*B;
Ak1 = dAdt*dt; /* Calculate k1 values */
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 );

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.

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, 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()
{

/***** Declare variables *****/

FILE *fptr; /* For file pointer */


float kA, kB; /* For parameters */
float t, A, B, C; /* For independent, dependent variables */
float dAdt, dBdt, dCdt; /* For 1st guess derivatives */
float Apre, Bpre; /* For predictions */
float dApre, dBpre, dCpre; /* For predicted derivatives */
float dt; /* For time increment */
int i, nsteps; /* Loop index, limit */

/***** Get parameters, time step, initial values *****/

printf( "\nDecay constant for A: " ); scanf( "%f", &kA );


printf( "Decay constant for B: " ); scanf( "%f", &kB );
printf( "Number of steps : " ); scanf( "%d", &nsteps );
printf( "Time step : " ); scanf( "%f", &dt );
printf( "Initial A : " ); scanf( "%f", &A );
printf( "Initial B : " ); scanf( "%f", &B );
printf( "Initial C : " ); scanf( "%f", &C );
t = 0.0;

/***** Open file, write label and initial values *****/

fptr = fopen( "decayie_c.dat", "w" );


fprintf( fptr, " t A B C\n" );
fprintf( fptr, "%6.2f %10.3f %10.3f %10.3f\n", t, A, B, C );

/***** Solve equations, writing results to file *****/

for( i=1; i<=nsteps; i++ )


{
dAdt = -kA*A; /* Calculate old derivatives */
dBdt = kA*A - kB*B;
dCdt = kB*B;
Apre = A + dAdt*dt; /* Calculate predicted values */
Bpre = B + dBdt*dt;
dApre = -kA*Apre; /* Calculate predicted derivatives */
dBpre = kA*Apre - kB*Bpre;
dCpre = kB*Bpre;
A = A + .5*(dAdt + dApre)*dt; /* Calculate corrected values */
B = B + .5*(dBdt + dBpre)*dt;
C = C + .5*(dCdt + dCpre)*dt;
t = t + dt;
Exercise 11.26 (C) 49

fprintf( fptr, "%6.2f %10.3f %10.3f %10.3f\n", t, A, B, C );


}

/***** Close file *****/

fclose( fptr );
}

Listing of decayrk.c

/* PROGRAM decayie.c */

#include <stdio.h>
#include <math.h>

main()
{

/***** Declare variables *****/

FILE *fptr; /* For file pointer */


float kA, kB; /* For parameters */
float t, A, B, C; /* For independent, dependent variables */
float dAdt, dBdt, dCdt; /* For derivatives */
float Ak1, Bk1, Ck1; /* For corrections */
float Ak2, Bk2, Ck2; /* For corrections */
float dt; /* For time increment */
int i, nsteps; /* Loop index, limit */

/***** Get parameters, time step, initial values *****/

printf( "\nDecay constant for A: " ); scanf( "%f", &kA );


printf( "Decay constant for B: " ); scanf( "%f", &kB );
printf( "Number of steps : " ); scanf( "%d", &nsteps );
printf( "Time step : " ); scanf( "%f", &dt );
printf( "Initial A : " ); scanf( "%f", &A );
printf( "Initial B : " ); scanf( "%f", &B );
printf( "Initial C : " ); scanf( "%f", &C );
t = 0.0;

/***** Open file, write label and initial values *****/

fptr = fopen( "decayrk_c.dat", "w" );


fprintf( fptr, " t A B C\n" );
fprintf( fptr, "%6.2f %10.3f %10.3f %10.3f\n", t, A, B, C );

/***** Solve equations, writing results to file *****/

for( i=1; i<=nsteps; i++ )


{
dAdt = -kA*A; /* Calculate old derivatives */
dBdt = kA*A - kB*B;
dCdt = kB*B;
Ak1 = dAdt*dt; /* Calculate k1 values */
50 Exercise 11.26 (C)

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

/***** Close file *****/

fclose( fptr );
}
Exercise 11.33 (Numerical Recipes-C) 51

11.33 Logistic Growth (Numerical Recipes-C)


Exercise: Use two different numerical recipes of your choice to study the logistic growth of a
population.
Solution: The logistic growth of a population is described by the equation
 
dN N
= kN 1 −
dt Nc
where N is the population at a given time, k is the growth rate, and Nc is the carrying capacity
of the environment. As a first approach, we elect to use the Numerical Recipes routine rk4.c,
patterning our approach after the program decayrk4.c discussed in CPSUP. We declare that the
parameters k and Nc will be named k and Nc, that time will be represented by the variable t, and
that N[1] and dNdt[1] will represent N and dN/dT . Further, we note that we must define N and
dNdt as arrays with dimension two (because indices in C start at zero), even though we have only a
single equation to solve. Then, we create the procedure

void derivs( float t, float N[], float dNdt[] )


{
float k=0.1, Nc=500.0;
dNdt[1] = k * N[1] * ( 1.0 - N[1]/Nc );
}

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

int i, n; /* For loop index, number of steps */


float dt, t; /* For time step, time */
float N[2], dNdt[2]; /* For N, and dN/dt */
FILE *fptr; /* For output file */

N[1] = 15.0; /* Initialize population */


t = 0.0; /* Initialize time */
dt = 0.25; /* Set time step */
n = 400; /* Set number of steps */

(2) open a suitably named file and write a label and the initial values into that file with the statements

fptr = fopen( "growrk4_c.dat", "w" );


fprintf( fptr, " T N\n" );
fprintf( fptr, " %12.4f %12.4f\n", t, N[1] );

and (3) step the solution forward the selected number of time steps, writing the solution at each
time into the file, with the statements

for ( i=0; i<n; i++ )


{
derivs( t, N, dNdt );
t = t + dt;
rk4( N, dNdt, 1, t, dt, N, derivs );
fprintf( fptr, " %12.4f %12.4f\n", t, N[1] );
}
fclose(fptr);
52 Exercise 11.33 (Numerical Recipes-C)

Table 11.2: Listing of growrk4.c.

/* Program growrk4.c */

#include <stdio.h>
#include <math.h>
#include "nr.h"
#include "nrutil.h"

void der( float t, float N[], float dNdt[] )


{
float k=0.1, Nc=500.0;
dNdt[1] = k * N[1] * ( 1.0 - N[1]/Nc );
}
void main(void)
{
int i, n; /* For loop index, number of steps */
float dt, t; /* For time step, time */
float N[2], dNdt[2]; /* For N, and dN/dt */
FILE *fptr; /* For output file */

N[1] = 15.0; /* Initialize population */


t = 0.0; /* Initialize time */
dt = 0.25; /* Set time step */
n = 400; /* Set number of steps */

fptr = fopen( "growrk4_c.dat", "w" );


fprintf( fptr, " T N\n" );
fprintf( fptr, " %12.4f %12.4f\n", t, N[1] );

for ( i=0; i<n; i++ )


{
der( t, N, dNdt );
rk4( N, dNdt, 1, t, dt, N, der );
t = t + dt;
fprintf( fptr, " %12.4f %12.4f\n", t, N[1] );
}
fclose(fptr);
}

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

cc -o growrk4.xc growrk4.c rk4.c nrutil.c


./growrk4.xc

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

rkqs( y, dydt, n, t, dttry, eps, yscale, dtact, dtnext, derivs)

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

void derivs( float t, float N[], float dNdt[] )


{
float k=0.1, Nc=500.0;
dNdt[1] = k * N[1] * ( 1.0 - N[1]/Nc );
}

Next, we need to set several parameters with the statements

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 */

N[1] = 15.0; /* Initialize population */


t = 0.0; /* Initialize time */
dttry = 0.25; /* Set time step */
eps = 1.0e-6; /* Set accuracy */
cscale[1] = 1.0; /* Set scaling reference */
Exercise 11.33 (Numerical Recipes-C) 55

Then, we open a suitably named file and write a label and the initial values into that file with the
statements

fptr = fopen( "growrkqs_c.dat", "w" );


fprintf( fptr, " T N\n" );
fprintf( fptr, " %12.4f %12.4f\n", t, N[1] );

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

while (t < 100.0 )


{ derivs( t, N, dNdt );
rkqs( N, dNdt, 1, &t, dttry, eps, cscale, &dtact, &dtnext, derivs );
fprintf( fptr, " %12.4f %12.4f\n", t, N[1] );
dttry = dtnext;
}

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

cc -o growrkqs.xc growrkqs.c rkqs.c rkck.c nrutil.c


./growrkqs.xc

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)

Table 11.3: Listing of growrkqs.c.

/* Program growrkqs.c */

#include <stdio.h>
#include <math.h>
#include "nr.h"
#include "nrutil.h"

void derivs( float t, float N[], float dNdt[] )


{
float k=0.1, Nc=500.0;
dNdt[1] = k * N[1] * ( 1.0 - N[1]/Nc );
}

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 */

N[1] = 15.0; /* Initialize population */


t = 0.0; /* Initialize time */
dttry = 0.25; /* Set time step */
eps = 1.0e-6; /* Set accuracy */
cscale[1] = 1.0; /* Set scaling reference */

fptr = fopen( "growrkqs_c.dat", "w" );


fprintf( fptr, " T N\n" );
fprintf( fptr, " %12.4f %12.4f\n", t, N[1] );

while (t < 100.0 )


{ derivs( t, N, dNdt );
rkqs( N, dNdt, 1, &t, dttry, eps, cscale, &dtact, &dtnext, derivs );
fprintf( fptr, " %12.4f %12.4f\n", t, N[1] );
dttry = dtnext;
}
fclose(fptr);
}
Exercise 11.36 (Numerical Recipes-C) 57

11.36 Lorenz system (Numerical Recipes-C)


Exercise: Choose one of the exercises from Section 11.2 and address it using at least one
numerical recipe.
Solution: The Lorenz system is described by the equations
dx dy dz
= a(y − x) ; = −xz + bx − y ; = xy − cx
dt dt dt
We elect to use rk4.c and establish the correspondences x 7→ y[1], y 7→ y[2], and z 7→ y[3]. Then,
the procedure returning the derivatives will assume the form

void derivs(float x,float y[],float dydt[])


{
float a = 10.0, b = 28.0, c = 8.0/3.0; /* Set parameters */

dydt[1] = a * ( y[2] - y[1] );


dydt[2] = -y[1]*y[3] + b*y[1] - y[2];
dydt[3] = y[1]*y[2] - c*y[3];
}

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

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 */

printf("\nInitial x : " ); scanf( "%f", &y[1] );


printf("Initial y : " ); scanf( "%f", &y[2] );
printf("Initial z : " ); scanf( "%f", &y[3] );
printf("Time step : " ); scanf( "%f", &dt );
printf("Number of steps : " ); scanf( "%d", &N );

t = 0.0; /* Initialize time */

Then, we open a suitable file and write a header and the initial values into the file with the statements

fptr = fopen( "lorenz_c.dat", "w" );


fprintf( fptr, " T X Y Z\n" );
fprintf( fptr, "%10.2f %14.5f %14.5f %14.5f\n",
t, y[1], y[2], y[3] );

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

for( i=1; i<=N; i++ )


{ derivs( t, y, dydt );
58 Exercise 11.36 (Numerical Recipes-C)

rk4( y, dydt, 3, t, dt, y, derivs );


t = t + dt;
fprintf( fptr, "%10.2f %14.5f %14.5f %14.5f\n",
t, y[1], y[2], y[3] );
}
fclose( fptr );

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

cc -o lorenz.xc lorenz.c rk4.c nrutil.c


./lorenz.xc
Initial x : 1.0
Initial y : 0.0
Initial z : 0.0
Time step : 0.01
Number of steps : 5000

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

Table 11.4: Listing of lorenz.c.

/* PROGRAM lorenz */

#include <stdio.h>
#include "nr.h"
#include "nrutil.h"

void derivs(float x,float y[],float dydt[])


{
float a = 10.0, b = 28.0, c = 8.0/3.0; /* Set parameters */

dydt[1] = a * ( y[2] - y[1] );


dydt[2] = -y[1]*y[3] + b*y[1] - y[2];
dydt[3] = y[1]*y[2] - c*y[3];
}

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 */

printf("\nInitial x : " ); scanf( "%f", &y[1] );


printf("Initial y : " ); scanf( "%f", &y[2] );
printf("Initial z : " ); scanf( "%f", &y[3] );
printf("Time step : " ); scanf( "%f", &dt );
printf("Number of steps : " ); scanf( "%d", &N );

t = 0.0; /* Initialize time */

fptr = fopen( "lorenz_c.dat", "w" );


fprintf( fptr, " T X Y Z\n" );
fprintf( fptr, "%10.2f %14.5f %14.5f %14.5f\n",
t, y[1], y[2], y[3] );

for( i=1; i<=N; i++ )


{ derivs( t, y, dydt );
rk4( y, dydt, 3, t, dt, y, derivs );
t = t + dt;
fprintf( fptr, "%10.2f %14.5f %14.5f %14.5f\n",
t, y[1], y[2], y[3] );
}
fclose( fptr );
}
60 Exercise 11.36 (Numerical Recipes-C)

49.93 0.51045 1.07401 15.90597


49.94 0.56669 1.12928 15.49328
49.95 0.62325 1.19329 15.09240
49.96 0.68096 1.26645 14.70317
49.97 0.74060 1.34923 14.32544
49.98 0.80294 1.44223 13.95910
49.99 0.86871 1.54612 13.60410
50.00 0.93867 1.66171 13.26042

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

13.34 Simpson’s Rule (C)


Exercise: Patterning your program after the program trapezoidal.c, write and test a C
program that uses Simpson’s rule to evaluate
Z b
2 2
I=√ e−x dx
π a

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

float func( x ) /* Define integrand as function */


float x;
{
return 2.0*exp(-pow(x,2))/sqrt(3.14159265);
}

The limits and number of divisions would be obtained from the keyboard with statements identical
to those in trapezoidal.c, namely

printf( "\nLower limit : " ); scanf("%f", &a );


printf( "Upper limit : " ); scanf("%f", &b );
printf( "Number of segments: " ); scanf("%d", &n );

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)

dx = (b-a)/n; /* Set size of segment */

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

value = func(a); /* Compute first term */


for ( i=1; i<=n-1; i=i+2 ) /* Add terms multiplied by 4 */
value = value + 4.0*func( a + i*dx );
for ( i=2; i<=n-2; i=i+2 ) /* Add terms multiplied by 2 */
value = value + 2.0*func( a + i*dx );
value = value + func(b); /* Add final term */
value = 3.0 * value * dx; /* Incorporate overall factor */

Finally, we display the value of the integral with the statement

printf("Integral = %12.6f\n", value);

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

printf( "Number of segments: " ); scanf("%d", &n );


while (n/2 != n/2.0)
{ printf( "Value of n must be even.\n" );
printf( "Number of segments: " ); scanf("%d", &n ); }

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

In response, the “conversation” with the computer might look like

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

This C program evaluates the integral of a user-


specified function between user-specified limits for a
user-specified number of divisions of the interval of
integration. Integration is achieved with Simpson’s
rule.
*/

#include <stdio.h> /* Load standard i/o routines */


#include <math.h> /* Load standard math routines */

float func( x ) /* Define integrand as function */


float x;
{
return 2.0*exp(-pow(x,2))/sqrt(3.14159265);
}

main()
{
float a, b, value; /* For limits, sum */
float dx; /* For step size */
int n, i; /* For number of segments, loop index */

/***** Read limits, number of segments *****/

printf( "\nLower limit : " ); scanf("%f", &a );


printf( "Upper limit : " ); scanf("%f", &b );
printf( "Number of segments: " ); scanf("%d", &n );
while (n/2 != n/2.0)
{ printf( "Value of n must be even.\n" );
printf( "Number of segments: " ); scanf("%d", &n ); }

/***** Evaluate integral *****/

dx = (b-a)/n; /* Set size of segment */


value = func(a); /* Compute first term */
for ( i=1; i<=n-1; i=i+2 ) /* Add terms multiplied by 4 */
value = value + 4.0*func( a + i*dx );
for ( i=2; i<=n-2; i=i+2 ) /* Add terms multiplied by 2 */
value = value + 2.0*func( a + i*dx );
value = value + func(b); /* Add final term */
value = value * dx / 3.0; /* Incorporate overall factor */

/***** Display result *****/

printf("Integral = %12.6f\n", value);


}
Exercise 13.35 (C) 65

13.35 Gaussian Integration (C)


Exercise: Write and test a C program that uses the five-point Gaussian formula to evaluate
the integral
Z b
2 2
I=√ e−x dx
π a

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

printf("%s","\nEnter lower Limit: "); scanf("%f", &a);


printf("%s", "Enter upper Limit: "); scanf("%f", &b);
printf("%s", "Enter number of steps: "); scanf("%d", &m);

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

float ans, sum, b, a, dt, tmid;


float x[6], w[6], g[6];
int i, j, m;

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

printf("%s %25.8f\n\n","Integral =", ans);

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

cc -o gauss.xc gauss.c -lm

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

dimension the corresponding variable to have six elements.


Exercise 13.35 (C) 67

/* PROGRAM gauss.c

This FORTRAN program evaluates the integral of a user-


specified function between user-specified limits for a
user-specified number of divisions of the interval of
integration. Integration is achieved with the five-point
Gaussian formula.
*/

#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 */

float ans, sum, b, a, dt, tmid;


float x[6], w[6], g[6];
int i, j, m;

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;

/* Read limits, number of segments */

printf("%s","\nEnter lower Limit: "); scanf("%f", &a);


printf("%s", "Enter upper Limit: "); scanf("%f", &b);
printf("%s", "Enter number of steps: "); scanf("%d", &m);

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

sum = sum + g[i]*w[i];


}
ans = ans + sum;
}
ans = (dt/2.0)*ans;

/* Display result */

printf("%s %25.8f\n\n","Integral =", ans);


}
Exercise 13.44 (Numerical Recipes–C) 69

13.44 Numerical Integration (Numerical Recipes–C)


Exercise: Following the pattern illustrated in Section 13.14.2, create and test C programs
errqsimp.c and errqromb.c using qsimp.c and qromb.c, respectively, to evaluate erf(1.0). Note
that, in addition to trapzd.c, qromb.c also invokes polint.c, which will have to be available and
included in the compile instruction before errqromb.c will compile successfully.
Solution: We wish to create and test C programs errqsimp.c and errqromb.c to evaluate
erf(1.0) using the Numerical Recipes routines qsimp.c and qromb.c, respectively. Recall that
Z 1
2 2
erf(1.0) = √ e−s ds
π 0

We construct errqsimp as a driving program for qsimp by assembling the statements

/* PROGRAM errqsimp */

#include <stdio.h>
#include <math.h>
#include "nr.h"

float func(float x) /* Define integrand as function */


{
return 2.0*exp(-pow(x,2))/sqrt(3.14159265);
}

main()
{
float a, b, s; /* For limits, sum */

a = 0.0; b = 1.0; /* Set limits */


s = qsimp( func, a, b ); /* Evaluate integral */

printf("Result using QSIMP = %10.6f\n", s); /* Display result */

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

cc -o errqsimp.xc errqsimp.c qsimp.c trapzd.c nrutil.c -lm


./errqsimp.xf

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

Result from routine QSIMP is 0.842701

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"

float func(float x) /* Define integrand as function */


{
return 2.0*exp(-pow(x,2))/sqrt(3.14159265);
}

main()
{
float a, b, s; /* For limits, sum */

a = 0.0; b = 1.0; /* Set limits */


s = qromb( func, a, b ); /* Evaluate integral */

printf("Result using QROMB = %10.6f\n", s); /* Display result */

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

cc -o errqromb.xc errqromb.c qromb.c trapzd.c polint.c nrutil.c -lm


./errqromb.xf

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

Result using QROMB = 0.842701

a value that agrees with all other evaluations we have made of this same integral.
Exercise 13.47 (Numerical Recipes–C) 71

13.47 Maxwell-Boltzmann Distribution (Numerical


Recipes–C)
Exercise: Edit the driving program errqtrap.c (or—better—the driving program errqsimp.c
created in an earlier exercise) so that it evaluates the integral
Z x
F (x) = g(s) ds
0

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

float func(float x) /* Define integrand as function */


{
return pow(x,2)*exp(-pow(x,2)/2.0);
}

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

cc -o errtrapzd.xc errtrapzd.c trapzd.c -lm


./errtrapzd.xc
n approx. integral
1 0.303265
2 0.261945
3 0.252266
4 0.249884
5 0.249291
6 0.249143
7 0.249106
8 0.249097
9 0.249095
10 0.249094
11 0.249094
12 0.249094
13 0.249094
14 0.249094

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)

cc -o errqtrap.xc errqtrap.c trapzd.c qtrap.c nrutil.c -lm


./errqtrap.xc
Result using QTRAP = 0.249095

cc -o errqsimp.xc errqsimp.c trapzd.c qsimp.c nrutil.c -lm


./errqsimp.xc
Result using QSIMP = 0.249094

cc -o errqromb.xc errqromb.c trapzd.c qromb.c polint.c nrutil.c -lm


./errqromb.xc
Result using QROMB = 0.249094

Happily, the results all agree with each other.


b) To evaluate the quantity as a function of v over the range 0.0 ≤ v ≤ 3.0, we modify the program
errtrapzd.c to become this:

/* PROGRAM errtrapzd */

#include <stdio.h> /* Load standard i/o routines */


#include <math.h> /* Load standard math functions */
#include "nr.h" /* Load necessary recipes headers */

float func(float x) /* Define integrand as function */


{
return pow(x,2)*exp(-pow(x,2)/2.0);
}

main()
{
float a, b, s; /* For limits, sum */
int numit, i, j; /* For number of iterations */
FILE *fptr; /* For file pointer */

numit = 14; /* Set number of iterations */


a = 0.0; /* Set lower limit */

/* Open the file to write to */


fptr=fopen( "errtrapzd_c.dat", "w");
for(j=1;j<=1000;j++)
{
b = j*0.003;
for (i=1;i<=numit;i++)
{
s=trapzd( func, a, b, i );
}
/* 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 */
}

We then compile, link, and run this program with the statements
Exercise 13.47 (Numerical Recipes–C) 73

cc -o errtrapzd.xc errtrapzd.c trapzd.c -lm


./errtrapzd.xc

Next, we modify the program errqtrap.c in the same way to become

/* PROGRAM errqtrap */

#include <stdio.h> /* Load standard i/o routines */


#include <math.h> /* Load standard math routines */
#include "nr.h" /* Load necessary recipes headers */

float func(float x) /* Define integrand as function */


{
return pow(x,2)*exp(-pow(x,2)/2.0);
}

main()
{
float a, b, s; /* For limits, sum */
int i; /* For iterations */
FILE *fptr; /* For file pointer */

a = 0.0; /* Set lower limit */

/* Open the file to write to */


fptr=fopen( "errqtrap_c.dat", "w");

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

2.6788 2.6788 2.6789 2.6789 2.679 2.6791 2.6791


Value of the upper limit
Chapter 14

Finding Roots

14.12 Square Root by Newton’s Method (C)


Exercise: One way to find the square root of a (positive) number a is to find the root of the
function f (x) = x2 −a. (a) Apply Newton’s method symbolically to show that xn+1 = (xn +a/xn )/2.
(b) Using a pocket calculator and starting with the guess x√ 0 = 2, work out the first few iterates
by hand and note how quickly this algorithm converges to 2 = 1.41421. (This algorithm is the
algorithm that most pocket calculators invoke when the square root key is pressed.) (c) Using
whatever √ computational tool appeals to you, write a program that asks for the√value of a, an initial
guess for a, and a tolerance and then implements Newton’s method to find a, printing out each
iterate along the way and stopping automatically when successive iterates differ by less than the
specified tolerance.
Solution: (a) The square root of a number a can be found by locating the roots of the equation
f (x) = x2 − a. With the shorthand notation fn = f (xn ) and fn0 = df (x)/dx|xn , we can write
Newton’s method for the function of interest in the form
x2 − a
 
fn xn a 1 a
xn+1 = xn − 0 = xn − n = xn − + = xn +
fn 2xn 2 2xn 2 xn

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

printf( "Number whose root is to be found: " ); scanf("%f", &a);


printf( "Initial guess: " ); scanf("%f", &val);
printf( "Absolute tolerance: " ); scanf("%f", &t);

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

A program implementing Newton’s method for finding the square root.

(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

to find the square root of 2 to a tolerance of 0.0001.


A few more sample sessions are shown below. First, we seek the square root of 2 again, but
with a different initial guess, with the “conversation”

./squareroot.xc
Number whose root is to be found: 2.0
Exercise 14.12 (C) 77

Initial guess: 5.0


Absolute tolerance: 0.0001
Iterative Results:
2.700000
1.720370
1.441455
1.414471
1.414214
1.414214
Square root = 1.414214

Next, we seek the square root of 55 with the “conversation”

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

14.26 Rootfinding with Newton’s Method (C)


Exercise: Write and test a C program paralleling bisect.c but using Newton’s method to
find the roots of f (x). Your program, which you might call newton.c, should

• 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

and for which we know that the roots are

x1 = −48.26826 ; x2 = −11.08043 ; x3 = 9.348704

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

printf("\nTolerance = "); scanf("%f", &tol);


printf("Max number of iterations = "); scanf("%d", &maxit);
printf("Print all iterates? (1 for yes, 0 for no)= ");
scanf("%d", &FLAG);
printf("Initial guess = "); scanf("%f", &xold);

The statements
Exercise 14.26 (C) 79

it = 0; /* Initialize the iteration counter */


ttol = tol + 1; /* Initialize the step difference variable */

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

if (FLAG != 0) /* Output value if wanted */


{
printf("\n%f", xold);
}

while(tol < ttol && it < maxit)


{
fn = func(xold); /* Evaluate function */
dfn = funcd(xold); /* Evaluatee derivative */
xnew = xold - fn/dfn; /* Calculate next iterate */
it = it + 1; /* Increment counter */
if (FLAG != 0) /* Output value if wanted */
{
printf("\n%f", xnew);
}
ttol = fabs(xold - xnew); /* Evaluate change in iterates */
xold = xnew; /* Prepare for next loop */
}

Finally, we print the requested warning (if appropriate) and output the final value with the state-
ments

if (ttol >= tol) /* Print warning */


{
printf("\nMax iterations exceeded!\n");
printf("Stopped at = %15.6f\n",xnew);/* PROGRAM newton.c */
printf( "with value = %15.6f\n\n", func(xnew) );
}
else /*Otherwise write the value out*/
{
printf("\nRoot = %15.6f\n", xnew);
printf("Attained in %4d iterates\n", it );
printf("Value at root = %15.6f\n\n", func(xnew) );
}

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)

/* ***** DEFINE FUNCTION AND DERIVATIVE ***** */

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

float tol, xold, ttol, fn, dfn, xnew;


int maxit, FLAG, it;

/* ***** INPUT COMTROLLING PARAMETERS ***** */

printf("\nTolerance = "); scanf("%f", &tol);


printf("Max number of iterations = "); scanf("%d", &maxit);
printf("Print all iterates? (1 for yes, 0 for no)= ");
scanf("%d", &FLAG);
printf("Initial guess = "); scanf("%f", &xold);

/* ***** INITIALIZE VARIABLES AND OUTPUT STARTING VALUE ***** */

it = 0; /* Initialize the iteration counter */


ttol = tol + 1; /* Initialize the step difference variable */

if (FLAG != 0) /* Output value if wanted */


{
printf("\n%f", xold);
}

/* ***** FIND ROOT ***** */

while(tol < ttol && it < maxit)


{
fn = func(xold); /* Evaluate function */
dfn = funcd(xold); /* Evaluatee derivative */
xnew = xold - fn/dfn; /* Calculate next iterate */
it = it + 1; /* Increment counter */
if (FLAG != 0) /* Output value if wanted */
{
printf("\n%f", xnew);
}
ttol = fabs(xold - xnew); /* Evaluate change in iterates */
xold = xnew; /* Prepare for next loop */
}
Exercise 14.26 (C) 81

/* ***** DISPLAY RESULTS ***** */

if (ttol >= tol) /* Print warning */


{
printf("\nMax iterations exceeded!\n");
printf("Stopped at = %15.6f\n",xnew);
printf( "with value = %15.6f\n\n", func(xnew) );
}
else /*Otherwise write the value out*/
{
printf("\nRoot = %15.6f\n", xnew);
printf("Attained in %4d iterates\n", it );
printf("Value at root = %15.6f\n\n", func(xnew) );
}
}

With the program in hand, we then compile, link and run it with test input with the statements

cc -o newton.xc newton.c -lm


./newton.xc

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

Max iterations exceeded!


Stopped at = -48.268887
with value = -0.000133

and to text the display of intermediate iterates, we use the input

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

14.14 Natural Frequencies of Bar (Numerical Recipes-C)


Exercise: The natural frequencies for the transverse vibrations of a bar of uniform cross section
that has length L and is free at both ends are given by
s
4K E 2
ωn = 2 α
L ρ n

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

static float fa( float a )


{
return tan(a) + j*tanh(a);
}

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.

3. The program xrtbis.f includes an automatic scanning of an interval to find subintervals in


which roots may exist. Because of the divergences of the tangent function, this process will
find phantom intervals at the points of divergence. We elect to replace the automatic finding of
intervals with an explicit coding of the upper and lower bounds in which we know meaningful
and correct roots to exist. Thus, we delete the call to zbrak.f and most of the variables
associated with that call. Further, we hard code the number of roots to be sought for each
function. Remember that indices in C run from 0. Thus, if we want to use indices from 1 to
3, we must dimension the vectors at 4.

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

6. We change all comments to fit our program.

With these changes, our final program natfreq bisect.c is

/* Program natfreq_bisect.c */

#include <stdio.h>
#include <math.h>
#include "nr.h"
#include "nrutil.h"

float j; /* Set j global */

static float fa( float a ) /* Define function */


{
return tan(a) + j*tanh(a);
}

int main(void)
{
int i; /* Loop index */
float aacc, root, tmp;
float ab1[4], ab2[4], sqr[7]; /* Bounds, square of root */

aacc = 1.0e-6; /* Set precision */


j = 1.0; /* Set for tan(a)+tanh(a) */
ab1[1] = 2.0; ab2[1] = 3.0; /* Set bounds */
ab1[2] = 5.2; ab2[2] = 6.0;
ab1[3] = 8.0; ab2[3] = 9.0;

printf("\n The roots of tan(a) + tanh(a) their squares:\n" );


printf("\n%21s %15s %13s %15s\n\n","a","f(a)","a^2", "a^2/a1^2");

for (i=1;i<=3;i++) /* Find, save display roots */


{ root = rtbis( fa, ab1[i], ab2[i], aacc );
sqr[2*i-1] = pow( root, 2 );
tmp = sqr[2*i-1]/sqr[1];
printf("root %3d %14.6f %14.6f %14.6f %14.6f\n",
2*i-1, root, fa(root), sqr[2*i-1], tmp );
}

j = -1; /* Set for tan(a)-tanh(a) */


ab1[1] = 3.5; ab2[1] = 4.5; /* Set bounds */
ab1[2] = 6.5; ab2[2] = 7.5;
ab1[3] = 10.0; ab2[3] = 10.7;

printf("\n The roots of tan(a) - tanh(a) and their squares:\n" );


printf("\n%21s %15s %13s %15s\n\n","a","f(a)","a^2", "a^2/a1^2");

for (i=1;i<=3;i++) /* Find, save, display roots */


{ root = rtbis( fa, ab1[i], ab2[i], aacc );
86 Exercise 14.14 (Numerical Recipes-C)

sqr[2*i] = pow( root, 2 );


tmp = sqr[2*i]/sqr[1];
printf("root %3d %14.6f %14.6f %14.6f %14.6f\n",
2*i, root, fa(root), sqr[2*i], tmp );
}
return 0;
}

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

cc -o natfreq_bisect.xc natfreq.c nrutil.c rtbis.c -lm

and then run the program with the statement

./natfreq_bisect.xc

The resulting output is

The roots of tan(a) + tanh(a) and their squares:

a f(a) a^2 a^2/a1^2

root 1 2.365020 -0.000001 5.593318 1.000000


root 3 5.497804 0.000000 30.225845 5.403920
root 5 8.639380 -0.000001 74.638878 13.344293

The roots of tan(a) - tanh(a) and their squares:

a f(a) a^2 a^2/a1^2

root 2 3.926601 -0.000002 15.418199 2.756539


root 4 7.068583 0.000000 49.964859 8.932955
root 6 10.210176 -0.000001 104.247681 18.637896

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"

float j; /* Set j global */

static float fa(float a)


{
return tan(a) + j*tanh(a);
}

static void funcd(float a,float *fn, float *df)


{
*fn = tan(a) + j*tanh(a);
*df = pow( tan(a), 2 ) - j * pow( tanh(a), 2 ) + j + 1.0;
}

int main(void)
{
int i; /* Loop index */
float aacc, root, tmp;
float ab1[4], ab2[4], sqr[7]; /* Bounds, square of root */

j = 1.0; /* Set for tan(a)+tanh(a) */


ab1[1] = 2.0; ab2[1] = 3.0; /* Set bounds */
ab1[2] = 5.2; ab2[2] = 6.0;
ab1[3] = 8.0; ab2[3] = 9.0;

printf("\n The roots of tan(a) + tanh(a) and their squares:\n" );


printf("\n%21s %15s %13s %15s\n\n","a","f(a)","a^2", "a^2/a1^2");

for (i=1;i<=3;i++) /* Find, save display roots */


{ aacc = (1.0e-6)*(ab1[i]+ab2[i])/2.0;
root = rtnewt( funcd, ab1[i], ab2[i], aacc );
sqr[2*i-1] = pow( root, 2 );
tmp = sqr[2*i-1]/sqr[1];
printf("root %3d %14.6f %14.6f %14.6f %14.6f\n",
2*i-1, root, fa(root), sqr[2*i-1], tmp );
}

j = -1; /* Set for tan(a)-tanh(a) */


ab1[1] = 3.5; ab2[1] = 4.5; /* Set bounds */
ab1[2] = 6.5; ab2[2] = 7.5;
ab1[3] = 10.0; ab2[3] = 10.7;

printf("\n The roots of tan(a) - tanh(a) and their squares:\n" );


printf("\n%21s %15s %13s %15s\n\n","a","f(a)","a^2", "a^2/a1^2");

for (i=1;i<=3;i++) /* Find, save display roots */


{ aacc = (1.0e-6)*(ab1[i]+ab2[i])/2.0;
root = rtnewt( funcd, ab1[i], ab2[i], aacc );
88 Exercise 14.14 (Numerical Recipes-C)

sqr[2*i] = pow( root, 2 );


tmp = sqr[2*i]/sqr[1];
printf("root %3d %14.6f %14.6f %14.6f %14.6f\n",
2*i, root, fa(root), sqr[2*i], tmp );
}

return 0;
}

This program uses the alternative recipe rtnewt.c and can be compiled, linked, and run with the
statements

cc -o natfreq_newt.xc natfreq_newt.c rtnewt.c nrutil.c -lm


./natfreq_newt.xc

The resulting output is

The roots of tan(a) + tanh(a) and their squares:

a f(a) a^2 a^2/a1^2

root 1 2.365020 0.000000 5.593321 1.000000


root 3 5.497804 0.000000 30.225845 5.403918
root 5 8.639380 -0.000001 74.638878 13.344287

The roots of tan(a) - tanh(a) and their squares:

a f(a) a^2 a^2/a1^2

root 2 3.926602 0.000000 15.418206 2.756539


root 4 7.068583 0.000000 49.964859 8.932951
root 6 10.210176 0.000001 104.247704 18.637892

Within roundoff error, this output is entirely consistent with that produced by the first program.
Exercise 14.18 (Numerical Recipes-C) 89

14.18 Single-Slit Diffraction (Numerical Recipes-C)

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.

Figure E14.2: Intensity as a function of x.


90 Exercise 14.18 (Numerical Recipes-C)

maxima, i.e., the points at which


sin2 x 2 sin x cos x 2 sin2 x
 
d
g(x) = = − =0
dx x2 x2 x3
Using the C version of Numerical Recipes routines to locate the roots of this equation is quite simple.
Furthermore, we see from the graph that the maxima occur at every other root of the first derivative.
We can use two different methods to find the roots of the first derivative of the intensity
function. To use rtbis.c, we copy xrtbis.c from $NRHEAD/recipes_c-ansi/demo/src into the
default directory under a new name (here ex18bin.c). It appears from the graph that if we modify
our parameters to take us through the range −0.5 ≤ x ≤ 20.0 or so, we will be able to find the first
six maxima. We must also tell the program that we are using an external function fx, which we
define in the beginning to be the first derivative of our intensity function. Furthermore, we add the
statement #include <math.h> so that we can use the sine, cosine, and pow functions. Finally, we
modify the xacc parameter in the for loop to be just 1.0e − 6. The resulting program is

/* 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

static float fx(float x)


{
return 2*sin(x)*cos(x)*pow(x, -2.0) - 2.0*pow(x, -3.0)*pow(sin(x), 2.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("%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.4e\n", i, root, fx(root) );
}
}

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

cc -o ex18bin.xc ex18bin.c rtbis.c zbrak.c nrutil.c -lm


./ex18bin.xc
Exercise 14.18 (Numerical Recipes-C) 91

The resulting output is

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

static float fx(float x)


{
return 2*sin(x)*cos(x)*pow(x, -2.0) - 2.0*pow(x, -3.0)*pow(sin(x), 2.0);
}

static void funcd(float x,float *fn, float *df)


{
*fn= 2.0*cos(x)*sin(x)*pow(x, -2.0) - 2.0*pow(sin(x), 2.0)*pow(x,-3.0);
*df = 2*pow(x, -2.0)*(pow(cos(x), 2.0)-pow(sin(x), 2.0)-
2*sin(x)*cos(x)/x) - 4*sin(x)*cos(x)*pow(x, -3.0)+
6*pow(sin(x),2.0)*pow(x, -4.0);
}
int main(void)
{
int i, nb=NBMAX;
92 Exercise 14.18 (Numerical Recipes-C)

float xacc,root, xb1[NBMAX+1], xb2[NBMAX+1];

zbrak( fx, X1, X2, N, xb1, xb2, &nb );


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.4e\n",i,root,fx(root));
}
}

When the program is compiled and executed with the statements

cc -o ex18newt.xc ex18newt.c zbrak.c nrutil.c rtnewt.c -lm


./ex18newt.xc

the screen displays the results

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

14.19 Roots of J0 (x) (Numerical Recipes-C)


Exercise: Using at least three different methods and at least two different computational
tools, find the first half dozen roots of the zeroth-order Bessel function J0 (x). Note that these
roots are related to the radii of circular nodes in some of the vibrations of a circular membrane.
The values of these roots tabulated in Abramowitz and Stegun3 are 2.4048255577, 5.5200781103,
8.6537279129, 11.7915344391, 14.9309177086, 18.0710639679. Hint: Most computational tools have
built-in capabilities for evaluating the Bessel functions. Consult the appropriate vendor manuals.
Solution: We wish to know the first half dozen roots of the zeroth-order Bessel function.
The values of the roots as tabulated by Abramowitz and Stegun are 2.40485 55577, 5.52007 81103,
8.65372 79129, 11.79153 44391, 14.93091 77086, and 18.07106 39679. Using rtbis.c in C, we can
evaluate the Bessel function quickly by noticing that there are recipes for different orders of the func-
tion. We can copy the xrtbis.c driver from $NRHEAD/recipes_c-ansi/demo/src into our default
directory (saving it as bessrtbis.c). In addition, we copy the files rtbis.c, zbrak.c, nrutil.c,
and bessj0.c from $NRHEAD/recipes_c-ansi/recipes into our default directory. Finally, we must
copy nr.h and nrutil.h from $NRHEAD/recipes_c-ansi/include into our default directory. When
we open bessrtbis.c to modify it, we see that the function we want is already in place to be eval-
uated. It will, however, use 20 iterations to find all the roots in the range 1.0 ≤ x ≤ 50.0. If we do
not mind the extra roots, we can simply execute the program by entering the statements

cc -o bessrtbis.xc bessrtbis.c zbrak.c bessj0.c nrutil.c rtbis.c -lm


./bessrtbis.xc

The roots found are as follows.

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

Mathematics Series #55, 1964.


94 Exercise 14.19 (Numerical Recipes-C)

cc -o bessrtnewt.xc bessrtnewt.c zbrak.c bessj0.c nrutil.c rtnewt.c bessj1.c -lm


./bessrtnewt.xc

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

14.20 Double-Welled Potential (Numerical Recipes-C)


Exercise: Suppose a particle moves in one dimension under the influence of the potential
energy
−V0 a2 (a2 + x2 ) V (x) (1 + x2 )
V (x) = =⇒ =−
8a4 + x4 V0 8 + x4
where x = x/a. Using at least three different methods and at least two different computational tools,
find the coordinates x of all turning points when the total energy E of the particle is E = −0.2V0
and also when the total energy is E = −0.1V0 . Optional : Obtain graphs of the position of each
turning point as a function of particle energy over the allowed range of energies for bound states.
Solution: We can examine the one-dimensional motion of a particle under the influence of a
potential energy
V (x) 1 + X2
=−
V0 8 + X4
We wish to know the coordinates of all the turning points when the total energy E is E = −0.1V0
as well as when E = −0.2V0 . To do this we must find the roots of the equations

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

static float fx(float x)


{
return 0.1 - (pow(x,2)+1.0)/(pow(x,4)+8.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

cc -o potenergy.xc potenergy.c zbrak.c nrutil.c rtbis.c -lm

and then run the program with the statement

./potenergy.xc

The resulting output is

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

static float fx(float x)


{
return 0.1 - (pow(x,2)+1.0)/(pow(x,4)+8.0);
}

static void funcd(float x,float *fn, float *df)


{
*fn=0.1 - (pow(x,2)+1.0)/(pow(x,4)+8.0);
*df = (2*pow(x,5) + 4 * pow(x,3)- 16 *x)/(pow(x,8) + 16*pow(x,4) + 64);
}

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

cc -o potnewt.xc potnewt.c zbrak.c nrutil.c rtnewt.c -lm


./potnewt.xc

The program returns the roots


98 Exercise 14.20 (Numerical Recipes-C)

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

14.24 A Fluid Mechanics Problem (Numerical Recipes-C)


Exercise: A particular problem—see Problem 3-19 in the fourth edition of Fluid Flow by Rolf
H. Sabersky, Allan J. Acosta, Edward G. Hauptmann, and E. M. Gates (Prentice-Hall, Upper Saddle
River, NJ, 1999)—in fluid flow leads to the need to find the roots of the fourth-order polynomial
12x4 − 12x3 + 4x − 1. Use graphical methods to find bounds on the roots and at least three different
computational approaches to find all real roots of this polynomial.
Solution: Graphs of the fourth-order polynomial

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

static float fx(float x)


{
return 12*pow(x,4) - 12*pow(x,3) + 4*x - 1;
}

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 = rtbis( fx, xb1[i], xb2[i], xacc );
printf("root %3d %14.6f %14.6f\n",i,root,fx(root));
}
}

Finally, we must copy 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. After copying these files, we can
compile, link, and execute the program by invoking the statements

cc -o fluidflow.xc fluidflow.c zbrak.c nrutil.c rtbis.c -lm


./fluidflow.xc

Roots of fluid flow problem:


x f(x)
root 1 -0.556951 -0.000007
root 2 0.313410 -0.000001

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

static float fx(float x)


{
return 12*pow(x,4) - 12*pow(x,3) + 4*x - 1;
}

static void funcd(float x,float *fn, float *df)


{
*fn = 12*pow(x,4) - 12*pow(x,3) + 4*x - 1;
*df = 48*pow(x,3) - 36*pow(x,2) + 4;
}

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

cc -o fluidnewt.xc fluidnewt.c zbrak.c nrutil.c rtnewt.c -lm


./fluidnewt.xc

Roots of fluid flow problem:


x f(x)
root 1 -0.556951 0.000000
root 2 0.313410 0.000000

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)

14.30 Finite Depth Square Well (Numerical Recipes-C)


Exercise: Study the demonstration programs xrtbis.c and xrtnewt.c, each of which is
written so as to be able to find all roots of a given function in a specified range. Then recast each
of these programs to produce a program that will find all of the positive roots of the equation for
each sign in the equation

sin s = ±0.04 s or sin s ∓ 0.04 s = 0

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

static float fs( float s)


{
return sin(s)+aj*s/c;
}

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

printf("\n Enter a value for c: " );


scanf("%f", &c);
printf("\n Enter -1 for an upper solution and 1 for a lower solution: " );
scanf("%f", &aj);

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

printf("\n Enter a value for the lower boundary: " );


scanf("%f", &s1);
printf("\n Enter a value for the upper boundary: " );
scanf("%f", &s2);

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;

static float fs( float s)


{
return sin(s)+aj*s/c;
}

int main(void)
{
104 Exercise 14.30 (Numerical Recipes-C)

float s1, s2;


char filename[20];
int i, nb=NBMAX;
float sacc, root, sb1[NBMAX+1], sb2[NBMAX+1];
FILE *fptr;

printf("\n Enter a value for the lower boundary: " );


scanf("%f", &s1);
printf(" Enter a value for the upper boundary: " );
scanf("%f", &s2);
printf(" Enter a value for c: " );
scanf("%f", &c);
printf(" Enter -1 for an upper solution and 1 for a lower solution: " );
scanf("%f", &aj);
printf(" Enter an appropriate file name (must be less than 20\n" );
printf(" characters and end in .dat): " );
scanf("%s", filename );

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

cc -o qwell.xc qwell.c zbrak.c nrutil.c rtbis.c -lm

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

upper: -0.000000 lower: -0.000000


1.895495

and those for c = 25 are

upper: -0.000000 lower: -0.000000


3.020478 3.272885
6.548204 6.039204
9.054186 9.828837
13.118793 12.062847
15.061390 16.424786
19.761087 18.043257
20.994287 23.177780
23.864494

Solution using rtnewt.c:


To use rtnewt.c, we must copy several files into our default directory. First, we copy the
demonstration program xrtnewt.c from $NRHEAD/recipes_c-ansi/demo/src and save it under an
appropriate name (here qwellnewt.c). Then we modify the file in the same way as we did to create
qwell.c,

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

static float fs( float s)


{
return sin(s)+aj*s/c;
}

static void funcd(float s, float *fn, float *df)


{
*fn = sin(s)+aj*s/c;
*df = cos(s) + aj/c;
}

int main(void)
{
float s1, s2;
char filename[20];
int i, nb=NBMAX;
float sacc, root, sb1[NBMAX+1], sb2[NBMAX+1];
FILE *fptr;

printf("\n Enter a value for the lower boundary: " );


scanf("%f", &s1);
printf(" Enter a value for the upper boundary: " );
scanf("%f", &s2);
printf(" Enter a value for c: " );
scanf("%f", &c);
printf(" Enter -1 for an upper solution and 1 for a lower solution: " );
scanf("%f", &aj);
printf(" Enter an appropriate file name (must be less than 20\n" );
printf(" characters and end in .dat): " );
scanf("%s", filename );

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

cc -o qwellnewt.xc qwellnewt.c zbrak.c nrutil.c rtnewt.c -lm

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

Enter a value for the lower boundary: -0.5


Enter a value for the upper boundary: 60.0
Enter a value for c: 25.0
Enter -1 for an upper solution and 1 for a lower solution: 1
Enter an appropriate file name (must be less than 20
characters and end in .dat): temp.dat
Numerical Recipes run-time error...
Jumped out of brackets in rtnewt
...now exiting to system...

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

Enter a value for the lower boundary: -2.0


Enter a value for the upper boundary: 60.0
Enter a value for c: 25.0
Enter -1 for an upper solution and 1 for a lower solution: 1
Enter an appropriate file name (must be less than 20
characters and end in .dat): temp.dat

yields no error message and finds the roots

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

Introduction to Partial Differential


Equations

15.2 A Vertically Hanging String


Exercise: Suppose the one-dimensional string of length l discussed in Section 15.1.1 hangs
vertically and is acted on by gravity. Suppose that u(x, t) and v(x, t) give the transverse (horizontal)
and longitudinal (vertical) displacements of the particle of the string nominally located at x, which
is measured downward from the top of the string. (a) Examine the forces acting on this string,
deduce the general equations for both longitudinal and transverse motion of the string, and show
ultimately that, if the motion is entirely transverse and the amplitude of the motion is small, the
equations reduce to
Z x
∂2u
 
∂ ∂u
ρ(x) 2 = τ (x) ; τ (x) = −g ρ(x0 ) dx0
∂t ∂x ∂x l

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

and then that


∂2u
 
∂ ∂u
2 = ∂x (1 − x) ∂x
∂t
112 Exercise 15.2

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

15.3 Second-Order ODEs Made Self-Adjoint


Exercise: Show that every second-order linear ODE can be cast in self-adjoint form, i.e., show
that functions α(x), γ(x), and f (x) can be found such that the general second-order linear ODE

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) b(x) − a0 (x)


   
dh dh
h0 (x) = = h(x) =⇒ = dx
dx a(x) h a(x)

This last expression can be integrated (indefinite integral) to yield that

b(x) − a0 (x)
Z Z   Z
dh b(x)
= dx =⇒ ln h = dx − ln a + C 0
h a(x) a(x)

or, on exponentiation, that R


(b(x)/a(x)) dx
e
h(x) = C
a(x)
0
where C = eC is an arbitrary constant.
Having found the integrating factor, we now multiply the original equation by this factor—
though we can ignore the constant C to find that

d2 ϕ(x) b dϕ(x) c g (b/a)dx


R R R R
(b/a) dx (b/a) dx (b/a)dx
e 2
+ e + e ϕ(x) = e
dx a dx a a
If this equation is to match the desired form, we must make the identification
R
α(x) = −e (b/a) dx
114 Exercise 15.2

With that choice, we then find that


Z
d  d b b
R  R R
α0 (x) = −e b/a dx = −e (b/a) dx dx = − e (b/a) dx
dx dx a a

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

15.7 FDM 1D Homogeneous Helmholtz Equation


Exercise: Consider the differential equation and boundary conditions

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

u(x) = A sin kx + B cos kx

The boundary conditions require that

u(0) = B = 0 ; u(L) = A sin kL = 0 =⇒ kL = nπ, n = 1, 2, 3, ...

so there is a family of solutions


 nπx  π
u(x) = A sin = A sin kn x ; kn = nk1 = n
L L
The non-trivial solutions are sinusoidal with arbitrary amplitude,and the wave number kn associated
with the n-th solution is an integer multiple of the wave number k1 = π/L associated with the first
solution.
(b) To cast the equation
d2 u
+ k2 u = 0
dx2
in a discretized form, we divide the interval 0 ≤ x ≤ L into N segments by placing nodes at the
points xi = i ∆x where ∆x = L/N , 0 ≤ i ≤ N , introduce the quantities ui = u(xi ), and remember
that, with that grid,
d2 u ui+1 − 2ui + ui−1
2
=
dx x=xi ∆x2
Thus, we find that evaluating the differential equation at x = xi leads to the algebraic equation

ui+1 − 2ui + ui−1 k 2 L2


+ k 2 ui = 0 =⇒ ui+1 − 2ui + ui−1 = −k 2 ∆x2 ui = − ui
∆x2 N2
116 Exercise 15.7

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

15.8 FEM 1D Homogeneous Helmholtz Equation


Exercise: Consider the differential equation and boundary conditions

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

u(x) = A sin kx + B cos kx

The boundary conditions require that

u(0) = B = 0 ; u(L) = A sin kL = 0 =⇒ kL = nπ, n = 1, 2, 3, ...

so there is a family of solutions


 nπx  π
u(x) = A sin = A sin kn x ; kn = nk1 = n
L L
The non-trivial solutions are sinusoidal with arbitrary amplitude,and the wave number kn associated
with the n-th solution is an integer multiple of the wave number k1 = π/L associated with the first
solution.
(b) We begin by identifying the values of the parameters in Eq. (15.67) that will reduce that general
equation to the equation of current interest. Specifically, we must identify α = −1, β = k 2 , and
f = 0. Further, to yield the boundary conditions in this exercise, we must set p = 0, γ = 0, and
q = 0.
Exercise 15.8 119

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

With these choices, the interpolation functions in Section 15.9.2 reduce to


(e) (e)
(e) x2 − x (e) x − x1
N1 (x) = ; N2 (x) =
L/N L/N
and the approximate solution over the e-th element is given by
2
(e) (e)
X
ũ(e) (x) = N1 (x) ũj
j=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

In matrix form, these several equations for 1 ≤ i, j ≤ 2 have the form


( (e) ) " (e) (e)
# ( (e) ) ( (e) )
R1 K11 K12 ũ1 g1
(e)
= (e) (e)
+ (e)
(e)
R2 K21 K22 ũ2 g2
or more compactly as
{R(e) } = [K (e) ] {ũ(e) } + {g (e) }
where (e) (e)
!
x2 (e)
dNj
Z
(e) dNi 2 (e) (e)
Kij =− −k Ni Nj dx
x1
(e) dx dx
and (e)
 (e)
 x2    
(e) (e) dũ (e) dũ (e) dũ
gi = Ni = Ni − Ni
dx x1
(e) dx x=x2
(e) dx (e)
x=x1
120 Exercise 15.8

(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

Next, note that1


(e) (e)
x2 x2
L2 L5
Z Z
(e) (e) (e)
N1 N1 dx = 2 (x2 − x)2 dx =
x1
(e) N (e)
x1 3N 5
(e) (e)
x2 x2
L2 L5
Z Z
(e) (e) (e)
N2 N2 dx = 2 (x − x1 )2 dx =
x1
(e) N (e)
x1 3N 5
(e) (e) (e)
x2 x2 x2
L2 L5
Z Z Z
(e) (e) (e) (e) (e) (e)
N1 N2 dx = N2 N1 dx = 2 (x2 − x)(x − x1 ) dx =
x1
(e)
x1
(e) N x1
(e) 6N 5
(e) (e)
(Remember that x2 = x1 + L/N .) We conclude that

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

To complete the evaluation, we note that2


   
(e) (e) dũ (e) dũ (e)
g1 = N1 − N1 = −ũ0 (x1 ) = −ũ0 (xe )
dx x=x(e) dx (e)
x=x1
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

Recast in the form


   
ũ0  ũ0 
 
1 0 0 0 0 0 

 
 0 0 0 0 0 0 

 

   
ũ1  ũ1 
     
 0 −2 1 0 0 0 
 
  0 4 1 0 0 0 
 

     
   
ũ2  ũ2 
     
 0 1 −2 1 0 0 0 1 4 1 0 0
    
= −β 
   
ũ3  ũ3 
  
 0 0
 1 −2 1 0 
 


 0 0 1 4 1 0 
 

   
ũ4  ũ4 
     
 0 0 0 1 −2 0 0 0 0 1 4 0
    
 



  




ũ5  ũ5 
   
0 0 0 0 0 1 0 0 0 0 0 0

  
 

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

15.9 1-D Inhomogeneous Helmholtz Equation


Exercise: When α, β, and f are constants, Eqs. (15.67), (15.68), and (15.69) can be solved
analytically. Show, for example, that the solution to this boundary value problem is given by
 
f f
ϕ(x) = A sin λx + p − cos λx +
β β
where
(q − γf /β) + (p − f /β)(αλ sin λL − γ cos λL)
A=
αλ cos λL + γ sin λL
p
and λ = −β/α when β/α < 0. Using graphical displays in particular, compare the analytic
solution in this exercise with the solution obtained numerically by finite difference and finite element
approaches. Optional : Find corresponding solutions when β/α = 0 and β/α > 0.
Solution: Assuming that α, β, and f are constants, we can rewrite Eqs. (15.67), (15.68), and
(15.69) as
d2 ϕ(x)
−α + βϕ(x) = f (E15.9.1)
dx2
where α0 is zero,
ϕ(0) = p (E15.9.2)
and  
dϕ(x)
α + γϕ(x) =q (E15.9.3)
dx x=L
p
If we divide the differential equation by −α, and let λ = −β/α or, equivalently, λ2 = −β/α
(assuming that β/α < 0), then the differential equation becomes

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

ϕ(x) = A sin(λx) + B cos(λx) (E15.9.5)

We find the particular solution by noting that, if we substitute ϕ = C, where C is an as yet


undetermined constant, the differential equation Eq. (E15.9.4) becomes
f f f
λ2 C = − =⇒ C=− 2
= (E15.9.6)
α λ α β
Thus, having identified the particular solution ϕp = f /β, we conclude that the general solution to
the inhomogeneous equation is
f
ϕ(x) = A sin(λx) + B cos(λx) + (E15.9.7)
β

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

(q − γf /β) + (p − f /β)(αλ sin λL − γ cos λL)


A= (E15.9.10)
αλ cos λL + γ sin λL

The final solution to Eq. (E15.9.1), is


 
(q − γf /β) + (p − f /β)(αλ sin λL − γ cos λL) f f
ϕ(x) = sin λx + p − cos λx + (E15.9.11)
αλ cos λL + γ sin λL β β
126 Exercise 15.9

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)

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

15.12 Assembly for Three-Element Linear System

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

We write the elemental equation for the first element as


 (1) (1) (1)
  (1)   (1)   (1) 
K11 K12 K13  ϕ̃1 
  b1
  g
 1
   
   
  
  

 (1) (1) (1) (1) (1)
 K21 K22 K23  ϕ̃2 = b2 + 0

  
 
 
 
 

(1) (1) (1)  (1) 
    (1)   (1) 
K31 K32 K33 ϕ̃3 b3
 g3

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

Figure E15.3: Figure for Exercise 15.12.

(1) (1) (1) (2) (2) (2) (3) (3) (3)


x1 x2 x3 , x1 x2 x3 , x1 x1 x2
x1 x2 x3 x4 x5 x6 x7

Element 1 Element 2 Element 3


Exercise 15.12 129

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

In Exercise 15.11, we found that

(e) (e) dϕ̃(e)


g1 = −α(x1 )
dx x1
(e)

(e)
g2 = 0

(e) (e) dϕ̃(e)


g3 = α(x3 )
dx x3
(e)

With these values, we conclude also that

(1) (2) (1) dϕ̃(1) (2) dϕ̃(2)


g3 + g1 = α(x3 ) −α(x1 ) =0
dx x3
(1) dx (2)
x1

and
(2) (3) (2) dϕ̃(2) (3) dϕ̃(3)
g3 + g1 = α(x3 ) −α(x1 ) =0
dx x3
(2) dx (3)
x1

(1) (2) (2) (3)


because x3 = x1 = x3 , x3 = x1 = x5 , and the derivative of ϕ̃ must be continuous across the
boundary between elements. Hence, all elements except the first and last in {g} are zero, and our
system of equations now becomes
 (1) (1) (1)   (1)
 
K11 K12 K13 0 0 0 0 b1 (1)

ϕ̃ g1 
  
1  
 
 (1)      
(1) (1) (1)
      
 K21 K22 K23 0 0 0 0 

 ϕ̃2 
  
   b2





 0 


  
 
 
 
 

 (1) (1) (1) (2) (2) (2)
  
  (1)  
(2) 

 K31 K32 K 33 + K 11 K12 K 13 0 0  

 ϕ̃ 3

 
 b 3 + b 1




 0



  
   
  

 (2) (2) (2)
 (2)
 0
 0 K 21 K22 K 23 0 0 ϕ̃
 4  
 = b 2
+ 0
     (2)  
  

 0 (2) (2) (2) (3) (3) (3)   ϕ̃  (3)   0 
 0 K 31 K32 K33 + K 11 K12 K13  

 5 

 
 b
 3
 + b 1


 






  
  
  

 0 (3) (3) (3)   ϕ̃6 
   (3)   0 
 0 0 0 K21 K22 K23    



 b2 








     (3) 
 
ϕ̃7
 
0 0 0 0
(3)
K31
(3)
K32
 (3)
K33
 (3)
b3 g3

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

15.20 FDM 2D Inhomogeneous Helmholtz Equation


Exercise: The inhomogeneous Helmholtz equation in two-dimensional Cartesian coordinates
is
∂2u ∂2u
+ 2 + k 2 u = r(x, y)
∂x2 ∂y
where k 2 is a constant and r(x, y) is the inhomogeneity. Apply finite difference methods to show
that
ui+1,j + ui−1,j + ui,j+1 + ui,j−1 − ∆x2 ri,j
ui,j ≈
4 − k 2 ∆x2
Here, ui,j = u(xi , yj ), the spacing between consecutive values of x is ∆x, the spacing between
consecutive values of y is ∆y and ∆y = ∆x. This result could be used in an iterative approach
to solving the inhomogeneous Helmholtz equation. Note: If k 2 = 0, the equation of this exercise
reduces to the inhomogeneous Laplace equation, i.e., to the Poisson equation. If, on the other hand,
r(x, y) = 0, then this equation reduces to the (homogeneous) Helmholtz equation.
Solution: Paralleling our argument on Eq. (15.75) in the text, we infer that

dϕ(x, y) dϕ(x, y)

2
dϕ (x, y) dx xi +∆x/2,yj dx xi −∆x/2,yj

dx2 xi ,yj ∆x

ϕi+1,j − ϕi,j ϕi,j − ϕi−1,j



≈ ∆x ∆x
∆x

ϕi+1,j − 2ϕi,j + ϕi−1,j



∆x2
Similarly, we conclude that

dϕ2 (x, y) ϕi,j+1 − 2ϕi,j + ϕi,j−1



dy 2 xi ,yj ∆x2

Finally, evaluating the original differential equation at (xi , yj ), we conclude that

ϕi+1,j − 2ϕi,j + ϕi−1,j ϕi,j+1 − 2ϕi,j + ϕi,j−1


+ + k 2 ui,j = ri,j
∆x2 ∆x2
which, on solution for ui,j yields that

ui+1,j + ui−1,j + ui,j+1 + ui,j−1 − ∆x2 ri,j


ui,j ≈
4 − k 2 ∆x2
Q.E.D.
132 Exercise 15.25

15.25 Finite Difference Formulas via Taylor Series


Exercise: (a) Starting with the Taylor series

ϕ̃(x + ∆x) = ϕ̃(x) + ∆x ϕ̃0 (x) + O(∆x2 )

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

ϕ̃(x + ∆x) − ϕ̃(x) = ∆x ϕ̃0 (x) + O(∆x2 )

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

dϕ̃(x) ϕ̃xi+1 − ϕ̃i


= + O(∆x)
dx xi ∆x

(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

ϕ̃00 (x) ∆x2 = 2 ϕ̃(x + ∆x) − 2 ϕ̃(x) − 2 ∆x ϕ̃0 (x) + O(∆x3 )

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

Combining those terms that can be combined, we learn that

ϕ̃(x + 2 ∆x) = −3 ϕ̃(x) − 2 ∆x ϕ̃0 (x) + 4 ϕ̃(x + ∆x) + O(∆x3 )

Finally, solving for ϕ̃0 (x), we find that

−3 ϕ̃(x) + 4 ϕ̃(x + ∆x) − ϕ̃(x + 2 ∆x)


ϕ̃0 (x) = + O(∆x2 )
2 ∆x
With x = xi , x + ∆x = xi+1 , and x + 2 ∆x = xi+2 , this result has the expression

dϕ̃(x) −3 ϕ̃i + 4 ϕ̃i+1 − ϕ̃i+2


= + O(∆x2 )
dx xi 2 ∆x
134 Exercise 15.26

15.26 Shape Functions for Two-Node Linear Element


Exercise: Starting with the first two numbered equations in Section 15.9.2, work through the
details in that section to derive the results
(e) (e)
(e) x2 − x (e) x − x1
N1 (x) = ; N2 (x) =
l(e) l(e)
(e) (e)
for the two interpolation functions. Here, l(e) = x2 − x1 .
(e) (e)
Solution: We begin by presuming that the solution in the interval x1 ≤ x ≤ x2 can be
approximated with the linear function

ϕ̃(e) = a(e) + b(e) x

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)

Then, the first equation yields that


! !
(e) (e) (e) (e)
(e) (e) ϕ̃2 − ϕ̃1 (e) (e) (e) ϕ̃2 − ϕ̃1 (e)
ϕ̃1 =a + (e) (e)
x1 =⇒ a = ϕ̃1 − (e) (e)
x1
x2 − x1 x2 − x1
(e) (e) (e) (e) (e) (e) (e) (e)
ϕ̃1 x2 − ϕ̃2 x1 ϕ̃1 x2 − ϕ̃2 x1
=⇒ a(e) = (e) (e)
=
x2 − x1 l(e)
Substituting these results for the two constants into the first equation in this solution, we learn that
! ! !
(e) (e) (e) (e) (e) (e) (e) (e)
(e) ϕ̃1 x2 − ϕ̃2 x1 ϕ̃2 − ϕ̃1 (e) x2 − x (e) x − x1
ϕ̃ = + x = ϕ̃1 + ϕ̃2
l(e) l(e) l(e) l(e)

from which we learn, finally, that the interpolation functions are


(e) (e)
(e) x2 − x (e) x − x1
N1 (x) = ; N2 (x) =
l(e) l(e)
Exercise 15.23 (C) 135

15.23 Laplace Equation: Alternate Convergence Test (C)


Exercise: Recast fdmlap2d so that iteration stops when the largest change occurring at any
single node from one iterate to the next does not exceed an externally prescribed tolerance. To
avoid all possibility of an infinite loop, you should halt iteration either when the tolerance has been
reached (or exceeded) or when some prescribed number of iterations has taken place. Code so that
your program displays the actual tolerance achieved. Further, if execution terminates because the
prescribed tolerance is not achieved, your program should print a message that alerts you to the
fact that the prescribed tolerance was not achieved. Test your program with the same example
as was used in the text. Hints: Before starting an iteration, set a variable, say maxch, equal to
zero. As you calculate a new value for each node in the iteration, store the result in a temporary
variable so you can compare that value with the old value it will replace, updating maxch to the
absolute value of the difference between the new and the old values if and only if that difference
exceeds the difference already stored in maxch. Then, substitute the new value for the old in the
array containing the evolving solution and go on to the next node. Once the iteration is completed,
maxch will contain the absolute value of the largest change at any node during that single iteration.
If maxch is less than the prespecified convergence criterion, stop the iteration; otherwise conduct one
more iteration.
Solution: The changes to be made to fdmlap2d.c in C to reflect the requirements in this
exercise involve

• 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

tmp = u[i][j]; /* Save current value */


u[i][j] = ... ; /* Calculate new value */
chg = fabs(tmp-u[i][j]); /* Calculate absolute value of change */
if (chg > change) then change = chg;
136 Exercise 15.23 (C)

and every calculation of a new entry for u[i][n]) with

tmp = u[i][n]; /* Save current value */


u[i][n]= ... ; /* Calculate new value */
chg = fabs(tmp-u[i][n]); /* Calculate absolute value of change */
if (chg > change) change = chg;

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

float tmp, change, chg;

• Adding the second criterion to the condition in the while statement by editing that statement
to read

while( (itcnt < maxits) & (change > tol) )

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;

immediately ahead of the while statement.

• 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

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

after the while loop.

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

providing the input values

Number of segments: 5
Length of side: 10
Maximum number of iterations: 10
Display frequency: 5
Tolerance: 2

which generates the output

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

Number of iterations completed = 9


Tolerance achieved = 1.712421
Specified tolerance 2.000000 achieved
Final solution =
100.0000 100.0000 100.0000 100.0000 100.0000 100.0000
80.0000 78.0059 76.6798 75.9948 75.8662 76.1778
60.0000 57.1748 55.2876 54.3078 54.1200 54.5610
40.0000 37.5223 35.8621 34.9969 34.8288 35.2153
20.0000 18.6549 17.7518 17.2802 17.1879 17.3978
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

This time, we are presented with the output


138 Exercise 15.23 (C)

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

Number of iterations completed = 10


Tolerance achieved = 1.325935
Specified tolerance 0.100000 not achieved
Final solution =
100.0000 100.0000 100.0000 100.0000 100.0000 100.0000
80.0000 78.4636 77.4365 76.9026 76.8001 77.0403
60.0000 57.8184 56.3562 55.5939 55.4460 55.7869
40.0000 38.0838 36.7972 36.1251 35.9936 36.2930
20.0000 18.9589 18.2591 17.8930 17.8211 17.9838
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

and find the output

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

Number of iterations completed = 30


Tolerance achieved = 0.007992
Specified tolerance 0.010000 achieved
Final solution =
100.0000 100.0000 100.0000 100.0000 100.0000 100.0000
80.0000 79.9908 79.9846 79.9814 79.9807 79.9822
60.0000 59.9869 59.9781 59.9735 59.9725 59.9746
40.0000 39.9885 39.9807 39.9766 39.9758 39.9776
20.0000 19.9937 19.9895 19.9873 19.9869 19.9878
0.0000 0.0000 0.0000 0.0000 0.0000 0.0000

The solution converged to within 0.01 in 30 iterations.


Exercise 15.23 (C) 139

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

The resulting output is

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

Number of iterations completed = 58


Tolerance achieved = 0.000008
Specified tolerance 0.000010 achieved
Final solution =
100.0000 100.0000 100.0000 100.0000 100.0000 100.0000
80.0000 80.0000 80.0000 80.0000 80.0000 80.0000
60.0000 60.0000 60.0000 60.0000 60.0000 60.0000
40.0000 40.0000 40.0000 40.0000 40.0000 40.0000
20.0000 20.0000 20.0000 20.0000 20.0000 20.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)

Listing of pde ex23.c

/* Command file pde_ex23.c */

#include <stdio.h> /* For I/O */


#include <math.h> /* For math functions */
#define ln 101 /* Use parameter for maximum dimension */
/* to facilitate editing */
void main()

{ /* Declare needed variables */

float x[ln], y[ln], u[ln][ln]; /* For solutions */


float l, dx, tol, tmp, change, chg; /* For parameters */
int i, j, itcnt; /* For loop counters */
int n, nf; /* For parameters */
int maxits; /* Maximum number of iterations */

/* Request input of necessary parameters, assuring that each is


stored with the proper data type, calculate dx, and
initialize x, y, and u */

printf( "\nNumber of segments: "); scanf( "%d", &n );


printf( "Length of side: "); scanf( "%f", &l );
printf( "Maximum number of iterations: "); scanf("%d", &maxits);
printf( "Display frequency: "); scanf( "%d", &nf );
printf( "Tolerance: "); scanf("%f", &tol);

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

/* Set Dirichlet boundary conditions */

for(i=0; i<=n; i++)


{ u[i][0] = 100.0-100.0*x[i]/l; u[0][i] = 100.0;
}

/* Display initial guess, labeling it as Iterate 0 */

printf(" Iterate 0\n" );


for(i=0; i<=n; i++)
{ for(j=0; j<=n; j++)
printf( "%10.4f", u[i][j] );
printf( "\n" );
}

/* Evaluate and display iterate number and solution at the


iterate */
Exercise 15.23 (C) 141

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

You might also like