Brief Notes On Solving PDE's and Integral Equations
Brief Notes On Solving PDE's and Integral Equations
Brief Notes On Solving PDE's and Integral Equations
In these notes adapted from the Physics 780 Computational Physics course, well consider
three simple programs to calculate three linear partial dierential equations (PDEs) with two
independent variables using nite dierence approximations. [Note: nite element methods can be
more powerful.] Code listings are included in these notes. All are generalizable to more variables
and more complicated boundary conditions. We give some brief background here while there is
more detail in Refs. [1], [2] and [3]. We also provide notes on the Schrodinger equation in momentum
space as an example of solving integral equations with gaussian quadrature and linear algebra.
a. Laplaces Equation in Two Dimensions
The code laplace.cpp solves for the electric potential U(x) in a two-dimensional region with
boundaries at xed potentials (voltages). For a static potential in a region where the charge
density
c
(x) is identically zero, U(x) satises Laplaces equation,
2
U(x) = 0. In the xy plane
(i.e., assuming it is constant in the z direction), the equation reduces to
2
U(x, y)
x
2
+
2
U(x, y)
y
2
= 0 , (1)
with boundary values enforced at the edges of the region. Well solve this problem with a relaxation
method. A PDE tells us locally how the value of the function is related to nearby values. Using
nite dierence approximations for the second derivatives we can derive the equation we need.
How do we derive a nite dierence form for a second derivative? From a Taylor expansion, of
course. Consider
U(x + x, y) = U(x, y) +
U
x
x +
1
2
2
U
x
2
(x)
2
+O(x)
3
(2)
U(x x, y) = U(x, y)
U
x
x +
1
2
2
U
x
2
(x)
2
O(x)
3
, (3)
which naturally sum to
U(x + x, y) +U(x x, y) = 2U(x, y) +
2
U
x
2
(x)
2
+O(x)
4
. (4)
Doing the same thing in the y variable yields expressions for the derivatives in Eq. (1):
2
U
x
2
U(x + x, y) +U(x x, y) 2U(x, y)
(x)
2
(5)
2
U
y
2
U(x, y + y) +U(x, y y) 2U(x, y)
(y)
2
. (6)
Now take x = y = and Eq. (1) gives us a relationship among neighboring points.
For our purposes we single out the point in the middle:
U(x, y) =
1
4
[U(x + x, y) +U(x x, y) +U(x, y + y) +U(x, y y)] +O(
4
) . (7)
1
The relaxation method consists of sweeping repeatedly through each point of the region (now
divided into a grid) in turn, replacing its current value with a new one given by Eq. (7). We start
with the xed boundary values and some guess at the interior values. We keep sweeping until
the values stop changing; at that point Laplaces equation and the boundary conditions must be
satised, so it must be the solution we seek! Instead of simply replacing the old value of U(x, y)
with U
new
(x, y) from Eq. (7), it is usually more eective to introduce a fraction and take
U(x, y) = (1 fraction) U
old
(x, y) + fraction U
new
(x, y) . (8)
Besides the laplace.cpp code, there are several MATLAB versions available (laplace relax.m,
laplace relax pbc.m, and laplace relax random.m) that use dierent boundary conditions and
dierent initial conditions.
b. Temperature Diusion in One Dimension
The code eqheat.cpp simulates the time dependence of the temperature of a metal bar that is
initially heated to 100
C) is the specic
heat, and = 7.8 g/cm
3
is the mass density.
Consider a small piece of metal with constant cross section A and length x. The heat energy
at time t, Q(t), is given by the specic heat times the mass of the piece times the temperature,
or
Q(t) = [c Ax]T(x, t) +O(x)
2
. (9)
(Dropping the (x)
2
contribution will mean that we can evaluate the temperature at x or x +x
or x + x/2 and it doesnt matter.) Now we can write:
Heat ow in at x:
T(x, t)
x
A (10)
Heat ow out at x + x: +
T(x + x, t)
x
A . (11)
The continuity equation equates the net heat ow to the time rate of change of the heat energy:
Q
t
= c Ax
T(x, t)
t
=
_
T(x + x, t)
x
T(x, t)
x
_
A . (12)
Upon dividing by x (and other factors), we recognize the dierence of rst derivatives in x as a
second derivative (up to (x)
2
corrections). Thus, we obtain the diusion equation
T(x, t)
t
=
c
_
T(x+x,t)
x
T(x,t)
x
_
x
=
c
2
T(x, t)
x
2
(13)
2
in the limit that x goes to zero. [Note: if we put an i on the time side, we get the time-dependent
Schrodinger equation.]
The code eqheat.cpp implements this equation by calculating the temperature change from
time t to time t + t at each point x using
T(x, t + t) T(x, t) + t
T(x, t)
t
+O(t)
2
(14)
and using the simplest nite-dierence formulas, as in Eq. (5), to evaluate the second derivative in
Eq. (13). The end result is
T(x, t + t) T(x, t) +
c
t
(x)
2
[T(x + x, t) +T(x x, t) 2T(x, t)] . (15)
To get started, we need to specify the temperature for 0 x L for the initial time t = 0 and also
the boundary conditions at x = 0 and x = L for all times. Then we can step to t = t for all x
using Eq. (15), then t = 2t, and so on.
This method might seem crude but foolproof, yet there is a major pitfall lurking: choosing
values for t and x. Unless
c
t
(x)
2
1
2
, (16)
the numerical solution will not decay exponentially (see Landau and Paez, Chapter 26 for an
explanation [1]). This means that decreasing t helps (up to a point, as usual), but if we decrease
x to increase accuracy, we better decrease t quadratically. In practice, if there are not analytic
solutions for guidance, one needs to try out dierent x and t values until the result is both
stable and physically reasonable.
c. Waves on a String
The code eqstring.cpp simulates the time dependence of a string of length l that is xed at each
end (dened as x = 0 and x = l) and plucked somehow at t = 0. The displacement (x, t) at each
point x as a function of time t is described by a wave equation:
2
(x, t)
x
2
=
1
c
2
2
(x, t)
t
2
, (17)
where c is the wave speed and the spatial boundary conditions are (0, t) = (l, t). For a string of
mass density (mass/length) under tension , the wave speed is c =
_
/.
We proceed with a now familiar pattern: replace the derivatives in Eq. (17) by our favorite
nite dierence formula. We choose to step in time, so we solve for the term with t + t:
(x, t + t) 2(x, t) (x, t t) +
c
2
c
2
[(x + x, t) +(x x, t) 2(x, t)] , (18)
with c
x/t. Thus we can step forward in time for every x once we know the values of at
earlier times. To get started we need to know the initial (x, 0) (which is determined by how the
3
string is plucked) and the initial value of d(x, 0)/dt, which we take equal to 0 (the plucked string
is released from rest). The latter condition is implemented in the code by applying the central
dierence formula for the rst derivative to derive a formula for the rst time step. It is claimed
that this method is stable if
c c
=
x
t
. (19)
For more details on stability conditions, see Ref. [3].
d. PDE C++ Code Listing: laplace.cpp
//*****************************************************************************
// laplace.cpp: Solution of Laplace equation with finite differences
//
// based on: laplace.c in "Projects in Computational Physics" by Landau/Paez,
// ch. 25, code copyrighted by RH Landau
// programmer: Dick Furnstahl furnstahl.1@osu.edu 12/04/02 (C), 01/04/03 (C++)
// 05/15/08 (minor upgrades)
// notes: uses gnuplot 3D grid format. plot with:
// gnuplot> splot "laplace.dat" with lines
//*****************************************************************************
#include <iostream>
#include <fstream>
int
main ()
{
const int size = 40; // grid size (grid points)
const int num_iter = 500; // number of interations
double potl[size][size]; // the electric potential
std::ofstream outfile ("laplace.dat"); // open an output file stream
for (int i = 0; i < size; i++) // set up boundary conditions
{
potl[i][0] = 100.0; // "top" edge is at 100 V
for (int j = 1; j < size; j++)
{
potl[i][j] = 0; // rest of grid is at 0 V
}
}
for (int iter = 0; iter < num_iter; iter++) // iterations
{
for (int i = 1; i < (size - 1); i++) // x-direction
{
for (int j = 1; j < (size - 1); j++) // y-direction
4
{
potl[i][j] = 0.25 * ( potl[i+1][j] + potl[i-1][j]
+ potl[i][j+1] + potl[i][j-1] );
}
}
}
for (int i = 0; i < size; i++) // write data in gnuplot 3D format
{
for (int j = 0; j < size; j++)
{
outfile << potl[i][j] << std::endl; // gnuplot 3D grid format
}
outfile << std::endl; // empty line for gnuplot
}
std::cout << std::endl << "data stored in laplace.dat" << std::endl;
outfile.close ();
return (0);
}
e. PDE C++ Code Listing: eqheat.cpp
//*****************************************************************************
// eqheat.cpp: Solution of heat equation using with finite differences
//
// based on: eqheat.c in "Projects in Computational Physics" by Landau/Paez,
// ch. 26, code copyrighted by RH Landau
// programmer: Dick Furnstahl furnstahl.1@osu.edu 12/04/02 (C), 01/04/03 (C++),
// 03/05/06 (minor upgrades)
// notes: uses gnuplot 3D grid format. plot with (might want "with lines"):
// gnuplot> set view 60,150; set xrange [0:100] reverse; splot "eqheat.dat"
//*****************************************************************************
#include <iostream>
#include <fstream>
int
main ()
{
const double length = 150.; // length in cm
const int size = 151; // grid size
const int num_steps = 30000; // number of timesteps
const double conductivity = 0.12; // units: (cal/s cm degree C)
const double specific_heat = 0.113; // units: (cal/ g degree C)
const double rho = 7.8; // density in (g/cm^3)
double T[size][2]; // temperature at x and two times
std::ofstream outfile ("eqheat.dat"); // open an output file stream
5
double delta_x = length/double(size-1); // mesh spacing in x
for (int i = 0; i < size; i++)
{
T[i][0] = 100.; // at t=0, all points are at 100 C
}
for (int j = 0; j < 2; j++) // except the endpoints, which are 0 C
{
T[0][j] = T[size-1][j] = 0.;
}
double constant = conductivity / (specific_heat*rho) / (delta_x*delta_x);
for (int i = 1; i <= num_steps; i++) // loop over num_steps timesteps
{
for (int j = 1; j < (size - 1); j++) // loop over space
{
T[j][1] = T[j][0]
+ constant * (T[j+1][0] + T[j-1][0] - 2.0 * T[j][0]);
}
if ((i % 1000 == 0) || (i == 1)) // save every 1000 time steps
{
for (int j = 0; j < size; j++)
{
outfile << T[j][1] << std::endl; // gnuplot 3D grid format
}
outfile << std::endl; // empty line for gnuplot
}
for (int j = 0; j < size; j++)
{
T[j][0] = T[j][1]; // shift new values to old
}
}
std::cout << std::endl << "data stored in eqheat.dat" << std::endl;
outfile.close ();
return (0);
}
f. PDE C++ Code Listing: eqstring.cpp
//*****************************************************************************
// eqstring.cpp: Solution of wave equation using time stepping
//
// based on: eqstring.c in "Projects in Computational Physics"
// by Landau/Paez, ch. 26, code copyrighted by RH Landau
//
// programmer: Dick Furnstahl furnstahl.1@osu.edu 12/04/02 (C), 01/04/03 (C++)
// 05/15/08 (major upgrades)
// notes: uses gnuplot 3D grid format. plot with
6
// gnuplot> set view 60,150; set xrange [0:100] reverse
// gnuplot> splot "eqstring.dat" with lines
//*****************************************************************************
#include <iostream>
#include <fstream>
const double length = 1; // length of string in meters
const double rho = 0.01; // density per length in kg/m
const double tension = 40.0; // tension in Newtons
const int num_pts = 101; // # of x points
const int time_steps = 100; // no. of time steps
int
main ()
{
double psi[num_pts][3]; // psi[i][j] is displacement at point i for
// time j=0 (past), time j=1 (present), and
// time j=2 (future)
double c_speed_sq = tension/rho; // speed^2 of the wave ("c")
double c_prime_sq = c_speed_sq; // c = Delta x / Delta t
std::ofstream outfile ("eqstring.dat"); // open an output file stream
// set initial configuration for plucked string by specifying the
// displacement at each x point
int i_pluck = 81;
for (int i = 0; i < i_pluck; i++)
{
// from book: psi(x,t=0) = 1.25 x/length for x < 0.8 length
psi[i][0] = 1.25 * double(i) / double(num_pts-1);
}
for (int i = i_pluck; i < num_pts; i++)
{
// from book: psi(x,t=0) = 5(1-x/l) for x > 0.8 length
psi[i][0] = 5. * (1. - double(i) / double(num_pts-1) );
}
// do the first time step of all x except the ends
for (int i = 1; i < num_pts-1; i++)
{
psi[i][1] = psi[i][0] + (c_speed_sq/c_prime_sq)/2.0
* ( psi[i+1][0] + psi[i-1][0] - 2.0*psi[i][0] );
}
psi[0][1] = psi[100][1] = 0.; // fixed boundary conditions
psi[0][2] = psi[100][2] = 0.; // fixed boundary conditions
for (int k = 1; k < time_steps; k++) // all later time steps
7
{
for (int i = 1; i < num_pts-1; i++)
{
psi[i][2] = 2.0*psi[i][1] - psi[i][0] + (c_speed_sq/c_prime_sq)
* ( psi[i+1][1] + psi[i-1][1] - 2.0*psi[i][1] );
}
for (int i = 0; i < num_pts; i++) // shift new values to old
{
psi[i][0] = psi[i][1];
psi[i][1] = psi[i][2];
}
if ((k % 5) == 0) // print every 5th point
{
for (int i = 0; i < num_pts; i++)
{
outfile << psi[i][2] << std::endl; // gnuplot 3D grid format
}
outfile << std::endl; // empty line for gnuplot
}
}
std::cout << std::endl << "data stored in eqstring.dat" << std::endl;
outfile.close ();
return (0);
}
g. Bound States in Momentum Space
The familiar time-independent Schrodinger equation in coordinate space for a local potential is an
ordinary dierential equation:
2
2
n
(r) +V (r)
n
(r) = E
n
n
(r) , (20)
where is the reduced mass (which is M/2 if we are considering two interacting particles of mass
M each). For scattering states, where E
n
> 0, any choice of E
n
will give an acceptable solution
(assuming V (r) 0 suciently fast as r ). For bound states, only discrete values of E
n
yield
normalizable wave functions, so we have an eigenvalue problem. In the more general (and less
familiar case), the potential is non-local and we have an integro-dierential equation to solve:
2
2
n
(r) +
_
d
3
r
V (r, r
)
n
(r
) = E
n
n
(r) . (21)
If we think about possible methods for solving the Schrodinger equation numerically, a direct
solution as a dierential equation is no longer available but we could apply a matrix diagonalization
in coordinate representation or use an expansion in an orthonormal basis with Eq. (21). (In the
former case, the potential would contribute everywhere in the matrix, since a non-local potential is
not diagonal in coordinate representation. In the latter case, there is little dierence with the local
8
potential case, since taking a matrix element of a non-local potential in a basis such as harmonic
oscillators simply involves an extra integration.) Here well consider yet another option: momentum
representation.
In momentum representation, the equation for the momentum space wave function
n
(k) is
(almost) always an integral equation (unless the potential is separable). Consider the abstract
Schrodinger equation,
H|
n
=
_
P
2
2
+
V
_
|
n
= E
n
|
n
. (22)
Now hit this on the left with k| and insert
1 =
_
d
3
k
|k
| (23)
to obtain
k
2
2
k|
n
+
_
d
3
k
k|V |k
|
n
= E
n
k|
n
(24)
or, in an alternative notation for the same thing,
k
2
2
n
(k) +
_
d
3
k
V (k, k
)
n
(k
) = E
n
n
(k) . (25)
If we expand in a partial wave basis (this means to use the spherical coordinate basis with spherical
harmonics), then the resulting one-dimensional equation in the l
th
partial wave takes the form
k
2
2
n
(k) +
2
_
0
V (k, k
)
n
(k
) k
2
dk
= E
n
n
(k) , (26)
where k |k| and we omit l labels on the potential and wave functions.
The potential in partial waves is the Bessel transform of the full potential (why not the
Fourier transform?):
V (k, k
) =
_
0
r dr
_
0
r
dr
j
l
(kr
)V (r
, r)j
l
(k
r) , (27)
which reduces for a local potential to
V (k, k
) =
_
0
r
2
dr j
l
(kr)V (r)j
l
(k
r) . (28)
Recall that the rst two spherical Bessel functions are
j
0
(z) =
sinz
z
, j
1
(z) =
sinz
z
2
cos z
z
, (29)
so for l = 0, the potential is simply
V (k, k
)
l=0
=
1
kk
_
0
dr sin(kr)V (r) sin(k
r) . (30)
9
h. Numerical Solution
So how do we solve for the E
n
s and corresponding
n
(k)s in Eq. (26)? An eective strategy is
to discretize it (that is, break up the continuous range in k into mesh points) and turn it into a
matrix eigenvalue problem. Thus, if we have an integration rule (such as Gaussian quadrature)
that performs an integral from 0 to as a sum over N points {k
i
} with weights {w
i
}, then the
integral over the potential becomes
_
0
k
2
dk
V (k, k
)
n
(k
)
N1
j=0
w
j
k
2
j
V (k, k
j
)
n
(k
j
) . (31)
Thus the Schrodinger equation becomes
k
2
i
2
n
(k
i
) +
2
N1
j=0
w
j
k
2
j
V (k
i
, k
j
)
n
(k
j
) = E
n
n
(k
i
) , i = 0, , N 1 . (32)
This is just the matrix problem
j
H
ij
[
n
]
j
= E
n
[
n
]
i
, (33)
with
H
ij
k
2
i
2
ij
+
2
V (k
i
, k
j
) k
2
j
w
j
, i, j = 0, , N 1 . (34)
We can turn this over to a packaged matrix eigenvalue routine and get the eigenvalues and eigen-
vectors directly.
Note, however, that the matrix is not symmetric. This is not a problem in principle, since there
are routines that can solve a general non-symmetric eigenvalue problem (e.g., in the LAPACK
subroutine library or the latest release of GSL). However, a better idea is to turn the problem into
a symmetric matrix problem. We do this by multiplying Eq. (33) by k
i
w
i
to get:
H
ij
[
n
]
j
= E
n
[
n
]
i
, (35)
where
[
n
]
i
k
i
w
i
[
n
]
i
. (36)
This means that
H
ij
is
H
ij
k
2
i
2
ij
+
2
k
i
w
i
V (k
i
, k
j
) k
j
w
j
, i, j = 0, , N 1 , (37)
so we now have a symmetric problem with the same eigenvalues. Note also that if the vector [
n
]
i
is normalized so that (assuming it is real)
[
n
] [
n
] =
i
[
n
]
2
i
= 1 , (38)
then
1 =
i
k
2
i
w
i
[
n
]
2
i
_
0
k
2
dk |(k)|
2
, (39)
so the continuum version is normalized as well.
10
i. Delta-Shell Potential
A useful potential for testing is the delta-shell potential, which in the coordinate representation
is
V (r) =
2
(r b) , (40)
where is the reduced mass of the particles interacting via V (or just think of as the mass of
a particle in the external potential V ). Note that this is not a delta function at the origin; the
potential is zero unless the particles are separated precisely by a distance r = b. So if we have a force
that eectively acts over a very short but nonzero range of distances, this might be a reasonable
(although crude) representation. Besides the mass, the parameters are the range b and the strength
. From Eq. (40) you should be able to directly determine the units of .
The s-wave (l = 0) Schrodinger equation has (at most) one bound-state (that is, E < 0) solution.
If we dene by writing the bound-state energy as
E =
2
2
, (41)
the value of is determined by the solution to the transcendental equation
e
2b
1 =
2
(l = 0) . (42)
For general l, the bound-state is the solution to [1]
1
i
(ib)
2
j
l
(ib)[n
l
(ib) ij
l
(ib)] . (43)
Can you derive either of these results? Is there always one bound state?
The delta-shell potential is trivial to convert to momentum space:
V (k
, k) =
_
0
r
2
dr j
l
(k
r)
2
(r b)j
l
(r) =
b
2
2
j
l
(k
b)j
l
(kb) , (44)
where l is the angular momentum state we are considering. Note that this is not a very well-behaved
function in momentum space! That means you may have to be clever in doing a numerical integral.
The wave function of the l = 0 bound state in coordinate space is
0
(r) =
_
0
k
2
dk
0
(k)j
0
(kr)
_
e
r
e
r
, for r < b ,
e
r
, for r > b .
(45)
j. References
[1] R.H. Landau and M.J. Paez, Computational Physics: Problem Solving with Computers (Wiley-
Interscience, 1997). [See the 780.20 info webpage for details on a new version.]
[2] M. Hjorth-Jensen, Lecture Notes on Computational Physics (2009). These are notes from a
course oered at the University of Oslo. See the 780.20 webpage for links to excerpts.
[3] W. Press et al., Numerical Recipes in C++, 3rd ed. (Cambridge, 2007). Chapters from the 2nd
edition are available online from http://www.nrbook.com/a/. There are also Fortran versions.
11