Phys 430 Python
Phys 430 Python
and
and
John Colton
© 2004–2021
Ross L. Spencer, Michael Ware, and Brigham Young University
You will need to read through each lab before class to complete the exercises
during the class period. The labs are designed so that the exercises can be done in
class (where you have access to someone who can help you) if you come prepared.
Please work with a lab partner. It will take a lot longer to do these exercises if you
are on your own.
To be successful in this class, you should have already taken an introductory
programming class in a standard language (e.g. C++) and be able to use a symbolic
mathematical program such as Mathematica. We will be teaching you Python
based on the assumption that you already know how to program in these other
languages. We also assume that you have studied upper division mathematical
physics (e.g. mathematical methods for solving partial differential equations with
Fourier analysis).
Suggestions for improving this manual are welcome. Please direct them to
Michael Ware ([email protected]).
Contents
Preface i
iii
7 The Diffusion Equation and Implicit Methods 41
Analytic approach to the Diffusion Equation . . . . . . . . . . . . . . . . 41
Numerical approach: a first try . . . . . . . . . . . . . . . . . . . . . . . . 41
Implicit Methods: the Crank-Nicolson Algorithm . . . . . . . . . . . . . 43
The diffusion equation with Crank-Nicolson . . . . . . . . . . . . . . . . 44
8 Schrödinger’s Equation 49
Derivations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
Particle in a box . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
Tunneling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
P1.1 Work through Chapter 1 of Introduction to Python. There you will learn the
basics of how to write a Python program, how to declare and use entities
called NumPy arrays, and also learn some basic plotting techniques.
With that Python knowledge under our belts, let’s move on to begin our study of
partial differential equations.
Spatial grids
When we solved ordinary differential equations in Physics 330 we were usually
moving something forward in time, so you may have the impression that differ-
ential equations always “flow.” This is not true. If we solve a spatial differential
equation, like the one that gives the shape of a chain draped between two posts,
the solution just sits in space; nothing flows. Instead, we choose a small spatial
step size (think of each individual link in the chain) and seek to find the correct
shape by somehow finding the height of the chain at each link. Cell-Edge Grid
In this course we will solve partial differential equations, which usually means
that the desired solution is a function of both space x, which just sits, and time t , 0 L
which flows. When we solve problems like this we will be using spatial grids, to
represent the x-part that doesn’t flow. The NumPy arrays that you just learned Cell-Center Grid
about above are perfect for representing these kinds of spatial grids.
We’ll encounter three basic types of spatial grids in this class. Figure 1.1 shows 0 L
a graphical representation of these three types of spatial grids for the region
0 ≤ x ≤ L. We divide this region into spatial cells (the spaces between vertical Cell-Center Grid with Ghost Points
lines) and functions are evaluated at N discrete grid points (the dots). In a cell-
edge grid, the grid points are located at the edge of the cell. In a cell-center grid, 0 L
the points are located in the middle of the cell. Another useful grid is a cell-center Figure 1.1 Three common spatial
grid with ghost points. The ghost points (unfilled dots) are extra grid points on grids
either side of the interval of interest and are useful when we need to consider the
derivatives at the edge of a grid.
1
2 Computational Physics 430
P1.2 (a) Write a Python program that creates a cell-edge spatial grid in the
variable x as follows:
4
import numpy as np
3
N=100 # the number of grid points
2 a=0
y(x) b=np.pi
1 x,h = np.linspace(a,b,N,retstep = True)
0 Plot the function y(x) = sin(x) sinh(x) on this grid. Explain the rela-
0 1 2 3
x tionship between the number of cells and the number of grid points
in a cell-edge grid.
Figure 1.2 Plot from 1.2(a)
(b) Explain the relationship between the number of cells and the num-
ber of grid points in a cell-center grid. Then write some code using
NumPy’s arange function to create a cell-centered grid that has ex-
actly 100 cells over the interval 0 ≤ x ≤ 2.
1 Evaluate the function f (x) = cos x on this grid and plot this function.
f(x) Then estimate the area under the curve by summing the products of
0.5 the centered function values f j with the widths of the cells h like this
(midpoint integration rule):
0
np.sum(f)*h
−0.5 Compare this result to the exact answer obtained by integration:
0 0.5 1 1.5 2 Z 2
x ¯2
A= cos x d x = sin(x)¯ = sin(2)
¯
Figure 1.3 Plot from 1.2(b) 0 0
(c) Build a cell-center grid with ghost points over the interval 0 ≤ x ≤ π/2
with 500 cells (502 grid points), and evaluate the function f (x) = sin x
on this grid. Now look carefully at the function values at the first
two grid points and at the last two grid points. The function sin x
has the property that f (0) = 0 and f 0 (π/2) = 0. The cell-center grid
doesn’t have points at the ends of the interval, so these boundary
conditions on the function need to be enforced using more than one
point. Explain how the ghost points can be used in connection with
interior points to specify both function-value boundary conditions
and derivative-value boundary conditions.
and (x 2 , y 2 ). The formula for a straight line that passes through these two points
is
(y 2 − y 1 )
y − y1 = (x − x 1 ) (1.1)
(x 2 − x 1 )
Once this line has been established it provides an approximation to the true
function y(x) that is pretty good in the neighborhood of the two data points. To
linearly interpolate or extrapolate we simply evaluate Eq. (1.1) at x values between
or beyond x 1 and x 2 .
y(x 2 + h) = 2y 2 − y 1 (1.2)
Derivatives on grids
When solving partial differential equations, we will frequently need to calculate
derivatives on our grids. In your introductory calculus book, the derivative was
probably introduced using the forward difference formula
f (x + h) − f (x)
f 0 (x) ≈ . (1.4)
h
The word “forward” refers to the way this formula reaches forward from x to x + h
to calculate the slope. The exact derivative represented by Eq. (1.4) in the limit that
h approaches zero. However, we can’t make h arbitrarily small when we represent
a function on a grid because (i) the number of cells needed to represent a region
of space becomes infinite as h goes to zero; and (ii) computers represent numbers
with a finite number of significant digits so the subtraction in the numerator of
Eq. (1.4) loses accuracy when the two function values are very close. But given
these limitation we want to be as accurate as possible, so we want to use the best
derivative formulas available. The forward difference formula isn’t one of them.
4 Computational Physics 430
The best first derivative formula that uses only two function values is usually
the centered difference formula:
f (x + h) − f (x − h)
f 0 (x) ≈ . (1.5)
2h
It is called “centered” because the point x at which we want the slope is centered
between the places where the function is evaluated. Take a minute to study
Fig. 1.5 to understand visually why the centered difference formula is so much
better than the forward difference formula. The corresponding centered second
Figure 1.5 The forward and cen-
derivative formula is
tered difference formulas both
approximate the derivative as the f (x + h) − 2 f (x) + f (x − h)
f 00 (x) ≈ (1.6)
slope of a line connecting two h2
points. The centered difference We will derive both of these formulas later, but for now we just want you to
formula gives a more accurate ap- understand how to use them.
proximation because it uses points
The colon operator provides a compact way to evaluate Eqs. (1.5) and (1.6)
before and after the point where
the derivative is being estimated.
on a grid. Unfortunately for those of you familiar with Matlab, Python’s colon
(The true derivative is the slope of operator acts a little differently from Matlab’s in that the last index is not included
the dotted tangent line). in the range, as we noted in the tutorial. If the function we want to take the
derivative of is stored in an array f, we can calculate the centered first derivative
like this (remember that Python array indexes are zero-based):
fp = np.zeros_like(f)
fp[1:N-1]=(f[2:N]-f[0:N-2])/(2*h)
and the centered second derivative at each interior grid point like this:
fpp = np.zeros_like(f)
fpp[1:N-1]=(f[2:N]-2*f[1:N-1]+f[0:N-2])/h**2
The variable h is the spacing between grid points and N is the number of grid
points. Study this code (focus on the indexing) until you are convinced that it
represents Eqs. (1.5) and (1.6) correctly.
The derivative at the first and last points on the grid can’t be calculated with
Eqs. (1.5) and (1.6) since there are not grid points on both sides of the endpoints.
Instead, we extrapolate the interior values of the two derivatives to the end points.
If we use linear extrapolation then we just need two nearby points, and the
formulas for the derivatives at the end points are found using Eq. (1.2):
fp[0]=2*fp[1]-fp[2]
1
fp[N-1]=2*fp[N-2]-fp[N-3]
f(x)
f (x)
fpp[0]=2*fpp[1]-fpp[2]
0.5 fpp[N-1]=2*fpp[N-2]-fpp[N-3]
0 P1.4 Create a cell-edge grid with N = 20 on the interval 0 ≤ x ≤ 5. Load f (x) with
−0.5 the Bessel function J 0 (x) and numerically differentiate it to obtain f 0 (x)
f (x) and f 00 (x). Then make overlaid plots of the numerical derivatives with the
−1
exact derivatives:
0 1 2 3 4 5
x f 0 (x) = −J 1 (x)
1
Figure 1.6 Plots from 1.4 f 00 (x) = (−J 0 (x) + J 2 (x))
2
Lab 1 Grids and Numerical Derivatives 5
1 00 hn
f (x + h) = f (x) + f 0 (x)h + f (x)h 2 + · · · + f (n) (x) + ··· (1.7)
2 n!
Let’s use this series to understand the forward difference approximation to f 0 (x).
If we apply the Taylor expansion to the f (x + h) term in Eq. (1.4), we get
f (x + h) − f (x) h 00
f 0 (x) ≈ − f (x) (1.9)
h 2
From Eq. (1.9) we see that the forward difference does indeed give the first deriva-
tive back, but it carries an error term which is proportional to h. But if h is small
enough then the contribution from the term containing f 00 (x) will be too small to
matter and we will have a good approximation to f 0 (x).
For the centered difference formula, we use Taylor expansions for both f (x+h)
and f (x − h) in Eq. (1.5) to write
h i
0 00 h2 000 h3
f (x + h) − f (x − h) f (x) + f (x)h + f (x) 2 + f (x) 6 + · · ·
= (1.10)
2h h 2h i
2 3
f (x) − f 0 (x)h + f 00 (x) h2 − f 000 (x) h6 + · · ·
−
2h
If we again neglect the higher-order terms, we can solve Eq. (1.10) for the exact
derivative f 0 (x). This time, we find that the f 00 terms exactly cancel to give
f (x + h) − f (x − h) h 2 000
f 0 (x) ≈ − f (x) (1.11)
2h 6
Notice that for the centered formula the error term is much smaller, only of order
h 2 . So if we decrease h in both the forward and centered difference formulas by a
factor of 10, the forward difference error will decrease by a factor of 10, but the
centered difference error will decrease by a factor of 100. This is the reason we try
to use centered formulas whenever possible in this course.
6 Computational Physics 430
P1.5 Write a Python program to compute the forward and centered difference
formulas for the first derivative of the function f (x) = e x at x = 0 with
h = 0.1, 0.01, 0.001. Verify that the error estimates in Eqs. (1.9) and (1.11)
agree with the numerical testing.
Note that at x = 0 the exact values of both f 0 and f 00 are equal to e 0 = 1, so
just subtract 1 from your numerical result to find the error.
h=1e-17
g=1+h
print(g-1)
for different values of h and noting that you don’t always get h back. Also notice
in Fig. 1.7 that this problem is worse for the second derivative formula than it is
for the first derivative formula. The lesson here is that it is impossible to achieve
arbitrarily high accuracy by using arbitrarily tiny values of h.
1 A computer uses 64 bits to represent a double precision number. 53 bits are used to represent
the significant digits. Thus the significant digit part can be any integer between 0 and 253 =
9007199254740992 (almost 16 digits). The exponent is represented by 11 bits. After you add the
possibility of NaN and Inf, the exponent can be −308 to +308. This leaves 1 bit for the overall sign
of the number. Extended precision uses more bits (memory) and computation time, so double
precision is mostly the standard for computational physics.
Lab 2
Differential Equations with Boundary Conditions
More Python
P2.1 Work through Chapter 2 of Introduction to Python, where you will learn
about lists, loops, and logic.
Notice that this differential equation has boundary conditions at both ends of the
interval instead of having initial conditions at x = 0. If we represent this equation
on a grid, we can turn this differential equation into a set of algebraic equations
that we can solve using linear algebra techniques. Before we see how this works,
let’s first specify the notation that we’ll use. We assume that we have set up a
cell-edge spatial grid with N grid points, and we refer to the x values at the grid
points using the notation x n , with n = 0..N −1. We represent the (as yet unknown)
function values y(x n ) on our grid using the notation y n = y(x n ).
Figure 2.1 A function y(x) repre-
Now we can write the differential equation in finite difference form as it sented on a cell-edge x-grid with
would appear on the grid. The second derivative in Eq. (2.1) is rewritten using the N = 9.
centered difference formula (see Eq. (1.5)), so that the finite difference version of
Eq. (2.1) becomes:
y n+1 − 2y n + y n−1
+ 9y n = sin(x n ) (2.2)
h2
7
8 Computational Physics 430
Now let’s think about Eq. (2.2) for a bit. First notice that it is not an equation, but
a system of many equations. We have one of these equations at every grid point n,
except the endpoints j = 0 and at j = N −1 where this formula reaches beyond the
ends of the grid and cannot, therefore, be used. Because this equation involves
y n−1 , y n , and y n+1 for the interior grid points n = 1 . . . N − 2, Eq. (2.2) is really a
system of N − 2 coupled equations in the N unknowns y 0 . . . y N −1 . If we had just
two more equations we could find the y n ’s by solving a linear system of equations.
But we do have two more equations; they are the boundary conditions:
y 0 = 0 ; y N −1 = 1 (2.3)
1 0 0 0 ... 0 0 0
y0 0
1
h2
− h22 + 9 1
h2
0 ... 0 0 0
y1
sin(x 1 )
1 2 1
0 h2
− h2 + 9 h2
... 0 0 0
y2
sin(x 2 )
. . . . ... . . . . .
= .
. . . . ... . . . . .
. . . . ... . . . . .
1
− h22 + 9 1 y
0 0 0 0 ... N −2 sin(x N −2 )
h2 h2
0 0 0 0 ... 0 0 1 y N −1 1
(2.5)
Convince yourself that Eq. (2.5) is equivalent to Eqs. (2.2) and (2.3) by mentally
doing each row of the matrix multiply by tipping one row of the matrix up on
end, dotting it into the column of unknown y-values, and setting it equal to the
corresponding element in the column vector on the right.
Once we have the finite-difference approximation to the differential equa-
tion in this matrix form (Ay = b), a simple linear solve is all that is required
to find the solution array y n . NumPy can do this solve with the command
linalg.solve(a, b).
P2.2 (a) Set up a cell-edge grid with N = 30 grid points, like this:
import numpy as np
Type this solution formula into the Python program that defines the
grid above and plot the exact solution as a blue curve on a cell-edge
grid with N points.
(c) Now create a matrix A filled with zeros using np.zeros, and write a
for loop to load A like the matrix in Eq. (2.5) and do the linear solve to
obtain y n and plot it on top of the exact solution with red dots ('r.')
to see how closely the two agree. Experiment with larger values of N
and plot the difference between the exact and approximate solutions
to see how the error changes with N . We think you’ll be impressed at Figure 2.2 The solution to 2.2(c)
how well the numerical method works, if you use enough grid points. with N = 30
Let’s pause a moment to review how to apply this technique to solve a problem.
First, write out the differential equation as a set of finite difference equations on a
grid, as we did in Eq. (2.2). Then translate this set of finite difference equations
(plus the boundary conditions) into a matrix form analogous to Eq. (2.5). Finally,
build the matrix A and the column vector y in Python and solve for the vector y.
Our example, Eq. (2.1), had only a second derivative, but first derivatives can be
handled using the centered first derivative approximation, Eq. (1.5). Let’s practice
this procedure for a couple more differential equations:
P2.3 (a) Write out the finite difference equations on paper for the differential 10
equation 8
y(x)
6
1 0 1 4
y 00 + y + (1 − 2 )y = x ; y(0) = 0, y(5) = 1 (2.6)
x x 2
0
Then write down the matrix A and the vector b for this equation. Fi-
−2
nally, build these matrices in a Python program and solve the equation 0 1 2 3 4 5
using the matrix method. Compare the solution found using the ma- x
trix method with the exact solution Figure 2.3 Solution to 2.3(a) with
−4 N = 30 (dots) compared to the
y(x) = J 1 (x) + x exact solution (line)
J 1 (5)
y(4.5) = 8.720623277763513
You’d run out of memory with the matrix method long before achiev-
ing this level of accuracy, but how many points do you have to use
in your numerical method to get agreement with Mathematica to 3
digits of accuracy (i.e. y(4.5) = 8.72)?
Hint: If your step size variable is h, the index for the for element where
x j ≈ 4.5 can be found this way:
j=int(4.5/h)
Figure 2.4 Solution to 2.3(b) with
The int command rounds the result and casts it as an integer so you
N = 200
can use it to access a specific element.
We can satisfy the boundary condition y(0) = 0 as before (just use y 1 = 0), but
what do we do with the derivative condition at the other boundary?
P2.4 (a) A crude way to implement the derivative boundary condition is to use
0.25 a forward difference formula for the last two points:
0.2 y(x) y N −1 − y N −2 ¯
= y 0¯
¯
0.15 h x=2
0.1
In the present case, where y 0 (2) = 0, this simply means that we need
0.05
to write the last row of our matrix to enforce
0
0 0.5 1 1.5 2
x y N −1 − y N −2 = 0.
Figure 2.5 The solution to 2.4(a) Think about what the new boundary conditions will do to the final row
with N = 30. The RMS difference of matrix A and the final element of vector b, and then solve Eq. (2.8)
from the exact solution is 8.8 × 10−4 using the matrix method with this boundary condition. Compare the
resulting numerical solution to the analytic solution:
x sin (3x)
y(x) = −
9 27 cos (6)
Lab 2 Differential Equations with Boundary Conditions 11
(b) You can improve the boundary condition formula using quadratic
extrapolation. In this method, you fit a parabola of the form
y(x) = a + bx + c x 2 (2.9)
to the last three points on your grid to find a, b, and c in terms of your
data points. Then you take the derivative of Eq. (2.9) and evaluate it at
the edge x = x N −1 . Normally we’d make you go through the math to
derive this, but since time is short, we’ll just tell you that this process 0.25
gives you the following finite difference approximation for the y 0 (x) at 0.2 y(x)
the end of the grid: 0.15
1 2 3 0.1
y N −3 − y N −2 + y N −1 = y 0 (x N −1 ) (2.10)
2h h 2h 0.05
0
Modify your program from part (a) to include this new condition and 0 0.5 1 1.5 2
show that it gives a more accurate solution than the crude technique of x
part (a). When you check the accuracy, don’t just look at the end of the
Figure 2.6 The solution to 2.4(b)
interval. All of the points are coupled by the matrix A, so you should with N = 30. The RMS difference
use a full-interval accuracy check like the RMS (root-mean-square) from the exact solution is 5.4 × 10−4
error:
np.sqrt(np.mean((y-yexact)**2))
P2.5 (a) Here is a simple example of a differential equation that isn’t linear:
0
00
£ ¤
y (x) + sin y(x) = 1 ; y(0) = 0, y(3) = 0 (2.11) −0.5
−1 y(x)
Work at turning this problem into a linear algebra problem to see why
it can’t be done, and explain the reasons to the TA. −1.5
(b) Let’s find a way to use a combination of linear algebra and iteration −2
Make a guess for y(x). It doesn’t have to be a very good guess. In this
case, the guess y(x) = 0 works just fine. Then treat the whole right side
of Eq. (2.12) as known so it goes in the b vector. Then you can solve
the equation to find an improved guess for y(x). Use this better guess
to rebuild b (again treating the right side of Eq. (2.12) as known), and
then re-solve to get and even better guess. Keep iterating until your
y(x) converges to the desired level of accuracy. This happens when
your y(x) satisfies (2.11) to a specified criterion, not when the change
in y(x) from one iteration to the next falls below a certain level. Iterate
until your RMS error is less than 10−5 , as described in the hint below.
HINT: An appropriate error vector would be
err = A@y-(1-np.sin(y))
(remember that @ is the matrix multiplication operator). Compare
this to Eq. (2.12) and convince yourself that the entire vector err will
be zero when the equation is exactly solved. We’ll compute the RMS
error at interior points only, like this
rmserr = np.sqrt(np.mean(err[1:-2]**2))
because the end points don’t satisfy the differential equation. Use
rmserr as your check condition.
Lab 3
The Wave Equation: Steady State and Resonance
Python Mechanics
P3.1 Work through Chapter 3 of Introduction to Python, where you will learn
about user-defined functions and good commenting practices. For the
remainder of the course, you will need to demonstrate good commenting
practices in your code before your exercises will be passed off.
Wave Equation
We are now ready to tackle our first partial differential equation: the wave equa-
tion. For a string of length L fixed at both ends with a force applied to it that varies
sinusoidally in time, the wave equation can be written as
∂2 y ∂2 y
µ = T + f (x) cos ωt ; y(0, t ) = 0, y(L, t ) = 0 (3.1)
∂t 2 ∂x 2
where y(x, t ) is the lateral displacement of the string as a function of position
and time, assuming that y(x, t ) ¿ L.1 We have written Eq. (3.1) in the form of
Newton’s second law, F = ma. The “ma” part is on the left of the equation, except
that µ is not the mass, but rather the linear mass density (mass per length). This
means that the right side should have units of force/length, and it does because
T is the tension (force) in the string and ∂2 y/∂x 2 has units of 1/length. Finally,
f (x) is the amplitude of the driving force (in units of force/length) applied to the
string as a function of position (so we are not necessarily just wiggling the end of
the string) and ω is the frequency of the driving force.
Before calculating, let’s train our intuition to guess how the solutions of this
equation behave. If we suddenly started to push and pull on a string under tension
with force density f (x) cos(ωt ), we would launch traveling waves, which would
reflect back and forth on the string as the driving force continued to launch more
waves. The string motion would rapidly become very messy. But suppose that
there is a bit of damping in the system (not included in the equation above, but in
Lab 5 we will add it). With damping, all of the transient waves due to the initial
launch and subsequent reflections would die away and we would be left with a
steady-state oscillation of the string at the driving frequency ω. This behavior is
the wave equation analog of damped transients and the steady final state of a
driven harmonic oscillator which you studied in Physics 330.
1 N. Asmar, Partial Differential Equations and Boundary Value Problems (Prentice Hall, New
13
14 Computational Physics 430
This function has the form of a spatially dependent amplitude g (x) which oscil-
lates in time at the frequency of the driving force. Substituting this “guess” into the
wave equation and carrying out the derivatives yields (after some rearrangement)
This is just a two-point boundary value problem of the kind we studied in Lab 2,
so we can solve it using our matrix technique.
−4
x 10
P3.2 (a) Write a program to solve Eq. (3.3) for N = 100 points with µ = 0.003,
4 T = 127, L = 1.2, and ω = 400. All quantities are in SI units. Find the
3 g(x) steady-state amplitude associated with the driving force density:
2 ½
1 0.73 if 0.8 ≤ x ≤ 1
f (x) = (3.4)
0
0 otherwise
0 0.2 0.4 0.6 0.8 1
x
which is something like grabbing the string toward one end and wig-
Figure 3.1 Solution to P3.2(a) gling. Plot g (x), and properly label the axes of your plot.
(b) Take your code from (a) and turn it into a library with two functions:
i. A function force that accepts the grid x as an argument and
returns the column vector representing f (x)
ii. A function steadySol that accepts the arguments f (x) (returned
from force), h, ω, T , and µ. This function should construct the
matrix, solve the matrix equation, and return g (x).
Save this library as “Lab3Funcs.py” and then write another program
that imports this library. In this program, write a for loop that sweeps
the value of ω from ω = 400 to ω = 1700 in 200 steps keeping the
values of the other parameters constant. Plot g (x) at each frequency
so that you make an animation of what happens when you sweep the
driving frequency. Here is some example code to help you make the
animation, assuming you’ve stored your ω values in wplot:
plt.figure(1)
for n in range(len(wplot)):
w = wplot[n]
g = l3.steadySol(f,h,w,T,u)
plt.clf() # Clear the previous plot
Figure 3.2 Photographs of the first plt.plot(x,g)
three resonant modes for a string plt.title('$\omega={:1.2e}$'.format(w))
fixed at both ends. plt.xlabel('x')
plt.ylim([-0.05, 0.05]) # prevent auto-scaling
plt.draw() # Request to draw the plot now
plt.pause(0.1) # Give the computer time to draw it
Lab 3 The Wave Equation: Steady State and Resonance 15
Maximum Amplitude
0.06
At certain frequencies, you should see distinct resonance modes ap-
0.05
pear as in Fig. 3.2.
0.04
(c) Modify your code from part (b) so that you find and record the maxi- 0.03
mum amplitude of g (x) at each ω. Then plot the maximum amplitude 0.02
as a function of ω to see the resonance behavior of this system. De- 0.01
scribe what the peaks in this plot mean. 0.00
400 600 800 1000 1200 1400 1600
The height of the peaks in this Fig. 3.3 isn’t meaningful—the height of the
actual peak is essentially infinite (since we don’t have any damping yet), while Figure 3.3 Solution to prob-
the height displayed on the plot is just a reflection of how closely our chosen lem 3.2(c).
frequencies happened to line up with the exact resonance frequency. Now we will
learn how to find these resonant frequencies directly without needing to solve
the differential equation over and over again.
µω2
µ ¶
g 00 (x) = − g (x) (3.6)
T
Ag = λg (3.7)
where A is a linear operator (the second derivative on the left side of Eq. (3.6))
and λ is the eigenvalue (−µω2 /T ).
Equation (3.6) is easily solved analytically, and its solutions are just the familiar
sine and cosine functions. The boundary condition g (0) = 0 tells us to try a sine
function form, g (x) = g 0 sin(kx). If we substitute this form into Eq. (3.6, we find
that it works, provided that k satisfies k = ω µ/T . We the have
p
µ
µ r ¶
g (x) = g 0 sin ω x (3.8)
T
Ag = λg (3.10)
where λ = −µω2 T . With finite differencing, this becomes the matrix equation
±
? ? ? ? ... ? ? ? g0 ?
1 −2 1
0 ... 0 0 0 g1 g1
h2 h2 h2
1 2 1
0 − ... 0 0 0 g g
h2 h2 h2
2 2
.
. . . ... . . .
.
= λ
.
(3.11)
.
. . . ... . . .
.
.
.
. . . ... . . .
.
.
0 0 0 0 ... h12 − h22 h12 g N −2 g N −2
? ? ? ? ... ? ? ? g N −1 ?
The question marks in the first and last rows remind us that we have to invent
something to put in these rows to implement the boundary conditions. The
answer we are seeking is the function g, and it appears on both sides of the
equation. Thus, the question marks in the g -vector on the right are a real problem
because without g 0 and g N −1 , we don’t have an eigenvalue problem (i.e. g on left
side of Eq. (3.11) is not the same as g on the right side).
The simplest way to deal with this question-mark problem and to also handle
the boundary conditions is to change the form of Eq. (3.7) to the slightly more
complicated form of a generalized eigenvalue problem, like this:
Ag = λBg (3.12)
where B is another matrix, whose elements we will choose to make the boundary
conditions come out right. To see how this is done, here is the generalized modifi-
cation of Eq. (3.11) with B and the top and bottom rows of A chosen to apply the
boundary conditions g (0) = 0 and g (L) = 0:
A g =λ B g
1 0 0 ... 0 0
g0 0 0 0 ... 0 0 g0
1
h2
− h22 1
h2
... 0 0
g1
0 1 0 ... 0 0
g1
1
0 h2
− h22 ... 0 0
g2
0 0 1 ... 0 0
g2
. . . ... . . . . . . ... . . .
= λ
. . . ... . . . . . . ... . . .
. . . ... . . . . . . ... . . .
− h22 1 g
0 0 0 ... N −2 0 0 0 ... 1 0 g N −2
h2
0 0 0 ... 0 1 g N −1 0 0 0 ... 0 0 g N −1
(3.13)
Lab 3 The Wave Equation: Steady State and Resonance 17
Note that B is just the identity matrix (made with NumPy’s np.identity(N))
except that the first and last rows are completely filled with zeros. Take a minute
to do the matrix multiplications corresponding the first and last rows and verify
that they correctly give g 0 = 0 and g N −1 = 0 no matter what the eigenvalue λ turns
out to be.
To numerically solve the generalized eigenvalue problem you do the following:
• Load a NumPy array A with the matrix on the left side of Eq. (3.13) and an
array B with the matrix on the right side.
import scipy.linalg as la
vals,vecs = la.eig(A,B)
which returns the eigenvalues λ as the entries of the matrix vals and the
eigenvectors as the columns of the square matrix vecs. These column
in this array are the amplitude functions g n = g (x n ) associated with each
eigenvalue on the grid x n .
P3.3 (a) Write a program to numerically find the eigenvalues and eigenvectors g(x)
of Eq. (3.5) using the procedure outlined above. Use N = 30, µ = 0.003, 0
T = 127, and L = 1.2. Write a loop that plots each of the eigenvectors.
n=3
You will find two infinite eigenvalues together with odd-looking eigen- −1
0 0.5 1
vectors that don’t satisfy the boundary conditions. These two show up x
because of the two rows of the B matrix that are filled with zeros. They
are numerical artifacts with no physical meaning, so just ignore them. Figure 3.4 The first three eigen-
The eigenvectors of the higher modes start looking jagged. These functions found in 3.3. The points
must also be ignored because they are poor approximations to the are the numerical eigenfunctions
and the line is the exact solution.
continuous differential equation in Eq. (3.5).
18 Computational Physics 430
Finally let’s explore what happens to the eigenmode shapes when we change
the boundary conditions.
1 P3.4 (a) Change your program from problem 3.3 to implement the boundary
condition
0 g(x) g 0 (L) = 0
This boundary condition describes what happens if one end of the
−1 n=1 string is free to slide up and down rather than attached to a fixed
0 0.5 1
x position. Use the approximation you derived in problem 2.4(b) for the
derivative g 0 (L) to implement this boundary condition, i.e.
1
1 2 3
g(x) g 0 (L) ≈ g N −3 − g N −2 + g N −1
0 2h h 2h
Explain physically why the resonant frequencies change as they do.
−1 n=2
0 0.5 1 (b) In some problems mixed boundary conditions are encountered, for
x example
1 g 0 (L) = 2g (L)
g(x) Find the first few resonant frequencies and eigenfunctions for this
0 case. Look at your eigenfunctions and verify that the boundary condi-
tion is satisfied. Also notice that one of your eigenvalues corresponds
−1 n=3 to ω2 being negative. This means that the nice smooth eigenfunction
0 0.5 1 associated with this eigenvalue is not physical in our wave resonance
x
problem with this boundary condition. The code snippet we gave you
Figure 3.5 The first three eigen- above will have trouble with the np.sqrt. You can just look at the
functions for 3.4(a). values of ω2 instead
Lab 4
The Hanging Chain and Quantum Bound States
P4.1 Use the fact that a stationary hanging chain is in equilibrium to draw a
free-body diagram for a link at an arbitrary x. Use this diagram to show that
the tension in the chain as a function of x is given by
Figure 4.1 The first normal mode
for a hanging chain.
T (x) = µg x (4.2)
where µ is the linear mass density of the chain and g = 9.8 m/s2 is the
acceleration of gravity. Then show that Eq. (4.1) reduces to
∂2 y ∂ ∂y
µ ¶
−g x =0 (4.3)
∂t 2 ∂x ∂x
µ(x), but the equations derived later in the lab are more complicated with a varying µ(x).
19
20 Computational Physics 430
ω2
f 0 (0) = − f (0) = λ f (0) (4.5)
g
In the past couple labs we dealt with derivative boundary conditions by fitting
a parabola to the last three points on the grid and then taking the derivative of
0 L
the parabola (e.g. Problems 2.4(b) and 3.4). This time we’ll handle the derivative
Figure 4.2 A cell-centered grid boundary condition by using a cell-centered grid with ghost points, as discussed
with ghost points. (The open cir- in Lab 1. Recall that a cell-center grid divides the spatial region from 0 to L into
cles are the ghost points.) N cells with a grid point at the center of each cell. We then add two more grid
points outside of [0, L], one at x 0 = −h/2 and the other at x N +1 = L + h/2. The
ghost points are used to apply the boundary conditions. By defining N as the
number of interior grid points (or cells), we have N + 2 total grid points, which
ceiling may seem weird to you. We prefer it, however, because it reminds us that we are
using a cell-centered grid with N physical grid points and two ghost points.
x=L
With this grid there isn’t a grid point at each endpoint, but rather we have two
grid points that straddle each endpoint. If the boundary condition specifies a
value, like f (L) = 0 in the problem at hand, we use a simple average like this:
f N +1 + f N
=0 . (4.6)
2
f N +1 − f N
=0 . (4.7)
h
P4.2 (a) On paper, write out the discretized version of Eq. (4.4) and put it in
the form of a generalized eigenvalue problem
A f = λB f (4.8)
x=0
Remember that for the interior points, the matrix B is just the identity
matrix with 1 on the main diagonal and zeros everywhere else. De-
cide on the values needed in the bottom rows of A and B to give the
Figure 4.3 The shape of the sec- boundary condition in Eq. (4.6) at x = L (the ceiling) no matter what
ond mode of a hanging chain λ turns out to be.
Lab 4 The Hanging Chain and Quantum Bound States 21
(b) Now let’s decide how to handle the derivative boundary condition at
x = 0 (the bottom of the chain), given by Eq. (4.5). Since this condition
is already in eigenvalue form we don’t load the top row of B with zeros.
Instead we load A with the left operator f 0 (0) according to Eq. (4.7)
ceiling
and B with the right operator f (0) according to Eq. (4.6). Leave the
eigenvalue λ = −ω2 /g out of the top row of B since the matrix equation x=L
A f = λB f already has the λ multiplying B. Write down the values
needed in the top rows of A and B, perform the matrix multiplication
for this row, and verify that your choices correctly produce Eq. (4.5).
(c) Write a program to load the matrices A and B with L = 2 m (or the mea-
sured length if different). Then solve for the normal modes of vibration
of a hanging chain. As in Lab 3, some of the eigenvectors are not phys-
ical because they don’t satisfy the boundary conditions; ignore them.
Compare your numerical resonance frequencies to measurements
made on the chain hanging from the ceiling in the classroom.
(d) The analytic solution to Eq. (4.4) without any boundary conditions is
³ p ´ ³ p ´
f (x) = c 1 J 0 2ω x/g + c 2 Y0 2ω x/g
where J 0 and Y0 are the Bessel functions of the first and second kind,
respectively. The boundary condition at x = 0 rules out Y0 , since it is
singular at x = 0. Apply the condition f (L) = 0 to find analytically the x=0
mode frequencies ωi in terms of the values x i that satisfy J 0 (x i ) = 0.
Verify that these values agree with the ω values from part (c).
HINT: The scipy.special library has a function jn_zeros(n,i)
that will return the first i values of x for which J n (x) is zero. Figure 4.4 The shape of the third
mode of a hanging chain
∂Ψ ħ2 ∂2 Ψ
iħ =− + V (x)Ψ (4.9)
∂t 2m ∂x 2
If we assume a separable solution of the form Ψ(x, t ) = ψ(x)e −i E t /ħ and plug this
into Eq. (4.9), we find the time-independent Schrödinger equation
ħ2 d 2 ψ
− + V (x)ψ = E ψ (4.10)
2m d x 2
For a particle in a one-dimensional harmonic oscillator, with V (x) = kx 2 /2, this
becomes
ħ2 d 2 ψ 1 2
− + kx ψ = E ψ (4.11)
2m d x 2 2
22 Computational Physics 430
with boundary conditions ψ = 0 at ±∞. Note that k is not the wave number; it is
the spring constant, F = −kx, with units of Newtons/meter.
The numbers that go into Schrödinger’s equation are so small that it makes
it difficult to tell what size of grid to use. For instance, using lengths like 2, 5, or
10 meters would be completely ridiculous for the bound states of an atom where
the typical size is on the order of 10−10 m. Some physicists just set ħ, m, and k
to unity, but this is bad practice. If you do this, you won’t know what physical
parameters your numerical results describe. The correct procedure is to “rescale”
the problem.
The goal of rescaling is to replace physical variables like x with unitless vari-
ables like ξ = x/a, where a is a “characteristic” length written in terms of the other
variables in the problem. Since a is a length, ξ is unitless, and since a is scaled
to the problem parameters, ξ will typically have magnitudes around the size of 1.
Let’s practice this procedure for the problem at hand.
P4.3 Substitute x = aξ into Eq. (4.11), and then do some algebra to put the
equation in the form
C d 2ψ 1 2 E
− + ξ ψ= ψ (4.12)
2 dξ 2 2 Ē
where the constants C and Ē involve factors like ħ, m, k, and a.
Now make the differential operator on the left be as simple as possible by
choosing to make C = 1. This determines how the characteristic length a
depends on ħ, m, and k. Once you have determined a in this way, check to
see that it has units of length. You should find
¶1/4 s s
ħ2 k
µ
ħ
a= = , where ω = (4.13)
km mω m
1 d 2ψ 1 2
− + ξ ψ = ²ψ (4.15)
2 d ξ2 2
When you solve this equation and find results in terms of ² and ξ, you can
use the equations above to translate them into real-world values of E and x.
Figure 4.5 The probability distri-
butions for the ground state and Now that Schrödinger’s equation is in dimensionless form, we are ready to
the first three excited states of the solve it with out standard technique.
harmonic oscillator.
Lab 4 The Hanging Chain and Quantum Bound States 23
P4.4 Discretize Eq. (4.15) on paper. Then write a program to do this eigenvalue
problem, similar to the other programs we’ve written recently. The bound-
ary conditions are that both the value and derivative of ψ should go to zero
at ξ = ±∞. Since you’ve scaled the problem, it makes sense to choose a
cell-edge grid that goes from ξ = −5 to ξ = 5, or some other similar pair
of numbers. These numbers are supposed to approximate infinity in this
problem. Just set the value of ψ to zero at the edge-points and make sure
(by looking at the eigenfunction solutions) that your grid is large enough
that the wave function has zero slope at the edges of the grid.
(a) Plot the first several bound states. As a guide, Figure 4.5 displays the square
of the wave function for the first few excited states. (The amplitude has
been appropriately normalized so that |ψ(x)|2 = 1
R
(b) If you look in a quantum mechanics textbook you will find that the bound
state energies for the simple harmonic oscillator are given by the formula
s
1 k 1
E n = (n + )ħ = (n + )Ē (4.16)
2 m 2
P4.5 Now redo this entire problem, but with the harmonic oscillator potential
replaced by
V (x) = µx 4 (4.18)
so that we have
ħ2 d 2 ψ
− + µx 4 ψ = E ψ (4.19)
2m d x 2
With this new potential you will need to find new formulas for the character-
istic length and energy so that you can use dimensionless scaled variables as
you did with the harmonic oscillator. Choose a so that your scaled equation
is
1 d 2ψ
− + ξ4 ψ = ²ψ (4.20)
2 d ξ2
with E = ²Ē . Use algebra by hand to show that
¶1/6 ¶1/3
ħ2 ħ4 µ
µ µ
a= Ē = (4.21) Figure 4.6 The probability distri-
mµ m2 butions for the ground state and
the first three excited states for the
Find the first 5 bound state energies by finding the first 5 values of ²n in the potential in Problem 4.5.
formula E n = ²n Ē .
Lab 5
Animating the Wave Equation: Staggered Leapfrog
In the last couple of labs we handled the wave equation by Fourier analysis,
turning the partial differential equation into a set of ordinary differential equa-
tions using separation of variables.1 But separating the variables and expanding
in orthogonal functions is not the only way to solve partial differential equations,
and in fact in many situations this technique is awkward, ineffective, or both. In
this lab we will study another way of solving partial differential equations using a
spatial grid and stepping forward in time. As an added attraction, this method
automatically supplies a beautiful animation of the solution. There are several
algorithms of this type that can be used on wave equations, so this is just an
introduction to a larger subject. The method we will show you here is called
staggered leapfrog; it is the simplest good method that we know.
∂2 y 2
2∂ y
− c =0 (5.1)
∂t 2 ∂x 2
p
For a string, the wave speed is related to tension and density via c = T /µ. The
boundary conditions are usually either of Dirichlet type (values specified):
∂y ∂y
(0) = g left (t ) ; (L) = g right (t ) (5.3)
∂x ∂x
Sometimes mixed boundary conditions specify a relation between the value
and derivative, as at the bottom of the hanging chain. In any case, some set of
conditions at the endpoints are required to solve the wave equation. It is also
necessary to specify the initial state of the string, giving its starting position and
velocity as a function of position:
∂y(x, t )
y(x, t = 0) = y 0 (x) ; |t =0 = v 0 (x) (5.4)
∂t
1 N. Asmar, Partial Differential Equations and Boundary Value Problems (Prentice Hall, New
25
26 Computational Physics 430
Both of these initial conditions are necessary because the wave equation is second
order in time, just like Newton’s second law, so initial displacement and velocity
must be specified at each point to find a unique solution.
To numerically solve the classical wave equation via staggered leapfrog we
approximate both the temporal and spatial derivatives in Eq. (5.1) with centered
finite differences, like this:
n+1 n n−1
∂2 y y j − 2y j + y j
≈
∂t 2 τ2 (5.5)
n n n
∂2 y y j +1 − 2y j + y j −1
≈
∂x 2 h2
In this notation, spatial position is indicated by a subscript j , referring to grid
points x j , while position in time is indicated by superscripts n, referring to time
points t n so that y(x j , t n ) = y nj . The time steps and the grid spacings are assumed
to be uniform with time step called τ and grid spacing called h. The staggered
leapfrog algorithm aims to find y j one time step into the future, denoted by y n+1 j
,
from the current and previous values of y j . To derive the algorithm put Eqs. (5.5)
into Eq. (5.1) and solve for y n+1 j
to find
c 2 τ2 ³ n ´
y n+1
j = 2y nj − y n−1
j + y j +1 − 2y n
j + y n
j −1 (5.6)
h2
P5.1 Derive Eq. (5.6) using the approximate second derivative formulas. This is
really simple, so just do it on paper.
Equation (5.6) can only be used at interior spatial grid points because the
j + 1 or j − 1 indices reach beyond the grid at the first and last grid points. The
behavior of the solution at these two end points is determined by the boundary
conditions. Since we will want to use both fixed value and derivative boundary
conditions, let’s use a cell-centered grid with ghost points (with N cells and N + 2
grid points) so we can easily handle both types without changing our grid. If the
values at the ends are specified we have
y 0n+1 + y 1n+1
= f left (t n+1 ) ⇒ y 0n+1 = −y 1n+1 + 2 f left (t n+1 )
2 (5.7)
n+1 n+1
y N +1 + y N n+1 n+1
= f right (t n+1 ) ⇒ yN +1 = −y N + 2 f right (t n+1 )
2
If the derivatives are specified then we have
y 1n+1 − y 0n+1
= g left (t n+1 ) ⇒ y 0n+1 = y 1n+1 − hg left (t n+1 )
h
n+1 n+1
(5.8)
y N +1 − y N n+1 n+1
= g right (t n+1 ) ⇒ yN +1 = yN + hg right (t n+1 )
h
To use staggered leapfrog, we first advance the solution at all interior points to
the next time step using Eq. (5.6), then we apply the boundary conditions using
Lab 5 Animating the Wave Equation: Staggered Leapfrog 27
the appropriate equation from Eqs. (5.7)-(5.8) to find the values of y at the end
points, and then we are ready to take another time step.
The staggered leapfrog algorithm in Eq. (5.6) requires not just y at the current
time level y nj but also y at the previous time level y n−1
j
. This means that we’ll need
to keep track of three arrays: an array y for the current values y nj , an array yold
for the values at the previous time step y n−1
j
, and an array ynew for the values
at the next time step y n+1
j
. At time t = 0 when the calculation starts, the initial
position condition gives us the current values y nj , but we’ll have to make creative
use of the initial velocity condition to create an appropriate yold to get started.
To see how this works, let’s denote the initial values of y on the grid by y 0j , the
values after the first time step by y 1j , and the unknown previous values (yold) by
y −1
j
. A centered time derivative at t = 0 turns the initial velocity condition from
Eq. (5.4) into
y 1j − y −1
j
= v 0 (x j ) (5.9)
2τ
This gives us an equation for the previous values y −1
j
, but it is in terms of the
still unknown future values y 1j . However, we can use Eq. (5.6) to obtain another
relation between y 1j and y −1
j
. Leapfrog at the first step (n = 0) says that
c 2 τ2 ³ 0 ´
y 1j = 2y 0j − y −1
j + y j +1 − 2y 0
j + y 0
j −1 (5.10)
h2
If we insert this expression for y 1j into Eq. (5.9), we can solve for y −1
j
in terms of
known quantities:
0 c 2 τ2 ³ 0 ´
y −1
j = y j − v 0 (x j )τ + 2
y j +1 − 2y 0j + y 0j −1 (5.11)
2h
P5.2 Derive Eq. (5.11) from Eqs. (5.9) and (5.10). Again, just use paper and pencil.
P5.3 (a) Start by making a cell-centered x grid with ghost points over the region
0 ≤ x ≤ L, with L = 1 m. Use N = 200 cells, so you have 202 grid
points. Define the initial displacement y as Gaussian bump with 1 cm
amplitude in the middle of the string, like this
y = 0.01 * np.exp(-(x-L/2)**2 / 0.02)
and the initial velocity vy to be zero everywhere along the string. Used
fixed-value boundary conditions, with y(0) = 0 and y(L) = 0. Plot the
initial position just to make sure your grid is right and that your initial
position array looks reasonable
(b) Assume that the wave speed on this string is c = 2 m/s, and pick
the time step as tau = 0.2*h/c. (We’ll explore this choice more
later.) Then create a variable yold using Eq. (5.11), and enforce the
28 Computational Physics 430
0.1 t=0
0.1
0 0.5 1
x
0.1 t = 0.2
Figure 5.2 Richard Courant (left), Kurt Friedrichs (center), and Hans Lewy (right) de-
scribed the CFL instability condition in 1928.
0
∂y
(e) Change the boundary conditions so that ∂x = 0 at each end and watch 0.1
how the reflection occurs in this case. 0 0.5 1
x
(f) Change the initial conditions from initial displacement with zero
0.1 t = 0.4
velocity to initial velocity with zero displacement. Use an initial Gaus-
sian velocity pulse just like the displacement pulse you used earlier
0
and use fixed-end boundary conditions. Watch how the wave motion
develops in this case. (You will need to change the y-limits in the axis
0.1
command to see the vibrations with these parameters.) Then find a
0 0.5 1
slinky, stretch it out, and whack it in the middle to verify that the math x
does the physics right. 0.1 t = 0.6
0
The damped wave equation
0.1
We can modify the wave equation to include damping of the waves using a linear 0 0.5 1
damping term, like this: x
∂2 y ∂y 2
2∂ y
+ γ − c =0 (5.12) Figure 5.3 Snapshots of the evo-
∂t 2 ∂t ∂x 2 lution of a wave on a string with
with c constant. The staggered leapfrog method can be used to solve Eq. (5.12) fixed ends and no initial displace-
also. To do this, we use the approximate first derivative formula ment but with an initial velocity.
(See Problem 5.3(c))
n+1 n−1
∂y y j − y j
≈ (5.13)
∂t 2τ
along with the second derivative formulas in Eqs. (5.5) and find an expression for
the values one step in the future:
2c 2 τ2 ³ n
µ ´¶
n+1 1 n n−1 n−1 n n
yj = 4y j − 2y j + γτy j + y j +1 − 2y j + y j −1 (5.14)
2 + γτ h2
(c) Modify your staggered leapfrog code to include damping with γ = 0.2.
Max Amplitude vs. Time
Then run your animation with the initial conditions in Problem 5.3(c)
0.06
and verify that the waves damp away. You will need to run for about
25 s and you will want to use a big skip factor so that you don’t have
0.04 to wait forever for the run to finish. Include some code to record the
maximum value of y(x) over the entire grid as a function of time and
0.02
then plot it as a function of time at the end of the run so that you can
0 see the decay caused by γ. The decay of a simple harmonic oscillator
0 5 10 15 20 25
is exponential, with amplitude proportional to e −γt /2 . Scale this time
Figure 5.4 The maximum ampli- decay function properly and lay it over your maximum y plot to see if
tude of oscillation decays expo- it fits. Can you explain why the fit is as good as it is? (Hint: think about
nentially for the damped wave doing this problem via separation of variables.)
equation. (Problem 5.4(c))
∂2 y ∂y 2
2∂ y f (x)
+ γ − c = cos(ωt ) (5.15)
∂t 2 ∂t ∂x 2 µ
P5.5 (a) Re-derive the staggered leapfrog algorithm to include both driving
and damping forces as in Eq. (5.15).
(b) Modify your code from Problem 5.4 to use this new algorithm. We’ll
have the string start from rest, so you don’t need to worry about finding
y old . Just set y = 0 and y old = 0 and enter the time-stepping loop.
This problem involves the physics of waves on a real guitar string,
so we’ll need to use realistic values for our parameters. Use T = 127,
µ = 0.003, and L = 1.2 (in SI units) and remember that c = T /µ. Use
p
½
0.73 if 0.8 ≤ x ≤ 1
f (x) = (5.16)
0 otherwise
Lab 5 Animating the Wave Equation: Staggered Leapfrog 31
γ that is the proper size to make the system settle down to steady state 5
t = 0.1
after 20 or 30 bounces of the string. (You will have to think about the
value of ω that you are using and about your damping rate result from 0
problem 5.4 to decide which value of γ to use to make this happen.)
−5
Run the model long enough that you can see the transients die away 0 0.5 1
and the string settle into the steady oscillation at the driving frequency. x
−4
You may find yourself looking at a flat-line plot with no oscillation at x 10
5
all. If this happens look at the vertical scale of your plot and remember t = 0.2
that we are doing real physics here. If your vertical scale goes from −1 0
to 1, you are expecting an oscillation amplitude of 1 meter on your
guitar string. Compare the steady state mode to the shape found in −5
Problem 3.2(a) (see Fig. 3.1). 0 0.5 1
x
Then run again with ω = 1080, which is close to a resonance, and again x 10
−4
see the system come into steady oscillation at the driving frequency. 5
t = 0.3
−5
0 0.5 1
x
−4
x 10
5
t = 0.4
−5
0 0.5 1
x
to spend a little time thinking about how to represent 2-D grids. For a simple d
rectangular grid where all of the cells are the same size, 2-D grids are pretty
straightforward. We just divide the x-dimension into equally sized regions and
the y-dimension into equally sized regions, and the two one dimensional grids
intersect to create rectangular cells. Then we put grid points either at the corners
of the cells (cell-edge) or at the centers of the cells (cell-centered). On a cell-center
grid we’ll usually want ghost point outside the region of interest so we can get the c
boundary conditions right. a b
33
34 Computational Physics 430
P6.1 (a) Use the code fragment above in a program to create a 30-point cell-
edge grid in x and a 50-point cell-edge grid in y with a = 0, b = 2, c =
−1, d = 3. Switch back and forth between the argument indexing='ij'
and indexing='xy', and look at the different matrices that result.
Convince the TA that you know what the difference is. (HINT: When
we write matrices, rows vary in the y dimension and columns vary
in the x direction, whereas with Z (x i , y i ) we have a different con-
vention. NumPy’s naming convention seems backward to us, but
we didn’t come up with it. Sorry.) We recommend that you use the
indexing='ij' convention, as it tends to more readable code for
representing Z (x i , y i ).
(b) Using this 2-D grid, evaluate the following function of x and y:
0.5 µ q ¶
−(x 2 +y 2 ) 2 2
0 f (x, y) = e cos 5 x + y (6.1)
−0.5
3
Use the plot_surface command to make a surface plot of this func-
2
tion. Properly label the x and y axes with the symbols x and y, to get
1
2 a plot like Fig. 6.2.
0
1
y
−1 0 x There are a lot more options for plotting surfaces in Python, but we’ll let you
Figure 6.2 Plot from Problem 6.1. explore those on your own. For now, let’s do some physics on a two-dimensional
The graphic in this figure was cre- grid.
ated with Matlab. Python’s graph-
ics engine is sort of privative in
comparison to Matlab’s, so you The two-dimensional wave equation
won’t get something quite so nice.
In particular, getting the scaling The wave equation for transverse waves on a rubber sheet is 1
right is painful in Python.
∂2 z
µ 2
∂ z ∂2 z
¶
µ = σ + (6.2)
∂t 2 ∂x 2 ∂y 2
In this equation µ is the surface mass density of the sheet, with units of mass/area.
The quantity σ is the surface tension, which has rather odd units. By inspecting
the equation above you can find that σ has units of force/length, which doesn’t
seem right for a surface. But it is, in fact, correct as you can see by performing the
following thought experiment. Cut a slit of length L in the rubber sheet and think
about how much force you have to exert to pull the lips of this slit together. Now
imagine doubling L; doesn’t it seem that you should have to pull twice as hard
1 N. Asmar, Partial Differential Equations and Boundary Value Problems (Prentice Hall, New
to close the slit? Well, if it doesn’t, it should; the formula for this closing force is
given by σL, which defines the meaning of σ.
We can solve the two-dimensional wave equation using the same staggered
leapfrog techniques that we used for the one-dimensional case, except now we
need to use a two dimensional grid to represent z(x, y, t ). We’ll use the nota-
tion z nj,k = z(x j , y k , t n ) to represent the function values. With this notation, the
derivatives can be approximated as
n+1 n n−1
∂2 z z j ,k − 2z j ,k + z j ,k
≈ (6.3)
∂t 2 τ2
n n n
∂2 z z j +1,k − 2z j ,k + z j −1,k
≈ (6.4)
∂x 2 h x2
n n n
∂2 z z j ,k+1 − 2z j ,k + z j ,k−1
≈ (6.5)
∂y 2 h 2y
where h x and h y are the grid spacings in the x and y dimensions. We insert these
three equations into Eq. (6.2) to get an expression that we can solve for z at the
next time (i.e. z n+1
j ,k
). Then we use this expression along with the discrete version
of the initial velocity condition
z n+1
j ,k
− z n−1
j ,k
v 0 (x j , y k ) ≈ (6.6)
2τ
P6.2 (a) Derive the staggered leapfrog algorithm for the case of square cells
with h x = h y = h. Write a program that animates the solution of the
two dimensional wave equation on a square region that is [−5, 5] ×
[−5, 5] and that has fixed edges. Use a cell-edge square grid with
the edge-values pinned to zero to enforce the boundary condition.
Choose σ = 2 N/m and µ = 0.3 kg/m2 and use a displacement initial
condition that is a Gaussian pulse with zero velocity
2
+y 2 )
z(x, y, 0) = e −5(x (6.7)
tfinal=10
t=np.arange(0,tfinal,tau)
skip=10
fig = plt.figure(1)
# here is the loop that steps the solution along
for m in range(len(t)):
µ
r
τ< f h (6.8)
σ
(i) Poisson’s equation for the electrostatic potential V (x, y) given the charge
density ρ(x, y)
∂2V ∂2V −ρ
+ = + Boundary Conditions (6.9)
∂x 2 ∂y 2 ²0
∂2 y 1 ∂2 y
− =0 + Boundary Conditions (6.10)
∂x 2 c 2 ∂t 2
(iii) The thermal diffusion equation for the temperature distribution T (x, t ) in a
medium with diffusion coefficient D
∂T ∂2 T
=D 2 + Boundary Conditions (6.11)
∂t ∂x
To this point in the course, we’ve focused mostly on the wave equation, but over
the next several labs we’ll start to tackle some of the other PDEs.
Mathematicians have special names for these three types of partial differential
equations, and people who study numerical methods often use these names, so
let’s discuss them a bit. The three names are elliptic, hyperbolic, and parabolic.
You can remember which name goes with which of the equations above by re-
membering the classical formulas for these conic sections:
x2 y 2
ellipse : + =1 (6.12)
a2 b2
x2 y 2
hyperbola : − =1 (6.13)
a2 b2
parabola : y = ax 2 (6.14)
Compare these equations with the classical PDE’s above and make sure you can
use their resemblances to each other to remember the following rules: Poisson’s
equation is elliptic, the wave equation is hyperbolic, and the diffusion equation is
parabolic. These names are important because each different type of equation
requires a different type of algorithm and boundary conditions.
38 Computational Physics 430
∂ψ ħ2 ∂2 ψ
iħ =− +V ψ (6.15)
∂t 2m ∂x 2
which is the basic equation of quantum (or “wave”) mechanics. The wavy nature
of the physics described by this equation might lead you to think that the proper
boundary conditions on ψ(x, t ) would be hyperbolic: end conditions on ψ and
Lab 6 The 2-D Wave Equation With Staggered Leapfrog 39
initial conditions on ψ and ∂ψ/∂t . But if you look at the form of the equation, it
looks like thermal diffusion. Looks are not misleading here; to solve this equation
you only need to specify ψ at the ends in x and the initial distribution ψ(x, 0), but
not its time derivative.
And what are you supposed to do when your system is both hyperbolic and
parabolic, like the wave equation with damping?
∂2 y 1 ∂2 y 1 ∂y
− − =0 (6.16)
∂x 2 c 2 ∂t 2 D ∂t
The rule is that the highest-order time derivative wins, so this equation needs
hyperbolic boundary conditions.
P6.3 Make sure you understand this material well enough that you are com-
fortable answering basic questions about PDE types and what types of
boundary conditions go with them on a quiz and/or an exam. Then explain
it to the TA to pass this problem off.
Lab 7
The Diffusion Equation and Implicit Methods
∂T ∂2 T
=D 2 . (7.1)
∂t ∂x
This equation describes how the distribution T (often temperature) diffuses
through a material with a constant diffusion coefficient D. The diffusion equation
can be approached analytically via separation of variables by assuming that T is
of the form T (x, t ) = g (x) f (t ). Plugging this form into the diffusion equation, we
find
1 f˙(t ) g 00 (x)
= (7.2)
D f (t ) g (x)
The left-hand side depends only on time, while the right-hand side depends only
on space, so both sides must equal a constant, say −a 2 . Thus, f (t ) must satisfy
f˙(t ) = −γ f (t ) (7.3)
where γ = a 2 D so that f (t ) is
f (t ) = e −γt . (7.4)
Meanwhile g (x) must satisfy
and the separation constant can take on the values a = nπ/L, where n is an integer.
Any initial distribution T (x, t = 0) that satisfies these boundary conditions can be
composed by summing these sine functions with different weights using Fourier
series techniques. Notice that higher spatial frequencies (i.e. large n) damp faster,
according to Eq. (7.4). We already studied how to use separation of variables
computationally in the first several labs of this manual, so let’s move directly to
time-stepping methods for solving the diffusion equation.
1 N. Asmar, Partial Differential Equations and Boundary Value Problems (Prentice Hall, New
41
42 Computational Physics 430
T jn+1 − T jn−1 D ³ n ´
n n
= T j +1 − 2T j + T j −1 (7.7)
2τ h2
2Dτ ³ n ´
T jn+1 = T jn−1 + T j +1 − 2T n
j + T n
j −1 (7.8)
h2
There is a problem starting this algorithm because of the need to have T one time
step in the past (T jn−1 ), but even after we work around this problem this algorithm
turns out to be worthless. We won’t make you code it up, but if you did, you’d
find that no matter how small a time step τ you choose, you encounter the same
kind of instability that plagues staggered leapfrog when the step size got too big
(infinite zig-zags). Such an algorithm is called unconditionally unstable, and is an
invitation to keep looking. This must have been a nasty surprise for the pioneers
of numerical analysis who first encountered it.
For now, let’s sacrifice second-order accuracy to obtain a stable algorithm. If
we don’t center the time derivative, but use instead a forward difference we find
T jn+1 − T jn D ³ n ´
n n
= T − 2T + T (7.9)
τ h 2 j +1 j j −1
This algorithm has problems since the left side of Eq. (7.9) is centered at time t n+ 1 ,
2
while the right side is centered at time t n . This makes the algorithm inaccurate,
but it turns out that it is stable if τ is small enough. Solving for T jn+1 yields
Dτ ³ n ´
T jn+1 = T jn + T j +1 − 2T n
j + T n
j −1 (7.10)
h2
P7.1 (a) Modify one of your staggered leapfrog programs that uses a cell-center
grid to implement Eq. (7.10) to solve the diffusion equation on the
0.6
0.4
interval [0, L] with initial distribution
0.2
0 T (x, 0) = sin (πx/L) (7.11)
3
2 2
x 1.5 and boundary conditions T (0) = T (L) = 0. Use D = 2, L = 3, and N =
1 1 t
0 0.5
20. You don’t need to make a space-time surface plot like Fig. 7.1. Just
Figure 7.1 Diffusion of the n = make a line plot that updates each time step as we’ve done previously.
1 sine temperature distribution This algorithm has a CFL condition on the time step τ of the form
given in Problem 7.1(a).
h2
τ≤C (7.12)
D
Lab 7 The Diffusion Equation and Implicit Methods 43
Even though this technique can give us OK results, the time step constraint for this Figure 7.2 Diffusion of the Gaus-
method is onerous. The constraint is of the form τ < C h 2 , where C is a constant. sian temperature distribution
Suppose, for instance, that to resolve some spatial feature you need to decrease h given in Problem 7.1(c) with in-
by a factor of 5; then you will have to decrease τ by a factor of 25. This will make sulating boundary conditions.
your code take forever to run, which motivates us to find a better way.
dy
= −y (7.13)
dt
P7.2 (a) Write a program to solve this equation using Euler’s method:
y n+1 − y n
= −y n . (7.14)
τ
44 Computational Physics 430
4 The program to solve for y using Euler’s method is only a few lines of
y(t) code (like less than 10 lines, including the plot command). Here are
2
the first few lines:
0 tau = 0.5
−2 tmax = 20.
Euler’s Method t = np.arange(0,tmax,tau)
−4 y = np.zeros_like(t)
0 5 10
t Show by numerical experimentation that Euler’s method is unstable
for large τ. You should find that the algorithm that is unstable if τ > 2.
Figure 7.3 Euler’s method is un-
stable for τ > 2. (τ = 2.1 in this Use y(0) = 1 as your initial condition. This is an example of an explicit
case.) method.
(b) Notice that the left side of Eq. (7.14) is centered on time t n+ 1 but the
2
right side is centered on t n . Fix this by centering the right-hand side
at time t n+ 1 by using an average of the advanced and current values
2
of y,
y n + y n+1
yn ⇒ .
2
Modify your program to implement this fix, then show by numerical
1
experimentation that when τ becomes large this method doesn’t blow
0.5 y(t) up. It isn’t correct because y n bounces between positive and negative
values, but at least it doesn’t blow up. The presence of τ in the denom-
0 inator is the tip-off that this is an implicit method, and the improved
stability is the point of using something implicit.
Euler’s Method
−0.5
0 5 10 (c) Now Modify Euler’s method by making it fully implicit by using y n+1
t in place of y n on the right side of Eq. (7.14) (this makes both sides of
the equation reach into the future). This method is no more accurate
Figure 7.4 The implicit method in
than Euler’s method for small time steps, but it is much more stable
7.2(b) with τ = 4.
and it doesn’t bounce between positive and negative values.
Show by numerical experimentation in a modified program that this
1 fully implicit method damps even when τ is large. For instance, see
what happens if you choose τ = 5 with a final time of 20 seconds. The
0.5 y(t) time-centered method of part (b) would bounce and damp, but you
Euler’s Method should see that the fully implicit method just damps. It’s terribly inac-
curate, and actually doesn’t even damp as fast as the exact solution,
0 but at least it doesn’t bounce like part (b) or go to infinity like part (a).
0 5 10 Methods like this are said to be “absolutely stable”. Of course, it makes
t
no sense to choose really large time steps, like τ = 100 when you only
Figure 7.5 The fully implicit want to run the solution out to 10 seconds.
method in 7.2(c) with τ = 2.1.
Lab 7 The Diffusion Equation and Implicit Methods 45
∂T ∂2 T
=D 2 (7.15)
∂t ∂x
We begin by finite differencing the right side as usual:
∂T j T j +1 − 2T j + T j −1
=D (7.16)
∂t h2
Now we discretize the time derivative by taking a forward time derivative on the
left:
T jn+1 − T jn T jn+1 − 2T jn + T jn−1
=D (7.17) Phyllis Nicolson (1917–1968, English)
τ h2
This puts the left side of the equation at time level t n+ 1 , while the right side is
2
at t n . To put the right side at the same time level (so that the algorithm will be
second-order accurate), we replace each occurrence of T on the right-hand side
by the average
1 T n+1 + T n
T n+ 2 = (7.18)
2
like this:
T jn+1 − T jn T jn+1
+1
+ T jn+1 − 2T jn+1 − 2T jn + T jn+1
−1
+ T jn−1
=D (7.19)
τ 2h 2
If you look carefully at this equation you will see that there is a problem: how are
we supposed to solve for T jn+1 ? The future values T n+1 are all over the place, and
they involve three neighboring grid points (T jn+1
−1
, T jn+1 , and T jn+1
+1
) so we can’t just
solve in a simple way for T jn+1 . This is an example of why implicit methods are John Crank (1916–2006, English)
We know this looks ugly, but it really isn’t so bad. To solve for T jn+1 we just need to
solve a linear system, as we did in Lab 2 on two-point boundary value problems.
When a system of equations must be solved to find the future values, we say
that the method is implicit. This particular implicit method is called the Crank-
Nicolson algorithm.
To see more clearly what we are doing, and to make the algorithm a bit more
efficient, let’s define a matrix A to describe the left side of Eq. (7.20) and another
matrix B to describe the right side, like this:
AT n+1 = BT n (7.21)
46 Computational Physics 430
A j ,k = 0 except for :
2h 2
A j , j −1 = −1 ; A j , j = + 2 ; A j , j +1 = −1 (7.22)
τD
and the elements of B are given by
B j ,k = 0 except for :
2h 2
B j , j −1 = 1 ; B j , j =
− 2 ; B j , j +1 = 1 (7.23)
τD
Once the boundary conditions are added to these matrices, Eq. (7.21) could be
solved symbolically to find T n+1
• Load the matrices A and B as given in Eq. (7.22) and Eq. (7.23) for all of
the rows except the first and last. Since the diffusion coefficient D doesn’t
change with time you can load A and B just once before the time loop starts.
• The first and last rows involve the boundary conditions. Usually it is easier
to handle the boundary conditions if we plan to do the linear solve of our
matrix equation AT n+1 = BT n in two steps, like this:
import scipy.linalg as la
With this code we can just load the top and bottom rows of B with zeros, cre-
ating a right-hand-side vector r with zeros in the top and bottom positions.
The top and bottom rows of A can then be loaded with the appropriate
terms to enforce the desired boundary conditions on T n+1 , and the top and
bottom positions of r can be loaded as required just before the linear solve,
as indicated above.
Lab 7 The Diffusion Equation and Implicit Methods 47
For example, if the boundary conditions were T (0) = 1 and T (L) = 5, the top
and bottom rows of A and the top and bottom positions of r would have
been loaded like this (assuming a cell-center grid with ghost points):
so that the equations for the top and bottom rows are
T0 + T1 T N + T N +1
= r0 = r N +1 (7.25)
2 2
The matrix B just stays out of the way (is zero) in the top and bottom rows.
(iii) Once the matrices A and B are loaded, finding the new temperature in-
side the time loop is accomplished in the time loop by solving the matrix
equation using the code fragment listed above.
P7.3 (a) Write program that implements the Crank-Nicolson algorithm with
fixed-edge boundary conditions, T (0) = 0 and T (L) = 0. Test your
program by running it with D = 2 and an initial temperature given
by T (x) = sin (πx/L). Try various values of τ and see how it compares
with the exact solution. Verify that when the time step is too large the
solution is inaccurate, but still stable. To do the checks at large time
step you will need to use a long run time and not skip any steps in the
plotting.
(b) Now study the accuracy of this algorithm by using various values of
the cell number N and the time step τ. For each pair of choices run
for t = 5 s and find the maximum difference between the exact and
numerical solutions. You should find that the time step τ matters less
than N . The number of cells N is the more important parameter for 1
in the first few grid points during the first few time steps.
Figure 7.6 Solution to 7.3(c)
Lab 8
Schrödinger’s Equation
Derivations
Here is the time-dependent Schrödinger equation which governs the way a quan- 0.4
tum wave function changes with time in a one-dimensional potential well V (x):1
∂ψ ħ2 ∂2 ψ 0
iħ =− + V (x)ψ (8.1)
∂t 2m ∂x 2
Note that except for the presence of the imaginary unit i , this is very much like 0.4
the diffusion equation. In fact, a good way to solve it is with the Crank-Nicolson
algorithm. Not only is this algorithm stable for Schrödinger’s equation, but it has 0
another important property: it conserves probability. This is very important. If
the algorithm you use does not have this property, then as ψ for a single particle
0.4
is advanced in time you have (after a while) 3/4 of a particle, then 1/2, etc.
The Schrödinger equation usually describes the behavior of very tiny things
on very short timescales, so SI quantities like kilograms, meters, and Joules are 0
10
x
5
Particle in a box
0
Let’s use this algorithm for solving Schrödinger’s equation to study the evolution
−5
of a particle in a box with
−10
½ 0 10 20 30 40 50
0 if − L < x < L t
V (x) = (8.2)
+∞ otherwise
Figure 8.2 The expectation posi-
tion 〈x〉 for the particle in Fig. 8.1
1 N. Asmar, Partial Differential Equations and Boundary Value Problems (Prentice Hall, New
as time progresses and the packet
Jersey, 2000), p. 470-506.
spreads out (Problem 8.2(c)).
49
50 Computational Physics 430
The infinite potential at the box edges is imposed by forcing the wave function to
be zero at these points:
1 t=0 ψ(−L) = 0 ; ψ(L) = 0 (8.3)
0.5 P8.2 Modify one of your programs from Lab 7 to implement the Crank-Nicolson
algorithm derived above for the case of a particle in a box with L = 10.
0 Note we are doing quantum mechanics and the imaginary parts matter
−10 −5 0 5 10
x now. When assigning complex variables in NumPy, you use the engineering
complex number j , like this:
1 t=1
a = 1.0 + 0.5j
0.5
When you allocate your arrays, you’ll need to specify up-front that they will
0
−10 −5 0 5 10 hold complex values, like this:
x
A = np.zeros((N,N),dtype=np.complex_)
1 t=2 B = np.zeros_like(A,dtype=np.complex_)
0.5
(a) Write a script to solve the time-dependent Schrödinger equation using
0
Crank-Nicolson. We find that a cell-edge grid is easiest, but you can
−10 −5 0 5 10 also do cell-center with ghost points if you like. Start with a localized
x
wave packet of width σ and momentum p:
1 t=3
1 2
± 2
ψ(x, 0) = p p e i px /ħ e −x (2σ ) (8.4)
0.5 σ π
0.4
t=0
Tunneling
0.2
Now we will allow the pulse to collide with a with a non-infinite potential barrier
0
of height V0 and width ∆x = 0.02L, and study what happens. Classically, the −20 −10 0 10 20
answer is simple: if the particle has a kinetic energy less than V0 it will be unable x
to get over the barrier, but if its kinetic energy is greater than V0 it will slow down t=1
0.4
as it passes over the barrier, then resume its normal speed in the region beyond
the barrier. (Think of a ball rolling over a bump on a frictionless track.) Can 0.2
the particle get past a barrier that is higher than its kinetic energy in quantum
0
mechanics? The answer is yes, and this effect is called tunneling. −20 −10 0 10 20
To see how the classical picture is modified in quantum mechanics we must x
first compute the energy of our pulse so we can compare it to the height of the 0.4
t=2
barrier. The quantum mechanical formula for the expectation value of the energy
is Z ∞ 0.2
〈E 〉 = ψ∗ H ψd x (8.6) 0
−∞ −20 −10 0 10 20
where ψ∗ is the complex conjugate of ψ and where x
0.4
t=3
ħ2 ∂2 ψ
Hψ = − + V (x)ψ(x) (8.7)
2m ∂x 2 0.2
In our case the initial wave function ψ(x, 0) given by Eq. (8.4) is essentially zero at 0
−20 −10 0 10 20
the location of the potential barrier, so we may take V (x) = 0 in the integral when x
we compute the initial energy. Performing the integral, we find
Figure 8.5 The probability distri-
p2 ħ2 bution |ψ(x)|2 for a particle inci-
〈E 〉 = + (8.8) dent on a narrow potential barrier
2m 4mσ2
located just before x = 10 with
Since this is a conservative system, the energy should remain constant throughout V0 > 〈E 〉. Part of the wave tunnels
the propagation. through the barrier and part inter-
feres with itself as it is reflected.
P8.3 (a) Modify your script from Problem 8.2 so that it uses a computing region
52 Computational Physics 430
∂2V ∂2V ρ
+ =− (9.2)
∂x 2 ∂y 2 ²0
Note that by studying this equation we are also studying Laplace’s equation (Pois-
son’s equation with ρ = 0) and the steady state solutions to the diffusion equation
in two dimensions (∂T /∂t = 0 in steady state).
This set of equations can only be used at interior grid points because on the edges
it reaches beyond the grid, but this is OK because the boundary conditions tell us
what V is on the edges of the region.
1 N. Asmar, Partial Differential Equations and Boundary Value Problems (Prentice Hall, New
53
54 Computational Physics 430
Equation (9.3) plus the boundary conditions represent a set of linear equa-
tions for the unknowns V j ,k , so we could imagine just doing a big linear solve to
find V all at once. Because this sounds so simple, let’s explore it a little to see why
we are not going to pursue this idea. The number of unknowns V j ,k is N x × N y ,
which for a 100×100 grid is 10,000 unknowns. So to do the solve directly we would
have to be working with a 10, 000×10, 000 matrix, requiring 800 megabytes of RAM
to store the matrix. Doing this big solve is possible for 2-dimensional problems
like this because computers with much more memory than this are common.
However, for larger grids the matrices can quickly get out of hand. Furthermore,
if you wanted to do a 3-dimensional solve for a 100 × 100 × 100 grid, this would
require (104 )3 × 8 = 8 × 1012 , or about 8 terabytes of memory. Computers like this
are becoming possible, but this is still a tiny computational grid. So even though
computers with large amounts of RAM are becoming common, people still use
iteration methods like the ones we are about to describe.
For large values of n, we find that the process converges to the exact solution
x̄ = 0.567. Let’s do a little analysis to see why it works. Let x̄ be the exact solution
of this equation and suppose that at the n th iteration level we are close to the
solution, only missing it by the small quantity δn like this: x n = x̄ + δn . Let’s
substitute this approximate solution into Eq. (9.5) and expand using a Taylor
series. Recall that the general form for a Taylor’s series is
1 00 hn
f (x + h) = f (x) + f 0 (x)h + f (x)h 2 + · · · + f (n) (x) + ··· (9.6)
2 n!
When we substitute our approximate solution into Eq. (9.5) and expand around
the exact solution x̄ we get
If we ignore the terms that are higher order in δ (represented by · · · ), then Eq. (9.7)
shows that the error at the next iteration step is δn+1 = −e −x̄ δn . When we are close
to the solution the error becomes smaller every iteration by the factor −e −x̄ . Since
x̄ is positive, e −x̄ is less than 1, and the algorithm converges. When iteration works
it is not a miracle—it is just a consequence of having this expansion technique
result in an error multiplier that is less than 1 in magnitude.
Lab 9 Poisson’s Equation: Iteration Methods 55
P9.1 Write a program to solve the equation x = e −x by iteration and verify that
it converges. Then try solving this same equation the other way round:
x = − ln x and show that the algorithm doesn’t converge. If you were to
use the x̄ + δ analysis above for the logarithm equation, you’d find that the
multiplier is bigger than one. Our time is short today, so we won’t make you
do this analysis now, but it is a good exercise if you want to look at it on
your own time.
ρ j ,k
à ! ,à !
V j +1,k + V j −1,k V j ,k+1 + V j ,k−1 2 2
V j ,k = + + + (9.8)
h x2 h 2y ²0 h x2 h 2y
With the equation in this form we could just iterate over and over by doing the
following.
2. Use this initial guess to evaluate the right-hand side of Eq. (9.8)
3. Replace our initial guess for V j ,k by this right-hand side, and then repeat.
If all goes well, then after many iterations the left and right sides of this equation
will agree and we will have a solution. 2
To see, let’s notice that the iteration process indicated by Eq. (9.8) can be
written in matrix form as
Vn+1 = LVn + r (9.9)
where L is the matrix which, when multiplied into the vector Vn , produces the
V j ,k part of the right-hand side of Eq. (9.8) and r is the part that depends on the
charge density ρ j ,k . (Don’t worry about what L actually looks like; we are just
going to apply general matrix theory ideas to it.) As in the exponential-equation
example given above, let V̄ be the exact solution vector and let δn be the error
vector at the n th iteration. The iteration process on the error is, then,
The answer, of course, is that it just multiplies each eigenvector by its eigenvalue.
Hence, for iteration to work we need all of the eigenvalues of the matrix L to have
magnitudes less than 1.
So we can now restate the conditions for this approach to work: Iteration on
Eq. (9.8) converges if all of the eigenvalues of the matrix L on the right-hand side
of Eq. (9.8) are less than 1 in magnitude. This statement is a theorem which can be
proved if you are really good at linear algebra, and the entire iteration procedure
described by Eq. (9.9) is known as Jacobi iteration. Unfortunately, even though
all of the eigenvalues have magnitudes less than 1 there are lots of them that have
magnitudes very close to 1, so the iteration takes forever to converge (the error
only goes down by a tiny amount each iteration).
But Gauss and Seidel discovered that the process can be accelerated by making
a very simple change in the process. Instead of only using old values of V j ,k on
the right-hand side of Eq. (9.8), they used values of V j ,k as they became available
during the iteration. (This means that the right side of Eq. (9.8) contains a mixture
of V -values at the n and n + 1 iteration levels.) This change, which is called Gauss-
Seidel iteration is really simple to code; you just have a single array in which to
store V j ,k and you use new values as they become available. Here as a coded
example of Guass-Seidel iteration for a rectangular region grounded on two sides,
with the two other sides held at a potential:
Listing 9.1 (GaussSeidel.py)
ymin = 0
ymax = 2
Ny = 40
y,hy = np.linspace(ymin,ymax,Ny,retstep = True)
hy2 = hy**2
X,Y = np.meshgrid(x,y,indexing='ij')
# Initialize potential
V = 0.5*np.ones_like(X)
V[:,-1] = 0
V[0,:] = 1
V[-1,:] = 1
# Iterate
denom = 2/hx2 + 2/hy2
fig = plt.figure(1)
for n in range(200):
# make plots every few steps
if n % 10 == 0:
plt.clf()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(X,Y,V)
ax.set_zlim(-0.1, 2)
plt.xlabel('x')
plt.ylabel('y')
plt.draw()
plt.pause(0.1)
P9.2 Paste the code above into a program, run it, and watch the solution iterate.
Study the code, especially the part in the loop. When you understand the
code, call a TA over and explain it to them to pass off this part.
Successive over-relaxation
Gauss-Seidel iteration is not the best we can do, however. To understand the next
improvement let’s go back to the exponential example
P9.3 Verify quickly on paper that even though Eq. (9.12) looks quite different
from Eq. (9.11), it is still solved by x = e −x .
If we insert x n = x̄ + δn into this new equation and expand as before, the error
changes as we iterate according to the following
Notice what would happen if we chose ω so that the factor in parentheses were
zero: The equation says that we would find the correct answer in just one step! Of
course, to choose ω this way we would have to know x̄, but it is enough to know
that this possibility exists at all. All we have to do then is numerically experiment
with the value of ω and see if we can improve the convergence.
P9.4 Write a program that accepts a value of ω and runs the iteration in Eq. (9.12).
Experiment with various values of ω until you find one that does the best
job of accelerating the convergence of the iteration. You should find that
the best ω is near 0.64, but it won’t give convergence in one step. See if you
can figure out why not.
Hint: Think about the approximations involved in obtaining Eq. (9.13).
Specifically go back to the previous derivation and look for terms repre-
sented by dots.
As you can see from Eq. (9.13), this modified iteration procedure shifts the
error multiplier to a value that converges better. So now we can see how to
improve Gauss-Seidel: we just use an ω multiplier like this:
when the computing region is rectangular and the boundary values of V are fixed
(Dirichlet boundary conditions) is given by
2
ω= p (9.16)
1 + 1 − R2
where
h 2y cos (π/N x ) + h x2 cos (π/N y )
R= . (9.17)
h x2 + h 2y
These formulas usually give a reasonable estimate of the best ω to use. Note,
however, that this value of ω was found for the case of a cell-edge grid with the
potential specified at the edges. If you use a cell-centered grid with ghost points,
and especially if you change to derivative boundary conditions, this value of ω
won’t be quite right. But there is still a best value of ω somewhere near the value
given in Eq. (9.16) and you can find it by numerical experimentation.
Finally, we come to the question of when to stop iterating. It is tempting just
to watch the values of V j ,k and quit when the values stabilize at some level, like
this for instance: quit when ² = |V ( j , k)n+1 − V ( j , k)n | < 10−6 . You will see this
error criterion sometimes used in books, but do not use it. We know of one person
who published an incorrect result in a journal because this error criterion lied.
We don’t want to quit when the algorithm has quit changing V ; we want to quit
when Poisson’s equation is satisfied. (Most of the time these are the same, but
only looking at how V changes is a dangerous habit to acquire.) In addition, we
want to use a relative (%) error criterion. This is easily done by setting a scale
voltage Vscale which is on the order of the biggest voltage in the problem and then
using for the error criterion ¯ ¯
¯ Lhs − Rhs ¯
² = ¯¯ ¯ (9.18)
Vscale ¯
where Lhs is the left-hand side of Eq. (9.8) and Rhs is its right-hand side. Because
this equation is just an algebraic rearrangement of our finite-difference approx-
imation to Poisson’s equation, ² can only be small when Poisson’s equation is
satisfied. (Note the use of absolute value; can you explain why it is important to
use it? Also note that this error is to be computed at all of the interior grid points.
Be sure to find the maximum error on the grid so that you only quit when the
solution has converged throughout the grid.)
And what value should we choose for the error criterion so that we know when
to quit? Well, our finite-difference approximation to the derivatives in Poisson’s
equation is already in error by a relative amount of about 1/(12N 2 ), where N is the
smaller of N x and N y . There is no point in driving ² below this estimate. For more
details, and for other ways of improving the algorithm, see Numerical Recipes,
Chapter 19.
P9.5 Starting with the Gauss-Seidel example above, implement all of the im-
provements described above to write a full successive over-relaxation rou-
tine. Note that you will need to replace the for loop on the variable n
60 Computational Physics 430
into a while loop that is based on the error criterion. Also move the plot
1 command out of the loop so it only executes once, after the solution has
converged. (So that you don’t have to wait for a bunch of plots to be drawn.)
0.5 Run your code for a spatial region of size L x = 4 in the x-dimension and
L y = 2 the y-dimension arranged as shown in Fig. 9.1. Use Nx = Ny = 30
0 and several different values of ω. Note that the boundary conditions on the
2
2 potential V (x, y) are V (−L x /2, y) = V (L x /2, y) = 1 and V (x, 0) = V (x, L y ) = 0.
1 0
y 0 −2 x Set the error criterion to 10−4 . Verify that the optimum value of ω given by
Eq. (9.16) is the best one to use.
Figure 9.1 The electrostatic po-
tential V (x, y) with two sides
grounded and two sides at con-
stant potential. Some Different Geometries
P9.6 (a) Modify your code to model a rectangular pipe with V (x = −2, y) = −1,
1 V (x = 2, y) = 1, and the y = 0 and y = L y edges held at V = 0.
0.5
(b) Modify your code from (a) so that the boundary condition at the x =
0
−L x /2 edge of the computation region is ∂V /∂x = 0 and the boundary
condition on the y = L y edge is ∂V /∂y = 0. You can do this problem
2 either by changing your grid and using ghost points or by using a
2
1 0
quadratic extrapolation technique (see Eq. (2.10)). Both methods
y 0 −2 x work fine, but note that you will need to enforce boundary conditions
inside the main SOR loop now instead of just setting the values at the
Figure 9.2 The potential V (x, y)
edges and then leaving them alone.
from Problem 9.6(a).
You may discover that the script runs slower on this problem. See if
1
you can make it run a little faster by experimenting with the value of
ω that you use. Again, changing the boundary conditions can change
0.5 the eigenvalues of the operator. (Remember that Eq. (9.16) only works
for cell-edge grids with fixed-value boundary conditions, so it only
0
2 gives a ballpark suggestion for this problem.)
2
1 0
y 0 −2 x (c) Study electrostatic shielding by going back to the boundary conditions
of Problem 9.6(a), while grounding some points in the interior of the
Figure 9.3 The potential V (x, y) full computation region to build an approximation to a grounded cage.
with zero-derivative boundary Allow some holes in your cage so you can see how fields leak in. First
conditions on two sides (Prob-
make a rectangular cage similar to Fig. 9.4, but then you can try other
lem 9.6(b).)
geometries.
You will need to be creative about how you build your cage and about
how you make SOR leave your cage points grounded as it iterates. One
thing that won’t work is to let SOR change all the potentials, then set
the cage points back to V = 0 before doing the next iteration. This
takes forever to converge. It is much better to set them to zero and
force SOR to never change them. An easy way to do this is to use a
cell-edge grid with a mask. A mask is an array that you build that is
the same size as V, initially defined to be full of ones like this
Lab 9 Poisson’s Equation: Iteration Methods 61
mask = np.ones_like(V)
Then you go through and set the elements of mask that you don’t want
SOR to change to have a value of zero. (We’ll let you figure out the
logic to do this for the cage.) Once you have your mask built, you
add an if statement to our code so that the SOR stuff inside the j
and k for loops only changes a given point and updates the error if
mask(j,k) is one. This logic assumes you have to set the values of
V for these points before the for loop execute, just like the boundary
conditions. Using this technique you can calculate the potential for
quite complicated shapes just by changing the mask array.
0.5
0.4
0.1
0.2
0
y 0 −0.1 x
P10.1 Let’s start with something really simple and inaccurate just to see what can
go wrong. If we use a nicely centered difference in x and an inaccurate
forward difference in t , we find
ρ n+1
j
− ρ nj 1 ³ n ´
+ ρ j +1 v j +1 − ρ nj−1 v j −1 = 0 (10.2)
τ 2h
63
64 Computational Physics 430
Run this algorithm with these two boundary conditions enough times, and
with small enough time steps, that you become convinced that ρ(L, t ) = 1 is
wrong and that the entire algorithm is worthless because it is unstable.
As you might guess from the previous problem, the diffusion equation’s sim-
ple appearance can be deceiving; it is one of the most difficult equations to solve
numerically in all of computational physics because stable methods tend to be in-
accurate and accurate methods tend either to be unstable, or non-conservative (as
time runs mass spontaneously disappears), or unphysical (mass density and/or
pressure become negative.)
Let’s try another method, known as the Lax-Wendroff method. The idea of the
Lax-Wendroff algorithm is to use a Taylor series in time to obtain a second-order
accurate method. Taylor expanding the density function in time gives us
∂ρ τ2 ∂2 ρ
ρ(x, t + τ) = ρ(x, t ) + τ + (10.6)
∂t 2 ∂t 2
We can write the continuity equation as
∂ρ ∂ ¡ ¢
=− ρv (10.7)
∂t ∂x
Substituting into our Taylor expansion then gives
∂ ¡ ¢ τ2 ∂ ∂ ¡ ¢
µ ¶
ρ(x, t + τ) = ρ(x, t ) − τ ρv + − ρv (10.8)
∂x 2 ∂t ∂x
If we reverse the order of the derivatives in the last term and assume that v is not
a function of time, we can use Eq. (10.7) again to obtain.
∂ρv τ2 ∂ ∂ρv
µ ¶
ρ(x, t + τ) = ρ(x, t ) − τ + v . (10.9)
∂x 2 ∂x ∂x
Lab 10 The Continuity Equation 65
If we interpret the left-hand side as a time derivative, the second term on the right
looks essentially like the diffusion equation. Since the equation we are solving is
pure convection, the appearance of diffusion is not good news, but at least this
algorithm is better than the horrible one in 10.1. Notice also that the diffusion
coefficient in Eq. (10.10) is proportional to τ (stare at it until you can see that this
is true), so if small time steps are being used diffusion won’t hurt us too much.
P10.2 Finite difference the expression in Eq. (10.10) assuming that v(x) = v 0 =
const, to find the Lax-Wendroff algorithm:
v0τ ³ n ´ v 2 τ2 ³ ´
ρ n+1
j = ρ nj − ρ j +1 − ρ nj−1 + 0 2 ρ nj+1 − 2ρ nj + ρ nj−1 (10.11)
2h 2h
Change your script from 10.1 to use the Lax-Wendroff algorithm. Again,
use a cell-center grid with ghost points and about 400 grid points. Also
use the same initial condition as in Problem 10.1 and use the extrapolated
boundary condition that just lets the pulse leave.
Show that Lax-Wendroff works pretty well unless the time step exceeds a
Courant condition. Also show that it has the problem that the peak density
slowly decreases as the density bump moves across the grid. (To see this use Peter Lax (b. 1926, American) Lax was
a relatively coarse grid and a time step just below the stability constraint. the PhD advisor for Burton Wendroff.
Warning: do not run with τ = h/v 0 . If you do you will conclude that this
algorithm is perfect, which is only true for this one choice of time step.)
This problem is caused by the diffusive term in the algorithm, but since this
diffusive term is the reason that this algorithm is not unstable like the one
in 10.1, we suppose we should be grateful.
Crank-Nicolson Again
Finally, let’s try an implicit method, Crank-Nicolson in fact. As a reminder, the
continuity equation is
∂ρ ∂ ¡ ¢
+ ρv = 0 , (10.12)
∂t ∂x
We can’t solve this equation directly because it has two unknowns (ρ and v). But
if we assume that v is known, then it is possible to solve the equation using Crank-
Nicolson. As usual for Crank-Nicolson, we forward difference in time and center
difference in space to find
ρ n+1
j
− ρ nj v nj+1 ρ nj+1 − v nj−1 ρ nj−1
=− (10.13)
τ 2h
66 Computational Physics 430
Then we use time averaging to put the right side of the equation at the same time
level as the left (i.e. at the n + 1/2 time level):
³ ´ ³ ´
ρ n+1
j − ρ n
j = C 1 ρ n
j +1 + ρ n+1
j +1 −C 2 ρ n
j −1 + ρ n+1
j −1 (10.14)
where
τ ³ n ´
C1 = − v j +1 + v n+1
j +1 (10.15)
8h ³
τ ´
C2 = − v nj−1 + v n+1
j −1 (10.16)
8h
Then we put the ρ n+1 terms on the left and the ρ n terms on the right:
C 2 ρ n+1 n+1
j −1 + ρ j −C 1 ρ n+1 n n n
j +1 = −C 2 ρ j −1 + ρ j +C 1 ρ j +1 (10.17)
Then we write these equations along with the boundary conditions in matrix form
Aρ n+1 = Bρ n (10.18)
which we solve using linear algebra techniques. For this algorithm to calculate
ρ n+1 , we need to feed it values for ρ n , v n , and v n+1 . For now, let’s side-step this
issue by assuming that v(x, t ) is known, and a constant in time. In the next lab
we’ll worry about advancing the velocity solution in parallel with the density.
P10.3 (a) Write a program that implements this algorithm, perhaps starting
from one of your programs from the Schrödinger equation lab. Work
out how to implement the boundary conditions, ρ(0, t ) = 1 and ρ(L, t )
is just allowed to leave, by properly defining the top and bottom rows
of the matrices A and B. This involves multiplying Bρ n to find an
1
6 8
r-vector as you have done before.
4 6
4 (b) Implement this algorithm with a constant convection velocity v =
2
2
t 0 0
x v 0 and show that the algorithm conserves amplitude to very high
precision and does not widen due to diffusion. These two properties
Figure 10.1 A pulse is convected
make this algorithm a good one as long as shock waves don’t develop.
across a region in which the con-
vection velocity v(x) is constant (c) Now use a convection velocity that varies with x:
(Problem 10.3(b)).
v(x) = 1.2 − x/L (10.19)
This velocity slows down as the flow moves to the right, which means
that the gas in the back is moving faster than the gas in the front,
causing compression and an increase in density. You should see the
slowing down of the pulse and the increase in density in your numeri-
cal solution.
(d) Go back to a constant convection velocity v = v 0 and explore the way
this algorithm behaves when we have a shock wave (discontinuous
Figure 10.2 A pulse is convected
density) by using as the initial condition
across a region in which the con-
vection velocity v(x) is decreasing. ½
Note that the pulse narrows and 1.0 if 0 ≤ x ≤ L/2
ρ(x, 0) = (10.20)
grows, conserving mass. (Prob- 0 otherwise
lem 10.3(c))
Lab 10 The Continuity Equation 67
The true solution of this problem just convects the step to the right;
you will find that Crank-Nicolson fails at this seemingly simple task.
(e) For comparison, try the same step-function initial condition in your
Lax-Wendroff script from Problem 10.2.
∂ρ ∂ ¡ ¢
+ ρv = 0 , (11.1)
∂t ∂x
the conservation of energy
∂T ∂T ∂v (γ − 1)M κ 1 ∂2 T
+v + (γ − 1)T = , (11.2)
∂t ∂x ∂x kB ρ ∂x 2
∂v ∂v 1 ∂P 4µ ∂2 v
+v =− + (11.3)
∂t ∂x ρ ∂x 3ρ ∂x 2
Here ρ(x, t ) is the density of the gas, v(x, t ) is the velocity of the gas, and T (x, t ) is
the temperature of the gas. The pressure P is given by the ideal gas law
kB
P= ρT (11.4)
M
Because of the nonlinearity of these equations and the fact that they are
coupled we are not going to be able to write down a simple algorithm that will ad-
vance ρ, T , and v in time. But if we are creative we can combine simple methods
that work for each equation separately into a stable and accurate algorithm for
the entire set. We are going to show you one way to do it, but the computational
physics literature is full of other ways, including methods that handle shock waves.
This is still a very active and evolving area of research, especially for problems in
2 and 3 dimensions.
Let’s try a predictor-corrector approach similar to second-order Runge-Kutta
(which you learned about back in 330) by first taking an approximate step in time
of length τ to obtain predicted values for our variables one time step in the future.
We’ll refer to these first-order predictions for the future values as ρ̃ n+1 , T̃ n+1 ,
and ṽ n+1 . In the predictor step we will treat v as constant in time in Eq. (11.1)
69
70 Computational Physics 430
to predict ρ̃ n+1 . Then we’ll use ρ̃ n+1 to help us calculate T̃ n+1 using Eq. (11.2),
while still treating v as fixed in time. Once these predicted values are obtained we
can use them with Eq. (11.3) to obtain a predicted ṽ. With all of these predicted
future values of our variables in hand, we do another round of Crank-Nicolson
on all three equations to step the solution forward in time. We can represent this
procedure schematically as follows:
Step 1 Use the old velocity v n as input for Eqn. (11.5) → Solve for the predicted
density ρ̃.
Step 2 Use v n and ρ̃ as inputs for Eqn. (11.2) → Solve for the predicted tempera-
ture T̃ .
Step 3 Use ρ̃ and T̃ as inputs for Eqn. (11.3) → Solve for the predicted velocity ṽ.
Step 4 Use ṽ as input for Eqn. (11.5) → Solve for the new density ρ n+1 .
Step 5 Use ṽ and ρ n+1 as inputs for Eqn. (11.2) → Solve for the new temperature
T n+1 .
Step 6 Use ρ n+1 and T n+1 as inputs for Eqn. (11.3) → Solve for the new velocity
v n+1 .
This procedure probably seems a bit nebulous at this point, so let’s go through
it in more detail. First we’ll derive the Crank-Nicolson algorithms for our three
equations, then we’ll show how to use these algorithms to solve the system using
the predictor-corrector method.
Continuity Equation
Conservation of mass is governed by the continuity equation
∂ρ ∂ ¡ ¢
+ ρv = 0 , (11.5)
∂t ∂x
We can’t solve this equation directly because it has two unknowns (ρ and v).
But if we assume that v is known as we did in the last lab, then it is possible to
solve the equation using Crank-Nicolson, as we did in the last lab. As usual for
Crank-Nicolson, we forward difference in time and center difference in space to
find
ρ n+1
j
− ρ nj v nj+1 ρ nj+1 − v nj−1 ρ nj−1
=− (11.6)
τ 2h
Then we use time averaging to put the right side of the equation at the same time
level as the left (i.e. at the n + 1/2 time level):
³ ´ ³ ´
ρ n+1
j − ρ n
j = C 1 ρ n
j +1 + ρ n+1
j +1 −C 2 ρ n
j −1 + ρ n+1
j −1 (11.7)
Lab 11 One Dimensional Gas Dynamics 71
where
τ ³ n ´
C1 = − v j +1 + v n+1
j +1 (11.8)
8h ³
τ ´
C2 = − v nj−1 + v n+1
j −1 (11.9)
8h
Then we put the ρ n+1 terms on the left and the ρ n terms on the right:
C 2 ρ n+1 n+1
j −1 + ρ j −C 1 ρ n+1 n n n
j +1 = −C 2 ρ j −1 + ρ j +C 1 ρ j +1 (11.10)
Then we write these equations along with the boundary conditions in matrix form
Aρ n+1 = Bρ n (11.11)
which we solve using linear algebra techniques. For the algorithm represented by
Eq. (11.11) to calculate ρ n+1 , we need to feed it values for ρ n , v n , and v n+1 . Since
the inputs for these variables will be different in the predictor and the corrector
steps, we need to invent some notation. We’ll refer to this Crank-Nicolson algo-
rithm for stepping forward to find ρ n+1 using the notation S ρ ρ n , v n , v n+1 so the
¡ ¢
Conservation of energy
The temperature of a gas is a macroscopic manifestation of the energy of the
thermal motions of the gas molecules. The equation that enforces conservation
of energy for our system is
∂T ∂T ∂v ∂2 T
+v = −(γ − 1)T + DT (11.12)
∂t ∂x ∂x ∂x 2
where γ is the ratio of specific heats in the gas: γ = C p /C v . This equation says that
as the gas is moved along with the flow and squeezed or stretched, the energy is
convected along with the flow and the pressure goes up and down adiabatically
(that’s why γ is in there). It also says that thermal energy diffuses due to thermal
conduction. Thermal diffusion is governed by the diffusion-like term containing
the thermal diffusion coefficient D T given in a gas by
(γ − 1)M κ
DT = (11.13)
kB ρ
where κ is the thermal conductivity, M is the mass of a molecule of the gas, and
where k B is Boltzmann’s constant.
It is probably easier to conceptualize pressure waves rather than temperature
waves. The ideal gas law gives us a way to calculate the pressure, given a density
ρ and a temperature T , so we’ll use the ideal gas law P = nk B T (where n is the
number of particles per unit volume) to calculate pressure P once the density ρ
and temperature T are known via
kB
P= ρT. (11.14)
M
72 Computational Physics 430
To find a predicted value for T one step in the future, we forward difference
the time derivative and center difference the space derivatives to find
T jn+1 − T jn
= T jn−1 D 1 + T jn D 2 + T jn+1 D 3 (11.17)
τ
where
v nj F
D1 = + (11.18)
2h ρ nj h 2
v nj+1 − v nj−1 2F
D 2 = −(γ − 1) − (11.19)
2h ρ nj h 2
v nj F
D3 = − + (11.20)
2h ρ nj h 2
P11.1 Finish deriving the Crank-Nicolson algorithm for T n+1 by putting the right-
hand side of Eq. (11.17) at the n + 1/2 time level. This means replacing
T n terms with (T n + T n+1 )/2 in Eq. (11.17) and making the replacements
ρ n ⇒ (ρ n + ρ n+1 )/2 and v n ⇒ (v n + v n+1 )/2 in D 1 , D 2 , and D 3 . Then put
your system of equations in the form
AT n+1 = BT n
and write out the coefficients in the A and B matrices so we can code them
later.
When you are finished with Problem 11.1 you will have an algorithm for stepping
T forward in time. We’ll refer to this algorithm as S T T n , v n , v n+1 , ρ n , ρ n+1 , so
¡ ¢
You should recognize the first term d v/d t as acceleration, and we’ll discuss the
origin of the other acceleration term in a minute. The first term on the right is the
pressure force that pushes fluid from high pressure toward low pressure, with the
pressure P given by the ideal gas law in Eq. (11.14). The second term on the right
represents the force of internal friction called viscosity, and the parameter µ is
referred to as the coefficient of viscosity. (Tar has high viscosity, water has medium
viscosity, and air has almost none.) The 1/ρ factor in the force terms represents
the mass m in a = F /m (but of course we are working with mass density ρ here).
You may be unconvinced that the left side of Eq. (11.21) is acceleration. To
become more convinced, let’s think about this situation more carefully. Newton’s
second law does not apply directly to a location in space where there is a moving
fluid. Newton’s second law is for particles that are moving through space, not for a
location in space that is sitting still with fluid moving through it. This distinction
is subtle, but important. Think, for instance, about a steady stream of honey
falling out of a honey bear held over a warm piece of toast. If you followed a
piece of honey along its journey from the spout down to the bread you would
experience acceleration. But if you watched a piece of the stream at a specific
location (say, 10 cm above the bread) you would see that the velocity of this part
of the stream is constant in time: ∂v ∂t = 0. This is a strong hint that there is
±
more to describing the acceleration of fluids through a region of space than just
the local ∂v ∂t for a given location. You also need to compare the local velocity
±
at your chosen point to the velocities at nearby points in space, which is what the
v∂v ∂x term does in the left side of Eq. (11.21).
±
Notice that Eq. (11.21) has a nonlinear term on the left: v(∂v/∂x). There is
no way to directly represent this nonlinear term using a linear matrix form like
Av n+1 = Bv n , so we’ll have to make an approximation. We’ll assume that the
leading v in the nonlinear term is somehow known and designate it as v̄. (We’ll
deal with finding something to use for v̄ later.) With a forward time derivative
and a centered space derivative, we have
Again, we’ll rewrite the equations with named groups of expressions that don’t
depend on v so that our algebra is manageable:
v n+1
j
− v nj
= E 0 + v nj−1 E 1 + v nj E 2 + v nj+1 E 3 (11.23)
τ
74 Computational Physics 430
where
à n n n n !
k B ρ j +1 T j +1 − ρ j −1 T j −1
E0 = − (11.24)
M ρ nj 2h
v̄ nj 4µ
E1 = + (11.25)
2h 3ρ nj h 2
8µ
E2 = − (11.26)
3ρ nj h 2
v̄ nj 4µ
E3 = − + (11.27)
2h 3ρ nj h 2
P11.2 Finish deriving the Crank-Nicolson algorithm for v by making the replace-
ments v n ⇒ (v n+1 + v n )/2 the right-hand side of Eq. (11.23) and ρ n ⇒
(ρ n + ρ̃ n+1 )/2, T n ⇒ (T n + T̃ n+1 )/2, and v̄ n ⇒ (v̄ n + v̄ n+1 )/2 in E 0 , E 1 , E 2 ,
and E 3 . Show that your system of equations needs to be in the form
Av n+1 = Bv n + E 0
where, as usual, we explicitly show the variables that are required as inputs.
ρ̃ n+1 = S ρ ρ n , v n , v n+1 = v n
¡ ¢
Then we predict v n+1 using ρ̃ n+1 and T̃ n+1 , while treating v̄ from the non-
linear term as a constant equal to the current v
Corrector Step: Now that we have predicted values for each variable, we step ρ
forward using
ρ n+1 = S ρ ρ n , v n , v n+1 = ṽ n
¡ ¢
Lab 11 One Dimensional Gas Dynamics 75
Now let’s put this algorithm into a script and use it to model waves in a tube
of length L = 10 m with closed ends through which there is no flow of heat. For
disturbances in air at sea level at 20◦ C we have temperature T = 293 K, mass
density ρ = 1.3 kg/m3 , adiabatic exponent γ = 1.4, coefficient of viscosity µ =
1.82 × 10−5 kg/(m·s), and coefficient of thermal conductivity κ = 0.024 J/(m·s·K).
Boltzmann’s constant is k B = 1.38 × 10−23 J/K and the mass of the molecules of
the gas is M = 29 × 1.67 × 10−27 kg for air.
P11.3 (a) As you might guess, debugging the algorithm that we just developed
takes a while to debug because there are so many steps and so many
terms to get typed accurately. (When we wrote the solution code, it
took over an hour to track down two minus sign errors and a factor
of two error.) We’d rather have you use the algorithm than beat your
head on the wall debugging it, so below is the code that implements
the algorithm. Go to the class web site now and download these files.
Study them and make sure you understand how they work.
(b) The one thing we haven’t included in the code is the boundary condi-
tions. The ends are insulating, so we have
∂T /∂x = 0 (11.28)
at both ends. Because the wall ends are fixed and the gas can’t pass
through these walls, the boundary conditions on the velocity are
∂ρ ∂v
+ρ = 0 at x = 0 and x = L (11.30)
∂t ∂x
This condition simply says that the density at the ends goes up and
down in obedience to the compression or rarefaction produced by the
divergence of the velocity.
Write down the finite difference form for all three of these boundary
conditions. Make sure they are properly centered in time and space
for a cell-center grid with ghost points. Then code these boundary
conditions in the proper spots in the code.
76 Computational Physics 430
P11.4 (a) Test the script byqmaking sure that small disturbances travel at the
γk T
M . To do this set T and ρ to their atmospheric
B
sound speed c =
values and set the velocity to
2
v(x, 0) = v 0 e −200(x/L−1/2) (11.31)
import Lab11Funcs as S
import matplotlib.pyplot as plt
import numpy as np
# System Parameters
L = 10.0 # Length of tube
T0 = 293. # Ambient temperature
rho0 = 1.3 # static density (sea level)
# speed of sound
c = np.sqrt(S.gamma * S.kB * T0 / S.M)
# initial distributions
Lab 11 One Dimensional Gas Dynamics 77
tau = 1e-4
tfinal = 0.1
t = np.arange(0,tfinal,tau)
for n in range(len(t)):
Tp = S.ST(T,v,vp,rho,rhop,tau,h)
import numpy as np
import scipy.linalg as la
# Physical Constants
gamma = 1.4 # Adiabatic Exponent
kappa = 0.024 # Thermal conductivity
kB = 1.38e-23 # Boltzman Constant
M = 29*1.67e-27 # Mass of air molecule (Average)
mu = 1.82e-5 # Coefficient of viscosity
F = (gamma-1)*M*kappa/kB # a useful constant
def Srho(rho,v,vp,tau,h):
# Step rho forward in time by using Crank-Nicolson
# on the continuity equation
N = len(rho)
A = np.zeros((N,N))
B = np.zeros_like(A)
return la.solve(A,r)
def ST(T,v,vp,rho,rhop,tau,h):
N = len(T)
A = np.zeros((N,N))
B = np.zeros_like(A)
A[j,j-1] = -0.5*D1
A[j,j] = 1/tau - 0.5*D2
A[j,j+1] = -0.5*D3
B[j,j-1] = 0.5*D1
B[j,j] = 1/tau + 0.5*D2
B[j,j+1] = 0.5*D3
def Sv(v,vbar,vbarp,rho,rhop,T,Tp,tau,h):
N = len(rho)
A = np.zeros((N,N))
80 Computational Physics 430
B = np.zeros_like(A)
E0 = np.zeros_like(v)
A[j,j-1] = -0.5*E1
A[j,j] = 1/tau - 0.5*E2
A[j,j+1] = -0.5*E3
B[j,j-1] = 0.5*E1
B[j,j] = 1/tau + 0.5*E2
B[j,j+1] = 0.5*E3
At the Lagoon amusement park in Utah, there is a water ride called the Log
Flume. It is a standard, old-fashioned water ride where people sit in a 6-seater
boat shaped like a log which slowly travels along a fiberglass trough through some
scenery, then is pulled up a ramp to an upper level. The slow ascent is followed
by a rapid slide down into the trough below, which splashes the passengers a bit,
after which the log slowly makes its way back to the loading area. But you can see
something remarkable happen as you wait your turn to ride if you watch what
happens to the water in the trough when the log splashes down. A large water
wave is pushed ahead of the log, as you might expect. But instead of gradually
dying away, as you might think a single pulse should in a dispersive system like
surface waves on water, the pulse lives on and on. It rounds the corner ahead
of the log that created it, enters the area where logs are waiting to be loaded,
pushes each log up and down in turn, then heads out into the scenery beyond,
still maintaining its shape.
This odd wave is called a “soliton”, or “solitary wave”, and it is an interesting
feature of non-linear dynamics that has been widely studied in the last 30 years, or
so. The simplest mathematical equation which produces a soliton is the Korteweg-
deVries equation
∂y ∂y ∂3 y
+y + α 3 = 0, (12.1)
∂t ∂x ∂x
which describes surface waves in shallow water. In the first two terms of this
equation you can see the convective behavior we studied in Lab 10, but the last
term, with its rather odd third derivative, is something new. We will be studying
this equation in this laboratory.
Numerical solution for the Korteweg-deVries equation
We will begin our study of the Korteweg-deVries equation by using Crank-Nicolson
to finite difference it on a grid so that we can explore its behavior by numerical
experimentation. The first step is to define a grid, and since we want to be able to
see the waves travel for a long time we will copy the trough at Lagoon and make
our computing region be a closed loop. We can do this by choosing an interval
from x = 0 to x = L, as usual, but then we will make the system be periodic by
declaring that x = 0 and x = L are actually the same point, as would be the case
in a circular trough. We will subdivide this region into N subintervals and let Figure 12.1 The log flume ride at
h = L/N and x j = ( j − 1)h, so that the grid is cell-edge. Normally such a cell-edge Lagoon produces a solitary wave
grid would have N + 1 points, but ours doesn’t because the last point ( j = N ) is (marked by arrows in the frames
just a repeat of the first point: x N = x 0 , because our system is periodic. above). The leading edge of the
soliton is where the water begins
to spill over the side of the trough.
81
82 Computational Physics 430
along with an error term on the order of h 2 . When we time average Eq. (12.2), we
find
Look closely at Eq. (12.3) and also at Eq. (12.2) to convince yourself that they are
not centered on a grid point, but at spatial location j + 1/2. The use of this third
derivative formula thus adds a new twist to the usual Crank-Nicolson differencing:
we will evaluate each term in the equation not only at time level n + 1/2, but also
at spatial location j + 1/2 (at the center of each subinterval) so that the first and
third derivative terms are both properly centered. This means that we will be
using a cell-edge grid, but that the spatial finite differences will be cell centered.
With this wrinkle in mind, we can write the first term in Eq. (12.1) at time level
n + 1/2 and space location j + 1/2 as
Diederik Korteweg (1848–1941, Dutch)
∂y 1 ³ n+1 ´
= y j + y n+1
j +1 − y n
j − y n
j +1 (12.4)
∂t 2τ
Now we have have to decide what to do about the nonlinear convection term
y∂y/∂x. We will assume that the leading y is known somehow by designating it
as ȳ and decide later how to properly estimate its value. Recalling again that we
need to evaluate at time level n + 1/2 and space location j + 1/2, the non-linear
term becomes
∂y ȳ j +1 + ȳ j ³ n+1 ´
y = y j +1 − y n+1
j + y nj+1 − y nj (12.5)
∂x 4h
For now, we’ve ignored the problem that the derivative in Eq. (12.5) is centered in
time at n + 1/2 while the ȳ term isn’t. We’ll have to deal with this issue later.
Each of these approximations in Eqs. (12.3)–(12.5) is now substituted into
Gustav de Vries (1866–1934, Dutch) Eq. (12.1), the y n+1 terms are gathered on the left side of the equation and the y n
Diederik Korteweg was Gustav’s disser- terms are gathered on the right, and then the coefficients of the matrices A and B
tation advisor.
are read off to put the equation in the form
Ay n+1 = By n (12.6)
Lab 12 Solitons: Korteweg-deVries Equation 83
P12.1 Derive the formulas in Eq. (12.8) for the a and b coefficients using the
finite-difference approximations to the three terms in the Korteweg-deVries
equation given in Eqs. (12.3)-(12.5).
Now that the coefficients of A and B are determined we need to worry about
how to load them so that the system will be periodic. For instance, in the first row
of A the entry A 1,1 is a − , but a −− should be loaded to the left of this entry, which
might seem to be outside of the matrix. But it really isn’t, because the system is
periodic, so the point to the left of j = 1 (which is also the point j = (N + 1)) is the
point j − 1 = N . The same thing happens in the last two rows of the matrices as
well, where the subscripts + and ++ try to reach outside the matrix on the right.
So correcting for these periodic effects makes the matrices A and B look like this:
a− a + a ++ 0 0 ... 0 a −−
a −− a − a + a ++ 0 ... 0 0
0
a −− a − a + a ++ ... 0 0
A= . . . . ... . . .
0
... 0 0 a −− a − a + a ++
a ++ 0 ... 0 0 a −− a − a+
a + a ++ 0 0 ... 0 a −− a −
(12.9)
b− b + b ++ 0 0 ... 0 b −−
b −− b − b + b ++ 0 ... 0 0
0
b −− b − b + b ++ ... 0 0
B= . . . . ... . . .
0
... 0 0 b −− b − b + b ++
b ++ 0 ... 0 0 b −− b − b+
b + b ++ 0 0 ... 0 b −− b −
84 Computational Physics 430
P12.2 Discuss these matrices with a TA and convince the TA that this structure cor-
rectly models a periodic system (it may help to think about the computing
grid as a circle with x 0 = x N .)
ṽ = v n (12.10)
Solitons
P12.3 (a) Run kdv.py with α = 0.1, y max = 2, τ = 0.5, t final = 100, and iskip=1.
2
After a while you should see garbage on the screen. This is to convince
you that you shouldn’t choose the time step to be too large.
1
v
(b) Now run (a) again, but with τ = 0.1, then yet again with τ = 0.02. Use
0 t final = 10 for both runs and skip big enough that you can see the
pulse moving on the screen. You should see the initial pulse taking off
−1
0 2 4 6 8 10 to the right, but leaving some bumpy stuff behind it as it goes. The
x
trailing bumps don’t move as fast as the big main pulse, so it laps them
Figure 12.2 A Gaussian pulse after and runs over them as it comes in again from the left side, but it still
1 second of propagation by the mostly maintains its shape. This pulse is a soliton. You should find
Korteweg-deVries equation (Prob- that there is no point in choosing a very small time step; τ = 0.1 does
lem 12.3) pretty well.
Lab 12 Solitons: Korteweg-deVries Equation 85
The standard “lore” in the field of solitons is that the moving bump you saw in
problem 12.3 is produced by a competition between the wave spreading caused
by the third derivative in the Korteweg-deVries equation and the wave steepening
caused by the y∂y/∂x term. Let’s run kdv.py in such a way that we can see the
effect of each of these terms separately.
only the third derivative term matters. You should see the pulse fall
apart into random pulses. This spreading is similar to what you saw 5
when you solved Schrödinger’s equation. Different wavelengths have
v
different phase velocities, so the different parts of the spatial Fourier
0
spectrum of the initial pulse get out of phase with each other as time
progresses.
(b) Non-linear wave-steepening dominates: Run kdv.py with α = 0.01, −5
0 2 4 6 8 10
x
y max = 2, τ = 0.01, and t final = 10. (If your solution develops short
wavelength wiggles this is an invitation to use a smaller time step. The Figure 12.3 Dispersion dominates
problem is that the predictor-correction algorithm we used on the (Problem 12.4(a).): after 3 seconds
nonlinear term is not stable enough, so we have a Courant condition of time.
in this problem.)
Now it is the dispersion term that is small and we can see the effect
of the non-linear convection term. Where y is large the convection is 2
rapid, but out in front where y is small the convection is slower. This
1
allows the fast peak to catch up with the slow front end, causing wave
v
steepening. (An effect just like this causes ocean waves to steepen and
0
then break at the beach.)
−1
The large pulse that is born out of our initial Gaussian makes it seem like 0 2 4
x
6 8 10
there ought to be a single pulse that the system wants to find. This is, in fact the
case. It was discovered that the following pulse shape is an exact solution of the Figure 12.4 Steepening dominates
Korteweg-deVries equation: (Problem 12.4(b).): after 0.5 sec-
onds of time.
12k 2 α
y(x, t ) = (12.14)
cosh2 (k(x − x 0 − 4αk 2 t ))
where x 0 is the center of the pulse at time t = 0.
P12.5 (a) Use Mathematica to show that this expression does indeed satisfy the
Korteweg-deVries equation.
(b) Now replace the Gaussian initial condition in kdv.py with this pulse
shape, using k = 1.1, x 0 = L/2, and adjusting α so that the height of
the initial pulse is exactly equal to 2, so that it matches the Gaussian
pulse you ran in 12.3. You should find that this time the pulse does
not leave trailing pulses behind, but that it moves without changing
shape. It is a perfect soliton.
86 Computational Physics 430
(c) The formula at the beginning of this problem predicts that the speed
of the pulse should be
c soliton = 4αk 2 (12.15)
Verify by numerical experimentation that your soliton moves at this
speed. To do this, you can just track the position of the maximum
value (the NumPy function argmax can help you do this).
P12.6 One of the most interesting things about solitons is how two of them interact
with each other. When we did the wave equation earlier you saw that left
and right moving pulses passed right through each other. This happens
because the wave equation is linear, so that the sum of two solutions is
also a solution. The Korteweg-deVries equation is nonlinear, so simple
superposition can’t happen. Nevertheless, two soliton pulses do interact
with each other in a surprisingly simple way.
To see what happens keep α = 0.1, but modify your code from 12.5 so that
you have a soliton pulse with k = 1.5 centered at x = 3L/4 and another
soliton pulse with k = 2 centered at x = L/4. Run for about 20 seconds and
watch how they interact when the fast large amplitude pulse in the back
catches up with the slower small amplitude pulse in the front. Is it correct
to say that they pass through each other? If not, can you think of another
qualitative way to describe their interaction?
# Physical constants
alpha = 0.1
# Time range
tau = 0.1
tfinal = 100
t = np.arange(0,tfinal,tau)
Lab 12 Solitons: Korteweg-deVries Equation 87
# load the matrices with the terms that don't depend on ybar
h3 = h**3
for j in range(N):
At[j,jwrap(j-1)] =-0.5*alpha/h3
At[j,j] = 0.5/tau + 1.5*alpha/h3
At[j,jwrap(j+1)] = 0.5/tau - 1.5*alpha/h3
At[j,jwrap(j+2)] = 0.5*alpha/h3
Bt[j,jwrap(j-1)] = 0.5*alpha/h3
Bt[j,j] = 0.5/tau - 1.5*alpha/h3
Bt[j,jwrap(j+1)] = 0.5/tau + 1.5*alpha/h3
Bt[j,jwrap(j+2)] =-0.5*alpha/h3
plt.figure(1)
skip = 10
for n in range(len(t)):
# Predictor step
A = np.copy(At)
B = np.copy(Bt)
# corrector step
A = np.copy(At)
B = np.copy(Bt)
if (n % skip == 0):
plt.clf()
plt.plot(x,y)
plt.xlabel('x')
plt.ylabel('y')
plt.title('time={:1.3f}'.format(t[n]))
plt.ylim(0,3)
plt.pause(.1)
Lab 13
Machine Learning with Neural Networks
Sometimes, you don’t have a measurable quantity you want to predict from
your data set, but rather want to explore the structure of the data itself. For
89
90 Computational Physics 430
example, say Facebook wants to divide their users into groups of people with
similar demographics so they can target advertising to these groups. They want
to be able to say something like “this ad is likely to be interesting to demographic
group α but uninteresting to group β.” But they don’t know the best way to define
the groups α and β. This is a job for a clustering algorithm, which is a type of
unsupervised learning. Facebook could feed a bunch of information about a set
of people into a clustering algorithm, and the algorithm would return suggestions
for how to assign those people to groups “similar” demographics (i.e. tell you how
to define groups α and β above). Once they’ve classified the data into groups,
they could then use a supervised machine learning algorithm to predict which
groups might be likely to click on which advertisements.
There are many, many more ideas in machine learning. You can take many full
courses on the subject. But just to get the flavor of how this works, let’s tackle
some specific problems using a common machine learning technique referred to
as a neural network.
Linear Regression
A computational neural network can be thought of as an extension of linear
regression. Linear regression finds a linear function which maps an input, x, onto
an output, y. Circles are often used to represent the nodes (values) of a neural
network. Using that notation, a linear regression mapping would be represented
this way:
Study Time (h) Score (%)
1.4 20
1.8 30
2.1 37
2.2 45
Questions that linear regression can answer are along the lines of this: given a set
2.3 26 of known inputs with corresponding known outputs, can one predict what the
2.9 86 output will be for a new input that wasn’t part of the original set? As an example,
3.5 67 perhaps you are trying to correlate exam scores with the amount of time students
3.6 100
studied for the exam as shown in Table 13.1.
4.2 72
4.6 82 You could make a plot of the data and fit a line to it as shown in Fig. 13.1.
4.7 99 Then you could use your fit to make a prediction for a student not in the given
data set, such as “Given a time x = 2.7 hours, the expected exam score would
Table 13.1 Table of exam scores vs. be y = 53 percent.” The linear fit is typically done by choosing the slope and
study time. intercept parameters to minimize some cost or loss function, for example the
squared error.
Lab 13 Machine Learning with Neural Networks 91
Simple linear prediction is often insufficient. For example, this linear fit
will predict a negative score for particularly low values of study time, which is
nonsensical. So it would make sense to apply a nonlinear function after the linear
fit, something like y = max(0, z) where we are now using the symbol z to represent
the value that is given from the linear fit, z = w x + b, so we can continue to use
the symbol y for the final output. (We are using the letter w to represent the
slope of the line for reasons made clear below.) Applying that particular nonlinear
function is equivalent to saying, “Set y to 0 if z is negative, otherwise use y = z”.
In a neural network, a function applied after the linear fit is called an ac-
tivation function, and that particular activation function is probably the most
commonly used one. It is called the relu function, which stands for rectified linear
unit. Other activation functions are also frequently used. For example, another Figure 13.1 Simple linear fit of the
common situation is when the output value must be between 0 and 1; in that case test data shown in Table 13.1.
a nonlinear function called a sigmoid function, is used to map all the z values
(potentially ranging from −∞ to +∞) to real values of y between 0 and 1.
Neural networks
A neural network is this same idea as multiple regression, just expanded to multi-
ple layers. The layers between input and output are called hidden layers. Each
hidden layer could have its own activation function. Post-activation values of
the nodes of the hidden layers are called the activations of the nodes and are
92 Computational Physics 430
}
}
}
Input Layer 3,
Layer 1 Layer 2
Layer Output Layer
Gradient descent
In the specific neural net shown in Fig. 13.2, layer 1 contained 15 fitting parame-
ters. Similarly, layer 2 contains 12 parameters and layer 3 has 4 parameters. (Take
Lab 13 Machine Learning with Neural Networks 93
a moment to look at the figure and make sure these numbers make sense to you.)
Therefore 31 total parameters need to be optimized using the input data. For
more complicated neural nets, it is not uncommon for there to be hundreds of
thousands of fitting parameters! How are these to be optimized? The process is
typically done using the gradient descent method (or a close relative of it).
Gradient descent is an iterative process whereby the location of the minimum
of a function (e.g. the cost function) is found by taking steps along the “downhill”
direction, i.e. the direction of the negative gradient. This is most easily visualized
with a function of two parameters, say w 1 and w 2 , as illustrated in Fig 13.3. cost
At each step in the iteration, the cost function is computed based on the
random starting point
current values of w 1 and w 2 , the gradients of the cost function with respect to w 1
and w 2 are computed at the present location of w 1 and w 2 , and then the values
of w 1 and w 2 are updated according to:
d (cost)
w 1,next = w 1 − α
d w1
Figure 13.3 Taking steps by ad-
d (cost) justing the w 1 and w 2 parameters
w 2,next = w 2 − α
d w2 along the negative gradient direc-
tion to find the minimum of the
For known activation functions and a known cost function, the gradients can
cost function.
be automatically computed by the machine learning software in an interesting
backpropagation process that minimizes calculation time, but is beyond the
scope of this lab.
for additional testing after the model is complete so we will avoid that usage.
The training set is used to optimize the network parameters to minimize the
cost function, i.e., to “train the model”, and then the trained neural network is
checked against the validation set to make sure the network can be relied on to
give predictions which make sense.
If keras still does not work after those commands, you may have a version conflict.
In that case, see Appendix A for some basics about environments in Anaconda
and download an environment file from our website, which should help. Install
and activate it, and then the specified libraries should work together.
import numpy as np
from keras.models import Sequential
from keras.layers import Dense
Lab 13 Machine Learning with Neural Networks 95
[X_train,Y_train] = data.transpose()
model = Sequential()
model.add(Dense(1, activation='linear', input_dim=1))
sgd = optimizers.sgd(lr=0.03) #lr is the learning rate
model.compile(loss='mean_squared_error',optimizer=sgd)
#displays some useful info, note how there are only 2 fitting parameters
model.summary()
P13.2 Execute the code above, and examine the output. Explain to the TA what
the code does.
more sophisticated version of gradient descent and is a popular choice for many
applications.
import numpy as np
from keras.models import Sequential
from keras.layers import Dense
from keras import optimizers
from keras.datasets import boston_housing
print(X_train.shape)
print(Y_train.shape)
print(X_validate.shape)
print(Y_validate.shape)
model = Sequential()
model.add(Dense(30, activation='relu',input_dim=13))
model.add(Dense(15, activation='relu'))
model.add(Dense(8, activation='relu'))
model.add(Dense(5, activation='relu'))
model.add(Dense(1, activation='linear'))
optimizer_choice = optimizers.adam(lr=0.05)
model.compile(loss='mean_squared_logarithmic_error',
optimizer=optimizer_choice, metrics=['mse'])
model.fit(X_train, Y_train, batch_size=32, epochs=50, verbose=1)
model.summary()
score = model.evaluate(X_validate, Y_validate, verbose=0)
print('The loss value and accuracy of the test set is: ' + str(score))
P13.3 Execute the code above, and examine the output. Explain to the TA what
the code does.
Lab 13 Machine Learning with Neural Networks 97
import numpy as np
from matplotlib import pyplot as plt
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras.utils import np_utils
from keras.datasets import mnist
own layers, but we prefer drawing the boundaries between layers at the convolution and dense
stages only.
98 Computational Physics 430
plt.figure(1)
plt.imshow(X_train[10])
plt.figure(2)
plt.imshow(X_train[11])
model = Sequential()
#layer 1:
model.add(Conv2D(32, (3, 3), activation='relu', input_shape=(28,28,1)))
#layer 2:
model.add(Conv2D(32, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Dropout(0.25))
#layer 3:
model.add(Flatten())
model.add(Dense(128, activation='relu'))
Lab 13 Machine Learning with Neural Networks 99
model.add(Dropout(0.5))
#layer 4 (output):
model.add(Dense(10, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam',
metrics=['accuracy'])
# And now we train the model. This takes a long time, perhaps
# 10 mins depending on your computer, since there are over
# 600,000 adjustable parameters in the 4-layer model we have
# just defined! The bulk of them come from the 4608 outputs
# from layer 2 connecting densely to 128 nodes in layer 3.
# The "batch_size" setting in the command indicates how many
# training inputs it should consider at once as it is adjusting
# the parameters, and the "epochs" number indicates how many
# complete iterations through the entire training set it should do.
# And now it's done! We can make predictions. To get a general sense as to
# the quality of the NN predictions, we can use the validation set... but we
#must first address the same three issues we did with the training set.
m_test = X_validate.shape[0]
#the output of this next command will tell you how good the NN did on the test set
score = model.evaluate(X_validate, Y_validate, verbose=0)
print('The loss value and accuracy of the test set is: ' + str(score))
#It's also fun to look at predictions for single examples. "140" here was just
#a random selection. You can copy and paste these next few lines into the
#console with different test numbers to see an image along with its predicted
#output value.
testimagenumber = 140
singletest=X_validate[testimagenumber]
plt.figure(3)
singleprediction = model.predict(np.array([singletest]))[0]
#argmax function converts from the length 10 output array back to a single digit
singleprediction = np.argmax(singleprediction)
P13.4 Execute the code above, and examine the output. Explain to the TA what
the code does.
Other resources
We have just scratched the surface of machine learning and neural networks. Here
are a few more resources which you may find helpful if you want to do additional
exploring.
iris = load_iris()
X = iris['data'] #150 entries, 4 parameters each
Y = iris['target'] #150 entries, values of 0, 1, or 2
Sepal
names = iris['target_names']
feature_names = iris['feature_names']
You may want to print out any or all of the defined variables to see what you are
working with.
P13.5 Develop a neural net which will learn to separate the irises based on the
four characteristics, with training and validation accuracies both over 95%.
Hint: This problem is much like Example 3 in that it’s a classification problem.
Here are some specific things you should do which are quite similar to that
example:
• Use a softmax activation function for the final layer, but with 3 nodes as
opposed to 10 nodes
Unlike Example 3, you should not use any convolution layers; instead, do
the problem with a fully connected network like Example 2. You can choose the
number of layers (at least 2, though) and the number of nodes in each layer. Also
like Example 2, be sure to specify the size of the input layer in your first model.add
command. If your network seems to be overfitting the data, as seen by a very high
accuracy with the training data but a low accuracy with the validation data, try
simplifying your network.
A note on the model.compile command: You may have noticed that the
model.compile command was used in two slightly different ways for Examples 2
and 3. In Example 2 it was done like this:
optimizer_choice = optimizers.adam(lr=0.05)
model.compile(loss='mean_squared_logarithmic_error',
optimizer=optimizer_choice, metrics=['mse'])
model.compile(loss='categorical_crossentropy',
optimizer='adam',metrics=['accuracy'])
The difference between the two is that if you want to specify any hyperparameters
for the optimizer (e.g. adam), such as the learning rate, then you must precede
the compile command with an optimizers command as in Example 2. And then
inside the model.compile command you set the optimizer equal to the name
of the optimizer variable you have just created, without quotation marks. If,
however, you are content to use the default hyperparameters for the optimizer,
then inside the model.compile command, you can set the optimizer equal to a
string indicating your choice, in quotes.
Appendix A
Anaconda Environments
With the large number of packages and libraries available for use with Python,
it is not uncommon for them to conflict with each other. (Libraries are collections
of packages.) Additionally, sometimes a package may work with one version of
Python but not with another. To address these types of issues, Anaconda has
implemented the ability to create and load “environments”, and in fact that is
one of the compelling reasons to use the Anaconda version of Python in research
settings where multiple people may be sharing code with each other.
An environment file is basically a list of all of the package versions that are
desired for a given configuration, including the version of Python itself. By loading
an environment file in Anaconda prior to running Python, the user tells Anaconda
to force Python to use those specified versions. That way, for example, someone
working on code can save his or her environment, send the environment file to a
collaborator along with the code, and be sure that the collaborator will be able
to run the code in the exact same way, with no version conflict errors or other
unexpected behavior.
Environments are created and loaded from the Anaconda Prompt, an app
which was installed when you installed Anaconda. The Anaconda Prompt is a
command line interface which lets you run commands related to Anaconda itself
(as opposed to Python commands).
Some of the details below may differ slightly based on your operating system.
For simplicity we will assume a Windows machine. If you open the Anaconda
Prompt from the Windows search menu, you should see the following text:
(base) C:\Users\yourname>
where yourname is your Windows username. The (base) prompt means that it
is using the default environment as opposed to a specially loaded one, and the
given directory (i.e. C:\Users\yourname>) is the default working directory from
which Anaconda will save and load files. After you load an environment, (base)
will be replaced by the name of the environment.
Environment files are often titled “environment.yml”. To load an environment,
first copy the environment.yml file into the working directory and type this into
the command prompt:
The -f option means the next word will be the filename. This command will
cause Anaconda to download all of the packages which are specified inside the
environment.yml file, with specific versions. It may take a while but is a one-time
thing.
103
104 Computational Physics 430
After Anaconda finishes with that command you have an environment you
can use! The name of the environment is specified inside the environment.yml,
and Anaconda will let you know its name when it is finished, by generating a
response like this [where “yourenvironment” is whatever name was specified
inside the file]:
At this point you will be able to see that a new folder with the yourenvironment
name has been created in your C:\Users\yourname\Anaconda3\envs direc-
tory, filled with lots of stuff you don’t need to worry about. If you have multiple
environments installed, you can check that directory to see them all.
As the response indicates, to use the environment you should type this into
the command prompt:
That tells Anaconda to start using the specified environment and the command
prompt will now look like this:
(yourenvironment) C:\Users\yourname>
For future usages, you can skip the conda env create command and just use the
conda activate command. As also indicated by the response, to go back to the
default environment without exiting and restarting Anaconda, you would type in:
conda deactivate
After activating an environment, type spyder into the Anaconda Prompt com-
mand line to start up Spyder using that environment. Note that starting Spyder
via the Windows search menu at this point will still run Spyder with the default
packages and default Python version, not with the new environment, so you
do need to start Spyder via the Anaconda Prompt. Also, even though you may
activate an environment which includes various packages, when running Python
code you still need to use import packagename-type commands in your Python
file. The difference is that when an environment is active, Python will import the
specified version of the package as opposed to just the default version.
If you would like to create your own environment, you can do so via the conda
create command. As an example, to create an environment named “newenviron-
ment” to run Python version 3.7.6 with the numpy, scipy, keras, tensorflow, and
matplotlib packages, you would type the following into the command prompt:
Lab A Anaconda Environments 105
The -n option indicates that the next word will be the name of the environment.
The above command will create a new folder in the
C:\Users\yourname\Anaconda3\envs
Algorithm a set of steps used to solve a problem; frequently these are expressed
in terms of a programming language.
Cell-center grid a grid with a point in the center of each cell. For instance, a
cell-center grid ranging from 0 to 10 by steps of 1 would have points at 0.5,
1.5, . . . , 8.5, 9.5. Note that in this case, there are exactly as many points as
cells (10 in this case).
Cell-center grid with ghost points the same as a regular cell-center grid, but
with one additional point at each end of the grid (with the same spacing as
the rest). A cell-center grid with ghost points ranging from 0 to 10 by steps
of 1 would have points at -0.5, 0.5, . . . , 9.5, 10.5. Note that in this case, there
are two more points than cells. This arrangement is useful when setting
conditions on the derivative at a boundary.
Cell-edge grid a grid with a point at the edge of each cell. For instance, a cell-
edge grid ranging from 0 to 10 by steps of 1 would have points at 0, 1, . . . ,
9, 10. Note that in this case, there are actually N − 1 cells (where N is the
number of points: 11 in this case). The discrepancy between the number
of cell and number of points is commonly called the “fence post problem”
because in a straight-line fence there is one more post than spaces between
posts.
f (x + h) − f (x − h)
f 0 (x) ≈
2h
107
108 Computational Physics 430
Eigenvalue problem the linear algebra problem of finding a vector g that obeys
Ag = λg.
Explicit algorithm an algorithm that explicitly use past and present values to
calculate future ones (often in an iterative process, where the future values
become present ones and the process is repeated). Explicit algorithms are
typically easier to implement, but more unstable than implicit algorithms.
f (x + h) − f (x)
f 0 (x) ≈
h
Grid A division of either a spatial or temporal range (or both), used to numerically
solve differential equations.
Implicit algorithm an algorithm that use present and future values to calculate
future ones (often in a matrix equation, whereby all function points at a
given time level are calculated at once). Implicit algorithms are typically
more difficult to implement, but more stable than explicit algorithms.
Resonance the point at which a system has a large steady-state amplitude with
very little driving force.
Roundoff an error in accuracy that occurs when dealing with fixed-point arith-
metic on computers. This can introduce very large errors when subtracting
two numbers that are nearly equal.
Second derivative formula this is a centered formula for finding the second
derivative on a grid:
f (x + h) − 2 f (x) + f (x − h)
f 00 (x) ≈
h2
Transients solutions to differential equations that are initially present but which
quickly die out (due to damping), leaving only the steady-state behavior.
Index
111
112 INDEX
Parabolic equations, 37
Partial differential equations, types,
37
Particle in a box, 50
Poisson’s equation, 53
Potential barrier
Schrödinger equation, 52
Resonance, 15
Roundoff, 6
Schrödinger equation, 38
bound states, 21
potential barrier, 52
time-dependent, 49
Second derivative, 4
Shock wave, 66
Solitons, 81
SOR, 58
Spatial grids, 1
Staggered leapfrog
wave equation, 25
Steady state, 14
Successive over-relaxation (SOR), 53,
58
Taylor expansion, 5
Thermal diffusion, 71
Two-dimensional grids, 33
Two-dimensional wave equation, 33
Wave equation, 13
boundary conditions, 25
initial conditions, 25
two dimensions, 33
via staggered leapfrog, 25