Main
Main
Main
1 Introduction 7
1.1 Acknowledgements and dedications . . . . . . . . . . . . . . . . . 7
1.2 Check Python and LiClipse plugin . . . . . . . . . . . . . . . . . 8
1.3 Scientific computing with Python . . . . . . . . . . . . . . . . . . 10
2
CONTENTS 3
7 Diffusjonsproblemer 264
7.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264
7.2 Confined, unsteady Couette flow . . . . . . . . . . . . . . . . . . 265
7.3 Stability: Criterion for positive coefficients. PC-criterion . . . . . 270
7.4 Stability analysis with von Neumann’s method . . . . . . . . . . 272
7.4.1 Example: Practical usage of the von Neumann condition . 276
7.5 Some numerical schemes for parabolic equations . . . . . . . . . 279
7.5.1 Richardson scheme (1910) . . . . . . . . . . . . . . . . . . 279
7.5.2 Dufort-Frankel scheme (1953) . . . . . . . . . . . . . . . . 280
CONTENTS 5
Bibliography 352
Chapter 1
Introduction
7
CHAPTER 1. INTRODUCTION 8
master thesis in March 2017. Her choice of courses were motivated by interest,
curiousity and a hunger for knowledge. The very same mentality brought her to
countries like Germany and China, where English is not the official language.
She was a energetic, independent, warm, and dedicated young student, with
good analytical skills and a talent for mathematics.
Hans Petter Langtangen, passed away on October 10th in 2016. Hans Petter
was an extraordinarily productive author, lecturer, supervisor and researcher.
His books on software and methods for solving differential equations are widely
used and have influenced these fields considerably. Ever since I meet Hans
Petter for the first time, when he acted as my opponent at my PhD-defense in
1999 and later on as a collaborator at CBC, I have learned to know him as an
enthusiastic and playful scholar with a great passion for science. He was truly
a unique character in our field. Who else would write his own flexible markup
language producing output to a range of web-formats and PDF, before setting
off to produce piles of textbooks? But maybe even more important was his
inspirational, energetic and natural ways of interaction with friends, colleagues,
and collaborators in a great variety of disciplines. I miss him dearly both as a
friend and collaborator.
During the work with this digital compendium, we have suffered the loss of
Hans Petter and Marie, two fantastic but quite different persons. Hans Petter
at the full bloom of his career, whereas Marie was a talented, young master
student, just about to start her career.
To honor the memories of both Hans Petter and Marie, I dedicate this digital
compendium to them.
Leif Rune Hellevik
Trondheim, October 19, 2017.
def systemCheck():
’’’
Check for necessary moduls needed in the course tkt4140:
matplotlib
numpy
scipy
sympy
’’’
installed = " ... installed!"
print ""
print "Check for necessary modules needed for the course tkt4140"
print ""
except:
print " IMPORT ERROR; no version of numpy found"
if __name__ == ’__main__’:
systemCheck()
CHAPTER 1. INTRODUCTION 10
2.1 Introduction
With an initial value problem for an ordinary differential equation (ODE) we
mean a problem where all boundary conditions are given for one and the same
value of the independent variable. For a first order ODE we get e.g.
y 0 (x) = f (x, y) (2.1)
y(x0 ) = a (2.2)
while for a second order ODE we get
11
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 12
∂2θ g
2
+ sin(θ) = 0 (2.5)
∂τ l
dθ
θ(0) = θ0 , (0) = 0 (2.6)
dτ
g
θ
θ0
The dot denotes derivation with respect to the dimensionless time t. For small
displacements we can set sin(θ) ≈ θ, such that (2.7) and (2.8) becomes
The difference between (2.7) and (2.9) is that the latter is linear, while the
first is non-linear. The analytical solution of Equations (2.7) and (2.8) is given
in Appendix G.2. in the Numeriske Beregninger.
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 13
an (x)y (n) (x) + an−1 (x)y (n−1) (x) + · · · + a1 (x)y 0 (x) + a0 (x)y(x) = b(x) (2.11)
where y (k) , k = 0, 1, . . . n is referring to the k’th derivative and y (0) (x) = y(x).
If one or more of the coefficients ak also are functions of at least one y (k) , k =
0, 1, . . . n, the ODE is non-linear. From (2.11) it follows that (2.7) is non-linear
and (2.9) is linear.
Analytical solutions of non-linear ODEs are rare, and except from some
special types, there are no general ways of finding such solutions. Therefore
non-linear equations must usually be solved numerically. In many cases this is
also the case for linear equations. For instance it doesn’t exist a method to solve
the general second order linear ODE given by
a2 (x) · y 00 (x) + a1 (x) · y 0 (x) + a0 (x) · y(x) = b(x)
From a numerical point of view the main difference between linear and
non-linear equations is the multitude of solutions that may arise when solving
non-linear equations. In a linear ODE it will be evident from the equation if
there are special critical points where the solution changes character, while this
is often not the case for non-linear equations.
For instance the equation y 0 (x) = y 2 (x), y(0) = 1 has the solution y(x) = 1−x
1
such that y(x) → ∞ for x → 1, which isn’t evident from the equation itself.
for all (y, x) and (y ∗ , x) in D. Note that this requirement is more restrictive than
plain continuity, which only requires that |f (y, x) − f (y ∗ , x)| → 0 as y → y ∗ .
In fact, Lipschitz continuity requires that |f (y, t) − f (y ∗ , t)| → O(|y − y ∗ |) as
y → y ∗ . To give a definition for L, consider that f (y, x) is differentiable with
respect to y in D and that its derivative ∂f ∂y (y,x)
is bounded, then we can take
∂f (y, x)
L = max | |, (2.12)
(y,x)∈D ∂y
since
∂f (v, x)
f (y, x) = f (y ∗ , x) + (y − y ∗ )
∂y
for some value v between y and y ∗ .
For (2.4), or higher order ODEs in general, one has to consider that such
equations can be written as systems of first order ODEs. Similar criteria apply
in that case to define the existence and uniqueness of solutions. However, special
attention must be paid to the definition of Lipschitz continuity, in particular to
the definition of appropriate bounds in terms of a given norm. This aspect will
not be covered in this notes.
Fulfillment of the criteria for existence and uniqueness
Consider the initial value problem
y 0 (x) = (y(x))2 , y(0) = a > 0.
It is clear that f (y) = y 2 is independent of x and Lipschitz continuous in y over
any finite interval |y − a| ≤ c with L = 2(a + c) and S = (a + c)2 . In this case, we
can say that there is unique solution at least up to time c/(a + c)2 and then take
the value of c that maximizes that time, i.e. c = a, yielding a time of 1/(4a).
In this simple problem we can verify our considerations by looking at the
exact solution
1
y(x) = .
a−1 − x
It can be easily seen that y(x) → ∞ as x → 1/a, so that there is no solution
beyond time 1/a.
Violation of the criteria for existence and uniqueness
Consider the initial value problem
p
y 0 (x) = y(x) , y(0) = 0.
√
In this case we have that f (y) = y is not Lipschitz continuous near y = 0 since
√
f 0 (y) = 1/(2 y) → ∞ as y → 0. Therefore, we can not find a constant L so
that the bound in (2.12) holds for y and y ∗ near 0. This consideration implies
that this initial value problem does not have an unique solution. In fact, it has
two solutions:
y(x) = 0
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 15
and
1 2
y(x) = x .
4
First we observe that the solution to the ODE in (2.15) may be expressed as
an Taylor expansion around the initial point:
t2 t3 ... t4
θ(t) ≈ θ(0) + t · θ̇(0) + θ̈(0) + θ (0) + θ(4) (0) (2.16)
2 6 24
By use of the initial conditions of the ODE in (2.15) θ(0) = θ0 , θ̇(0) = 0 we
get
t2 t3 ... t4
θ(t) ≈ θ0 + θ̈ + θ (0) + θ(4) (0) (2.17)
2 6 24
From the ODE in (2.15) we obtain expressions for the differentials at the initial
point:
θ̈(t) = −θ(t) → θ̈(0) = −θ(0) = −θ0 (2.18)
Expressions for higher order differentials evaluated at the initial point may
be obtained by further differentiation of the ODE (2.15)
...
θ (t) = −θ̇(t) → θ̈(0) = −θ(0) = −θ0 (2.19)
and
If we let n → ∞ we see that the parentheses give the series for cos(t). In this
case we have found the exact solution θ(t) = θ0 cos(t) of the differential equation.
Since this equation is linear we manage in this case to find a connection between
the coefficients such that we recognize the series expansion of cos(t).
x2 00 x3 x4
y(x) ≈ x · y 0 (0) + y (0) + y 000 (0) + y (4) (0)
2 6 24
From the differential equation we get y 0 (0) = 1. By consecutive differentiation
we get
y 00 (x) = −3 + y 0 + 2x + xy 0 + y → y 00 (0) = −2
y (x) = y 00 + 2 + xy 00 + 2y 0
000
→ y 000 (0) = 2
y (4) (x) = y 000 + xy 000 + 3y 00 → y (4) (0) = −4
3 4
Inserting above gives y(x) ≈ x − x2 + x3 − x6 .
3 4 5 6
Newton gave the following solution: y(x) ≈ x − x2 + x3 − x6 + x30 − x45 .
Now you can check if Newton calculated correctly. Today it is possible to
give the solution on closed form with known functions as follows,
√ √
√
h x i 2 2
y(x) =3 2πe · exp x 1 + · erf (1 + x) − erf
2 2 2
h x i
+4 · 1 − exp[x 1 + −x
2
√
Note the combination 2πe. See Hairer et al. [6] section 1.2 for more details
on classical differential equations.
y 0 (x) =g(x)
g 0 (x) = − y(x)
y(0) =a0 , g(0) = b0
Example 2:
Another of a third order ODE is:
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 18
y 0 (x) =g(x)
g 0 (x) =f (x)
f 0 (x) = − 2f (x) + (g(x))2 − 2y(x) + x2
with initial values y(0) = a0 , g(0) = b0 , f (0) = c0 .
This is fair enough for hand calculations, but when we use program packages
a more systematic procedure is needed. Let’s use the equation above as an
example.
We start by renaming y to y0 . We then get the following procedure:
y 0 = y00 = y1
y 00 = y000 = y10 = y2
Finally, the third order ODE in (2.21) may be represented as a system of
first order ODEs:
where
dm y
y (m) ≡
dxm
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 19
y00 = y1
y10 = y2
. (2.23)
.
0
ym−2 = ym−1
0
ym−1 = f (x, y0 , y1 , y2 , . . . , ym−1 )
2.5 Differences
We will study some simple methods to solve initial value problems. Later we
shall see that these methods also may be used to solve boundary value problems
for ODEs.
For this purpose we need to introduce a suitable notation to enable us to
formulate the methods in a convenient manner. In Figure 2.2) an arbitrary
function y is illustrated as a continuous function of x, ie y = y(x). Later y will
be used to represent the solution of an ODE, which only may be represented
and obtained for discrete values of x. We represent these discrete, equidistant
values of x by:
xj = x0 + jh
where h = ∆x is assumed constant unless otherwise stated and j is an integer
counter referring to the discrete values xj for which the corresponding discrete
value
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 20
y
y(xj)
y(xj-1)
y(xj-2)
y(x1)
y(x0)
h h h
x0 x1 xj-2 xj-1 xj x
yj = y(xj )
may be found (see Figure 2.3).
y yj+1½ yj+2
yj+1
yj+½
yj
yj-½
yj-1
½h ½h ½h ½h
Having introduced this notation we may the develop useful expressions and
notations for forward differences, backward differences, and central differences,
which will be used frequently later:
Forward differences:
∆yj = yj+1 − yj
Backward differences:
Central differences:
δyj+ 21 = yj+1 − yj
The linear difference operators ∆, ∇ and δ are useful when we are deriving
more complicated expressions. An example of usage is as follows,
However, for clarity we will mainly write out the formulas entirely rather
than using operators.
We shall find difference formulas and need again:
Taylor’s theorem:
1
y(x) =y(x0 ) + y 0 (x0 ) · (x − x0 ) + y 00 (x0 ) · (x − x0 )2 + (2.25)
2
1 (n) n
· · · + y (x0 ) · (x − x0 ) + Rn
n!
The remainder Rn is given by
1
Rn = y (n+1) (ξ) · (x − x0 )n+1 (2.26)
(n + 1)!
where ξ ∈ (x0 , x)
Now, Taylor’s theorem (2.25) may be used to approximate the value of y(xj+1 ),
i.e. the forward value of y(xj ), by assuming that y(xj ) and it’s derivatives are
known:
h2 00
y(xj+1 ) ≡y(xj + h) = y(xj ) + hy 0 (xj ) + y (xj )+ (2.27)
2
hn y (n) (xj )
··· + + Rn
n!
where the remainder Rn = O(hn+1 ), h → 0.
From (2.27) we also get
y(xj+1 ) − y(xj )
y 0 (xj ) = + O(h) (2.29)
h
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 22
y(xj ) − y(xj−1 )
y 0 (xj ) = + O(h) (2.30)
h
By adding (2.28) and (2.27) together we get an approximation of the second
derivative at the location xj :
y(xj+1 ) − y(xj−1 )
y 0 (xj ) = + O(h2 ) (2.32)
2h
Notation:
We let y(xj ) always denote the function y(x) with x = xj . We use yj both
for the numerical and analytical value, the intended meaning is hopfullye clear
from the context.
Equations (2.29), (2.30), (2.31) and (2.32) then may be used to deduce the
following difference expressions:
yj+1 − yj
yj0 = ; truncation error O(h) (2.33)
h
yj − yj−1
yj0 = ; truncation error O(h) (2.34)
h
yj+1 − 2yj + yj−1
yj00 = ; truncation error O(h2 ) (2.35)
h2
yj+1 − yj−1
yj0 = ; truncation error O(h2 ) (2.36)
2h
In summary, equation (2.33) is a forward difference, (2.34) is a backward
difference while (2.35) and (2.36) are central differences.
The expressions in (2.33), (2.34), (2.35) and (2.36) may also conveniently be
established from Figure 2.4.
Whereas, (2.33) follows directly from the definition of the derivative, whereas
the second order derivative (2.35) may be obtained as a derivative of the derivative
by:
00 yj+1 − yj yj − yj−1 1 yj+1 − 2yj + yj−1
yj (xj ) = − · =
h h h h2
and an improved expression for the derivative (2.36) may be obtained by
averaging the forward and the backward derivatives:
0 yj+1 − yj yj − yj−1 1 yj+1 − yj−1
yj = + · =
h h 2 2h
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 23
y yj+1
yj
yj-1
½h ½h ½h ½h
xj-1 xj xj+1 x
Figure 2.4: Illustration of how the discrete values may be used to estimate
various orders of derivatives at location xi .
a · yj−1 + b · yj + c · yj+1 =
h2 h3
a · yj − hyj0 + yj00 − yj000 (ξ) + b · yj + (2.38)
2 6
2
h 00 h3 000
0
c · yj + hyj + yj + yj (ξ)
2 6
By collecting terms in Eq. (2.38) we get:
a · yj−1 + b · yj + c · yj+1 =
(a + b + c) yj + (c − a) h yj0 + (2.39)
2 3
h 00 h 000
(a + c) y + (c − a) y (ξ)
2 j 6
From Eq.(2.39) we may then find a, b and c such that yj0 gets as high accuracy
as possible by :
a+b+c=0
(c − a) · h = 1 (2.40)
a+c=0
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 24
Forward differences:
dyi yi+1 − yi 1
= + y 00 (ξ)∆x
dx ∆x 2
dyi −3yi + 4yi+1 − yi+2 1
= + y 000 (ξ) · (∆x)2
dx 2∆x 3
dyi −11yi + 18yi+1 − 9yi+2 + yi+3 1
= + y (4) (ξ) · (∆x)3
dx 6∆x 4
d2 yi yi − 2yi+1 + yi+2
= + y 000 (ξ) · ∆x
dx2 (∆x)2
d2 yi 2yi − 5yi+1 + 4yi+2 − yi+3 11
= + y (4) (ξ) · (∆x)2
dx2 (∆x)2 12
Backward differences:
dyi yi − yi−1 1
= + y 00 (ξ)∆x
dx ∆x 2
dyi 3yi − 4yi−1 + yi−2 1
= + y 000 (ξ) · (∆x)2
dx 2∆x 3
dyi 11yi − 18yi−1 + 9yi−2 − yi−3 1
= + y (4) (ξ) · (∆x)3
dx 6∆x 4
d2 yi yi − 2yi−1 + yi−2 000
= + y (ξ) · ∆x
dx2 (∆x)2
d2 yi 2yi − 5yi−1 + 4yi−2 − yi−3 11
= + y (4) (ξ) · (∆x)2
dx2 (∆x)2 12
Central differences:
dyi yi+1 − yi−1 1
= − y 000 (ξ)(∆x)2
dx 2∆x 6
dyi −yi+2 + 8yi+1 − 8yi−1 + yi−2 1
= + y (5) (ξ) · (∆x)4
dx 12∆x 30
d2 yi yi+1 − 2yi + yi−1 1
= − y (4) (ξ) · (∆x)2
dx2 (∆x) 2 12
d2 yi −yi+2 + 16yi+1 − 30yi + 16yi−1 − yi−2 1
= + y (6) (ξ) · (∆x)4
dx2 12(∆x)2 90
d3 yi yi+2 − 2yi+1 + 2yi−1 − yi−2 1
= + y (5) (ξ) · (∆x)2
dx3 2(∆x)3 4
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 26
1 1
p(x1+ 12 ) ≈ (pi+1 + pi ), p(x1− 12 ) ≈ (pi + pi−1 ) (2.46)
2 2
Note that for p(x) = 1 = constant we get the usual expression
d2 u
ui+1 − 2ui + ui−1
= + O(h2 )
dx2 i h2
which gives
≈ h
2
which gives
y
y(xn+2)
y(x) numerical
solution
yn+2
y(xn+1)
yn+1
yn h∙f(xn,yn)
xn xn+1 xn+2 x
import numpy as np
import matplotlib.pylab as plt
Eulers method:
y^(n + 1) = y^(n) + h*f(x, y^(n)), h = dx
"""
N = 30
x = np.linspace(0, 1, N + 1)
h = x[1] - x[0] # steplength
y_0 = 1 # initial condition
Y = np.zeros_like(x) # vector for storing y values
Y[0] = y_0 # first element of y = y(0)
for n in range(N):
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 29
f = Y[n]
Y[n + 1] = Y[n] + h*f
Y_analytic = np.exp(x)
2.8
ex
2.6
euler
2.4
2.2
2.0
y
1.8
1.6
1.4
1.2
1.0
0.0 0.2 0.4 0.6 0.8 1.0
x
import numpy as np
import matplotlib.pylab as plt
from math import pi
thetha = Y[0, :]
thetha_analytic = thetha_0*np.cos(t)
plt.figure()
plt.plot(t, thetha_analytic, ’b’)
plt.plot(t, thetha, ’r--’)
import numpy as np
import matplotlib.pylab as plt
from math import pi
# define Euler solver
def euler(func, y_0, time):
""" Generic implementation of the euler scheme for solution of systems of ODEs:
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 31
0.15
0.10
0.05
0.00
−0.05
−0.10
0 cos(t)
euler
−0.15
0 1 2 3 4 5 6 7
t
y0’ = y1
y1’ = y2
.
.
yN’ = f(yN-1,..., y1, y0, t)
method:
y0^(n+1) = y0^(n) + h*y1
y1^(n+1) = y1^(n) + h*y2
.
.
yN^(n + 1) = yN^(n) + h*f(yN-1, .. y1, y0, t)
Args:
func(function): func(y, t) that returns y’ at time t; [y1, y2,...,f(yn-1, .. y1, y0, t)]
y_0(array): initial conditions
time(array): array containing the time to be solved for
Returns:
y(array): array/matrix containing solution for y0 -> yN for all timesteps"""
y = np.zeros((np.size(time), np.size(y_0)))
y[0,:] = y_0
for i in range(len(time)-1):
dt = time[i+1] - time[i]
y[i+1,:]=y[i,:] + np.asarray(func(y[i,:], time[i]))*dt
return y
y0’ = y1
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 32
y1’ = -y0
Args:
y(array): array [y0, y1] at time t
t(float): current time
Returns:
dy(array): [y0’, y1’] = [y1, -y0]
"""
dy = np.zeros_like(y)
dy[:] = [y[1], -y[0]]
return dy
N = 100
time = np.linspace(0, 2*pi, N + 1)
thetha_0 = [0.1, 0]
theta = euler(pendulum_func, thetha_0, time)
thetha = theta[:, 0]
thetha_analytic = thetha_0[0]*np.cos(time)
plt.figure()
plt.plot(time, thetha_analytic, ’b’)
plt.plot(time, thetha, ’r--’)
dz
z v=
dt
mg
d2 z
1 dz dz
= B − C · Cd (2.59)
dt2 A dt dt
For Equation (2.59) two initial conditions must be specified, e.g. v = v0 and
z = z0 for t = 0.
Figure 2.9 illustrates CD as a function of Re. The values in the plot are not
as accurate as the number of digits in the program might indicate. For example
is the location and the size of the "valley" in the diagram strongly dependent of
the degree of turbulence in the free stream and the roughness of the sphere. As
the drag coefficient CD is a function of the Reynolds number, it is also a function
of the solution v (i.e. the velocity) of the ODE in Equation (2.58). We will use
the function CD (Re) as an example of how functions may be implemented in
Python.
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 34
3
10
2
10
1
10
CD 10
0
-1
10
-2
10 -1 0 1 2 3 4 5 6 7
10 10 10 10 10 10 10 10 10
Re
y10 = f1 (x, y1 , y2 , . . . yp )
y20 = f2 (x, y1 , y2 , . . . yp )
. (2.60)
.
yp0 = fp (x, y1 , y2 , . . . yp )
y0 = f (x, y) (2.62)
y(x0 ) = a
y10 =y2
y20 =y3 (2.64)
y30 = − y1 y3
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 35
zn+1 = zn + ∆t · vn
vn+1 = nn + ∆t · [g − α(vn )2 ]
med z0 = 0, v0 = 0
dz
=v (2.67)
dt
dv
= g − αv 2 (2.68)
dt
where
3ρf
α= · CD
4ρk · d
The analytical solution with z(0) = 0 and v(0) = 0 is given by
√
ln(cosh( αg · t))
z(t) = (2.69)
r α
g √
v(t) = · tanh( αg · t) (2.70)
α
r
dv g
The terminal velocity vt is found by = 0 which gives vt = .
dt α
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 36
3 3
We use data from a golf ball: d = 41 mm, ρk = 1275 kg/m , ρk = 1.22 kg/m ,
and choose CD = 0.4 which gives α = 7 · 10−3 . The terminal velocity then
becomes r
g
vt = = 37.44
α
If we use Taylor’s method from section 2.3 we get the following expression
by using four terms in the series expansion:
1 1
z(t) = gt2 · (1 − αgt2 ) (2.71)
2 6
1
v(t) =gt · (1 − αgt2 ) (2.72)
3
By applying the Euler scheme (2.55) on (2.67) and (2.68)
zn+1 = zn + ∆t · vn (2.73)
vn+1 = vn + ∆t · (g − α · vn2 ), n = 0, 1, . . . (2.74)
dz0
= z1
dt
dz1
= g − αz12
dt
One way of implementing the integration scheme is given in the following
function euler():
def euler(func,z0, time):
"""The Euler scheme for solution of systems of ODEs.
z0 is a vector for the initial conditions,
the right hand side of the system is represented by func which returns
a vector with the same size as z0 ."""
z = np.zeros((np.size(time),np.size(z0)))
z[0,:] = z0
for i in range(len(time)-1):
dt = time[i+1]-time[i]
z[i+1,:]=z[i,:] + np.asarray(func(z[i,:],time[i]))*dt
return z
# src-ch1/FallingSphereEuler.py;DragCoefficientGeneric.py @ git@lrhgit/tkt4140/src/src-ch1/DragCoeffi
from DragCoefficientGeneric import cd_sphere
from matplotlib.pyplot import *
import numpy as np
# change some default values to make plots more readable
LNWDT=2; FNT=11
rcParams[’lines.linewidth’] = LNWDT; rcParams[’font.size’] = FNT
z = np.zeros((np.size(time),np.size(z0)))
z[0,:] = z0
for i in range(len(time)-1):
dt = time[i+1]-time[i]
z[i+1,:]=z[i,:] + np.asarray(func(z[i,:],time[i]))*dt
return z
def v_taylor(t):
# z = np.zeros_like(t)
v = np.zeros_like(t)
alpha = 3.0*rho_f/(4.0*rho_s*d)*CD
v=g*t*(1-alpha*g*t**2)
return v
T = 10 # end of simulation
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 38
N = 20 # no of time steps
time = np.linspace(0, T, N+1)
z0=np.zeros(2)
z0[0] = 2.0
ze = euler(f, z0, time) # compute response with constant CD using Euler’s method
ze2 = euler(f2, z0, time) # compute response with varying CD using Euler’s method
k1 = np.sqrt(g*4*rho_s*d/(3*rho_f*CD))
k2 = np.sqrt(3*rho_f*g*CD/(4*rho_s*d))
v_a = k1*np.tanh(k2*time) # compute response with constant CD using analytical solution
# plotting
legends=[]
line_type=[’-’,’:’,’.’,’-.’,’--’]
plot(time, v_a, line_type[0])
legends.append(’Analytical (constant CD)’)
plot(time, ze[:,1], line_type[1])
legends.append(’Euler (constant CD)’)
40
35
30
25
Velocity [m/s]
Analytical (constant CD)
20 Euler (constant CD)
15 Euler (varying CD)
Taylor (constant CD)
10
−5
0 2 4 6 8 10
Time [s]
if Re <= 0.0:
CD = 0.0
elif Re > 8.0e6:
CD = 0.2
elif Re > 0.0 and Re <= 0.5:
CD = 24.0/Re
elif Re > 0.5 and Re <= 100.0:
p = array([4.22, -14.05, 34.87, 0.658])
CD = polyval(p, 1.0/Re)
elif Re > 100.0 and Re <= 1.0e4:
p = array([-30.41, 43.72, -17.08, 2.41])
CD = polyval(p, 1.0/log10(Re))
elif Re > 1.0e4 and Re <= 3.35e5:
p = array([-0.1584, 2.031, -8.472, 11.932])
CD = polyval(p, log10(Re))
elif Re > 3.35e5 and Re <= 5.0e5:
x1 = log10(Re/4.5e5)
CD = 91.08*x1**4 + 0.0764
else:
p = array([-0.06338, 1.1905, -7.332, 14.93])
CD = polyval(p, log10(Re))
return CD
# Make plot
from matplotlib import pyplot
# change some default values to make plots more readable
LNWDT=2; FNT=11
pyplot.rcParams[’lines.linewidth’] = LNWDT; pyplot.rcParams[’font.size’] = FNT
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 40
In the following, we will break up the program and explain the different parts.
In the first code line,
from numpy import logspace, zeros
the functions logspace and zeros are imported from the package numpy.
The numpy package (NumPy is an abbreviation for Numerical Python) enables
the use of array objects. Using numpy a wide range of mathematical operations
can be done directly on complete arrays, thereby removing the need for loops
over array elements. This is commonly called vectorization and may cause a
dramatic increase in computational speed of Python programs. The function
logspace works on a logarithmic scale just as the function linspace works on
a regular scale. The function zeros creates arrays of a certain size filled with
zeros. Several comprehensive guides to the numpy package may be found at
http://www.numpy.org.
In CDsphere.py a function cd_sphere was defined as follows:
def cd_sphere(Re):
"This function computes the drag coefficient of a sphere as a function of the Reynolds number Re.
# Curve fitted after fig . A -56 in Evett and Liu: "Fluid Mechanics and Hydraulics"
if Re <= 0.0:
CD = 0.0
elif Re > 8.0e6:
CD = 0.2
elif Re > 0.0 and Re <= 0.5:
CD = 24.0/Re
elif Re > 0.5 and Re <= 100.0:
p = array([4.22, -14.05, 34.87, 0.658])
CD = polyval(p, 1.0/Re)
elif Re > 100.0 and Re <= 1.0e4:
p = array([-30.41, 43.72, -17.08, 2.41])
CD = polyval(p, 1.0/log10(Re))
elif Re > 1.0e4 and Re <= 3.35e5:
p = array([-0.1584, 2.031, -8.472, 11.932])
CD = polyval(p, log10(Re))
elif Re > 3.35e5 and Re <= 5.0e5:
x1 = log10(Re/4.5e5)
CD = 91.08*x1**4 + 0.0764
else:
p = array([-0.06338, 1.1905, -7.332, 14.93])
CD = polyval(p, log10(Re))
return CD
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 41
The function takes Re as an argument and returns the value CD. All Python func-
tions begin with def, followed by the function name, and then inside parentheses
a comma-separated list of function arguments, ended with a colon. Here we have
only one argument, Re. This argument acts as a standard variable inside the
function. The statements to perform inside the function must be indented. At
the end of a function it is common to use the return statement to return the
value of the function.
Variables defined inside a function, such as p and x1 above, are local variables
that cannot be accessed outside the function. Variables defined outside functions,
in the "main program", are global variables and may be accessed anywhere, also
inside functions.
Three more functions from the numpy package are imported in the func-
tion. They are not used outside the function and are therefore chosen to be
imported only if the function is called from the main program. We refer to the
documentation of NumPy for details about the different functions.
The function above contains an example of the use of the if-elif-else
block. The block begins with if and a boolean expression. If the boolean
expression evaluates to true the indented statements following the if statement
are carried out. If not, the boolean expression following the elif is evaluated. If
none of the conditions are evaluated to true the statements following the else
are carried out.
In the code block
Npts = 500
Re = logspace(-1, 7, Npts, True, 10)
CD = zeros(Npts)
i_list = range(0, Npts-1)
for i in i_list:
CD[i] = cd_sphere(Re[i])
following, and, as we shall see, some are more time consuming than others. We
will also demonstrate how the time consumption (or efficiency) may be tested.
A simple extension of the single-valued function cd_sphere is as follows:
def cd_sphere_py_vector(ReNrs):
CD = zeros_like(ReNrs)
counter = 0
for Re in ReNrs:
CD[counter] = cd_sphere(Re)
counter += 1
return CD
CD = where((Re > 3.35e5) & (Re <= 5.0e5), 91.08*(log10(Re/4.5e5))**4 + 0.0764, CD) #condition 6
A third approach we will try is using boolean type variables. The 8 variables
condition1 through condition8 in the function cd_sphere_vector_bool are
boolean variables of the same size and shape as Re. The elements of the boolean
variables evaluate to either True or False, depending on if the corresponding
element in Re satisfy the condition the variable is assigned.
def cd_sphere_vector_bool(Re):
"Computes the drag coefficient of a sphere as a function of the Reynolds number Re."
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 44
# Curve fitted after fig . A -56 in Evett and Liu: "Fluid Mechanics and Hydraulics"
from numpy import log10, array, polyval, zeros_like
condition1 = Re < 0
condition2 = logical_and(0 < Re, Re <= 0.5)
condition3 = logical_and(0.5 < Re, Re <= 100.0)
condition4 = logical_and(100.0 < Re, Re <= 1.0e4)
condition5 = logical_and(1.0e4 < Re, Re <= 3.35e5)
condition6 = logical_and(3.35e5 < Re, Re <= 5.0e5)
condition7 = logical_and(5.0e5 < Re, Re <= 8.0e6)
condition8 = Re > 8.0e6
CD = zeros_like(Re)
CD[condition1] = 0.0
CD[condition2] = 24/Re[condition2]
p = array([4.22,-14.05,34.87,0.658])
CD[condition3] = polyval(p,1.0/Re[condition3])
p = array([-30.41,43.72,-17.08,2.41])
CD[condition4] = polyval(p,1.0/log10(Re[condition4]))
p = array([-0.1584,2.031,-8.472,11.932])
CD[condition5] = polyval(p,log10(Re[condition5]))
CD[condition8] = 0.2
return CD
solve e.g. the problems outlined in (2.6.4). In general, a file containing Python-
code may be executed either as a main program (script), typically with python
filename.py or imported in another script/module with import filename.
A module file should not execute a main program, but rather just define
functions, import other modules, and define global variables. Inside modules,
the standard practice is to only have functions and not any statements outside
functions. The reason is that all statements in the module file are executed from
top to bottom during import in another module/script [10], and thus a desirable
behavior is no output to avoid confusion. However, in many situations it is also
desirable to allow for tests or demonstration of usage inside the module file, and
for such situations the need for a main program arises. To meet these demands
Python allows for a fortunate construction to let a file act both as a module with
function definitions only (i.e. no main program) and as an ordinary program we
can run, with functions and a main program. The latter is possible by letting
the main program follow an if test of the form:
if __name__ ==’__main__’:
<main program statements>
The __name__ variable is automatically defined in any module and equals the
module name if the module is imported in another program/script, but when the
module file is executed as a program, __name__ equals the string ’__main__’.
Consequently, the if test above will only be true whenever the module file
is executed as a program and allow for the execution of the <main program
statements>. The <main program statements> is normally referred to as the
test block of a module.
The module name is the file name without the suffix .py [10], i.e. the module
contained in the module file filename.py has the module name filename. Note
that a module can contain executable statements as well as function definitions.
These statements are intended to initialize the module and are executed only
the first time the module name is encountered in an import statement. They
are also run if the file is executed as a script.
Below we have listed the content of the file DragCoefficientGeneric.py
to illustrate a specific implementation of the module DragCoefficientGeneric
and some other useful programming features in Python. The functions in the
module are the various implementations of the drag coefficient functions from
the previous section.
Python lists and dictionaries
• Lists hold a list of values and are initialized with empty brackets, e.g.
fncnames = []. The values of the list are accessed with an index, start-
ing from zero. The first value of fncnames is fncnames[0], the second
value of fncnames is fncnames[1] and so on. You can remove values
from the list, and add new values to the end by fncnames. Example:
fncnames.append(name) will append name as the last value of the list
fncnames. In case it was empty prior to the append-operation, name will
be the only element in the list.
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 46
which allows for convenient looping over all of the functions with the following
construction:
for func in funcs:
Afterwards the results may be printed with name and execution time, ordered
by the latter, with the most efficient function at the top:
for name_key in exec_keys_sorted:
print name_key, ’\t execution time = ’, ’%6.6f’ % exec_times[name_key]
Clearly, the function with the boolean variables was fastest, the straight forward
vectorized version cd_sphere_py_vector was slowest and the built-in function
vectorize was nearly as inefficient.
The complete module DragCoefficientGeneric is listed below.
# src-ch1/DragCoefficientGeneric.py
from numpy import linspace,array,append,logspace,zeros_like,where,vectorize,\
logical_and
import numpy as np
from matplotlib.pyplot import loglog,xlabel,ylabel,grid,savefig,show,rc,hold,\
legend, setp
from numpy.core.multiarray import scalar
# single-valued function
def cd_sphere(Re):
"Computes the drag coefficient of a sphere as a function of the Reynolds number Re."
# Curve fitted after fig . A -56 in Evett and Liu: "Fluid Mechanics and Hydraulics"
if Re <= 0.0:
CD = 0.0
elif Re > 8.0e6:
CD = 0.2
elif Re > 0.0 and Re <= 0.5:
CD = 24.0/Re
elif Re > 0.5 and Re <= 100.0:
p = array([4.22,-14.05,34.87,0.658])
CD = polyval(p,1.0/Re)
elif Re > 100.0 and Re <= 1.0e4:
p = array([-30.41,43.72,-17.08,2.41])
CD = polyval(p,1.0/log10(Re))
elif Re > 1.0e4 and Re <= 3.35e5:
p = array([-0.1584,2.031,-8.472,11.932])
CD = polyval(p,log10(Re))
elif Re > 3.35e5 and Re <= 5.0e5:
x1 = log10(Re/4.5e5)
CD = 91.08*x1**4 + 0.0764
else:
p = array([-0.06338,1.1905,-7.332,14.93])
CD = polyval(p,log10(Re))
return CD
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 48
CD = where((Re > 3.35e5) & (Re <= 5.0e5), 91.08*(log10(Re/4.5e5))**4 + 0.0764, CD) #condition 6
# vectorized boolean
def cd_sphere_vector_bool(Re):
"Computes the drag coefficient of a sphere as a function of the Reynolds number Re."
# Curve fitted after fig . A -56 in Evett and Liu: "Fluid Mechanics and Hydraulics"
condition1 = Re < 0
condition2 = logical_and(0 < Re, Re <= 0.5)
condition3 = logical_and(0.5 < Re, Re <= 100.0)
condition4 = logical_and(100.0 < Re, Re <= 1.0e4)
condition5 = logical_and(1.0e4 < Re, Re <= 3.35e5)
condition6 = logical_and(3.35e5 < Re, Re <= 5.0e5)
condition7 = logical_and(5.0e5 < Re, Re <= 8.0e6)
condition8 = Re > 8.0e6
CD = zeros_like(Re)
CD[condition1] = 0.0
CD[condition2] = 24/Re[condition2]
p = array([4.22,-14.05,34.87,0.658])
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 49
CD[condition3] = polyval(p,1.0/Re[condition3])
p = array([-30.41,43.72,-17.08,2.41])
CD[condition4] = polyval(p,1.0/log10(Re[condition4]))
p = array([-0.1584,2.031,-8.472,11.932])
CD[condition5] = polyval(p,log10(Re[condition5]))
return CD
if __name__ == ’__main__’:
#Check whether this file is executed (name==main) or imported as a module
import time
from numpy import mean
ReNrs = logspace(-2,7,num=500)
# make a vectorized version of the function automatically
cd_sphere_auto_vector = vectorize(cd_sphere)
fncnames.append(name)
# benchmark
t0 = time.clock()
CD[name] = func(ReNrs)
exec_times[name] = time.clock() - t0
# sort the dictionary exec_times on values and return a list of the corresponding keys
exec_keys_sorted = sorted(exec_times, key=exec_times.get)
z = np.zeros((np.size(time),np.size(z0)))
z[0,:] = z0
for i in range(len(time)-1):
dt = time[i+1]-time[i]
z[i+1,:]=z[i,:] + np.asarray(func(z[i,:],time[i]))*dt
return z
def v_taylor(t):
# z = np.zeros_like(t)
v = np.zeros_like(t)
alpha = 3.0*rho_f/(4.0*rho_s*d)*CD
v=g*t*(1-alpha*g*t**2)
return v
z0=np.zeros(2)
z0[0] = 2.0
for i in range(Ndts+1):
time = np.linspace(0, T, N+1)
ze = euler(f, z0, time) # compute response with constant CD using Euler’s method
v_a = k1*np.tanh(k2*time) # compute response with constant CD using analytical solution
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 52
abs_error=np.abs(ze[:,1] - v_a)
log_error = np.log2(abs_error[1:])
max_log_error = np.max(log_error)
plot(time[1:], log_error)
legends.append(’Euler scheme: N ’ + str(N) + ’ timesteps’ )
N*=2
if i > 0:
error_diff.append(previous_max_log_err-max_log_error)
previous_max_log_err = max_log_error
# fix plot
legend(legends, loc=’best’, frameon=False)
xlabel(’Time [s]’)
#ylabel(’Velocity [m/s]’)
ylabel(’log2-error’)
#savefig(’example_euler_timestep_study.png’, transparent=True)
show()
The plot resulting from the code above is shown in Figure (2.11). The
difference or distance between the curves seems to be rather constant after an
initial transient. As we have plotted the logarithm of the absolute value of the
error i , the difference di+1 between two curves is di+1 = log2 i − log2 i+1 =
i
log2 . A rough visual inspection of Figure (2.11) yields di+1 ≈ 1.0, from
i+1
which we may deduce the apparent order of the scheme:
i
n = log2 ≈ 1 ⇒ i+1 ≈ 0.488483620 i (2.75)
i+1
−2
−4
log2 -error
−6
−8
Euler scheme: N 10 timesteps
−10
Euler scheme: N 20 timesteps
−12 Euler scheme: N 40 timesteps
Euler scheme: N 80 timesteps
−14
Euler scheme: N 160 timesteps
−16
0 2 4 6 8 10
Time [s]
Figure 2.11: Plots for the logarithmic errors for a falling sphere with constant
drag. The timestep ∆t is reduced by a factor two from one curve to the one
immediately below.
f (xn + h) − f (xn )
y 00 (xn , yn ) = f 0 (xn , y(xn , yn )) ≈ (2.76)
h
The Taylor series expansion (2.27) gives
h2 00
y(xn + h) = y(xn ) + hy 0 [xn , y(xn )] + y [xn , y(xn )] + O(h3 )
2
which, inserting (2.76), gives
h
yn+1 = yn + · [f (xn , yn ) + f (xn+1 , y(xn+1 ))] (2.77)
2
This formula is called the trapezoidal formula, since it reduces to computing
an integral with the trapezoidal rule if f (x, y) is only a function of x. Since yn+1
appears on both sides of the equation, this is an implicit formula which means
that we need to solve a system of non-linear algebraic equations if the function
f (x, y) is non-linear. One way of making the scheme explicit is to use the Euler
scheme (2.55) to calculate y(xn+1 ) on the right side of (2.77). The resulting
scheme is often denoted Heun’s method.
The scheme for Heun’s method becomes
p
yn+1 = yn + h · f (xn , yn ) (2.78)
h p
yn+1 = yn + · [f (xn , yn ) + f (xn+1 , yn+1 )] (2.79)
2
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 54
Index p stands for "predicted". (2.78) is then the predictor and (2.79) is the
corrector. This is a second order method. For more details, see [3]. Figure 2.12
is a graphical illustration of the method.
y
corrected
yn+1 slope
slope
f(xn+1,yn+1)
y(x)
ypn+1
slope
yn
f(xn,yn)
xn xn+1 x
√ √
√
x 2 2
y(x) =3 2πe · exp x 1 + · erf (1 + x) − erf
2 2 2
h x i
+4 · 1 − exp x 1 + −x (2.83)
2
Here we have f (x, y) = 1 − 3x + y + x2 + xy = 1 + x(x − 3) + (1 + x)y
The following program NewtonHeun.py solves this problem using Heun’s
method, and the resulting figure is shown in Figure 2.13.
# src-ch1/NewtonHeun.py
# Program Newton
# Computes the solution of Newton’s 1st order equation (1671):
# dy/dx = 1-3*x + y + x^2 +x*y , y(0) = 0
# using Heun’s method.
import numpy as np
xend = 2
dx = 0.1
steps = np.int(np.round(xend/dx, 0)) + 1
y, x = np.zeros((steps,1), float), np.zeros((steps,1), float)
y[0], x[0] = 0.0, 0.0
for n in range(0,steps-1):
x[n+1] = (n+1)*dx
xn = x[n]
fn = 1 + xn*(xn-3) + y[n]*(1+xn)
yp = y[n] + dx*fn
xnp1 = x[n+1]
fnp1 = 1 + xnp1*(xnp1-3) + yp*(1+xnp1)
y[n+1] = y[n] + 0.5*dx*(fn+fnp1)
# Analytical solution
from scipy.special import erf
a = np.sqrt(2)/2
t1 = np.exp(x*(1+ x/2))
t2 = erf((1+x)*a)-erf(a)
ya = 3*np.sqrt(2*np.pi*np.exp(1))*t1*t2 + 4*(1-t1)-x
# plotting
import matplotlib.pylab as plt
0.5
0.0
−0.5
−1.0
−1.5
y
−2.0
−2.5
−3.0 Heun
Analytical
−3.5
0.0 0.5 1.0 1.5 2.0
x
Figure 2.13: Velocity of falling sphere using Euler’s and Heun’s methods.
Corrector:
p
zn+1 = zn + 0.5∆t · (vn + vn+1 ) (2.85)
2 p
)2
vn+1 = vn + 0.5∆t · 2g − α[vn + (vn+1
with initial values z0 = z(0) = 0, v0 = v(0) = 0. Note that we don’t use the
p
predictor zn+1 since it doesn’t appear on the right hand side of the equation
system.
One possible way of implementing this scheme is given in the following
function named heun(), in the program ODEschemes.py:
def heun(func, z0, time):
"""The Heun scheme for solution of systems of ODEs.
z0 is a vector for the initial conditions,
the right hand side of the system is represented by func which returns
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 57
for i, t in enumerate(time[0:-1]):
dt = time[i+1] - time[i]
zp = z[i,:] + np.asarray(func(z[i,:],t))*dt # Predictor step
z[i+1,:] = z[i,:] + (np.asarray(func(z[i,:],t)) + np.asarray(func(zp,t+dt)))*dt/2.0 # Correct
Using the same time steps as in (2.6.6), we get the response plotted in
Figure 2.14.
40
35
30
25
Velocity [m/s]
20
Figure 2.14: Velocity of falling sphere using Euler’s and Heun’s methods.
alpha = 3.0*rho_f/(4.0*rho_s*d)*CD
zout[:] = [z[1], g - alpha*z[1]**2]
return zout
T = 10 # end of simulation
N = 20 # no of time steps
time = np.linspace(0, T, N+1)
z0=np.zeros(2)
z0[0] = 2.0
ze = euler(f, z0, time) # compute response with constant CD using Euler’s method
ze2 = euler(f2, z0, time) # compute response with varying CD using Euler’s method
zh = heun(f, z0, time) # compute response with constant CD using Heun’s method
zh2 = heun(f2, z0, time) # compute response with varying CD using Heun’s method
k1 = np.sqrt(g*4*rho_s*d/(3*rho_f*CD))
k2 = np.sqrt(3*rho_f*g*CD/(4*rho_s*d))
v_a = k1*np.tanh(k2*time) # compute response with constant CD using analytical solution
# plotting
legends=[]
line_type=[’-’,’:’,’.’,’-.’,’--’]
xlabel(’Time [s]’)
ylabel(’Velocity [m/s]’)
#savefig(’example_sphere_falling_euler_heun.png’, transparent=True)
show()
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 59
f (xn + p1 h, yn + p2 K1 h) = f + p1 h fx + p2 K1 h fy + h.o.t.
= f + p1 h fx + p2 h f fy + h.o.t. (2.89)
where we for convenience have adopted the common notation for partial deriva-
tives
∂f ∂f
fx ≡ and fy ≡ (2.90)
∂x ∂y
yn+1 = yn + a1 h f (xn , yn ) + a2 h (f + p1 h fx + p2 h f fy )
= yn + (a1 + a2 ) hf + (a2 p1 fx + a2 p2 f fy ) h2 (2.91)
d2 y df dx dy
y 00 = 2
= = ∂f x + ∂f y = fx + f fy (2.92)
dx dx dx dx
which may be used in a second order Taylor expansion of the solution of (2.2):
h2 00
y(xn + h) = yn + hy 0 + y + O(h3 ) (2.93)
2
Substitution of (2.92) and (2.2) into (2.93) yields:
h2
y(xn + h) = yn + hf + (fx + f fy ) + O(h3 ) (2.94)
2
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 60
Now the idea of the generic second order Runge-Kutta method is to select
a1 , a2 , K1 , and K2 in such a way that (2.91) approximates (2.94), which will be
true if:
a1 + a2 = 1
1
a2 p1 = (2.95)
2
1
a2 p2 =
2
Note, that since we have 4 unknowns (a1 , a2 , K1 , and K2 ) and only 3
equations in (2.95), several methods of the kind proposed in (2.86) are possible. 2.9
is retrieved by selecting a2 = 1/2, from which we get a1 = 1/2 and p1 = p2 = 1
from (2.95).
4. The four gradients are averaged with weights 1/6, 2/6, 2/6 and 1/6. Using
the averaged gradient we calculate the final value yn+1 .
Each of the steps above are Euler steps.
y
y(x)
yn+1 4
3
2
yn 1
xn xn+½ xn+1 x
h
(y1 )n+1 = (y1 )n + (k1 + 2k2 + 2k3 + k4 )
6
h
(y2 )n+1 = (y2 )n + (l1 + 2l2 + 2l3 + l4 ) (2.97)
6
h
(y3 )n+1 = (y3 )n + (m1 + 2m2 + 2m3 + m4 )
6
(2.98)
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 62
where
k1 = y2
l1 = y3
m1 = −y1 y3
k4 = (y2 + hl3 )
l4 = (y3 + hm3 )
m4 = −[(y1 + hk3 )(y3 + hm3 )
for i, t in enumerate(time[0:-1]):
dt = time[i+1] - time[i]
dt2 = dt/2.0
k1 = np.asarray(func(z[i,:], t)) # predictor step 1
k2 = np.asarray(func(z[i,:] + k1*dt2, t + dt2)) # predictor step 2
k3 = np.asarray(func(z[i,:] + k2*dt2, t + dt2)) # predictor step 3
k4 = np.asarray(func(z[i,:] + k3*dt, t + dt)) # predictor step 4
z[i+1,:] = z[i,:] + dt/6.0*(k1 + 2.0*k2 + 2.0*k3 + k4) # Corrector step
Figure 2.16 shows the results using Euler, Heun and RK4. AS seen, RK4
and Heun are more accurate than Euler. The complete program Falling-
SphereEulerHeunRK4.py is listed below. The functions euler, heun and
rk4 are imported from the program ODEschemes.py.
# src-ch1/FallingSphereEulerHeunRK4.py;ODEschemes.py @ git@lrhgit/tkt4140/src/src-ch1/ODEschemes.py;
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 63
40
35
30
25
Velocity [m/s]
20
Analytical (constant CD)
Euler (constant CD)
15 Heun (constant CD)
RK4 (constant CD)
10
Euler (varying CD)
5 Heun (varying CD)
RK4 (varying CD)
0
0 2 4 6 8 10
Time [s]
Figure 2.16: Velocity of falling sphere using Euler, Heun and RK4.
z0=np.zeros(2)
z0[0] = 2.0
ze = euler(f, z0, time) # compute response with constant CD using Euler’s method
ze2 = euler(f2, z0, time) # compute response with varying CD using Euler’s method
zh = heun(f, z0, time) # compute response with constant CD using Heun’s method
zh2 = heun(f2, z0, time) # compute response with varying CD using Heun’s method
zrk4 = rk4(f, z0, time) # compute response with constant CD using RK4
zrk4_2 = rk4(f2, z0, time) # compute response with varying CD using RK4
k1 = np.sqrt(g*4*rho_s*d/(3*rho_f*CD))
k2 = np.sqrt(3*rho_f*g*CD/(4*rho_s*d))
v_a = k1*np.tanh(k2*time) # compute response with constant CD using analytical solution
# plotting
legends=[]
line_type=[’-’,’:’,’.’,’-.’,’:’,’.’,’-.’]
xlabel(’Time [s]’)
ylabel(’Velocity [m/s]’)
#savefig(’example_sphere_falling_euler_heun_rk4.png’, transparent=True)
show()
y
vf
Fɭ
vr
ϕ
Fd
v0 m
g
y1 = y2 = 0
y3 = v0 cos(α)
y4 = v0 sin(α)
—–
Let’s first look at the case of a smooth ball. We use the following data (which
are the data for a golf ball):
6m 3
Diameter d = 41mm, mass m = 46g which gives ρk = = 1275kg/m
πd3
We use the initial velocity v0 = 50 m/s and solve (2.106) using the Runge-Kutta
4 scheme. In this example we have used the Python package Odespy (ODE
Software in Python), which offers a large collection of functions for solving
ODE’s. The RK4 scheme available in Odespy is used herein.
The right hand side in (2.106) is implemented as the following function:
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 67
25
angle=30.0, smooth ball
angle=25.0, smooth ball
angle=20.0, smooth ball
20 angle=15.0, smooth ball
15
y [m]
10
00 20 40 60 80 100
x [m]
—–
Now let’s look at the same case for a golf ball. The dimension and weight
are the same as for the sphere. Now we need to account for the lift force from
the spin of the ball. In addition, the drag data for a golf ball are completely
different from the smooth sphere. We use the data from Bearman and Harvey
[1] who measured the drag and lift of a golf ball for different spin velocities in
a vindtunnel. We choose as an example 3500 rpm, and an initial velocity of
v0 = 50 m/s.
The right hand side in (2.106) is now implemented as the following function:
def f3(z, t):
"""4x4 system for golf ball with drag and lift in two directions."""
zout = np.zeros_like(z)
C = 3.0*rho_f/(4.0*rho_s*d)
vrx = z[2] - vfx
vry = z[3] - vfy
vr = np.sqrt(vrx**2 + vry**2)
Re = vr*d/nu
CD, CL = cdcl(vr, nrpm)
zout[:] = [z[2], z[3], -C*vr*(CD*vrx + CL*vry), C*vr*(CL*vrx - CD*vry) - g]
The function cdcl() (may be downloaded here) gives the drag and lift data
for a given velocity and spin.
The results are shown in Figure 2.19. The motion of a golf ball with drag
but without lift is also included. We see that the golf ball goes much farther
than the smooth sphere, due to less drag and the lift.
The complete program ParticleMotion2D.py is listed below.
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 68
50
angle=30.0, smooth ball
angle=25.0, smooth ball
angle=20.0, smooth ball
40 angle=15.0, smooth ball
angle=30.0, golf ball
angle=25.0, golf ball
30 angle=20.0, golf ball
angle=15.0, golf ball
y [m]
angle=30.0, golf ball (with lift)
angle=25.0, golf ball (with lift)
20 angle=20.0, golf ball (with lift)
angle=15.0, golf ball (with lift)
10
# src-ch1/ParticleMotion2D.py;DragCoefficientGeneric.py @ git@lrhgit/tkt4140/src/src-ch1/DragCoeffici
# smooth ball
def f(z, t):
"""4x4 system for smooth sphere with drag in two directions."""
zout = np.zeros_like(z)
C = 3.0*rho_f/(4.0*rho_s*d)
vrx = z[2] - vfx
vry = z[3] - vfy
vr = np.sqrt(vrx**2 + vry**2)
Re = vr*d/nu
CD = cd_sphere(Re) # using the already defined function
zout[:] = [z[2], z[3], -C*vr*(CD*vrx), C*vr*(-CD*vry) - g]
return zout
T = 7 # end of simulation
N = 60 # no of time steps
time = np.linspace(0, T, N+1)
N2 = 4
alfa = np.linspace(30, 15, N2) # Angle of elevation [degrees]
angle = alfa*np.pi/180.0 # convert to radians
legends=[]
line_color=[’k’,’m’,’b’,’r’]
figure(figsize=(20, 8))
hold(’on’)
LNWDT=4; FNT=18
rcParams[’lines.linewidth’] = LNWDT; rcParams[’font.size’] = FNT
z, t = solver.solve(time)
plot(z[:,0], z[:,1], ’.’, color=line_color[i])
legends.append(’angle=’+str(alfa[i])+’, golf ball (with lift)’)
where f (x, y) is the source term. Moreover, the ODE is equipped with initial
conditions
y(x0 ) = y0 . (2.108)
Finally, we define y(xn ) as the solution of IVP defined by (2.107) and (2.108)
evaluated at x = xn , whereas yn is a numerical approximation of y(xn ) at the
same location. We can now define an approximation error as
en = y(xn ) − yn (2.109)
|y(xn ) − yn |
rn = . (2.111)
|y(xn )|
Now we have introduced all necessary concepts to define the local trunca-
tion error (LTE)
1
τn = La (y(xn )). (2.115)
h
In practice, one applies the approximate differential operator, that is defined
by the numerical scheme, to the exact solution of the problem at hand. The
evaluation of the exact solution for different x around xn , as required by La is
performed using a Taylor series expansion.
Finally, we can state that a scheme is p-th order accurate by examining its
LTE and observing its leading term
y 0 = λy, (2.117)
with initial condition
y(0) = 1. (2.118)
The approximation operator (2.114) for Euler’s scheme is
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 72
Leuler
a = yn+1 − [yn + hλyn ], (2.119)
1 1
τn = {La (y(xn ))} = {y(xn+1 ) − [y(xn ) + hλy(xn )]} , (2.120)
h h
h2
1 1
= y(xn ) + hy 0 (xn ) + y 00 (xn ) + . . . + hp y (p) (xn ) − y(xn ) − hλy(xn )
h 2 p!
(2.121)
1 00 1 p−1 (p)
= hy (xn ) + . . . + h y (xn ) (2.122)
2 p!
1
≈ hy 00 (xn ). (2.123)
2
1 00
τneuler = hy (xn ).
2
The error of the numerical approximation depends on h in a linear fashion,
as expected for a first order scheme. However, the approximation error does also
depend on the solution itself, more precisely on its second derivative. This is a
property of the problem and we have no way of intervening on it. If one wants
to set a maximum admissible error, then the only parameter left is the time step
h. The wisest manner to use this freedom is to adopt a variable time-stepping
strategy, in which the time step is adapted according to estimates of the error as
long as one advances in x. Here we briefly present one possible approach, namely
the 5-th order Runge-Kutta-Fehlberg scheme with variable time step (RKF45),
as presented for example in [3]. Schematically, the main steps for implementing
such scheme are listed bellow:
# src-ch1/fixed_vs_variable.py;
import numpy as np
import matplotlib.pyplot as plt
# Solve
# y’ = beta * y
# y(0) = 1.
# with explicit Euler (fixed h) and RKF45
# ode coefficient
beta = -10.
# ode initial condition
y0 = 1.
# time step for Euler’s scheme
h = 0.02
# domain
xL = 0.
xR = 1.
# number of intervals for Euler’s scheme
N = int((xR-xL)/h)
# correct h for Euler’s scheme
h = (xR-xL)/float(N)
# independent variable array for Euler’s scheme
x = np.linspace(xL,xR,N)
# unknown variable array for Euler’s scheme
y = np.zeros(N)
# x for exact solution
xexact = np.linspace(xL,xR,max(1000.,100*N))
# ode function
def f(x,y):
return beta * y
# exact solution
def exact(x):
return y0*np.exp(beta*x)
# RKF45 increment
def rkf45step(x,yn,h,ode):
# min and max time step
hmin = 1e-5
hmax = 5e-1
# min and max errors
emin = 1e-7
emax = 1e-5
# max number of iterations
nMax = 100
# match final time
if x+h > xR:
h = xR-x
# loop to find correct error (h)
update = 0
for i in range(nMax):
k1 = ode(x,yn)
k2 = ode(x+h/4.,yn+h/4.*k1)
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 74
k3 = ode(x+3./8.*h,yn+3./32.*h*k1+9./32.*h*k2)
k4 = ode(x+12./13.*h,yn+1932./2197.*h*k1-7200./2197.*h*k2+7296./2197.*h*k3)
k5 = ode(x+h,yn+439./216.*h*k1-8.*h*k2+3680./513.*h*k3-845./4104.*h*k4)
k6 = ode(x+h/2.,yn-8./27.*h*k1+2.*h*k2-3544./2565.*h*k3+1859./4140.*h*k4-11./40.*h*k5)
# 4th order solution
y4 = yn + h * (25./216*k1 + 1408./2565.*k3+2197./4104.*k4-1./5.*k5)
# fifth order solution
y5 = yn + h * (16./135.*k1 + 6656./12825.*k3 + 28561./56430.*k4 - 9./50.*k5 +2./55.*k6)
# error estimate
er = np.abs(y5-y4)
if er < emin:
# if error small, enlarge h, but match final simulation time
h = min(2.*h,hmax)
if x+h > xR:
h = xR-x
break
elif er > emax:
# if error big, reduce h
h = max(h/2.,hmin)
else:
# error is ok, take this h and y5
break
if i==nMax-1:
print "max number of iterations reached, check parameters"
xrk = np.zeros(1)
yrk = y0*np.ones(1)
hrk = np.zeros(1)
h = 0.5
for i in range(nMax):
xloc , yloc, h , er = rkf45step(xrk[-1],yrk[-1],h,f)
xrk = np.append(xrk,xloc)
yrk = np.append(yrk,yloc)
if i==0:
hrk[i] = h
else:
hrk = np.append(hrk,h)
if xrk[-1]==xR:
break
plt.subplot(211)
plt.plot(xexact,exact(xexact),’b-’,label=’Exact’)
plt.plot(x,y,’rx’,label=’Euler’)
plt.plot(xrk,yrk,’o’, markersize=7,markeredgewidth=1,markeredgecolor=’g’,markerfacecolor=’None’,label
plt.xlabel(’x’)
plt.ylabel(’y(x)’)
plt.legend()
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 75
plt.subplot(212)
plt.plot(xrk[1:],hrk,’o’, markersize=7,markeredgewidth=1,markeredgecolor=’g’,markerfacecolor=’None’,l
plt.ylabel(’h’)
plt.legend(loc=’best’)
#plt.savefig(’fixed_vs_variable.png’)
plt.show()
Figure 2.20: Numerical solution for the model exponential decay equation
(2.117) using the Euler scheme and the RKF45 variable time step scheme (top
panel) and time step used at each iteration by the variable time stepping scheme
(bottom panel).
—– The basic idea for the convergence test in the function convergence_test is
that we start out by solving numerically an ODE with an analytical solution on
a relatively coarse grid, allowing for direct computations of the error. We then
reduce the timestep by a factor two (or double the grid size), repeatedly, and
compute the error for each grid and compare it with the error of previous grid.
The Euler scheme (2.55) is O(h), whereas the Heun scheme (2.78) is O(h2 ),
and Runge-Kutta (2.96) is O(h4 ), where the h denote a generic step size which
for the current example is the timestep ∆t. The order of a particular scheme is
given exponent n in the error term O(hn ). Consequently, the Euler scheme is a
first oder scheme, Heun is second order, whereas Runge-Kutta is fourth order.
By letting i+1 and i denote the errors on two consecutive grids with
∆ti
corresponding timesteps ∆ti+1 = . The errors i+1 and i for a scheme of
2
order n are then related by:
1
i+1 = n i (2.126)
2
Consequently, whenever i+1 and i are known from consecutive simulations an
estimate of the order of the scheme may be obtained by:
i
n ≈ log2 (2.127)
i+1
elements N . The plot is not reproduced below, but you may see the result by
downloading and running the module yourself.
Having completed the given number of refinements Ndts for a specific scheme
we store the order_approx for the scheme in a dictionary using the name of
the scheme as a key by schemes_orders[scheme.func_name] = order_approx.
This allows for an illustrative plot of the order estimate for each scheme with
the clause:
for key in schemes_orders:
plot(N_list, (np.asarray(schemes_orders[key])))
and the resulting plot is shown in Figure 2.21, and we see that our numerical
approximations for the orders of our schemes approach the theoretical values as
the number of timesteps increase (or as the timestep is reduced by a factor two
consecutively).
5
rk4
heun
4
euler
Scheme order approximation
theoretical
3
0
120
240
480
960
192
0
Number of unknowns
Figure 2.21: The convergence rate for the various ODE-solvers a function of
the number of timesteps.
z0 = 2
schemes =[euler, heun, rk4]
legends=[]
schemes_order={}
order_approx = []
for i in range(Ndts+1):
z = scheme(f3, z0, time)
abs_error = abs(u_nonlin_analytical(z0, time)-z[:,0])
log_error = log2(abs_error[1:]) # Drop 1st elt to avoid log2-problems (1st elt is zer
max_log_err = max(log_error)
plot(time[1:], log_error, linestyles[i]+colors[iclr], markevery=N/5)
legends.append(scheme.func_name +’: N = ’ + str(N))
hold(’on’)
schemes_order[scheme.func_name] = order_approx
iclr += 1
legend(legends, loc=’best’)
xlabel(’Time’)
ylabel(’log(error)’)
grid()
N = N/2**Ndts
N_list = [N*2**i for i in range(1, Ndts+1)]
N_list = np.asarray(N_list)
figure()
for key in schemes_order:
plot(N_list, (np.asarray(schemes_order[key])))
f = (1/(sigma*sqrt(2*pi)))*exp(-((t-mu)**2)/(2*sigma**2))
dfdt = diff(f, t)
d2fdt = diff(dfdt, t)
d3fdt = diff(d2fdt, t)
RHS = d3fdt + dfdt*d2fdt + f
f = lambdify([t], f)
dfdt = lambdify([t], dfdt)
d2fdt = lambdify([t], d2fdt)
RHS = lambdify([t], RHS)
def func(y,t):
yout = np.zeros_like(y)
yout[:] = [y[1], y[2], RHS(t) -y[0]- y[1]*y[2]]
return yout
z0 = np.array([f(t0), dfdt(t0), d2fdt(t0)])
figure()
tol = 1E-15
Ndts = 5 # Number of times to refine timestep in convergence test
schemes =[euler, heun, rk4]
legends=[]
schemes_order={}
order_approx = []
for i in range(Ndts+1):
z = scheme(func, z0, time)
abs_error = abs(fanalytic-z[:,0])
log_error = log2(abs_error[1:]) # Drop 1st elt to avoid log2-problems (1st elt is zer
max_log_err = max(log_error)
plot(time[1:], log_error, linestyles[i]+colors[iclr], markevery=N/5)
legends.append(scheme.func_name +’: N = ’ + str(N))
hold(’on’)
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 80
N *=2
time = linspace(t0, tend, N+1)
fanalytic = np.zeros_like(time)
k = 0
for tau in time:
fanalytic[k] = f(tau)
k = k + 1
schemes_order[scheme.func_name] = order_approx
iclr += 1
legend(legends, loc=’best’)
xlabel(’Time’)
ylabel(’log(error)’)
grid()
N = N/2**Ndts
N_list = [N*2**i for i in range(1, Ndts+1)]
N_list = np.asarray(N_list)
figure()
for key in schemes_order:
plot(N_list, (np.asarray(schemes_order[key])))
t = symbols(’t’)
sigma= 0.5 # standard deviation
mu = 0.5 # mean value
#### Perform needed differentiations based on the differential equation ####
f = (1/(sigma*sqrt(2*pi)))*exp(-((t-mu)**2)/(2*sigma**2))
dfdt = diff(f, t)
d2fdt = diff(dfdt, t)
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 81
d3fdt = diff(d2fdt, t)
RHS = d3fdt + dfdt*d2fdt + f
#### Create Python functions of f, RHS and needed differentiations of f ####
f = lambdify([t], f, np)
dfdt = lambdify([t], dfdt, np)
d2fdt = lambdify([t], d2fdt)
RHS = lambdify([t], RHS)
def func(y,t):
""" Function that returns the dfn/dt of the differential equation f + f’’*f + f’’’ = RHS
as a system of 1st order equations; f = f1
f1’ = f2
f2’ = f3
f3’ = RHS - f1 - f2*f3
Args:
y(array): solutian array [f1, f2, f3] at time t
t(float): current time
Returns:
yout(array): differantiation array [f1’, f2’, f3’] at time t
"""
yout = np.zeros_like(y)
yout[:] = [y[1], y[2], RHS(t) -y[0]- y[1]*y[2]]
return yout
if k==0:
h.append(time[1] - time[0]) # add this iteration’s dt to list h
z = scheme(func, z0, time) # Solve the ODE by calling the scheme with arguments. e.g:
fanalytic = f(time) # call analytic function f to compute analytical solutions at tim
ax[0][k].plot(time, z[:,0])
legendList.append(’$h$ = ’ + str(h[i]))
N *=2 # refine dt
Args:
error(list): list of calculated errors [error(h1), error(h2)]
h(list): list of steplengths corresponding to the list of errors
x0(list): list of starting (guessed) values for x
Returns:
x(array): iterated solution of x = [C, p]
"""
from sympy import Matrix
#### Symbolic computiations: ####
C, p = symbols(’C p’)
f1 = C*h[-2]**p - error[-2]
f2 = C*h[-1]**p - error[-1]
F = [f1, f2]
x = [C, p]
def jacobiElement(i,j):
return diff(F[i], x[j])
x[1] = round(x[1], 3)
return x
ht = np.asarray(h)
eulerError = np.asarray(schemes_error["euler"])
heunError = np.asarray(schemes_error["heun"])
rk4Error = np.asarray(schemes_error["rk4"])
print epsilon_euler_latex
print epsilon_heun_latex
print epsilon_rk4_latex
epsilon_euler = lambdify(h, epsilon_euler, np)
epsilon_heun = lambdify(h, epsilon_heun, np)
epsilon_rk4 = lambdify(h, epsilon_rk4, np)
N = N/2**(Ntds + 2)
N_list = [N*2**i for i in range(1, Ntds + 2)]
N_list = np.asarray(N_list)
print len(N_list)
print len(eulerError)
figure()
plot(N_list, log2(eulerError), ’b’)
plot(N_list, log2(epsilon_euler(ht)), ’b--’)
plot(N_list, log2(heunError), ’g’)
plot(N_list, log2(epsilon_heun(ht)), ’g--’)
plot(N_list, log2(rk4Error), ’r’)
plot(N_list, log2(epsilon_rk4(ht)), ’r--’)
LegendList = [’${\epsilon}_{euler}$’, epsilon_euler_latex, ’${\epsilon}_{heun}$’, epsilon_heu
legend(LegendList, loc=’best’, frameon=False)
xlabel(’-log(h)’)
ylabel(’-log($\epsilon$)’)
# #savefig(’../figs/MMS_example2.png’)
The complete module ODEschemes is listed below and may easily be down-
loaded in your Eclipse/LiClipse IDE:
# src-ch1/ODEschemes.py
import numpy as np
from matplotlib.pyplot import plot, show, legend, hold,rcParams,rc, figure, axhline, close,\
xticks, title, xlabel, ylabel, savefig, axis, grid, subplots, setp
return z
z = np.zeros((np.size(time), np.size(z0)))
z[0,:] = z0
zp = np.zeros_like(z0)
for i, t in enumerate(time[0:-1]):
dt = time[i+1] - time[i]
zp = z[i,:] + np.asarray(func(z[i,:],t))*dt # Predictor step
z[i+1,:] = z[i,:] + (np.asarray(func(z[i,:],t)) + np.asarray(func(zp,t+dt)))*dt/2.0 # Correct
return z
z = np.zeros((np.size(time),np.size(z0)))
z[0,:] = z0
zp = np.zeros_like(z0)
for i, t in enumerate(time[0:-1]):
dt = time[i+1] - time[i]
dt2 = dt/2.0
k1 = np.asarray(func(z[i,:], t)) # predictor step 1
k2 = np.asarray(func(z[i,:] + k1*dt2, t + dt2)) # predictor step 2
k3 = np.asarray(func(z[i,:] + k2*dt2, t + dt2)) # predictor step 3
k4 = np.asarray(func(z[i,:] + k3*dt, t + dt)) # predictor step 4
z[i+1,:] = z[i,:] + dt/6.0*(k1 + 2.0*k2 + 2.0*k3 + k4) # Corrector step
return z
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 85
if __name__ == ’__main__’:
a = 0.2
b = 3.0
u_exact = lambda t: a*t + b
def f_local(u,t):
"""A function which returns an np.array but less easy to read
than f(z,t) below. """
return np.asarray([a + (u - u_exact(t))**5])
def test_ODEschemes():
"""Use knowledge of an exact numerical solution for testing."""
from numpy import linspace, size
tol = 1E-15
T = 2.0 # end of simulation
N = 20 # no of time steps
time = linspace(0, T, N+1)
z0 = np.zeros(1)
z0[0] = u_exact(0.0)
schemes = [euler, heun, rk4]
z0 = 2
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 86
order_approx = []
for i in range(Ndts+1):
z = scheme(f3, z0, time)
abs_error = abs(u_nonlin_analytical(z0, time)-z[:,0])
log_error = log2(abs_error[1:]) # Drop 1st elt to avoid log2-problems (1st elt is zer
max_log_err = max(log_error)
plot(time[1:], log_error, linestyles[i]+colors[iclr], markevery=N/5)
legends.append(scheme.func_name +’: N = ’ + str(N))
hold(’on’)
N *=2
time = linspace(0, T, N+1)
schemes_order[scheme.func_name] = order_approx
iclr += 1
legend(legends, loc=’best’)
xlabel(’Time’)
ylabel(’log(error)’)
grid()
N = N/2**Ndts
N_list = [N*2**i for i in range(1, Ndts+1)]
N_list = np.asarray(N_list)
figure()
for key in schemes_order:
plot(N_list, (np.asarray(schemes_order[key])))
def manufactured_solution():
""" Test convergence rate of the methods, by using the Method of Manufactured solutions.
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 87
f = lambdify([t], f)
dfdt = lambdify([t], dfdt)
d2fdt = lambdify([t], d2fdt)
RHS = lambdify([t], RHS)
def func(y,t):
yout = np.zeros_like(y)
yout[:] = [y[1], y[2], RHS(t) -y[0]- y[1]*y[2]]
return yout
for i in range(Ndts+1):
z = scheme(func, z0, time)
abs_error = abs(fanalytic-z[:,0])
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 88
log_error = log2(abs_error[1:]) # Drop 1st elt to avoid log2-problems (1st elt is zer
max_log_err = max(log_error)
plot(time[1:], log_error, linestyles[i]+colors[iclr], markevery=N/5)
legends.append(scheme.func_name +’: N = ’ + str(N))
hold(’on’)
figure()
for key in schemes_order:
plot(N_list, (np.asarray(schemes_order[key])))
# Plot theoretical n for 1st, 2nd and 4th order schemes
axhline(1.0, xmin=0, xmax=N, linestyle=’:’, color=’k’)
axhline(2.0, xmin=0, xmax=N, linestyle=’:’, color=’k’)
axhline(4.0, xmin=0, xmax=N, linestyle=’:’, color=’k’)
xticks(N_list, rotation=-70)
legends = schemes_order.keys()
legends.append(’theoretical’)
legend(legends, loc=’best’, frameon=False)
title(’Method of Manufactured Solution’)
xlabel(’Number of unknowns’)
ylabel(’Scheme order approximation’)
axis([0, max(N_list), 0, 5])
# savefig(’MMSODEschemes.png’, transparent=True)
# test using MMS and solving a set of two nonlinear equations to find estimate of order
def manufactured_solution_Nonlinear():
""" Test convergence rate of the methods, by using the Method of Manufactured solutions.
The coefficient function f is chosen to be the normal distribution
f = (1/(sigma*sqrt(2*pi)))*exp(-((t-mu)**2)/(2*sigma**2)).
The ODE to be solved is than chosen to be: f’’’ + f’’*f + f’ = RHS,
leading to f’’’ = RHS - f’’*f - f
"""
from numpy import linspace, abs
from sympy import exp, symbols, diff, lambdify
from math import sqrt, pi
from numpy import log, log2
t = symbols(’t’)
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 89
def func(y,t):
""" Function that returns the dfn/dt of the differential equation f + f’’*f + f’’’ = RHS
as a system of 1st order equations; f = f1
f1’ = f2
f2’ = f3
f3’ = RHS - f1 - f2*f3
Args:
y(array): solutian array [f1, f2, f3] at time t
t(float): current time
Returns:
yout(array): differantiation array [f1’, f2’, f3’] at time t
"""
yout = np.zeros_like(y)
yout[:] = [y[1], y[2], RHS(t) -y[0]- y[1]*y[2]]
return yout
if k==0:
h.append(time[1] - time[0]) # add this iteration’s dt to list h
z = scheme(func, z0, time) # Solve the ODE by calling the scheme with arguments. e.g:
fanalytic = f(time) # call analytic function f to compute analytical solutions at tim
ax[0][k].plot(time, z[:,0])
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 90
legendList.append(’$h$ = ’ + str(h[i]))
N *=2 # refine dt
# #savefig(’../figs/normal_distribution_refinement.png’)
def Newton_solver_sympy(error, h, x0):
""" Function that solves for the nonlinear set of equations
error1 = C*h1^p --> f1 = C*h1^p - error1 = 0
error2 = C*h2^p --> f2 = C h2^p - error 2 = 0
where C is a constant h is the step length and p is the order,
with use of a newton rhapson solver. In this case C and p are
the unknowns, whereas h and error are knowns. The newton rhapson
method is an iterative solver which take the form:
xnew = xold - (J^-1)*F, where J is the Jacobi matrix and F is the
residual funcion.
x = [C, p]^T
J = [[df1/dx1 df2/dx2],
[df2/dx1 df2/dx2]]
F = [f1, f2]
This is very neatly done with use of the sympy module
Args:
error(list): list of calculated errors [error(h1), error(h2)]
h(list): list of steplengths corresponding to the list of errors
x0(list): list of starting (guessed) values for x
Returns:
x(array): iterated solution of x = [C, p]
"""
from sympy import Matrix
#### Symbolic computiations: ####
C, p = symbols(’C p’)
f1 = C*h[-2]**p - error[-2]
f2 = C*h[-1]**p - error[-1]
F = [f1, f2]
x = [C, p]
def jacobiElement(i,j):
return diff(F[i], x[j])
Jacobi = Matrix(2, 2, jacobiElement) # neat way of computing the Jacobi Matrix
JacobiInv = Jacobi.inv()
#### Numerical computations: ####
JacobiInvfunc = lambdify([x], JacobiInv)
Ffunc = lambdify([x], F)
x = x0
F = np.asarray(Ffunc(x))
Jinv = np.asarray(JacobiInvfunc(x))
xnew = x - np.dot(Jinv, F)
x = xnew
#print "n, x: ", n, x
x[0] = round(x[0], 2)
x[1] = round(x[1], 3)
return x
ht = np.asarray(h)
eulerError = np.asarray(schemes_error["euler"])
heunError = np.asarray(schemes_error["heun"])
rk4Error = np.asarray(schemes_error["rk4"])
print epsilon_euler_latex
print epsilon_heun_latex
print epsilon_rk4_latex
N = N/2**(Ntds + 2)
N_list = [N*2**i for i in range(1, Ntds + 2)]
N_list = np.asarray(N_list)
print len(N_list)
print len(eulerError)
figure()
plot(N_list, log2(eulerError), ’b’)
plot(N_list, log2(epsilon_euler(ht)), ’b--’)
plot(N_list, log2(heunError), ’g’)
plot(N_list, log2(epsilon_heun(ht)), ’g--’)
plot(N_list, log2(rk4Error), ’r’)
plot(N_list, log2(epsilon_rk4(ht)), ’r--’)
LegendList = [’${\epsilon}_{euler}$’, epsilon_euler_latex, ’${\epsilon}_{heun}$’, epsilon_heu
legend(LegendList, loc=’best’, frameon=False)
xlabel(’-log(h)’)
ylabel(’-log($\epsilon$)’)
# #savefig(’../figs/MMS_example2.png’)
def plot_ODEschemes_solutions():
"""Plot the solutions for the test schemes in schemes"""
from numpy import linspace
figure()
T = 1.5 # end of simulation
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 92
N = 50 # no of time steps
time = linspace(0, T, N+1)
z0 = 2.0
manufactured_solution_Nonlinear()
#test_ODEschemes()
#convergence_test()
#plot_ODEschemes_solutions()
#manufactured_solution()
show()
3.0
=1
2.5 = -2
2.0
x
1.5
e
1.0
0.5
0.0
0.0 0.2 0.4 0.6 0.8 1.0
x
Figure 2.22: Solution of ODE for exponential growth with positive and negative
growth factor λ.
The numerical soltion yn at the n-th iteration may be related with the initial
solution y0 by applying the scheme (2.131) iteratively:
y1 = (1 + λh) y0
y2 = (1 + λh) y1 = (1 + λh)2 y0
..
.
which yields:
yn = (1 + λh)n y0 , n = 1, 2, 3, . . . (2.132)
To investigate stability we first introduce the analytic amplification factor
Ga as
y(xn+1 )
Ga = (2.133)
y(xn )
Note that y(xn ) represents the analytical solution of (2.128) at xn , whereas
yn denotes the numerical approximation. For the model problem at hand we see
that Ga reduces to:
y(xn+1 )
Ga = = exp[λ(xn+1 − xn )] = eλh = 1 + λh + (λh)2 /2 . . . (2.134)
y(xn )
yn = (1 − αh)n y0 (2.136)
yn+1
G= (2.139)
yn
yn+1
G= = 1 + λh = 1 − αh (2.140)
yn
For the current example G > 1 for λ > 0 and G < 1 for λ < 0. Compare with
the expression for the analytical amplification factor Ga (2.134).
As we shall see later on, the numerical amplification factor G(z) is some
function of z = hλ, commonly a polynomial for an explicit method and a
rational function for implicit methods. For consistent methods G(z) will be an
approximation to ez near z = 0, and if the method is p-th order accurate, then
G(z) − ez = O(z p+1 ) as z → 0.
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 95
|G(z)| ≤ 1 (2.142)
For example, in the case under examination, when α = 100 we must choose
h < 0.02 to produce an stable numerical solution.
By the introduction of the G in (2.140) we may rewrite (2.132) as:
yn = Gn y0 , n = 0, 1, 2, . . . (2.144)
Consequently, the Euler scheme will be stable and free of spurious oscillations
when
For example the numerical solution with α = 10 will be stable in the intervall
0 < h < 0.2, but exhibit oscillations in the interval 0.1 < h < 0.2. Note, however,
that the latter restriction on avoiding spurious oscillations will not be of major
concerns in pratice, as the demand for descent accuracy will dictate a far smaller
h.
h p 1
yn+1 = yn + · (fn + fn+1 ) = (1 − αh + (αh)2 ) · yn (2.149)
2 2
which shows that Heun’s method may be represented as a one-step method
for this particular and simple model problem. From (2.149) we find the numerical
amplification factor G (ref<eq:1618a);
yn+1 1
G= = 1 − αh + (αh)2 (2.150)
yn 2
Our taks is now to investigate for which values of αh the condition for stability
(2.142) may be satisfied.
First, we may realize that (2.150) is a second order polynomial in αh and
that G = 1 for αh = 0 and αh = 2. The numerical amplification factor has an
extremum where:
dG
= αh − 1 = 0 ⇒ αh = 1
dαh
and since
d2 G
>0
d(αh)2
this extremum is a minimum and Gmin = G(αh = 1) = 1/2. Consequently we
may conclude that 2.9 will be conditionally stable for (2.128) in the following
αh-range (stability range):
(λh)2 (λh)3
G = 1 + λh + + (2.155)
2! 3!
The limiting values for the G-polynomial are found for G = ±1 which for
real roots are:
−2.5127 < λh < 0 (2.156)
For RK4 we get correspondingly
• ODE solution has different time scales. For example, the ODE solution
can be written as the sum of a fast and a slow component as:
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 98
3 RK4
RK3
2
RK2
1 RK1
imaginary part of h
3
4 3 2 1 0 1 2 3 4
real part of h
Figure 2.23: Regions of stability for RK-methods order 1-4 for complex roots.
This last point is related to the definition of absolute stability regions for explicit
schemes, which is inversely proportional to λ. Note that λ is in fact the (only)
eigenvalue of the Jacobian for ODE (2.128)!
Example for stiff ODE: Consider the IVP
)
y 0 = λy − λsin(x) + cos(x), λ < 0,
(2.159)
y(0) = 1.
1.00 y(x)
yfast(x)
0.75 yslow(x)
0.50
0.25
0.00
y(x)
0.25
0.50
0.75
1.00
0 1 2 3 4 5 6
x
Figure 2.24: Solution (2.15.4) of stiff ODE IVP (2.15.4) for λ = −25.
A way to avoid having to use excessively small time steps, which can be even
harmful for the accuracy of the approximation, due to error accumulation, is
to use numerical schemes which have a larger region of absolute stability than
explicit schemes.
2.16 Exercises
Exercise 1: Solving Newton’s first differential equation us-
ing euler’s method
One of the earliest known differential equations, which Newton solved with series
expansion in 1671 is:
x3 x4 x5 x6
y(x) ≈ x − x2 + − + − (2.161)
3 6 30 45
Today it is possible to give the solution on closed form with known functions
as follows,
√ √
√
h x i 2 2 h x i
y(x) =3 2πe · exp x 1 + · erf (1 + x) − erf + 4 · 1 − exp[x 1 + −x
2 2 2 2
(2.162)
√
Note the combination 2πe.
a) Solve Eq. (2.160) using Euler’s method. Plot and compare with Newton’s
solution Eq. (2.161) and the analytical solution Eq. (2.162). Plot between x = 0
and x = 1.5
d2 Y
3Y A 3
2
= 2 − Y (2.163)
dX D D 2D
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 102
0.4
0.2
0.0
y −0.2
−0.4 analytical
euler
Newton
−0.6
0.0 0.2 0.4 0.6 0.8 1.0 1.2 1.4 1.6
x
A
Y
O X
where D is the middle depth, Y (X) is the wave height above middle depth
and A is the wave height at X = 0. The wave is symmetric with respect to
X = 0. See Figure 2.26. The coordinate system follows the wave.
By using dimensionless variables: x = X A Y
D , a = D , y = A , Eq. (2.163) can be
written as:
3
y 00 (x) = a 3 y(x) 1 − y(x) (2.164)
2
Z
dy p
√ = −2 arctanh 1−y
y 1−y
Hint 1. Solutions:
a) y(0.6) = 0.88, y 0 (0.6) = −0.569.
b) y(0.6) = 0.8337, y 0 (0.6) = −0.4858.
2 4
c) y ≈ 1 − x2 + x6 , y 0 ≈ −x + 23 x3
d y = cosh2 1x /√2 = 1+cosh1 √2·x
( ) ( )
1.00
0.98
0.96
0.94
0.92
0.90
0.88
euler, y(0.6)=0.88
0.86 heun, y(0.6)=0.8336
taylor, y(0.6)=0.8416
0.84
analytic, y(0.6)=0.8396
0.82
0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7
Hint 2. If you want you can use this template and fill in the lines where it’s
indicated.
#import matplotlib; matplotlib.use(’Qt4Agg’)
import matplotlib.pylab as plt
#plt.get_current_fig_manager().window.raise_()
import numpy as np
""" This script solves the problem with the solitary wave:
y’’ = a*3*y*(1-y*3/2)
y(0) = 1, y’(0) = 0
or as a system of first order differential equations (y0 = y, y1 = y’):
y0’ = y’
y1’ = a*3*y0*(1-y0*3/2)
y0(0) = 1, y1(0) = 0
"""
a = 2./3
h = 0.2 # steplength dx
x_0, x_end = 0, 0.6
Y0_heun = np.zeros_like(x)
Y1_heun = np.zeros_like(x)
Y0_heun[0] = 1
Y1_heun[0] = 0
y0_p =
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 105
y1_p =
f0_p =
f1_p =
"Fill in lines above"
g
θ
θ0
d2 θ g
+ sin θ = 0 (2.165)
dτ 2 l
with initial conditions
θ(0) = θ0 (2.166)
dθ
(0) = θ̇0 (2.167)
dτ
We introduce a dimensionless time t = gl t such that (2.165) may be written
p
as
CHAPTER 2. INITIAL VALUE PROBLEMS FOR ODES 106
θ(0) = θ0 (2.169)
θ̇(0) = θ̇0 (2.170)
Movie 1: mov-ch1/pendulum.mp4
a) Write a Python program that solves the ODE in (2.168) with the specified
initial conditions using Heun’s method, for given values of θ0 and θ̇0 . Set for
instance θ0 = 85o and θ̇0 = 0. (remember to convert use rad in ODE) Experiment
with different time steps ∆t, and carry out the computation for at least a whole
period. Plot both the amplitude and the energy function in (2.172) as functions
of t. Plot in separate figures.
b) Solve a) using Euler’s method.
c) Solve the linearized version of the ODE in (2.168):
using both euler and Heuns method. Plot all four solutions (Problem 2, 3a
and b) in the same figure. Experiment with different timesteps and values of θ0 .
2.0
EulerLin
1.5
HeunLin
EulerNonlin
1.0
HeunNonlin
Amplitude [rad]
0.5
0.0
−0.5
−1.0
−1.5
−2.0
0 2 4 6 8 10 12 14 16
Dimensionless time [-]
y
v0
m
v
α
mg
x
L
Figure 3.1: The trajectory of a ballistic object launched with an inital angle α.
108
CHAPTER 3. SHOOTING METHODS 109
y 00 = y(x) (3.1)
which is a second order, linear ODE with initial conditions:
and consequently an initial value problem which can be shown to have the
following analytical solution:
y 0 (x) = g(x)
g 0 (x) = p(x) · g(x) + q(x) · y(x) + r(x) (3.8)
We start the shooting method by chosing the given boundary value y(a) = α
as an initial condition. As we need an initial condition for y 0 (α) ≡ g(α) to solve
(3.8) as an initial value problem, we have to guess and initial value in a way
such that the boundary value y(b) = β is satisfied. As (3.6) is a linear ODE it
suffices to guess two values of s = g(α) ≡ y 0 (a). The correct value for the initial
value of s which gives the correct value of the solution y(b) = β, may then be
found by linear interpolation. Note that y(x) is always proportional to s when
the ODE is linear.
To quantify the goodness of how well the boundary value resulting from our
initial guess s = g(a) ≡ y 0 (a), we introduce a boundary value error function φ:
φ(s∗ ) = 0 (3.10)
The procedure may be outlined as follows:
φ1 − φ0 s1 · φ0 − φ1 · s0
ka = , kb = (3.12)
s1 − s0 s1 − s0
CHAPTER 3. SHOOTING METHODS 111
φ1 s0 − φ0 s1
s∗ = (3.13)
φ1 − φ0
which also may be presented on an incremental form as:
1
s − s0
s∗ = s1 + ∆s, ∆s = −φ1 · (3.14)
φ1 − φ0
Let us go back to our model boundary value example (3.1) with boundary
values as prescribed in (3.4) which may be presented as reduced system of first
order ODEs:
y 0 (x) = g(x)
(3.15)
g 0 (x) = y(x)
For the given values of b and β and s = y 0 (0) = g(0) the boundary value
error function in (3.9) takes the form:
In accordance with the shooting method we choose two initial values s0 = 0.2
og s1 = 0.7 and solve (3.15) as an initial value problem with e.g. and RK4-solver
with ∆x = 0.1 and obtain the following results:.
m sm φ(sm )
0 0.2 -0.7650
1 0.7 -0.1774
Note that we use m as a superindex for iterations, which will be used in case of
nonlinear equations. Substitution into (3.13) gives the s∗ = 0.8510. Subsequent
use of the s∗ -value in the RK4-solver yields φ(0.8510) = 0.0001. We observe a
good approximation to the correct value for the initial value may be found from
1
the analytical solution as: y 0 (0) = = 0.8509
sinh(1)
The presentation above with the shooting method is chosen as it can easily
be generalized to the solution of nonlinear ODEs.
Alternative approach for linear second order ODEs. For linear second
order ODEs the solution may be found in somewhat simpler fashion, by solving
the following sub-problems:
Sub-problem 1
y000 (x) = p(x) · y00 (x) + q(x) · y0 (x) + r(x), y0 (a) = α, y00 (a) = 0 (3.17)
and
Sub-problem 2
y100 (x) = p(x) · y10 (x) + q(x) · y1 (x), y1 (a) = 0, y10 (a) = 1 (3.18)
CHAPTER 3. SHOOTING METHODS 112
1.0
y(x)
y(x,s1 )
0.8
y(x,s0 )
0.6
y 0.4
0.2
0.0
0.0 0.2 0.4 0.6 0.8 1.0
x
Figure 3.2: The solution y(x) of a boundary value problem resulting from two
initial guesses y(x; s0 ) and y(x; s1 ).
That is, the two sub-problems differ only by the source term r(x) and the
boundary conditions. Notice that the condition y 0 (α) = 0 in (3.17) corresponds
to s0 = 0 and the condition y 0 (α) = 1 i (3.18) corresponds to s1 = 1.
Let y0 (x) represent the solution to (3.17) and y1 (x) be the solution to (3.18).
The complete solution of the boundary value problem in (3.6) with the boundary
conditions (3.7) may then be shown to be:
φ0
β − y0 (b)
y(x) = y0 (x) + · y1 (x) = y0 (x) − 1 · y1 (x) (3.19)
y1 (b) φ +β
y
U0
p(x) U
∂p
The equation of motion in the Y-direction simplifies to ∂Y = −ρg, whereas
the equation of motion in the X-direction has the form:
2
∂2U
∂U ∂p ∂ U
ρU · =− +µ +
∂X ∂X ∂X 2 ∂Y 2
d2 U 1 dp
= (3.20)
dY 2 µ dX
d2 u
= −P (3.21)
dy 2
with corresponding boundary conditions:
u0 (y) = u1 (y)
(3.24)
u01 (y) = −P
CHAPTER 3. SHOOTING METHODS 114
1.0
P = -8
P = -4
0.8 P = -2
P=0
P=4
0.6
P=8
y 0.4
0.2
Figure 3.4: Velocity profiles for Couette-Poiseuille flow with various pressure
gradients.
N=200
L = 1.0
y = np.linspace(0,L,N+1)
CHAPTER 3. SHOOTING METHODS 115
def u_a(y,dpdx):
return y*(1.0 + dpdx*(1.0-y)/2.0);
# Guessed values
s=[1.0, 1.5]
z0=np.zeros(2)
# Solve the initial value problem which is a solution to the boundary value problem
z = rk4(f, z0, y)
plot(z[:,0],y,’-.’)
legends.append(’rk4: dp=’+str(dpdx))
# Plot the analytical solution
plot(u_a(y, dpdx),y,’:’)
legends.append(’exa: dp=’+str(dpdx))
d2 U P q
2
+ U =− (L2 − X 2 ), P > 0 (3.26)
dX EI 2EI
CHAPTER 3. SHOOTING METHODS 116
q
A B P
U(X)
X
L L
Figure 3.5: Simply supported beam with an evenly distributed load q per length
unit.
x P 2 P L2
x= , u= · U, θ = (3.28)
L qL2 EI
which by substitution in (3.26) and (3.27) yield:
d2 u (1 − x2 )
2
+ θ2 · u = θ2 , −1 < x < 1 (3.29)
dx 2
with the corresponding boundary conditions:
(1 − x2 )
1 cos(θx)
u(x) = 2 · −1 − (3.31)
θ cos(θ) 2
πEI
The buckling load for this case is given by Pk = such that
4L2
π
0≤θ≤ (3.32)
2
Numerical solution. We wish to solve the second order linear ODE in (3.29)
by means of shooting methods and choose the alternative approach as outlined
in equation (3.19).
Two similar sub-problems are then two be solved; they differ only with the
source term and the boundary conditions.
CHAPTER 3. SHOOTING METHODS 117
Sub-problem 1
(1 − x2 )
u000 (x) = −θ2 u0 (x) + θ2 , u0 (−1) = 0, u00 (−1) = 0 (3.33)
2
(3.34)
Sub-problem 2
From the superposition principle for linear equation given in equation n (3.19)
a complete solution is obtained as a combination of the solutions to the two
sub-problems:
u0 (1)
u(x) = u0 (x) − · u1 (x) (3.37)
u1 (1)
Now, to solve the equation numerically we write (3.33) and (3.35) as a system
of first order ODEs:
Sub-problem 1 as a system of first order ODEs
For convenience we introduce the notation u0 = y1 and u0o = y2 :
# src-ch2/beam_deflect_shoot_constant.py;ODEschemes.py @ git@lrhgit/tkt4140/src/src-ch2/ODEschemes.py
from ODEschemes import euler, heun, rk4
from numpy import cos, sin
import numpy as np
from matplotlib.pyplot import *
return yout
N = 20 # number of elements
L = 1.0 # half the length of the beam
x = np.linspace(-L, L, N + 1) # allocate space
theta = 1 # PL**2/EI
theta2 = theta**2
u0 = solver(SubProblem1, y0Sys1,x)
u1 = solver(SubProblem2, y0Sys2,x)
plot(x,u,’y’,)
plot(x,ua,’r:’)
legendList.append(’shooting technique’)
legendList.append(’analytical solution’)
## Add the labels
legend(legendList,loc=’best’,frameon=False)
ylabel(’u’)
xlabel(’x’)
grid(b=True, which=’both’, color=’0.65’,linestyle=’-’)
#savefig(’../fig-ch2/beam_deflect_shoot_constant.png’, transparent=True)
#savefig(’../fig-ch2/beam_deflect_shoot_constant.pdf’, transparent=True)
#sh
show()
0.40
0.35
0.30
0.25
0.20
u
0.15
0.10
0.05 shooting technique
analytical solution
0.001.0 0.5 0.0 0.5 1.0
x
Figure 3.6: The anyalytical and numerical solutions for simply supported beam
with an evenly distributed load q per length unit. The results are produced with
a shooting method implemented in beam_deflect_shoot_constant.py.
We denote the second area moment at X = 0 as I0 , and let the second area
moment I vary in the following manner:
I0
I(X) = , n = 2, 4, 6, . . . (3.42)
1 + (X/L)n
Consider a rectangular cross-sectional area with a constant width b and a
varying height h:
h0
h(X) = (3.43)
[1 + (X/L)n ]1/3
where h0 denotes h at X = 0. From equation (3.26) in example (3.1.2) we
have:
d2 U P q
+ U =− (L2 − X 2 ) (3.44)
dX 2 EI 2EI
dU (0)
U (−L) = U (L) = 0, =0 (3.45)
dX
We start by computing the moment distribution M (X) in the beam, which
is related with the displaceent by the following expression:
d2 U M
2
=− (3.46)
dX EI
By substitution of equation (3.46) in (3.44) and two esubsequent differentia-
tions we get:
d2 M P
2
+ @, M = −q (3.47)
dX EI
To represent the resulting second order ODE on a more convenient and
generic form we introduce the dimensionless variables:
X M
x= , m= (3.48)
L qL2
and let
EI0
P = (3.49)
L2
such that equation (3.47) becomes:
Once the moment distribution has been computed, the dimensionless dis-
placement u(x) may retrieved from:
1
u(x) = m(x) − (1 − x2 ) (3.52)
2
The displacement in physical dimensions U may subsequently computed from
the dimensionless u:
qL4
U= u (3.53)
EI0
Further, the differential equaiton for u(x) may be found by substitution of
equation (3.52) into (3.50):
1
u00 (x) + (1 + xn ) · u(x) = − (1 − x2 ) · (1 + xn ) (3.54)
2
By expoliting the symmetry in the problem, we solve equation (3.50) numeri-
cally with a shooting method. The boundary conditions are:
m(1) = 0, m0 (0) = 0 (3.55)
The dimensionless displacement u(x) is subsequently computed from equation
(3.52). We have implemented the outlined approach in beam_deflect_shoot_varying.py
in such a way that one easily may choose between various RK-solvers (euler,
heun, og rk4) from the module ODEschemes.
We represent the second order ODE (3.50) as a system of first order ODEs
by introducing the following conventions m(x) = y1 (x) og m0 (x) = y2 (x):
y10 = y2 (3.56)
y20 n
= − (1 + (1 + x ) · y1 )
and the boundary conditions become:
y2 (0) = y1 (1) = 0 (3.57)
In this case y2 (0) ≡ m0 (0) is given, which leaves m(0) ≡ y1 (0) to be guessed
such that the boundary condition at the other end m(1) ≡ y1 (1) = 0 is fulfilled.
To express the approach algorithmically as previously, we let s = y1 (0) and let
the bondary value error function be φ(s) = y1 (1; s).
φ0
s∗ = (3.58)
φ0 − φ1
The simplfied expression for s in equation (3.58) may be found from (3.14)
by setting s0 = 0 og s1 = 1.
One should be aware of that the choice in (3.58) is not necessarily always a
good choice even though the ODE is linear. If the solution is of the kind eαx
when both α and x are large, we may end up outside the allowable range even
for double precision which is approximately 10308 . In our example above this is
not a problem and we may use (3.58) and we have implemented the approach
in beam_deflect_shoot_varying.py as shown below where both the moment
and deflection is computed for n = 2.
# src-ch2/beam_deflect_shoot_varying.py;ODEschemes.py @ git@lrhgit/tkt4140/src/src-ch2/ODEschemes.py;
N = 10 # number of elements
L = 1.0 # half the length of the beam
x = np.linspace(0,L,N+1) # vector of
theta = 1 # PL**2/EI
theta2 = theta**2
h0=1 # height of beam at x = 0
n=2 # polynomial order to go into h(x)
h=h0/(1+(x/L)**n)**(1/3.) # height of beam assuming constant width
solvers = [euler, heun, rk4]
solver = solvers[2]
y02 = [s[1], 0]
m0 = solver(f, y01, x)
m1 = solver(f, y02, x)
phi0 = m0[-1,0]
phi1 = m1[-1,0]
sfinal = phi0/(phi0 - phi1) # find correct initial value of moment m(x=0) using secant method
y03 = [sfinal, 0]
mfinal = solver(f,y03,x)
m = mfinal[:, 0] # extract moment from solution data
plot(x, m, ’b’,)
legendList.append(’m’)
plot(x, u, ’r’,)
legendList.append(’u’)
## Add the labels
legend(legendList,loc=’best’,frameon=False)
ylabel(’m, u’)
xlabel(’x’)
grid(b=True, which=’both’, axis=’both’,linestyle=’-’)
show()
1.0
m
0.8 u
0.6
m, u
0.4
0.2
0.0
−0.2
0.0 0.2 0.4 0.6 0.8 1.0
x
y 00 (x) = 32 y 2
(3.59)
y(0) = 4, y(1) = 1
Our model problem (3.59) may be proven to have two solutions.
Solution I:
4
yI = (3.60)
(1 + x)2
−2
−4
y
−6
−8
−10 yI
yII
−12
0.0 0.2 0.4 0.6 0.8 1.0
x
Figure 3.8: The two solutions yI og yII of the non-linear ODE (3.59).
We will use the shooting method to find s = y 0 (0) = y1 (0) such that the
boundary condition y0 (1) = 1 is satisfied. Our boundary value error function
becomes:
sm+1 = sm + ∆s (3.64)
where
sm − sm−1
∆s = sm+1 − sm = −φ(sm ) · , m = 1, 2, . . .
φ(sm ) − φ(sm−1 )
ϕ
ϕ(sm-1)
ϕ(sm)
ϕ(sm+1)
sm-1 sm sm+1 s* s
Figure 3.9: An illustration of the usage of the secant method label to find the
zero for a non-linear boundary value error function.
Iteration process
• sm−1 ← sm
• sm ← sm+1
• φ(sm−1 ) ← φ(sm )
∆s
sm+1 < ε2 (3.66)
The crietria (3.65) and (3.66) are frequently used in combination with:
N=20
L = 1.0
x = np.linspace(0,L,N+1)
# Guessed values
#s=[-5.0, 5.0]
z0=np.zeros(2)
z0[0] = 4.0
z = solver(f,z0,x)
phi0 = z[-1,0] - beta
nmax=10
eps = 1.0e-3
phi = []
for s in s_guesses:
z0[1] = s
z = solver(f,z0,x)
phi.append(z[-1,0] - beta)
show()
close()
Based on our plots of φ we may now provided qualified inital guesses for
and choose s0 = −3.0 og s1 = −6.0. The complete shooting method for the our
boundary value problem may be found here:
# src-ch2/non_lin_ode.py;ODEschemes.py @ git@lrhgit/tkt4140/src/src-ch2/ODEschemes.py;
N=40
L = 1.0
x = np.linspace(0,L,N+1)
def dsfunction(phi0,phi1,s0,s1):
if (abs(phi1-phi0)>0.0):
return -phi1 *(s1 - s0)/float(phi1 - phi0)
else:
return 0.0
def y_analytical(x):
return 4.0/(1.0+x)**2
# Guessed values
# s=[-3.0,-9]
s=[-40.0,-10.0]
z0=np.zeros(2)
z0[0] = 4.0
z0[1] = s[0]
z = solver(f,z0,x)
phi0 = z[-1,0] - beta
nmax=10
eps = 1.0e-3
for n in range(nmax):
z0[1] = s[1]
z = solver(f,z0,x)
phi1 = z[-1,0] - beta
ds = dsfunction(phi0,phi1,s[0],s[1])
s[0] = s[1]
s[1] += ds
phi0 = phi1
print ’n = {} s1 = {} and ds = {}’.format(n,s[1],ds)
if (abs(ds)<=eps):
print ’Solution converged for eps = {} and s1 ={} and ds = {}. \n’.format(eps,s[1],ds)
break
plot(x,z[:,0])
legends.append(’y’)
plot(x,y_analytical(x),’:^’)
legends.append(’y analytical’)
After selection ∆x = 0.1 and using the RK4-solver, the following iteration
output may be obtained:
After four iterations s = y 0 (0) = −7.9989 , while the analytical value is −8.0. The
code non_lin_ode.py illustrates how our non-linear boundary value problem
may be solve with a shooting method and offer graphical comparison of the
numerical and analytical solution.
The secant method is simple and efficient and does not require any knowledge
of the analytical expressions for the derivatives for the function for which the
zeros are sought, like e.g. for Newton-Raphsons method. Clearly a drawback is
that two initial guesses are mandatory to start the itertion process. However, by
using some physical insight for the problemn and ploting the φ-function for a
wide range of s-values, the problem is normally possible to deal with.
A
θ y
P δ
ɭ
C B
ɭh
dθ P L2
= (lh − x) (3.69)
dl EI
From figure 3.10 we may deduce the following geometrical relations:
dx dy
= cos θ, = sin θ (3.70)
dl dl
Further, differentiation of (3.69) with respect to the arch length l and substi-
tution of (3.70) yields:
d2 θ P L2
+ cos θ = 0 (3.71)
dl2 EI
For convenience we introduce the parameter α which is defined as:
P L2
α2 = (3.72)
EI
As a result we must solve the following differential equations:
d2 θ
+ α2 cos θ = 0 (3.73)
dl2
dy
= sin θ (3.74)
dl
with the following boundary conditions:
dθ
y(0) = 0, θ(0) = 0, (1) = 0 (3.75)
dl
The first two boundary conditions are due the anchoring in A, whereas the
latter is due to a vanishing moment in B. The analytical solution of this problem
may be found in appendix G, section G.3 in Numeriske Beregninger.
Numerical solution
CHAPTER 3. SHOOTING METHODS 131
z00 = z1
z10 = −α2 cos z0 (3.76)
z20 = sin z0
Additionally we find:
s∗
lh = (3.79)
α2
Due to the nonlinear nature of (3.76) the function (3.78) will be nonlinear
too and we will use the secant method to find s∗ . To start the secant iterations
we need two intial guesses s0 og s1 . Suitable initial guesses may be found by
first looking at φ(s) graphically. The python-code phi_plot_beam_shoot.py
produces Figure 3.11.
# src-ch2/phi_plot_beam_shoot.py;ODEschemes.py @ git@lrhgit/tkt4140/src/src-ch2/ODEschemes.py;
from ODEschemes import euler, heun, rk4
from numpy import cos, sin
import numpy as np
from matplotlib.pyplot import *
N=20
L = 1.0
y = np.linspace(0,L,N+1)
CHAPTER 3. SHOOTING METHODS 132
z0=np.zeros(3)
phi = []
for s_guess in s_guesses:
z0[1] = s_guess
z = solver(f,z0,y)
phi.append(z[-1,1] - beta)
0
(s)
−1
−2
−3
−4
1.0 1.5 2.0 2.5 3.0 3.5
s
Given this approximation of the zero we may provide two initial guesses
s0 = 2.5 and s1 = 5.0 which are ’close enough’ for the secant method to
converge. An example for how the solution may be found and presented is given
in beam_deflect_shoot.py.
# src-ch2/beam_deflect_shoot.py;ODEschemes.py @ git@lrhgit/tkt4140/src/src-ch2/ODEschemes.py;
from ODEschemes import euler, heun, rk4
from numpy import cos, sin
import numpy as np
from matplotlib.pyplot import *
N=20
L = 1.0
y = np.linspace(0,L,N+1)
def dsfunction(phi0,phi1,s0,s1):
if (abs(phi1-phi0)>0.0):
return -phi1 *(s1 - s0)/float(phi1 - phi0)
else:
return 0.0
alpha2 = 5.0
beta=0.0 # Boundary value at y = L
# Guessed values
s=[2.5, 5.0]
z0=np.zeros(3)
z0[1] = s[0]
z = solver(f,z0,y)
phi0 = z[-1,1] - beta
nmax=10
eps = 1.0e-10
for n in range(nmax):
z0[1] = s[1]
z = solver(f,z0,y)
phi1 = z[-1,1] - beta
ds = dsfunction(phi0,phi1,s[0],s[1])
s[0] = s[1]
s[1] += ds
phi0 = phi1
CHAPTER 3. SHOOTING METHODS 134
plot(y,z[:,0])
legends.append(r’$\theta$’)
plot(y,z[:,1])
legends.append(r’$d\theta/dl$’)
plot(y,z[:,2])
legends.append(r’$y$’)
3.5
3.0 d /dl
2.5 y
=5.0
2.0
2
1.5
,d /dl,y for
1.0
0.5
0.0
0.50.0 0.2 0.4 0.6 0.8 1.0
y/L
Figure 3.12: Solutions generated with a shooting method for large deflections
of a cantilever subjected to a point load.
CHAPTER 3. SHOOTING METHODS 135
∂T ∂2T
=α (3.80)
∂τ ∂X 2
where τ and X denote time and spatial coordinates, respectively. The
temperature T is a function of time and space T = T (X, τ ), and α is the thermal
diffusivity. (See appendix B in Numeriske Beregninger for a derivation)
X
Ts
T0
∂u ∂2u
= , 0<x<∞ (3.84)
∂t ∂x2
accompanied by the dimensionless initial condition:
x̄ = a x and t̄ = b t (3.87)
where a and b are some positive constants. Substitution of equation (3.87)
into equation (3.84) yields the following equation:
∂u a2 ∂ 2 u
= (3.88)
∂ t̄ b ∂ x̄2
We chose b = a2 to bring the scaled equation (3.88) on the canonical,
dimensionless form of equation (3.84) with the boundary conditions:
and the factor 2 has been introduced to obtain a simpler end result only. By
introducing the similariy variable η in equation (3.91), we transfor the solution
(and the differential equation) from being a function of x and t, to only depend
on one variable, namely η. A consequence of this transformation is that one
profile u(η), will define the solution for all x and t, i.e. the solutions will be
similar and is denoted a similarity solution for that reason and (3.92) a similarity
transformation.
The Transformation in equation (3.92) is often referred to as the Boltzmann-
transformation.
The original PDE in equation (3.84) has been transformed to an ODE,
which will become clearer from the following. We have introduced the following
variables:
τ ·α x X
t= , η= √ = √ (3.93)
L2 2 t 2 τα
Let us now solve equation (3.84) analytically and introduce:
u = f (η) (3.94)
with the boundary conditions:
∂u ∂2u
By substituting the expressions for and in equation (3.84) we
∂t ∂x2
transform the original PDE in equation (3.84) to an ODE in equation (3.96) as
stated above:
1 η
f 00 (η) + f 0 (η) = 0
4t 2t
or equivalently:
f 00 (η)
= −2η
f 0 (η)
CHAPTER 3. SHOOTING METHODS 138
1.0
0.8
0.6
erf
erf , erfc
erfc
0.4
0.2
T − T0 0 − (−15) 3
u= = = = 0.4286
Ts − T0 20 − (−15) 7
Some values for erf(η) are tabulated below :
x erf(η) η erf(η)
0.00 0.00000 1.00 0.84270
0.20 0.22270 1.20 0.91031
0.40 0.42839 1.40 0.95229
0.60 0.60386 1.60 0.97635
0.80 0.74210 1.80 0.98909
From the tabulated values for erf (x) and (3.101) we find erf(η) = 0.4286 →
η ≈ 0.4 which according to (3.93) yields:
√ √
X = 0.4 · 2 τ · α = 0.8 · 720 · 5 · 10−4 = 0.48m
While we have used a constant value for the diffusivity α, in can vary in
the range α ∈ [3 10−4 . . . 10−3 ] m2 /s, and the soil will normaly contain some
humidity.
CHAPTER 3. SHOOTING METHODS 140
Y U=0
U(Y,τ)
U = U0
Figure 3.15: Stokes’ first problem: flow over a suddenly started plate.
∂U ∂2U
=ν , 0<Y <∞ (3.105)
∂τ ∂Y 2
The assumption of a suddenly started plate implies the initial condition:
U (U, 0) = 0 (3.106)
and the assumptions of no slip at the plate and a quiescent fluid far away
from the wall implies the following boundary conditions:
∂u ∂2u
= , 0<y<∞ (3.109)
∂t ∂y 2
with corresponding initial condition:
u(y, 0) = 0 (3.110)
and boundary conditions
u(0, t) = 1
(3.111)
u(∞, t) = 0
We now realize that the Stokes’ first problems is the same as the on given by
(3.84) and (3.85), save for a substitution of 0 and 1 in the boundary conditions. .
The solution of (3.109) and (3.110) and the appropriate boundary conditions
(3.111) is:
y
u(y, t) = 1 − erf(η) = erfc(η) = erfc √ (3.112)
2 t
which has the following expression in physical variables:
Y
U (Y, τ ) = U0 erfc √ (3.113)
2 τ ·ν
U = constant
y
v δ
∂u ∂u 1 dp ∂2u
u +v =− +ν 2 (3.115)
∂x ∂y ρ dx ∂y
For the case of a constant free stream velocity U = U0 we may show that
dp
the streamwise pressure gradient dx = 0. (see equation (C.1.8) in appendix C in
Numeriske Beregninger), and the equation eq:23028) reduces to:
∂u ∂u ∂2u
u +v =ν 2 (3.116)
∂x ∂y ∂y
with corresponding boundary conditions:
u(0) = 0 (3.117)
u → U0 for y → δ (3.118)
The physical velocity components may be derived from the stream function
by:
u v η · f 0 (η) − f (η)
= f 0 (η), = √ (3.122)
U0 U0 2Rex
where the Reynolds number has been introduced as
U0 x
Rex = (3.123)
ν
The no slip condition u = 0 for y = 0 will concequently correspond to
f 0 (0) = 0 ad f 0 (η) = Uu0 in equation (3.122).
In abscence of suction and blowing at the boundary for η = 0, the other no
slip condition, i.e v = 0 for η = 0, correponds to f (0) = 0 from equation (3.122).
Further, the condition for the free stream of u → U0 from equation (3.118),
corresponds to f 0 (η) → 1 for η → ∞.
Consequently, the boundary conditions for the boundary layer reduce to:
f00 = f1
f10 = f2 (3.126)
f20 = −f0 f2
As f 00 (0) is unknown we must find the correct value with the shooting method,
00
f (0) = f2 (0) = s. Note that s, which for the Blasius equaiton corresponds to the
wall shear stress, must be chosen such that the boundary condition f1 (η∞ ) = 1
is satisfied. We formulate this condition mathematically by introducing the
boundary value error function φ:
sm − sm−1
sm+1 = sm + ∆s, ∆s = −φ(sm ) · , m = 1, 2. . . .
φ(sm ) − φ(sm−1 )
(3.129)
To formulate the iteration procedure we assume two values sm−1 sm to be
know, which intitially correspond to s0 and s1 . The interation procedure the
becomes:
3. Update
• sm−1 ← sm
• sm ← sm+1
• φ(sm−1 ) ← φ(sm )
For this particular problem, the Blasisus equation, the correct value is s∗ =
0.46960....
Note that the convergence criteria is ∆s < ε, i.e. a test on the absolute value
∆s
of ∆s, whereas a relative metric would be .
s
For some problems it may be difficult to guess appropriate initial guesses s0
og s1 to start the iteration procedure.
A very simple procedure to get an overview of the zeros for a generic function
is to plot it graphically. In Figure 3.17 we have plotted φ(s) for a very wide
range s ∈ [0.05, 5.0].
The computations of φ were performeed with the code phi_plot_blasius_shoot_v2.py,
and by visual inspection we observe that the zero is in the range [0.45, 0.5],
i.e. much more narrow that our initial guess.
CHAPTER 3. SHOOTING METHODS 145
# src-ch2/phi_plot_blasius_shoot_v2.py;ODEschemes.py @ git@lrhgit/tkt4140/src/src-ch2/ODEschemes.py;
# Guessed values
#s=[0.1,0.8]
s_guesses=np.linspace(0.01,5.0)
z0=np.zeros(3)
phi = []
for s_guess in s_guesses:
z0[2] = s_guess
u = solver(fblasius, z0, x)
phi.append(u[-1,1] - beta)
CHAPTER 3. SHOOTING METHODS 146
plot(s_guesses,phi)
Once approriate values for s0 and s1 have been found we are ready to find
the solution of the Blasius equation with the shooting method as outlined in
blasius_shoot_v2.py.
# src-ch2/blasius_shoot_v2.py;ODEschemes.py @ git@lrhgit/tkt4140/src/src-ch2/ODEschemes.py;
def dsfunction(phi0,phi1,s0,s1):
if (abs(phi1-phi0)>0.0):
return -phi1 *(s1 - s0)/float(phi1 - phi0)
else:
return 0.0
N = 400 # no x-values
x = linspace(xmin, xmax, N+1)
# Guessed values
s=[0.1,0.8]
z0=np.zeros(3)
z0[2] = s[0]
## Compute phi0
u = solver(fblasius, z0, x)
phi0 = u[-1,1] - beta
nmax=10
eps = 1.0e-3
CHAPTER 3. SHOOTING METHODS 147
for n in range(nmax):
z0[2] = s[1]
u = solver(fblasius, z0, x)
phi1 = u[-1,1] - beta
ds = dsfunction(phi0,phi1,s[0],s[1])
s[0] = s[1]
s[1] += ds
phi0 = phi1
print ’n = {} s1 = {} and ds = {}’.format(n,s[1],ds)
if (abs(ds)<=eps):
print ’Solution converged for eps = {} and s1 ={} and ds = {}. \n’.format(eps,s[1],ds)
break
plot(u[:,1],x,u[:,2],x)
xlabel(’u og u\’’)
ylabel(’eta’)
legends=[]
legends.append(’velocity’)
legends.append(’wall shear stress’)
legend(legends,loc=’best’,frameon=False)
title(’Solution of the Blaisus eqn with ’+str(solver.func_name)+’-shoot’)
show()
close() #Close the window opened by show()
6
f'( ) - velocity
5 f''( ) - wall shear stress
0
0.0 0.2 0.4 0.6 0.8 1.0 1.2
Figure 3.18: Solutions of the Blasius equation: dimensionles velocity f 0 (η) and
shear stress f 00 (η).
In Figure 3.18 the dimensionless velocity f 0 (η) and shear stress f 00 (η) are
plotted. The maxial f 00 (η) is inntreffer for η = 0 , which means that the maximal
shear stress is at the wall.
Notice further that f 000 (0) = 0 means f 00 (η) has a vertical tanget at the wall.
CHAPTER 3. SHOOTING METHODS 148
ϕ,Ψ
ϕ = A∙s + B∙r + C
Ψ = D∙s + E∙r + F
r
Ψ=0
(s*,r*)
ϕ=0
s
Figure 3.19: The boundary value error functions for boundary value problems
with two unknown initial conditions.
φ=A·s+B·r+C
(3.130)
ψ =D·s+E·r+F
where A, B, C, · · · , F are constants to be determined. The correct values r∗
og s∗ satisfy φ(r∗ , s∗ ) = 0 and ψ(r∗ , s∗ ) = 0. (See Figure 3.19.)
Consequently we have six constants which may be found from the equations;
three equations for φ:
CHAPTER 3. SHOOTING METHODS 149
φ0 = A · s0 + B · r0 + C
φ1 = A · s1 + B · r1 + C (3.131)
φ(2) = A · s(2) + B · r(2) + C
ψ 0 = D · s0 + E · r0 + F
ψ 1 = D · s1 + E · r1 + F (3.132)
(2) (2) (2)
ψ =D·s +E·r +F
where s0 , s1 , s(2) , r0 , r1 og r(2) are initial guesses for the unknown initial
values. For convenience we choose the following pairs of initial guesses:
s0 = 0, r0 = 0
s1 = 0, r1 = 1 (3.133)
(2) (2)
s = 1, r =0
A = φ(2) − φ0 , B = φ 1 − φ0 , C = φ0 ,
(3.134)
D = ψ (2) − ψ 0 , E = ψ1 − ψ0 , F = ψ0
The correct values r∗ og s∗ which satisfies φ(r∗ , s∗ ) = 0 and ψ(r∗ , s∗ ) = 0,
i.e. A · s∗ + B · r∗ + C = D · s∗ + E · r∗ + C = 0 may be expressed by the
coefficients:
E·C −B·F A·F −D·C
s∗ = , r∗ = (3.135)
D·B−A·E D·B−A·E
which after substitution of A, B, C, . . . , F results in:
ψ 1 · φ0 − φ1 · ψ 0
s∗ =
(ψ (2) − ψ 0 ) · (φ1 − φ0 ) − (φ(2) − φ0 ) · (ψ 1 − ψ 0 )
(3.136)
(2) 0 (2) 0
φ ·ψ −ψ ·φ
r∗ =
(ψ (2) − ψ 0 ) · (φ1 − φ0 ) − (φ(2) − φ0 ) · (ψ 1 − ψ 0 )
The shooting method solution procedure for a linear ODE boundary value
problem with two initial guesses then becomes:
CHAPTER 3. SHOOTING METHODS 150
1. Solve the ODE for the three pairs of initial guesses r og s as given in
(3.133)
2. Find the correspondning values of φ and ψ.
3. Compute the correct initial values r∗ og s∗ from equation (3.136)).
R
g
t
∇
M X
V W
V H
M
d4 W H −X
+ B · W = −γ (3.137)
dX 4 D
where:
CHAPTER 3. SHOOTING METHODS 151
12(1 − ν 2 ) Et3
B= , D= , γ = ρg
R 2 t2 12(1 − ν 2 )
and ν is Poisson’s ratio, E the E-modulus and ρ the density of the fluid. For
convenience we introduce dimensionless variables:
2
X E t
x= and w= W (3.138)
H γHt R
which after substitution in equation (3.137) results in the following dimen-
sionless, fourth order ODE:
d4 w 3(1 − ν 2 )H 4
+ 4 β 4 w = −4 β 4 (1 − x) where β4 = (3.139)
dx4 R 2 t2
Boundary conditions
To solve the ODE in equation (3.139) we need to supply some boundary
conditions at both ends. At x = 0:
dW
W = 0, and = 0 (fixed beam)
dX
At the other end, for X = H both the moment and the shear force must be
zero, which mathematically corresponds to:
d2 W d3 W
M = −D 2
= 0, V = −D =0
dX dX 3
d2 w
The dimensionless expression for the moment is m(x) = − , while the
dx2
d3 w
dimemsionless shear force has the expression: v(x) = − 3 .
dx
In dimensionless variables the boundary conditions takes the form:
dw
w = 0, =0 for x = 0 (3.140)
dx
d2 w d3 w
= 0, =0 for x = 1 (3.141)
dx2 dx3
For our example we select a container with the following dimensions R = 8.5m,
H = 7.95m, t = 0.35m, γ = 9810N/m3 (water), ν = 0.2, and the E-modulus
E = 2 · 104 MPa. For these values the nondimensional parameter β = 6.0044.
Numerical solution with the shooting method for two unknown
initial values
First we need to reformulate the fourth order ODE in (3.139) as a system of
first order ODEs by following the procedure outlined in 2.4.
By using the following auxilliary variables w = y0 , w0 = y00 = y1 , w00 = y10 =
y2 , w000 = y20 = y3 we may write (3.137) as a system of four, first order ODEs:
CHAPTER 3. SHOOTING METHODS 152
y00 = y1
y10 = y2
y20 = y3 (3.142)
y30 4
= −4β (y0 + 1 − x)
• Solve (3.142) three times, always with y0 (0) and y1 (0) = 0. The values of
r and s for each solution is taken from (3.133).
• Compute the correct values for r∗ and s∗ from (3.136).
Below you find a python implementation of the procedure. Rune the code and
check for yourself if the boundary conditions are satisfied.
# src-ch2/tank1.py;ODEschemes.py @ git@lrhgit/tkt4140/src/src-ch2/ODEschemes.py;
dydx[0] = y[1]
dydx[1] = y[2]
dydx[2] = y[3]
dydx[3] = -4*beta4*(y[0]+1-x)
return dydx
# evaluate the boundary value error functions for the initial guesses in s and r
for k in range(3):
y0 = np.array([0, 0, s[k], r[k]])
y = solver(tank1, y0, x)
phi[k] = y[-1, 2]
psi[k]=y[-1, 3]
# calculate correct r and s
denominator = (psi[2] - psi[0])*(phi[1] - phi[0]) - (phi[2] - phi[0])*(psi[1] - psi[0])
rstar = (phi[2]*psi[0] - psi[2]*phi[0])/denominator
sstar = (psi[1]*phi[0] - phi[1]*psi[0])/denominator
print ’rstar’, rstar, ’sstar’, sstar
plot(x,-y[:,3]/beta**2)
plot(x,-y[:,2]/beta)
legendList.append(r’$v(x)/\beta^2$ ’)
legendList.append(r’$m(x)/\beta$ ’)
# Add the labels
legend(legendList,loc=’best’,frameon=False)
ylabel(’v, m’)
xlabel(’x’)
grid(b=True, which=’both’, color=’0.65’,linestyle=’-’)
#savefig(’../figs/tank1.pdf’)
show()
CHAPTER 3. SHOOTING METHODS 154
Varying wall thickness. In the following we will modify the example above
of liquid in a cylindrical container by allowing for a wall thickness which varies
linearly from t0 at the bottom to t1 at the top.
R
g t1
∇
M X W
H
V
V
M
t0
t0 − t1
Based on t0 and t1 , we introduce α = the steepness of the wall
t0
thickness, which allows for the thickness t to be represented as:
X
t= 1−α · t0 , 0 ≤ X ≤ H (3.144)
H
The differential equaiton for the displacement W for this situation is given
by:
d2 d2 W
tW
D +E = −γ(H − X) (3.145)
dX 2 dX 2 R2
Et3
where D = , γ = ρg, with the same meaning as in the previous
12(1 − ν 2 )
part of the example.
Dimensionless form
2
3(1 − ν 2 ) 4
X E t0
x= , w= W, β4 = H (3.146)
H γHt0 R R2 t20
CHAPTER 3. SHOOTING METHODS 155
d2 2
3d w
(1 − αx) + 4β 4 (1 − αx) · w = −4β 4 (1 − x) (3.147)
dx2 dx2
which may be differentiated to yield:
y00 = y1
y10 = y2
y20 = y3 (3.150)
6α 6α2 4β 4 4β 4 (1 − x)
y30 = y3 − 2 y2 − 2 y0 −
z z z z3
with the following boundary conditions:
LNWDT=3; FNT=11
rcParams[’lines.linewidth’] = LNWDT; rcParams[’font.size’] = FNT
font = {’size’ : 16}; rc(’font’, **font)
# shoot:
s = np.array([0, 0, 1])
r = np.array([0, 1, 0])
phi = np.zeros(3)
psi = np.zeros(3)
for k in range(3):
y0 = np.array([0,0,s[k],r[k]])
y = solver(tank2,y0,x)
phi[k] = y[-1, 2]
psi[k]=y[-1, 3]
3.5 Exercises
Exercise 5: Stokes first problem for a non-Newtonian fluid
y
u = U0
u(y,t)
Figure 3.22: Stokes first problem for a non-Newtonian fluid: schematic repre-
sentation of the velocity profile.
∂u ∂τxy
ρ = (3.152)
∂t ∂y
∂u
The relation between the shear stress τxy and the velocitygradient ∂y is given
by:
21
∂u ∂u
τxy = K · (3.153)
∂y ∂y
CHAPTER 3. SHOOTING METHODS 158
y u
η=C· 2 , f (η) = (3.154)
t5 U0
where C is a positive constant, y ≥ 0 and t ≥ 0. By inserting Eq. (3.153)
into Eq. (3.152) and using (3.154), we get:
p
f 00 (η) + 2 η f 0 (η) = 0 (3.155)
With boundaryconditions:
Movie 2: mov-ch2/stokes.mp4
Pen and paper: The following problems should be done using pen and
paper:
Hint 2. Programming:
You may use the template script below:
# src-ch2/stokes.py;ODEschemes.py @ git@lrhgit/tkt4140/src/src-ch2/ODEschemes.py;
#### a: ####
def func(f, eta):
f0 = f[0]
f1 = f[1]
if f1<0:
# f1 should not be negative
f1=0
df0 = ?
df1 = ?
dF = np.array([df0, df1])
return dF
N = 5
Eta = np.linspace(0, 1, N + 1)
d_eta = Eta[1] - Eta[0]
f_0 = ?
df_0 = ?
F_0 = [f_0, df_0]
f_0 = 0
F_0 = [?, ?]
F = euler(func, F_0, Eta)
phi0 = ?
phi1 = ?
s = ?
s1, s0 = s, s1
print "n = {0}, s = {1}, ds = {2}, delta = {3} ".format(n, s0, s1 - s0, delta)
C = 1.
U0 = 1
dt = 0.05
t0, tend = dt, 2.
dy = 0.01
y0, yend = 0, 2.
f = F[:, 0]*U0
tck = splrep(Eta, f) # create interpolation splines
for n, t in enumerate(time):
Eta_n = C*Y/(t**(2./5))
fig = plt.figure()
ax = plt.axes(xlim=(0, 1.1), ylim=(0, Y[-1]))
lines=[] # list for plot lines for solvers and analytical solutions
legends=[] # list for legends for solvers and analytical solutions
# call the animator. blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate, frames=len(time), init_func=init, blit=False)
# Writer = animation.writers[’ffmpeg’]
# writer = Writer(fps=15, metadata=dict(artist=’Me’), bitrate=1800)
# anim.save(’../mov/stokes.mov’, writer=writer)
plt.show()
f 000 + f f 00 + β · [1 − (f 0 )2 ] = 0. (3.157)
It can be shown that the velocity U (x) is defined as:
U0
η
π
β
2
π
β
2
2m β
Connection between m and β: β = m+1 and m = 2−β . We see that β = 0
results in U (x) = U0 .
Falkner-Skan transformation is given by:
s
U ψ
η= · y, f (η) = p , (3.159)
(2 − β)νx (2 − β)U νx
where ψ is a potential function such that
∂ψ ∂ψ
u= , v=
∂y ∂x
If we choose the same boundary conditions as in the example (3.3.3), we get:
f 000 + f f 00 + β · [1 − (f 0 )2 ] = 0, (3.160)
with boundary conditions:
f (0) = f 0 (0) = 0,
(3.161)
f 0 (η∞ ) = 1.
Chapter 4
4.1 Introduction
Consider the following boundary value problem
Our goal is to solve this problem using finite differences. First we discretize
the independent variable xi = ih, with h = 1/(N + 1). Having defined a grid,
automatically defines our unknowns, i.e. y0 , y1 , . . . yN , yN +1 . From boundary
conditions we know that y0 = α and yN +1 = β, so that the number of true
unknowns is N . Next we approximate y 00 (x) by a centered finite difference
approximation
1
y 00 (xi ) ≈ (yi−1 − 2yi + yi+1 ) , (4.3)
h2
obtaining the following algebraic equations
1
(yi−1 − 2yi + yi+1 ) = f (xi ) (4.4)
h2
for i = 1, 2, . . . , N . It is important to note that the first equation (i = 1) involves
the boundary value y0 = α and that the last equation (i = N ) involves the value
163
CHAPTER 4. FINITE DIFFERENCES FOR ODES 164
AY = F , (4.5)
f (x1 ) − α/h2
−2 1
1 −2 1 f (x2 )
· · · ·
1
A= 2 · · · ,F = · (4.6)
h
· · ·
·
1 −2 1 f (xN −1 )
1 −2 f (xN ) − β/h2
This is a nonsingular tridiagonal system, that can be solved for Y from any
right-hand side F .
E = Y − Ŷ . (4.8)
Once the error has been define we will concentrate in determining how big
this error is. We can do that by using some norm for vector E. For example, we
can use the max-norm
the 1-norm
N
X
kEk1 = h |ei | (4.10)
i=1
or the 2-norm
N
!1/2
X
2
kEk2 = h |ei | . (4.11)
i=1
CHAPTER 4. FINITE DIFFERENCES FOR ODES 165
Now our goal is to obtain a bound on the magnitude of the error vector E,
so that, for example, if we can show that kEk∞ = O(h2 ), then it follows that
the error in each point of the grid must be O(h2 ) as well. In order to achive
this goal, we will first define the local truncation error (LTE) of the numerical
scheme and then, via stability, show that the global error can be bounded by
the LTE.
τ = AŶ − F , (4.15)
and so
AŶ = F + τ . (4.16)
AE = −τ . (4.17)
4.2.3 Stability
In the above made considerations we have assumed that solving the difference
equations results in a decent approximation to the solution of the underlying
differential equations. But since this is exactly what we are trying to prove, so
that the reasoning is rather circular. Let us pursue a different approach and
look at the discrete system
Ah E h = −τ h , (4.21)
where we have used the superscript h to indicate that these quantities depend
on h. If we define (Ah )−1 as the inverse of Ah , then we can write
E h = −(Ah )−1 τ h (4.22)
and taking the norms gives
kE h k = k(Ah )−1 τ h k (4.23)
≤ k(Ah )−1 kkτ h k. (4.24)
We already know that kτ h k = O(h2 ) and we want to verify that this is also
true for kE h k. Therefore, we need that k(Ah )−1 k is bounded by some constant
independent of h as h → 0, in other words
k(Ah )−1 k ≤ C (4.25)
for all h sufficiently small. If that is true, we will then have that
kE h k ≤ Ckτ h k (4.26)
4.2.4 Consistency
A method is said to be consistent with the differential equation and boundary
conditions if
kτ h k → 0 as h → 0 . (4.27)
CHAPTER 4. FINITE DIFFERENCES FOR ODES 167
4.2.5 Convergence
We are finally in the position of giving a proper definition of convergence. We
say that a method is convergent if kE h k → 0 as h → 0. Combining the concepts
introduced above, we can say that
where λp refers to the p-th eigenvalue A. We further note that A−1 is also
symmetric, so that its eigenvalues are the inverses of the eigenvalues of A
−1
kA−1 k2 = ρ(A−1 ) = max |(λp |)−1 = min |λp | . (4.30)
1≤p≤N 1≤p≤N
These considerations tell us that in order to verify the stability of the numerical
scheme under investigation we just have to compute the eigenvalues of A and
show that they are bounded away from zero as h → 0. In general this can be
very trick, since we have an infinite set of matrices A (because A changes with
h). However, in this case the structure is so simple that we can obtain a general
expression for the eigenvalues.
We consider a single value of h = 1/(N + 1). Then the N eigenvalues of A
are
2
λp = (cos(pπh) − 1) , (4.31)
h2
for p = 1, 2, . . . , N . Moreover, the eigenvector y p , corresponding to λp has
components yip for i = 1, 2, . . . , N given by
b1 x1 + c1 x2 = d1
···
ai xi−1 + bi xi + ci xi+1 = di
(4.36)
···
aN xN −1 + bN xN = dN
i = 1, 2, ...N , a1 = cN = 0
or more convenient in a matrix representation:
b1 c1 x1 d1
a2 b2 c2 x2
d2
· · ·
·
·
· · · ·
·
=
· (4.37)
· · ·
·
·
aN −1 bN −1 cN −1 xN −1 dN −1
aN bN xN dN
The linear algebraic equation system (4.36) may be solved by Gauss-elimination,
which has a particular simple form for tridiagonal matrices and is often referred
to as the Thomas-algorithm. More details on the derivation may be found in
appendix I of Numeriske beregninger (in Norwegian).
The Thomas-algorithm has two steps; an elimination step and a substitution
step. In the elimination step, sub-diagonal elements are eliminated by summation
of two consecutive appropriate scaled rows in the matrix, starting at the first
row at the top. By completion of the elimination step, the last unknown xN
may the be computed as xN := dbN N
.
CHAPTER 4. FINITE DIFFERENCES FOR ODES 169
In the equation for xN −1 immediately above the last, there are only two
unknows, namely xN and xN −1 . But since we already have calculated xN in
the elimination step, we substitute its value in this equation and may thereby
find xN −1 . By moving on to the equation for xN −2 , there will be only two
unknowns as well, xN −2 and xN −1 , and since we have already calculated xN −1 ,
it may be substituted into this equation such that xN −2 may be found. This
procedure of back-substitution may be repeated until all the unknows for the
tridiagolnal equation system is known. This step of the procedure is known as
back substitution or simply substitution.
The algorithms for the two steps are listed outlined below in mathematical
terms:
Elimination:
aj
qj := for j = 2, 3 · · · , N
bj−1
bj := bj − qj · cj−1 (4.38)
dj := dj − qj · dj−1
Back substitution:
dN
xN :=
bN
dj − cj · xj+1
xj := for j = N − 1, N − 2, · · · , 1 (4.39)
bj
(4.40)
Args:
a(array): an array containing lower diagonal (a[0] is not used)
b(array): an array containing main diagonal
c(array): an array containing lower diagonal (c[-1] is not used)
d(array): right hand side of the system
Returns:
x(array): solution array of the system
"""
n = len(b)
x = np.zeros(n)
# elimination:
for k in range(1,n):
CHAPTER 4. FINITE DIFFERENCES FOR ODES 170
q = a[k]/b[k-1]
b[k] = b[k] - c[k-1]*q
d[k] = d[k] - d[k-1]*q
# backsubstitution:
q = d[n-1]/b[n-1]
x[n-1] = q
for k in range(n-2,-1,-1):
q = (d[k]-c[k]*q)/b[k]
x[k] = q
return x
| b1 |>| c1 |> 0
| bi |≥| ai | + | ci |, ai · ci 6= 0 , i = 2, 3, · · · , N − 1 (4.41)
| bn |>| aN |> 0
Matrices satisfying (4.41) are called diagonally dominant for obvious reasons,
and strictly diagonally in case ≥ may be substitued with > in (4.41). Pivoting
during Gauss-elimination is not necessary for diagonally dominant matrices,
and thus the band structure is preserved and the algorithm becomes less CPU-
intensive. See appendix I in Numeriske beregninger for a proof of (4.41).
Notice that all coefficients in each row of (4.37) has the same index. However,
the linear algebraic equation system (4.36) may also be presented:
b1 x1 + c2 x2 = d1
···
ai−1 xi−1 + bi xi + ci+1 xi+1 = di
··· (4.42)
aN −1 xN −1 + bN xN = dN
i = 1, 2, ...N , a1 = cN = 0
or in matrix form:
b1 c2 x1 d1
a1 b2 c3 x2
d2
· · ·
·
·
· · · ·
·
=
· (4.43)
· · ·
·
·
aN −2 bN −1 cN xN −1 dN −1
aN −1 bN xN dN
We notice that in this notation the coefficients of each column in the matrix
(4.43) has the same index.
CHAPTER 4. FINITE DIFFERENCES FOR ODES 171
n = len(b)
x = np.zeros(n)
fail = 0
# reordering
a[0] = b[0]
b[0] = c[0]
c[0] = 0
# elimination:
l = 0
for k in range(0,n):
q = a[k]
i = k
if l < n-1:
l = l + 1
for j in range(k+1,l+1):
q1 = a[j]
if (np.abs(q1) > np.abs(q)):
q = q1
i = j
if q == 0:
fail = -1
if i != k:
q = d[k]
d[k] = d[i]
d[i] = q
q = a[k]
a[k] = a[i]
a[i] = q
q = b[k]
b[k] = b[i]
b[i] = q
q = c[k]
c[k] =c[i]
CHAPTER 4. FINITE DIFFERENCES FOR ODES 172
c[i] = q
for i in range(k+1,l+1):
q = a[i]/a[k]
d[i] = d[i]-q*d[k]
a[i] = b[i]-q*b[k]
b[i] = c[i]-q*c[k]
c[i] = 0
# backsubstitution
x[n-1] = d[n-1]/a[n-1]
x[n-2] = (d[n-2]-b[n-2]*x[n-1])/a[n-2]
for i in range(n-3,-1,-1):
q = d[i] - b[i]*x[i+1]
x[i] = (q - c[i]*x[i+2])/a[i]
return x
4.4 Examples
4.4.1 Example: Heat exchanger with constant cross-section
T∞ D
X
Tr
which for rod with constant cross-section, i.e. A(x) = A reduces to:
d2 h̄P
2
(T − T∞ ) = (T − T∞ ) (4.45)
dX kA
For convenience we introduce dimensonless variables:
X T − T∞ h̄P 2
x= , θ= , β2 = L (4.46)
L Tr − T∞ kA
where L is a characteristic length (see Figure B.5 i Numeriske Beregninger).
A relative, dimensionless temperature may be constructed by using a reference
temperature Tr and temperature and the temperature T∞ in combination. The
parameteren β 2 is frequently referred to as the Biot-number, Bi = β 2 .
Given the assumptions and prerequesites above (4.44) may be presented in
the more convenient and generic form:
d2 θ
− β2θ = 0 (4.47)
dx2
which is a second order ODE with the following generic analytical solution:
T = T∞ for X = 0
T = Tr for X = L
The analytical solution (4.48) for these particular boundary conditions reduces
to:
sinh(βx) dθ cosh(βx)
θ(x) = , =β (4.50)
sinh(β) dx sinh(β)
With D = 0.02m and L = 0.2m the Biot-number β 2 = 4. By doubling the
length the Biot-number quadurples. For eksemplet ovenfor blir Biot-tallet:
h̄P 2 2 2
β2 = L = L (4.51)
kA D
CHAPTER 4. FINITE DIFFERENCES FOR ODES 174
dT
Qx = 0 = for X = 0
dX
T = Tr for X = L
The analytical solution (4.48) reduces for the mixed boundary conditions to:
cosh(βx) dθ sinh(βx)
θ(x) = , =β (4.54)
cosh(β) dx cosh(β)
d2 θ
Numerical solution. We will use central differences for the term and
dx2
2
d θ θi−1 − 2θi + θi+1
with ≈ we get the following difference equation:
dx2 i h2
θi−1 − (2 + β 2 h2 ) θi + θi+1 = 0 (4.55)
Prescribed temperatures
We enumerate the nodes for the unkowns as shown in Figure 4.2:
0.0 1.0
X
0 1 2 3 i N-1 N N+1
θ0 − (2 + β 2 h2 ) θ1 + θ2 = 0 → (2 + β 2 h2 ) θ1 + θ2 = 0
which may be simplified by substitution of the prescribed boundary value
θ(0) = θ0 = 0
CHAPTER 4. FINITE DIFFERENCES FOR ODES 175
(2 + β 2 h2 ) θ1 + θ2 = 0
For the other boundary at i = N the generic scheme (4.55) is:
θN −1 − (2 + β 2 h2 ) θN + θN +1 = 0
which by substitution of the prescribed value for θN +1 = 1 yields:
θN −1 − (2 + β 2 h2 ) θN = −1
A complete system of equations may finally be obtained from (4.4.1), (4.55),
and (4.4.1):
i = 1 : − (2 + β 2 h2 ) θ1 + θ2 = 0
i = 2, 3, ..., N − 1 : θi−1 − (2 + β 2 h2 ) θi + θi+1 = 0 (4.56)
2 2
i = N : θN −1 − (2 + β h ) θN = −1
ai = 1 , i = 2, 3, ..., N
2 2
bi = −(2 + β h ) , i = 1, 2, ..., N (4.57)
ci = 1 , i = 1, 2, ..., N − 1
di = 0 , i = 1, 2, ..., N − 1
dN = −1
are not used of the k super-diagonal, whereas the last k elements are not used
of the −k sub-diagonal. For a quick tutorial of the usage of these sparse solvers
see SciPy sparse examples.
We have implemented a simple means to compare the two solution procedures
with a tic-toc statements. You may experiment with various problems sizes
(by varying the element size h) to assess the impact on the computational speed
of the two procedures. The analytical solution is given by (4.50).
# src-ch3/section321/ribbe1.py
import numpy as np
import scipy as sc
import scipy.linalg
import scipy.sparse
import scipy.sparse.linalg
import time
from math import sinh
def theta_analytical(beta,x):
return np.sinh(beta*x)/np.sinh(beta)
tic=time.clock()
theta2=sc.linalg.solve(A,d,)
toc=time.clock()
print ’linalg solver time:’,toc-tic
# Plot solutions
plot(x[1:-1],theta,x[1:-1],theta2,’-.’,x,theta_analytical(beta,x),’:’)
legend([’sparse’,’linalg’,’analytical’])
show()
close()
print ’done’
The numerical predictions are presented and compared with analytical values
in the table below:
x numerical analytical rel.err
0.0 0.00000 0.00000
0.1 0.05561 0.05551 0.00176
0.2 0.11344 0.11325 0.00170
0.3 0.17582 0.17554 0.00159
0.4 0.24522 0.24487 0.00144
0.5 0.32444 0.32403 0.00126
0.6 0.41663 0.41619 0.00105
0.7 0.52548 0.52506 0.00082
0.8 0.65536 0.65499 0.00056
0.9 0.81145 0.81122 0.00029
1.0 1.00000 1.00000 0.00000
0.0 1.0
X
0 1 2 i N-2 N-1 N
For a generic node ’i’ the central difference approximation may be denoted:
dθ θi+1 − θi−1
≈ (4.58)
dx i 2h
θ0 = θ2 (4.60)
Due to the zero gradient boundary condition, the first of the N equations,
represented on generic form by (4.55), takes the particular form:
This first equation (4.61) is the only equation which differs from the resulting
equation system for prescibed boundary conditions in (4.56). All the coefficients
ai , bi , and di are the same as in for the prescribed temperature version in (4.57),
except for c1 :
c1 = 2, ci = 1, i = 2, ...N − 1 (4.62)
The first difference equation (4.55) in which θ3 occurs, is the one for node 2
θ1 − (2 + β 2 h2 ) θ2 + θ3 = 0 (4.66)
CHAPTER 4. FINITE DIFFERENCES FOR ODES 179
2 θ1 − (2 + β 2 h2 ) θ2 = 0 (4.67)
This is the first equation in the system of equations and the only one which
differs from (4.56). Rather than (4.57), we get the following equations for the
coefficients:
b1 = 2 bi = (2 + β 2 h2 ) i = 2, ..., N (4.68)
2 2
c1 = −(2 − β h ) ci = 1 i = 2, ..., N − 1 (4.69)
2 θ1 − (2 − β 2 h2 ) θ2 = 0, i=1
2 2
θi−1 − (2 + β h ) θi + θi+1 = 0, i = 2, 3, ..., N − 1 (4.70)
2 2
θN −1 − (2 + β h ) θN = −1, i=N
import numpy as np
import scipy as sc
import scipy.linalg
import scipy.sparse
import scipy.sparse.linalg
import time
from numpy import cosh
def theta_analytical(beta,x):
return np.cosh(beta*x)/np.cosh(beta)
A=tri_diag_setup(a,b,c)
#Create matrix for sparse solver
diagonals=np.zeros((3,n))
diagonals[0,:]= 1.0 # all elts in first row is set to 1
diagonals[0,0]= 1.0
diagonals[1,:]= -(2+(beta*h)**2)
diagonals[2,:]=1.0
if (version==1):
diagonals[2,1]= 2.0 # index 1 as the superdiagonal of spdiags is not used,
else:
diagonals[1,0]=2.0 # Sets the first element in the main diagonal
diagonals[2,1]= -(2+(beta*h)**2) # index 1 as the superdiagonal of spdiags is not used,
super-diagonal
tic=time.clock()
theta2=sc.linalg.solve(A,d,)
toc=time.clock()
print ’linalg solver time:’,toc-tic
# Plot solutions
plot(x[0:-1],theta,x[0:-1],theta2,’-.’,x,theta_analytical(beta,x),’:’)
xlabel(’x’)
ylabel(r’Dimensionless temperature $\mathregular{\theta}$’)
legend([’sparse’,’linalg’,’analytical’])
show()
close()
print ’done’
CHAPTER 4. FINITE DIFFERENCES FOR ODES 181
The relative error is computed from εrel = |(θnum − θanalyt )/θanalyt |, and
the results of the computations are given in the table below:
We observe that the two versions for zero-gradient boundary condition yields
approximately the same result except close to x = 0, where the forward difference
is somewhat better. In section (6.3.1) we will take a closer look at the accuracy
of gradient boundary conditions.
Mixed boundary conditions. Version 3
Rather than the enumeration in Figure (4.3), we may use the enumeration in
Fig. (4.2) with xi = i h , i = 0, 1, ..., N + 1 where h = N1+1 such that we must
take i = 1 in (4.55) to get:
θ0 − (2 + β 2 h2 ) θ1 + θ2 = 0
The boundary condition in (4.63) the becomes:
dθ −3θ0 + 4θ1 − θ2
= =0
dx 0 2h
from which an elimination equation for θ0 may be obtained:
D
A(X)
b
L
T = TL
X
½D
½L
ϕ ½d
X
X=0 X=L
We assume that the temperature mostly varies in the X-direction such that the
derivation in appendix B in Numeriske metoder is valid. We assume accordingly
d
that D L and that 0 < D <1.
In Numeriske metoder the quasi one-dimensional differential equation for
heat conduction is derived, and for stationary conditions is reduces to equation
(B.0.22):
d d(T − T∞ ) h̄P (X)
A(X) = (T − T∞ ) (4.74)
dX dX k
with corresponding boundary conditions:
dT (0) h̄0
= [T (0) − T∞ ] , T (L) = TL (4.75)
dX k
For the trapezoidal cross-section in Fig. (4.4):
2h̄ L2 h̄0
β2 = and β02 = L (4.76)
Dk k
Equipped with these dimensionless quantities we may render equation (4.74)
as:
d dθ(x)
{α + (1 − α) x} − β 2 θ(x) = 0 (4.77)
dx dx
and corresponding boundary conditions become:
dθ
(0) = β02 θ(0) , θ(1) = 1 (4.78)
dx
As d → 0 the trapezoidal profile approaches a triangular profile and α → 0,
and consequently equation (4.77) takes the form:
d2 θ dθ
x 2
+ − β 2 θ(x) = 0 (4.79)
dx dx
For the left boundary at x = 0 we get:
dθ
(0) − β 2 θ(0) = 0 (4.80)
dx
An analytical solution of equation (4.77) and (4.79) is outlined in G.5 of
Numeriske Beregninger.
CHAPTER 4. FINITE DIFFERENCES FOR ODES 184
import numpy as np
from matplotlib.pyplot import *
from numpy import sqrt as sqrt
from scipy.special import kv as besselk
from scipy.special import iv as besseli
def trapes_analytical(h):
N=int(round(1.0/h)) # number of unknowns, assuming the RHS boundary value is known
x=np.arange(N+1)*h
# x = np.linspace(0, 1, N)
z0 = 4*sqrt(2)
z1 = 8
g = sqrt(2)/8
z = sqrt(32.*(1 + x))
theta = A* besseli(0, z) + B* besselk(0, z)
dtheta = 16*(A*besseli(1, z) - B*besselk(1, z))/z
return x, theta, dtheta
if __name__ == ’__main__’:
legends=[] # empty list to append legends as plots are generated
h=0.2
x, theta, dtheta = trapes_analytical(h)
plot(x,theta,’b’,)
legends.append(r’$\theta$’)
plot(x,dtheta,’r’)
legends.append(r’$\theta^{\prime}$’)
## Add the labels
legend(legends,loc=’best’,frameon=False) # Add the legends
ylabel(r’$\theta$ and $\theta^{\prime}$’)
xlabel(’x’)
show()
close()
Numerical solution
CHAPTER 4. FINITE DIFFERENCES FOR ODES 186
d2 θ dθ
(1 + x)
2
+ − 8 θ(x) = 0 (4.84)
dx dx
with the boundary conditions:
dθ θ(0)
(0) = β02 θ(0) = , θ(1) = 1 (4.85)
dx 2
The procedure for this example will follow the along the lines of the procedure
in section (4.4.1) for mixed boundary conditions.
0.0 1.0
X
0 1 2 i N-2 N-1 N
[2 + h (1 + 15 γ0 )] θ0 − 2 θ1 = 0 (4.88)
CHAPTER 4. FINITE DIFFERENCES FOR ODES 187
−(1 − γN −1 ) θN −2 + 2 (1 + 8h γN −1 ) θN −1 = (1 + γN −1 ) θN = 1 + γN −1 (4.89)
import numpy as np
from matplotlib.pyplot import *
import scipy as sc
import scipy.sparse
import scipy.sparse.linalg
import time
from trapes import trapes_analytical
from Carbon.Aliases import false
def solve_trapes_numerically(h):
L=1.0 # length of domain
n=int(round(L/h)) # number of unknowns, assuming the RHS boundary value is known
x=np.arange(n+1)*h # x includes xmin and xmax at boundaries
gamma=h/(2*(1+x[0:-1])) # uses all but the last x-value for gamma
subdiag=gamma[:-1]-1.0 #the subdiagonal has one less element than the diagonal
diag=2.0*(1+8.0*h*gamma)
diag[0]= 2.0+h*(1+15.0*gamma[0])
CHAPTER 4. FINITE DIFFERENCES FOR ODES 188
superdiag=np.zeros(n-1)
superdiag[0]=-2
superdiag[1:]=-(gamma[0:-2]+1.0)
A_diags=[subdiag,diag,superdiag] #note the offdiags are one elt shorter than the diag
A = sc.sparse.diags(A_diags, [-1,0,1], format=’csc’)
#Create rhs
d=np.zeros(n)
d[n-1]=1+gamma[-1]
return x,theta
if __name__ == ’__main__’:
h=0.2
xa, atheta, adtheta = trapes_analytical(h)
x, theta=solve_trapes_numerically(h)
# Plot solutions
figure(’Solutions’)
plot(xa,atheta,)
plot(x[:-1],theta,’-.’)
legends=[] # empty list to append legends as plots are generated
legends.append(r’$\theta$-analytical’)
legends.append(r’$\theta$’)
log_error = np.log2(error)
dlog_err=log_error[0:-1]-log_error[1:]
print(’Approximate order:{0:.3f}’.format(1./np.mean(dlog_err)))
plot_error=0
if (plot_error):
figure(’error’)
ax = plot(h_range,error)
yscale(’log’,basey=2)
xlabel(r’meshsize $h$’)
ylabel(r’$\frac{\epsilon}{\theta_a}$’)
CHAPTER 4. FINITE DIFFERENCES FOR ODES 189
show()
close()
x0 x1 xi xN-1 xN xN+1
0.0 1.0
3
− 2 + h2 y1 y1 + y2 = −4
2
..
.
3
yi−1 − 2 + h2 yi yi + yi+1 = 0 (4.94)
2
..
.
3
yN −1 − 2 + h2 yN yN = −1
2
3
− 2 + y1m+1 h2 y1m+1 + y2m+1 = −4
2
..
.
m+1 3
yi−1 − 2 + yim+1 h2 yim+1 + yi+1
m+1
=0 (4.95)
2
..
.
m+1 3 m+1 2 m+1
yN −1 − 2 + yN h yN = −1
2
3
− 2 + y1m h2 y1m+1 + y2m+1 = −4
2
..
.
m+1 3
yi−1 m+1
− 2 + yim h2 yim+1 + yi+1 =0 (4.96)
2
..
.
m+1 3 m 2 m+1
yN −1 − 2 + yN h yN = −1
2
import numpy as np
from matplotlib.pyplot import *
CHAPTER 4. FINITE DIFFERENCES FOR ODES 192
h = 0.05 # steplength
n = int(round(1./h)-1) # number of equations
fac = (3./2.)*h**2
Nmax = 30 # max number of iterations
if(m>0):
max_dym = np.max(np.abs((ym1 - ym)/ym))
print(’m = {} \t max dym = {:6.4g} ’.format(m, max_dym))
ym = ym1
Note that error is a vector by construction, since ym1 and ya are vectors.
Thus, to print a scalar measure of the error after Nmax iterations we issue the
following command:
print(’\nThe max relative error after {} iterations is: {:4.3g}’.format(Nmax,np.max(error)))
CHAPTER 4. FINITE DIFFERENCES FOR ODES 193
Once we have gained some experience on how the iteration procedure develops,
we may make use of a stop criterion based on relative changes in the iterative
solutions as shown in the python-snippet below:
# iterative solution with max_dym stop criterion
m=0; RelTol=1.0e-8
max_dym=1.0
ym = np.zeros(n) # initial guess of y. ym = np.zeros(n) will converge to y1. ym = -20*x*(1-x) for ins
a = np.ones(n)
c = np.ones(n)
d = np.zeros(n)
d[n-1] = -1.
d[0] = -4.
b = -(np.ones(n)*2. + fac*ym)
if(m>0):
max_dym = np.max(np.abs((ym1 - ym)/ym))
print(’m = {} \t max dym = {:6.4g} ’.format(m, max_dym))
m+=1
ym = ym1
# plotting:
legends=[] # empty list to append legends as plots are generated
# Add the labels
plot(xv,ya,"r") # plot analytical solution
legends.append(’analytic’)
plot(xv,ym1,"b--") # plot numerical solution
legends.append(’delayed linearization’)
CHAPTER 4. FINITE DIFFERENCES FOR ODES 194
show()
2 2
yim+1 = (yim + δyi ) = (yim )2 +2yim δyi +(δyim )2 ≈ (yim )2 +2 yim δyi = yim 2yim+1 − yim
(4.99)
That is, quadratic expression in equation (4.99) is linearized by neglecting
the quadratic terms (δy)2 which are small compared to the other terms in (4.99).
Substitution of the lineariztion (4.99) in (4.94) results in the following system
of equations:
# src-ch3/taylor34.py;TRIdiagonalSolvers.py @ git@lrhgit/tkt4140/src/src-ch3/TRIdiagonalSolvers.py;
import numpy as np
from matplotlib.pyplot import *
from TRIdiagonalSolvers import tdma
from TRIdiagonalSolvers import tripiv
# change some default values to make plots more readable
LNWDT=3; FNT=11
rcParams[’lines.linewidth’] = LNWDT; rcParams[’font.size’] = FNT
font = {’size’ : 16}; rc(’font’, **font)
h = 0.05 # steplength
n = int(round(1./h)-1) # number of equations
fac = (3.)*h**2
#initilizing:
x = np.linspace(h,1-h,n)
ym = -20*x*(1-x) # initial guess of y.
#ym = np.zeros(n) # will converge to y1. ym = -20*x*(1-x) for instance will converge to y2
legend(legends,loc=’best’,frameon=False)
ya = 4./(1+x)**2
feil = np.abs((ym1-ya)/ya)
print "\n"
for l in range(len(x)):
print ’x = {}, y = {}, ya = {}’.format(x[l], ym1[l], ya[l])
figure()
# plotting:
legends=[] # empty list to append legends as plots are generated
# Add the labels
plot(x,ya,"r") # plot analytical solution
CHAPTER 4. FINITE DIFFERENCES FOR ODES 196
legends.append(’y1 analytic’)
plot(x,ym1,"b--") # plot numerical solution
legends.append(’y2 taylor linearization’)
You may experiment with by changing the initial, guessed solution, and
observe what happens with the resulting converged solution. Which solution
does the numerical solution converge to?
Observe that the iteration process converges much faster than for the Picard
linearization approach. Notably, somewhat slow in the first iterations, but after
some iterations quadratic convergence is obtained.
δzi → δyi , zim+1 → yim+1 , zim → yim , F (yi )m+1 = (yim+1 )2 , F (yi )m = (yim )2
which after substitution in (4.101) yields: (yim+1 )2 ≈ (yim )2 + 2yim · δyi which
is identical to what was achieved in equaiton (4.99) .
The linearization in equation (4.101) is often referred to as Newton-linearization.
In many applications, the nonlinear term is composed z at various discrete,
spatial locations and this has to be accounted for in the linearization. Assume to
begin with that we have terms involving both zi og zi+1 . The Taylor expansion
will then take the form:
∂F ∂F
F (zi , zi+1 )m+1 ≈ F (zi , zi+1 )m + δzi + δzi+1 (4.102)
∂zi m ∂zi+1 m
CHAPTER 4. FINITE DIFFERENCES FOR ODES 197
where:
∂F ∂F ∂F
F (zi−1 , zi , zi+1 )m+1 ≈ F (zi−1 , zi , zi+1 )m + δzi−1 + δzi + δzi+1
∂zi−1 m ∂zi m ∂zi+1 m
(4.103)
where
m+1
δzi−1 = zi−1 m
− zi−1 , δzi = zim+1 − zim , m+1
δzi+1 = zi+1 m
− zi+1
Naturally, the same procedure may be expaned whenever the nonlinear term
is composed of instances of the dependent variable at more spatial locations.
∂F 3 3 m 1
F (yi )m+1 ≈ F (yi )m + δyi = (yim ) 2 + δyi (y ) 2 (4.106)
∂yi m 2 i
where δyi = yim+1 − yim . Equivalently by using the squareroot-symbols:
3p m
q
yim+1 yim+1 ≈ yim yim +
p
yi δyi (4.107)
2
Observe that we have succeeded in the linearization process as all occurences
of yim+1 in the two equivalent discretization in equation (4.106) and (4.107) are
of power one.
After substition in equaiton (4.105) the following linear difference equation
results:
h2 m p m
m+1 3 2p m
yi−1 + h yi − 2 yim+1 + yi+1 m+1
= y yi (4.108)
2 2 i
CHAPTER 4. FINITE DIFFERENCES FOR ODES 198
linearization. Againg, we have only one index and may make use of (4.101) with
zi → yi and F (yi ) = sin(yi ):
∂F
F (yi )m+1 = sin(yim+1 ) ≈ F (yi )m + δyi = sin(yim ) + δyi cos(yim )
∂yi m
m+1
yi−1 + (h2 cos(yim ) − 2)yim+1 + yi+1
m+1
= h2 (yim cos(yim ) − sin(yim )) (4.111)
p
Nonlinear ODE 3 Consider the ODE y 00 (x) + y(x) y 0 (x) = 0 which after
discretization with central differences may be presented:
q p
m+1
yi+1 − 2yim+1 + yi−1
m+1
+ α yim+1 m+1
yi+1 m+1
− yi−1 = 0 , der α = h h/2 (4.112)
q
The term yim+1 yi+1 m+1 m+1
− yi−1 is nonlinear and must be linearized. As three
indices are involved in the term we utilize (4.103) with zi−1 → yi−1 , zi → yi ,
√
zi+1 → yi+1 , and finally F (yi−1 , yi , yi+1 )m = yi yi+1 − yi−1 .
We present the individual terms in equation(4.103) :
ym
∂F
F (yi−1 , yi , yi+1 )m = yim yi+1 =− p mi
p m m ,
− yi−1 m
∂yi−1 m 2 yi+1 − yi−1
m
∂F ∂F y
= p mi
p m m ,
= yi+1 − yi−1 m
∂yi m ∂yi+1 m 2 yi+1 − yi−1
yim yim
q
yim+1 m+1 m+1
≈ yim
p m − ym − p
p m m δy + p
yi+1 − yi−1 yi+1 i−1 m − ym
δyi−1 + yi+1 − yi−1 i m − ym
δyi+1
2 yi+1 i−1 2 yi+1 i−1
(4.113)
CHAPTER 4. FINITE DIFFERENCES FOR ODES 199
m+1
Note that equation (4.113) is linear as yi−1 , yim+1 and yi+1
m+1
are only in
first power. By substitution in equaiton (4.112) we get the linear disicretized
equation:
ym ym
m+1
1 − α 2gim yi−1 − (2 − αg m ) yim+1 + 1 + α 2gim yi+1
m+1
ym m m (4.114)
= α 2gim (yi+1 − yi−1 )
der g m =
p m m
yi+1 − yi−1
In the next section we will show how we can operate diretly with the incre-
m+1
mental quantities δyi−1 , δyim+1 and δyi+1
m+1
.
Comparison with the method of Picard linearization
How would the examples above be presented with the method of Picard
linearization?
Equation (4.105) takes the following form the Picard linearization:
q
m+1
yi−1 + (h2 yim+1 − 2) yim+1 + yi−1
m+1
=0 (4.115)
q
The coefficient for the yim+1 -term contains yim+1 which will be replaced by
p m
yi such that:
m+1
+ (h2 yim − 2) yim+1 + yi−1
m+1
p
yi−1 =0 (4.116)
For Picard linearization equation (4.110) become:
m+1
yi−1 − 2yim+1 + yi+1
m+1
= −h2 sin(yim ) = 0 (4.117)
whereas equation (4.112) looks like:
q
m+1 m+1 m+1
yi−1 + (α yi+1 − yi−1 − 2) yim+1 + yi+1
m+1
=0 (4.118)
with
q Picard linearization. The coefficient in front of the yim+1 -term con-
m+1 m+1 p m − y m such that the equation
tains yi+1 − yi−1 which is replaced by yi+1 i−1
becomes:
m+1 m − 2) y m+1 + y m+1 = 0
p m
yi−1 + (α yi+1 − yi−1 i i+1
When we use the method of Picard linearization, the system first has to be
presented in a form which identifies the coefficients in the equation system. If
the coefficients have dependent variable at iteration m + 1, the are to be replaced
with the corresponding value at iteration m. Often this procedure is identical to
a Taylor expansion truncated after the first term.
Thus, we may expect that the method of Picard linearization will converge
more slowly compared to methods which use more terms in the Taylor expansion.
CHAPTER 4. FINITE DIFFERENCES FOR ODES 200
The method of Picard linearization is most frequently used for partial differential
equations where e.g. the material parameters are functions of the dependent
variables, such as temperature dependent heat conduction.
N
1X
|δyi | < εa (4.121)
n i=1
v
uN
1u X
t (δy )2 < ε
i a (4.122)
n i=1
max(|δyi |)
< εr (4.125)
max(|yim+1 |)
Note that if the quantity of interest, i.e. yi , is of order 1, the absolute and
the relative criteria are equivalent. Normally we will prefer a relative criterion
as these comply with the expression number of correct digits. If cases where the
values of yi are small in the whole computational domain (i.e. for all i) we may
resort to a absolute value crterion
CHAPTER 4. FINITE DIFFERENCES FOR ODES 201
4
ys
2 y II
0
2
y s , y II
4
6
8
10
120.0 0.2 0.4 0.6 0.8 1.0
x
import numpy as np
from matplotlib.pyplot import *
from TRIdiagonalSolvers import tdma
from TRIdiagonalSolvers import tripiv
# change some default values to make plots more readable
LNWDT=3; FNT=11
rcParams[’lines.linewidth’] = LNWDT; rcParams[’font.size’] = FNT
font = {’size’ : 16}; rc(’font’, **font)
# numerical parameters
h = 0.05 # steplength.
n = round(1./h)-1 # number of equations. h should be set so that n is an integer
fac = (3.)*h**2
#initilizing:
x = np.linspace(h,1-h,n)
ym = -20*x*(1-x) # initial guess of y. ym = np.zeros(n) will converge to y1. ym = -20*x*(1-x) for ins
itmax = 15
for it in range(itmax):
"""iteration process of linearized system of equations"""
# need to set a, b, c and d vector inside for loop since indices are changed in tdma solver
a = np.ones(n)
c = np.ones(n)
b = -(np.ones(n)*2. + fac*ym)
d = -(np.ones(n)*fac*0.5*ym**2)
d[n-1] = -1.
d[0] = -4.
ya = 4./(1+x)**2
feil = np.abs((ym1-ya)/ya)
# plotting:
legends=[] # empty list to append legends as plots are generated
# Add the labels
plot(x,ym1,"b") # plot numerical solution
legends.append(’y2 numerical’)
plot(x,ya,"r") # plot analytical solution
legends.append(’y1 analytic’)
CHAPTER 4. FINITE DIFFERENCES FOR ODES 203
A code where the absolute criteria is used is almost identical, except for the
substitution of tr2 og tr3
ta1 = max(dy);
ta2 = sum(dy)/n;
ta3 = sqrt(dot(dy,dy))/n;
# src-ch3/avvika.py;TRIdiagonalSolvers.py @ git@lrhgit/tkt4140/src/src-ch3/TRIdiagonalSolvers.py;
import numpy as np
from matplotlib.pyplot import *
from TRIdiagonalSolvers import tdma
from TRIdiagonalSolvers import tripiv
# change some default values to make plots more readable
LNWDT=3; FNT=11
rcParams[’lines.linewidth’] = LNWDT; rcParams[’font.size’] = FNT
font = {’size’ : 16}; rc(’font’, **font)
# numerical parameters
h = 0.05 # steplength.
n = round(1./h)-1 # number of equations. h should be set so that n is an integer
fac = (3.)*h**2
#initilizing:
x = np.linspace(h,1-h,n)
ym = -20*x*(1-x) # initial guess of y. ym = np.zeros(n) will converge to y1. ym = -20*x*(1-x) for ins
itmax = 15
for it in range(itmax):
"""iteration process of linearized system of equations"""
# need to set a, b, c and d vector inside for loop since indices are changed in tdma solver
a = np.ones(n)
c = np.ones(n)
b = -(np.ones(n)*2. + fac*ym)
d = -(np.ones(n)*fac*0.5*ym**2)
d[n-1] = -1.
d[0] = -4.
ym = ym1
xv = np.linspace(h,1-h,n)
ya = 4./(1+xv)**2
CHAPTER 4. FINITE DIFFERENCES FOR ODES 204
# plotting:
legends=[] # empty list to append legends as plots are generated
# Add the labels
plot(xv,ym1,"b")
legends.append(’y2 numerical’)
#plot(xv,ya,"r")
#legends.append(’y1 analytic’)
legend(legends,loc=’best’,frameon=False) # Add the legends
ylabel(’y’)
xlabel(’x’)
#grid(b=True, which=’both’, axis=’both’,linestyle=’-’)
grid(b=True, which=’both’, color=’0.65’,linestyle=’-’)
show()
i.e. we linearize by ingnoring higher order terms of the increments, and solve
equation (4.129) with respect to yim+1 .
Alternatively, we may think of δyi as the unknown quantity and solve the
resulting equation system with respect to the increments. For example we may
substitute ykm+1 = ykm +δyk for k = i−1, i, i+1 and (yim+1 )2 ≈ (yim )2 +2 yim δyi
into equation (4.95) (or equivalently (4.100)), the following equation system
results:
3 m 2
−(2 + 3y1m h2 ) δy1 + δy2 = −(y2m − 2y1m + 4) + (y h)
2 1
3
δyi−1 − (2 + 3yim h2 ) δyi + δyi+1 m
= −(yi+1 − 2yim + yi−1
m
) + (yim h)2 (4.130)
2
m 2 m m 3 m 2
δyN −1 − (2 + 3yN h ) δyN = −(1 − 2yN + yN −1 ) + (yN h)
2
where i = 2, 3, . . . , N − 1 adn m = 0, 1, 2, . . .. In the equation system above
we have incorporated the boundary conditions y0 = 4, δy0 = 0, yN +1 = 1, and
δyN +1 = 0. For each iteration we update the y-values by:
# src-ch3/delta34.py;ODEschemes.py @ git@lrhgit/tkt4140/src/src-ch3/ODEschemes.py;
import numpy as np
from matplotlib.pyplot import *
from TRIdiagonalSolvers import tdma
from TRIdiagonalSolvers import tripiv
# change some default values to make plots more readable
LNWDT=3; FNT=11
rcParams[’lines.linewidth’] = LNWDT; rcParams[’font.size’] = FNT
font = {’size’ : 16}; rc(’font’, **font)
h = 0.05 # steplength
n = int(round(1./h)-1) # number of equations
fac = (3.)*h**2
nitr = 6 # number of iterations
#initilizing:
ym = np.zeros(n) # initial guess of y. ym = np.zeros(n) will converge to y1. ym = -20*x*(1-x) for ins
it, itmax, dymax, relTol = 0, 10, 1., 10**-5 # numerical tollerance limits
dy = tdma(a,b,c,d) # solution
ym = ym + dy
dymax = np.max(np.abs((dy)/ym))
xv = np.linspace(h,1-h,n)
ya = 4./(1+xv)**2
print "\n"
for l in range(len(xv)):
print ’x = {}, y = {}, ya = {}’.format(xv[l], ym[l], ya[l])
ylabel(’y’)
xlabel(’x’)
#grid(b=True, which=’both’, axis=’both’,linestyle=’-’)
grid(b=True, which=’both’, color=’0.65’,linestyle=’-’)
show()
4.5.7 Quasi-linearization
In previous examples, we have always linearized the discretization of the original
problem. However, it is possible to first linearize the differential equation and
only succesively discretize it. This method is normally called quasi-linearization.
Let us now look at an example given by a second order non linear equation:
δy = ym+1 − ym , δy 0 = ym+1
0 0
− ym , δy 00 = ym+1
00 00
− ym (4.134)
where m stands for the iteration number (in this section we will be using
sub-scripts for iteration number)
By expanding (4.133) around iteration m we get:
0 ∂g ∂g ∂g
g(x, ym+1 , ym+1 , y 00 m+1 ) ≈ g(x, ym , ym
0
, y 00 m )+ δy+ δy 0 + 00 δy 00
∂y m ∂y 0 m ∂y m
(4.135)
Suppose that we have iterated so many times that:
00 00
0 0
g(x, ym+1 , ym+1 , ym+1 ) ≈ g(x, ym , ym , ym ) ≈ 0
which inserted in (4.135) gives:
∂g ∂g 0 ∂g 00
δy + δy + δy = 0 (4.136)
∂y m ∂y 0 m ∂y 00 m
After derivation of (4.133):
∂g ∂f ∂g ∂f ∂g
=− , =− 0 , =1 (4.137)
∂y ∂y ∂y 0 ∂y ∂y 00
which inserted in (4.136) gives:
00 ∂f ∂f
δy = δy + δy 0 (4.138)
∂y m ∂y 0 m
CHAPTER 4. FINITE DIFFERENCES FOR ODES 207
Finally we re-write (4.139) with our regular notation, i.e. using super-scripts
for iteration number:
00 m+1 ∂f 0 m+1 ∂f
(y ) − (y ) − y m+1
∂y 0 m ∂y m
(4.140)
∂f ∂f
= f (x, y m , (y 0 )m ) − (y 0 )m − ym
∂y 0 m ∂y m
We have used a second order equation as an example, but (4.138) - (4.140)
allows for generalization to n-th order equations. Take for example a third order
equation:
000 ∂f ∂f ∂f
(y )m+1 − (y 00 )m+1 − (y 0 )m+1 − y m+1
∂y 00 ∂y 0 m ∂y m
∂f
= f (x, y m , (y 0 )m , (y 00 )m ) − (y 00 )m (4.141)
∂y 00 m
∂f ∂f
− (y 0 )m − (y)m
∂y 0 m ∂y m
4.5.8 Example:
1)
Consider the following equation
3 2
y 00 (x) = y
2
∂f ∂f
One can easily check that ∂y 0 = 0 and ∂y = 3y. Inserting these terms in
(4.140) gives:
3
(y 00 )m+1 − 3y m y m+1 = − (y m )2
2
yi+1 −2yi +yi−1
By discretizing with central differences yi00 ≈ h2 we get the follow-
ing algebraic relation:
m+1 3
yi−1 − (2 + 3h2 yim )yim+1 + yi+1
m+1
= − (hyim )2
2
CHAPTER 4. FINITE DIFFERENCES FOR ODES 208
y 00 + y y 00 + β [1 − (y 0 )2 ] = 0
y 00 = − y y 00 + β [1 − (y 0 )2 ] = f (x, y, y 0 , y 00 )
∂f ∂f ∂f
Noting that in this case ∂y 00 = −y , ∂y 0 = 2βy 0 and ∂y = −y 00 , equation
(4.141) gives:
Using central differences one can verify that the result is in agreement with
equation (F.O.16) in Appendix F of the Numeriske Beregninger.
4.6 Exercises
Exercise 7: Circular clamped plate with concentrated single
load
R R0
P
W ϕ
Figure 4.10 show a rigidly clamped plate with radius R0 . The plate is
loaded with a single load P in the plate centre. The differential equation for the
deflection W (R) is given by:
d3 W 1 d2 W 1 dW P
3
+ − 2 = (4.142)
dR R dR2 R dR 2πD · R
CHAPTER 4. FINITE DIFFERENCES FOR ODES 209
3
Et
The plate stifness D is given by: D = 12(1−ν) , where E is the modulus of
elasticity, ν is the Poissons ratio and t is the plate thickness. The boundary
conditions are given by:
dW dW
W (R0 ) = (R0 ) = 0, (0) = 0 (4.143)
dR dR
In which the two first are because it is rigidly clamped, and the last due to
the symmetry. We introduce dimmensionless variables: r = RR0 , ω(R) = 16πDW P R02
,
so that Eq. (4.142) can be written:
d3 ω 1 d2 ω 1 dω 8
3
+ 2
− 2 = , 0<r<1 (4.144)
dr r dr r dr r
and Eq. (4.143):
dω dω
ω(1) = (1) = 0, (0) = 0 (4.145)
dr dr
The analytical solution is given by:
dω
ω(r) = r2 [2 ln(r) − 1] + 1, (r) = 4 r ln(r) (4.146)
dr
Pen and paper:
The following problems should be done using pen and paper:
a) We introduce the inclination φ(r) = − dω
dr and insert into Eq. (4.144):
d2 φ 1 dφ φ 8
2
+ − 2 =− , (4.147)
dr r dr r r
with boundary conditions:
discretize Eq. (4.147) with central differences. Partion the interval [0, 1] into
N segments, so that h = ∆r = N1 , which gives r = h · i, i = 0, 1, . . . , N . The
coefficient matrix should be tridiagonal. Write out the discretized equation for
i = 1, i and i = N − 1. Write the expressions for the diagonals, and the right
hand side.
b) Choose N = 4 (h = 14 ) in a) and solve the corresponding system of
equations.
c) We will now find ω(r) by integrating the equation dω dr = −φ(r) using
Heuns method. Since the right hand side is independenpt of ω , Heuns method
reduces to the trapes method. (The predictor is not necessary).
CHAPTER 4. FINITE DIFFERENCES FOR ODES 210
d 1 d
[r · φ(r)] (4.149)
dr r dr
Discretize Eq. (4.149) using Eq. (2.45). The coefficient matrix should be
tridiagonal. Write out the discretized equation for i = 1, i and i = N − 1. Write
the expressions for the diagonals, and the right hand side.
e) Solve Eq. (4.144) and (4.145) directly by introducing a new independant
variable z = ln(r) and show that Eq. (4.144) can be written: ω 000 (z) − 2ω 00 (z) =
8r2 ≡ 8e2z . Next guess a particular solution on the form ωp (z) = k · z · e2z ,
where k is a constant. Lastly decide the constants using (4.145).
Programing:
a) Write a python program that solves b) and c) from the pen and paper
exercise, numerically. Experiment with finer segmentation. If you want you can
download the python scelleton clampedPlate.py and fill in where applicable.
Hint 2. programming:
# src-ch2/clampedPlate.py
import scipy
import scipy.linalg
import scipy.sparse
import scipy.sparse.linalg
# mainDiag = ?
CHAPTER 4. FINITE DIFFERENCES FOR ODES 211
# subDiag = ?
# superDiag = ?
# RHS = ?
return Phi
N = 3
r = np.linspace(0, 1, N + 1)
h = 1./N
Phi = solve_phi(N, h)
Omega = solve_omega(Phi, N, h)
Omega_analytic = r[1:]**2*(2*np.log(r[1:]) - 1) + 1
Omega_analytic = np.append(1, Omega_analytic) # log(0) not possible
Phi_analytic = - 4*r[1:]*np.log(r[1:])
Phi_analytic = np.append(0, Phi_analytic) # log(0) not possible
ax[0][0].set_ylabel(r’$\phi$’)
plt.show()
0.1m 0.1m
d D
Part a Part b
2h̄ L2
β2 = (4.155)
Dk
which in our example corresponds to
xi = (i − 1) h , i = 1, 2, ..., N + 1 with h = N1
(4.157)
M = N2 + 1 with N being an even number and xM = 0.5
3 2 1
i− θi−1 + [2(i − 1) + β h] θi − i − θi+1 = 0
2 2
or divided by i:
β2h
3 1 1
− 1− θi−1 + 2 1 − + θi − 1 − θi+1 = 0 (4.159)
2i i i 2i
β2h
3 1 1
− 1− θ1 + 2 1 − + a θ2 − 1 − θ3
4 2 2 4
and considering (4.155) and (4.161) results in:
3h h
1+ θ1 − 1 − θ2 = 0 (4.162)
2 2
This is the first equation of our system.
For i = M.
0.5
2
βM − 12
= βa2 = 2.0 , βM
2
+ 21
= βb2 = 8.0
h (1 − h) h (1 + h)
xM − 12 = (N − 1) = , xM + 12 = (N + 1) =
2 2 2 2
which provides:
−4 (1 − h) θM −1 + [5 − 3h + 16h2 ] θM − (1 + h) θM +1 = 0 (4.163)
β 2 xi
Z
dθi
= θ(x)dx , i = 2, 3, ..., N + 1 (4.165)
dx xi x1
Rx
Integral I = x1i θ(x)dx can be calculated using the trapezium method, for
example . The computation of (4.165) is shown below in pseudo-code form:
θ10 := βa2 θ1
s := 0
Utfør for i := 2, ..., N + 1
x := h (i − 1)
s := s + 0.5h (θi + θi−1 )
βa2 s β2 s
Dersom (i ≤ M ) sett θi0 := ellers θi0 := b
x x
In the following table we have used (4.165) and the trapezium method. In
this case, the accuracy of the two methods for calculating θ0 (x) is quite similar
as both θ and θ0 are smooth functions (except at x = 0.5), but generally the
integration method will be more accurate if there are discontinuities..
Solution of equation (4.154) with h = 0.01
CHAPTER 4. FINITE DIFFERENCES FOR ODES 216
Mathematical properties of
partial differential equations
∂2u ∂2
2
+ 2 = f (x, y) (5.4)
∂x ∂y
217
CHAPTER 5. MATHEMATICAL PROPERTIES OF PDES 218
Laplace for f (x, y) = 0 Potential flow theory, stationary heat conduction, etc.
One-dimensional diffusion equation.
∂u ∂2u
=α 2 (5.5)
∂t ∂x
Wave equation. Fundamental equation for acoustics and other applications.
∂2u 2
2∂ u
= α0 (5.6)
∂t2 ∂x2
1. order linear advection equation.
∂u ∂u
+ α0 =0 (5.7)
∂t ∂x
Inviscid Burger’s equation. Model for Euler’s equation of gas dynamics.
∂u ∂u
+u =0 (5.8)
∂t ∂x
Burger’s equation. Model for incompressible Navier-Stokes equations.
∂u ∂u ∂2u
+u =ν 2 (5.9)
∂t ∂x ∂x
Tricomi equation. Model for transonic flow.
∂2u ∂2u
y + 2 =0 (5.10)
∂x2 ∂y
Convection-diffusion equation. Linear advection and diffusion.
∂u ∂u ∂2u
+ u0 =α 2 (5.11)
∂t ∂x ∂x
The expressions transport, convection and advection are equivalent. (5.7)
above is known as the advection equation.
∂u
The equation is non-linear if a or b are functions of either u, and/or
∂x
∂u
. For example, the transport terms of the Navier-Stokes equations, i.e. the
∂y
left-hand side of (5.1), are quasi-linear 1st order PDEs, even if we normally call
them non-linear.
Writing (5.12) as:
∂u b ∂u
a + +c=0 (5.13)
∂x a ∂y
Assuming that u is continuously differentiable:
∂u ∂u du ∂u dy ∂u
du = dx + dy → = +
∂x ∂y dx ∂x dx ∂y
Defines the characteristic of (5.12) by:
dy b
= (5.14)
dx a
(5.14) inserted above provides:
∂u b ∂u du
a + =a
∂x a ∂y dx
which further provides:
a · du + c · dx = 0 (5.15)
dy
Along the characteristic curve defined by dx = ab , (5.12) reduces to an ODE
given by (5.15). (5.15) is called the compatibility equation for (5.12). If (5.12)
is linear, we can in principle first find the characteristic and then solve (5.15).
This is normally not possible when (5.12) is quasi-linear or non-linear because in
dy
that case dx is a function of u, ie: of the solution itself.
Example 5.2.1. .
The advection equation (5.7) given by ∂u ∂u
∂t + a0 ∂x has the solution u(x, t) =
f (x − a0 t), which is a wave propagating with constant profile and velocity along
x. The wave moves to the right for a0 > 0. The characteristic is given here by:
dx
dt = a0 .
This equation can be integrated directly, yielding: x = a0 t + C1 . Constant
C1 can be determined, for example, by noting that: x = x0 for t = 0, which
implies that C1 = x0 . The characteristic curve equation becomes x = a0 t + x0 .
(5.15) with c = 0 and a = 1 results in du = 0 . This means that u is here the
particle velocity, which is constant along the characteristic.
Let us see an example with initial conditions, choose:
(
1 − x2 for |x| ≤ 1
u(x, 0) =
0for |x| > 1
CHAPTER 5. MATHEMATICAL PROPERTIES OF PDES 220
u
u(x,0) = 1 - x2 1
-1 1
x
t1
dx = a > 0
0
dt
t
Example 5.2.2. .
The inviscid Burger’s equation (5.8) is given by:
∂u ∂u
+u =0
∂t ∂x
This has the solution u(x, t) = f (x − u · t) and characteristic curve given by:
dx
dt = u. This is the same solution as in the previous example, with the important
difference that now a0 is replaced with u. The slope of characteristics varies now
with x, as indicated in Figure 5.2.
The same initial condition as in the previous example provide the following
solution:
(
1 − ζ 2 for |ζ| ≤ 1
u(x, t) = der ζ = x − u · t
0 for |ζ| > 1
Again, we see that the only difference from the previous example is that a0
is now replaced by u. Explicit solution, noting that u(x, t) = 0 for |ζ| > 1:
CHAPTER 5. MATHEMATICAL PROPERTIES OF PDES 221
t
dx = u
dt
Figure 5.2: Characteristic curves for the Burger’s equation (5.2.2) at different
locations along x.
1
u(x, t) = 2 [(2xt − 1) ± g(x, t)],
2t
p
g(x, t) = 1 − 4xt + 4t2 , |ζ| ≤ 1 (5.16)
∂u g(x, t) ± 1
= (5.17)
∂x t · g(x, t)
3.0
2.5
2.0
1.5
t
1.0
0.5
0.0
−2 −1 0 1 2 3
x
1.2
t=0
1.0 t = 12
t =1
0.8
0.6
u
0.4
0.2
0.0
−1.0 −0.5 0.0 0.5 1.0 1.5
x
Figure 5.4: Inviscid Burger’s equation solution. Note that solution for time
t = 1 is multiply. After tcrit additional mathematical notions have to be applied
to identify a unique solution.
∂ 2 φ ∂φ
∂φ
− − exy sin φ = 0 quasi-linear (5.19)
∂x ∂x2 ∂x
∂ 2 φ ∂φ
x 2 − − exy sin φ = 0 semi-linear (5.20)
∂x ∂x
2 2 2
∂ φ ∂ φ
= 0 non-linear (5.21)
∂x2 ∂y 2
∂u ∂u ∂v
A +B +C +f =0 (5.23)
∂x ∂y ∂y
∂u ∂v
− =0 (5.24)
∂y ∂x
It can be shown that a higher order (quasi-linear) PDE can always be written
as a system of 1st order PDEs. Moreover, the resulting first order system
can have many forms. If, for example, (5.18) is the potential equation in gas
dynamics, we can denote φ as the potential speed. (5.24) is the condition of
irrotational flow.
We will now try to write (5.23) and (5.24) as a total differential (see (5.13) –
(5.15)). Multiply (5.24) with any scalar σ and add to (5.23):
∂u B + σ ∂u ∂v C ∂v
A + −σ − +f =0 (5.25)
∂x A ∂y ∂x σ ∂y
Now we have:
du ∂u dy ∂u dv ∂v dy ∂v
= + , = +
dx ∂x dx ∂y dx ∂x dx ∂y
Hence:
du ∂u ∂u dv ∂v ∂v
= +λ , = +λ (5.26)
dx ∂x ∂y dx ∂x ∂y
CHAPTER 5. MATHEMATICAL PROPERTIES OF PDES 224
B − Cλ
λ= (5.31)
A
The following 2nd order equation can be obtained from (5.31) to determine
λ:
Aλ2 − Bλ + C = 0 (5.32)
or by using (5.27):
The above terms come from the analogy with the cone cross-section equation
of the expression Ax2 + Bxy + Cy 2 + Dx + F = 0. For example, x2 − y 2 = 1
represents a hyperbola if B 2 − 4AC > 0.
The roots λ1 and λ2 are characteristics. Hence:
∂2φ ∂2φ
(1 − M 2 ) + 2 = 0, M = Mach-number. (5.36)
∂x2 ∂y
• M = 1: Parabolic (degenerate).
• M < 1: Elliptic. Subsonic flow.
∂2u ∂2u
+ 2 = 0 : Elliptic . No real characteristics. (5.37)
∂x2 ∂y
∂ u ∂2u
2
− 2 = 0 : Hyperbolic. Two rel characteristics. (5.38)
∂x2 ∂y
∂u ∂2u
= : Parabolic. One real characteristic. (5.39)
∂y ∂x2
The model equations shown above differ by the number of real characteristics.
This directly affects the boundary conditions that are possible in the three cases,
as physical information propagates along characteristics.
∂2u 2
2∂ u
= α 0 (5.40)
∂t2 ∂x2
dx
(5.40) has the characteristic = ±a0 , where a0 is the wave propagation
dt
dx dx
speed. We denote = +a0 with C + and = −a0 with C − . The area of
dt dt
influence for (5.40) is shown in Figure 5.5.
C+ C-
Region of
dependency
a b x
Figure 5.5: Regions of dependence and influence with respect to point P for
the wave equation(5.40).
The solution at point P depends only on the solution in the region of depen-
dence, while the value at P only affects the solution within the region of influence.
Boundary values for domain borders can not be prescribed independently of
CHAPTER 5. MATHEMATICAL PROPERTIES OF PDES 227
∂2u ∂2u
+ 2 =0 (5.41)
∂x2 ∂y
The solution domain is shown in Figure 5.6.
y
n
C
Region of influence
and region of dependency
Figure 5.6: Regions of dependence and influence for the model elliptic equation
(5.41).
∂u ∂2u
= (5.42)
∂t ∂x2
The solution domain for (5.42) is shown in Figure 5.7.
Region of influence
P
Characteristic
Region of dependency
a b x
Figure 5.7: Regions of dependence and influence for the model parabolic
equation (5.42).
6.1 Introduction
Important and frequently occuring practical problems are governed by elliptic
PDEs, including steady-state temperature distribution in solids.
• Laplace:
∇2 u = 0 (6.1)
• Poisson:
∇2 u = q (6.2)
• Helmholtz:
∇2 u + c · u = q (6.3)
∂2 ∂2 ∂2
where ∇2 = + + in 3D
∂x2 ∂y 2 ∂z 2
229
CHAPTER 6. ELLIPTIC PDES 230
and
∂2 ∂2
where ∇2 = + in 2D
∂x2 ∂y 2
The 2D versions of the famous equations above are special cases of the a
generic elliptic PDE may be represented by:
∂ ∂u ∂ ∂u
a + b +c·u=q (6.4)
∂x ∂x ∂y ∂y
where a, b, c og q may be functions of x and y and a and b have the same sign.
• Transient heat conduction is governed by:
Possion problems.
• The stress equation for torsion of an elastic rod
• The displacement of an membrane under constant pressure.
CHAPTER 6. ELLIPTIC PDES 231
C
n
u(x,y)
xi = x0 + i · ∆x, i = 0, 1, 2, . . .
yj = y0 + j · ∆y, j = 0, 1, 2, . . .
i-1 i i+1
j+1
y
i, j
j
Δy
j-1
x
Δx
Backward difference:
∂u ui,j − ui−1,j
= + O(∆x) (6.8)
∂x i,j ∆x
Central differences:
∂u ui+1,j − ui−1,j
+ O (∆x)2
= (6.9)
∂x i,j 2∆x
∂ 2 u
ui+1,j − 2ui,j + ui−1,j
+ O (∆x)2
2
= 2
(6.10)
∂x i,j (∆x)
∂u ∂2u
Similar formulas for ∂y and ∂y 2 follow directly:
Forward difference:
∂u ui,j+1 − ui,j
= (6.11)
∂y i,j
∆y
Backward difference:
∂u ui,j − ui,j−1
= (6.12)
∂y i,j
∆y
Central differences:
∂u ui,j+1 − ui,j−1
= (6.13)
∂y i,j 2∆y
∂ 2 u
ui,j+1 − 2ui,j + ui,j−1
= (6.14)
∂y 2 i,j (∆y)2
CHAPTER 6. ELLIPTIC PDES 233
j+1
j-1
i-1 i i+1
Figure 6.3: 5-point numerical stencil for the discretization of Laplace equations
using central differences.
It is easy to note that in (6.2.1), the value for the central point is the mean
of the values of surrounding points. Equation (6.2.1) is very well-known and is
usually called the 5-point formula (used in Chapter (6) ).
A problem arises at boundaries when using central differences. At those
locations the stencil might fall outside of the computational domain and special
strategies must be adopted. For example, one can replace central differences
with backward or forward differences, depending on the case. Here we report
some of these formulas with 2nd order accuracy.
Forward difference:
∂u −3ui,j + 4ui+1,j − ui+2,j
= (6.15)
∂x i,j
2∆x
Backward difference:
∂u 3ui,j − 4ui−1,j + ui−2,j
= (6.16)
∂x i,j 2∆x
The formulas for 2.5 , 2.5 and 2.5 differences given in Chapter 2 can be used
now if equipped with two indexes. A rich collection of difference formulas can
be found in Anderson [14]. Detailed derivations are given in Hirsch [7].
stationary conditions occurs. Normally, this will be time consuming task and
one may encounter limitations to ensure a stable solution. By disregarding such
a timestepping approach one does not have to worry about stability. Apart from
seeking a fast solution, we are also looking for schemes with efficient storage
management a reasonable programming effort.
Let us start by discretizing the stationary heat equation in a rectangular
plated with dimension as given in Figure 6.4:
∂2T ∂2T
+ =0 (6.17)
∂x2 ∂y 2
y
100 100 100 100
100
1.0
xi = x0 + i · h, i = 0, 1, 2, . . .
yj = y0 + j · h, j = 0, 1, 2, . . .
j+1
j-1
i-1 i i+1
which states that the temperature Ti,j in at the location (i, j) depends on the
values of its neighbors to the left, right, up and down. Frequently, the neighbors
are denoted in compass notation, i.e. west = i − 1, east = i + 1, south = j − 1,
and north = j + 1. By referring to the compass directions with their first letters,
and equivalent representation of the stencil in (6.18) reads:
Te + Tw + Tn + Ts − 4Tm = 0 (6.19)
Tn
Tw Tm Te
Ts
The smoothing nature of elliptic problems may be seen even more clearly by
isolating the Ti,j in (6.19) on the left hand side:
Te + Tw + Tn + Ts
Tm = (6.20)
4
showing that the temperature Tm in each point is the average temperature of
the neighbors (to the east, west, north, and south).
The temperature is prescribed at the bondaries (i.e. Dirichlet boundary
conditions) and are given by:
T = 0.0 at y = 0
T = 0.0 at x = 0 and x=1 for 0 ≤ y < 1.5 (6.21)
T = 100.0 at y = 1.5
CHAPTER 6. ELLIPTIC PDES 236
Our mission is now to find the temperature distribution over the plate by
using (6.18) and (6.21) with ∆x = ∆y = 0.25. In each discretized point in (6.5)
the temperatures need to satisfy (6.18), meaning that we have to satisfy as many
equations as we have unknown temperatures. As the temperatures in each point
depends on their neighbors, we end up with a system of algebraic equations.
To set up the system of equations we traverse our our unknows one by one
in a systematic manner and make use of use of (6.18) and (6.21) in each. All
unknown temperatures close to any of the boundaries (left, right, top, bottom)
in Figure 6.4 will be influenced by the prescribed and known temperatures the
wall via the 5-point stencil (6.18). Prescribed values do not have to be calculated
an can therefore be moved to the right hand side of the equation, and by doing
so we modify the numerical stencil in that specific discretized point. In fact,
inspection of Figure 6.4, reveals that only three unknown temperatures are not
explicitly influenced by the presences of the wall (T2,2 , T2,3 , and T2,3 ). The four
temperatures in the corners (T1,1 ,T1,5 , T3,1 , and T3,5 ) have two precribed values
to be accounted for on the right hand side of their specific version of the generic
numerical stencil (6.18). All other unknown temperatures close to the wall have
only one prescribed value to be accounted for in their specific numerical stenscil.
By starting at the lower left corner and traversing in the y-direction first,
and subsequently in the x-direction we get the following system of equations:
−4 1 0 0 0 1 0 0 0 0 0 0 0 0 0 T11 0
1 −4 1 0 0 0 1 0 0 0 0 0 0 0 0
T12
0
0 1 −4 1 0 0 0 1 0 0 0 0 0 0 0
T13
0
0 0 1 −4 1 0 0 0 1 0 0 0 0 0 0
T14
0
0 0 0 1 −4 0 0 0 0 1 0 0 0 0 0
T15
−100
1 0 0 0 0 −4 1 0 0 0 1 0 0 0 0
T21
0
0 1 0 0 0 1 −4 1 0 0 0 1 0 0 0
T22
0
0 0 1 0 0 0 1 −4 1 0 0 0 1 0 0 ·
T23 =
0
0 0 0 1 0 0 0 1 −4 1 0 0 0 1 0
T24
0
0 0 0 0 1 0 0 0 1 −4 0 0 0 0 1
T25
−100
0 0 0 0 0 1 0 0 0 0 −4 1 0 0 0
T31
0
0 0 0 0 0 0 1 0 0 0 1 −4 1 0 0
T32
0
0 0 0 0 0 0 0 1 0 0 0 1 −4 1 0
T33
0
0 0 0 0 0 0 0 0 1 0 0 0 1 −4 1 T34 0
0 0 0 0 0 0 0 0 0 1 0 0 0 1 −4 T35 −100
(6.24)
The analytical solution of (6.17) and (6.21) may be found to be:
∞
X
T (x, y) = 100 · An sinh(λn y) · sin(λn x) (6.25)
n=1
4
where λn = π · n and An =
λn sinh( 23 λn )
import scipy.linalg
import scipy.sparse
import scipy.sparse.linalg
import matplotlib; matplotlib.use(’Qt4Agg’)
import matplotlib.pylab as plt
import time
from math import sinh
tic=time.clock()
theta = scipy.sparse.linalg.spsolve(A,b) #theta=sc.linalg.solve_triangular(A,d)
toc=time.clock()
print ’sparse solver time:’,toc-tic
tic=time.clock()
theta2=scipy.linalg.solve(A.toarray(),b)
toc=time.clock()
print ’linalg solver time:’,toc-tic
# surfaceplot:
x = np.linspace(0, 1, 5)
y = np.linspace(0, 1.5, 7)
X, Y = np.meshgrid(x, y)
T = np.zeros_like(X)
T[-1,:] = 100
for n in range(1,6):
T[n,1] = theta[n-1]
T[n,2] = theta[n+5-1]
T[n,3] = theta[n+10-1]
fig = plt.figure()
ax = fig.gca(projection=’3d’)
surf = ax.plot_surface(X, Y, T, rstride=1, cstride=1, cmap=cm.coolwarm,
linewidth=0, antialiased=False)
ax.set_zlim(0, 110)
ax.zaxis.set_major_locator(LinearLocator(10))
ax.zaxis.set_major_formatter(FormatStrFormatter(’%.02f’))
ax.set_xlabel(’x’)
ax.set_ylabel(’y’)
ax.set_zlabel(’T [$^o$C]’)
ax.set_xticks(x)
ax.set_yticks(y)
# Change some default values to make plots more readable on the screen
LNWDT=2; FNT=15
plt.rcParams[’lines.linewidth’] = LNWDT; plt.rcParams[’font.size’] = FNT
# Set temperature at the top
Ttop=100
Tbottom=10
Tleft=10.0
Tright=10.0
xmax=1.0
ymax=1.5
nx = Nx-1
ny = Ny-1
n = (nx)*(ny) #number of unknowns
print n, nx, ny
CHAPTER 6. ELLIPTIC PDES 240
d = np.ones(n) # diagonals
b = np.zeros(n) #RHS
d0 = d*-4
d1 = d[0:-1]
d5 = d[0:-ny]
A = scipy.sparse.diags([d0, d1, d1, d5, d5], [0, 1, -1, ny, -ny], format=’csc’)
tic=time.clock()
theta = scipy.sparse.linalg.spsolve(A,b) #theta=sc.linalg.solve_triangular(A,d)
toc=time.clock()
print ’sparse solver time:’,toc-tic
tic=time.clock()
theta2=scipy.linalg.solve(A.toarray(),b)
toc=time.clock()
print ’linalg solver time:’,toc-tic
# surfaceplot:
x = np.linspace(0, xmax, Nx + 1)
y = np.linspace(0, ymax, Ny + 1)
X, Y = np.meshgrid(x, y)
T = np.zeros_like(X)
for j in range(1,ny+1):
for i in range(1, nx + 1):
T[j, i] = theta[j + (i-1)*ny - 1]
fig = plt.figure()
ax = fig.gca(projection=’3d’)
CHAPTER 6. ELLIPTIC PDES 241
nx=4
xticks=np.linspace(0.0,xmax,nx+1)
ax.set_xticks(xticks)
ny=8
yticks=np.linspace(0.0,ymax,ny+1)
ax.set_yticks(yticks)
nTicks=5
dT=int(Ttop/nTicks)
Tticklist=range(0,Ttop+1,dT)
ax.set_zticks(Tticklist)
#fig.colorbar(surf, shrink=0.5, aspect=5)
plt.show()
Numerical values are listed below with analytical values from (6.25) enclosed
in paranthesis.
∂2T ∂2T
2
+ =0 (6.26)
∂x ∂y 2
CHAPTER 6. ELLIPTIC PDES 242
y
T = 1.0
1.0
∂T
=0 ∇ 2T = 0 T = 0.0
∂x
0.0 ∂T 1.0 x
=0
∂y
T =1 for y = 1, and 0 ≤ x ≤ 1
T =0 for x = 1, and 0 ≤ y < 1
j+1
T0,j T1,j
T-1,j T2,j
j-1
Figure 6.8: Illustration of how ghostcells with negative indices may be used to
implement Neumann boundary conditions.
∂T −3Ti,j + 4Ti+1,j − Ti+2,j
=
∂x i,j 2∆x
∂T
which for = 0 for x = 0 reduce to:
∂x
4T1,j − T2,j
T0,j = (6.30)
3
For the problem at hand, illustrated in Figure 6.7, central difference ap-
proximation (6.29) and forward difference approximation (6.30) yields fairly
similar results, but (6.30) results in a shorter code when the methods in 6.4 are
employed.
To solve the problem in Figure 6.7 we employ the numerical stencil (6.19)
for the unknows in the field (not influenced by the boundaries)
Tw + Te + Ts + Tn − 4Tm = 0 (6.31)
where we used the same ordering as given in Figure 6.9. For the boundary
conditions we have chosen to implement the by means of (6.29) which is illustrated
in Figure 6.9 by the dashed lines.
Before setting up the complete equation system it normally pays of to look
at the boundaries, like the lowermost boundary, which by systematic usage of
(6.31) along with the boundary conditions in (6.28) yield:
CHAPTER 6. ELLIPTIC PDES 244
y
1.0 1.0 1.0 1.0 1.0
T6 T5 T6 T7 T8 0.0
T2 T1 T2 T3 T4 0.0
x
T5 T6 T7 T8
T9 + 2T14 − 4T13 = −1
T10 + T13 + T15 − 4T14 = −1
T11 + T14 + T16 − 4T15 = −1
T12 + T15 − 4T16 = −1
Notice that for the coarse mesh with only 16 unknown temperatures (see
Figure 6.7) only 4 (T6 , T7 , T10 , and T11 ) are not explicitly influenced by the
CHAPTER 6. ELLIPTIC PDES 245
−4 2 0 0 2 0 0 0 0 0 0 0 0 0 0 0 T1 0
1 −4 1 0 0 2 0 0 0 0 0 0 0 0 0 0
T2
0
0 1 −4 1 0 0 2 0 0 0 0 0 0 0 0 0
T3
0
0 0 1 −4 0 0 0 2 0 0 0 0 0 0 0 0
T4
0
1 0 0 0 −4 2 0 0 1 0 0 0 0 0 0 0
T5
0
0 1 0 0 1 −4 1 0 0 1 0 0 0 0 0 0
T6
0
0 0 1 0 0 1 −4 1 0 0 1 0 0 0 0 0
T7
0
0 0 0 1 0 0 1 −4 0 0 0 1 0 0 0 0
· T8
= 0
0 0 0 0 1 0 0 0 −4 2 0 0 1 0 0 0
T9
0
0 0 0 0 0 1 0 0 1 −4 1 0 0 1 0 0
T10
0
0 0 0 0 0 0 1 0 0 1 −4 1 0 0 1 0
T11
0
0 0 0 0 0 0 0 1 0 0 1 −4 0 0 0 1
T12
0
0 0 0 0 0 0 0 0 1 0 0 0 −4 2 0 0
T13
−1
0 0 0 0 0 0 0 0 0 1 0 0 1 −4 1 0
T14
−1
0 0 0 0 0 0 0 0 0 0 1 0 0 1 −4 1 T15 −1
0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 −4 T16 −1
(6.32)
# src-ch7/laplace_Neumann.py; Visualization.py @ git@lrhgit/tkt4140/src/src-ch7/Visualization.py;
import numpy as np
import scipy
import scipy.linalg
import scipy.sparse
import scipy.sparse.linalg
import matplotlib.pylab as plt
import time
from math import sinh
#import matplotlib.pyplot as plt
# Change some default values to make plots more readable on the screen
LNWDT=2; FNT=15
plt.rcParams[’lines.linewidth’] = LNWDT; plt.rcParams[’font.size’] = FNT
1 1 1 -4 2 2 0 0 0 0
T6 T5 T6 0 1 -4 0 2 0 0 0
T4 T3 T4 0 1 0 -4 2 1 0 0
T2 T1 T2 0 --> A = 0 1 1 -4 0 1 ,b = 0
T3 T4 0 0 1 0 -4 2 -1
0 0 0 1 1 -4 -1
Args:
nx(int): number of elements in each row in the grid, nx=2 in the example above
ny(int): number of elements in each column in the grid, ny=3 in the example above
CHAPTER 6. ELLIPTIC PDES 246
Returns:
A(matrix): Sparse matrix A, in the equation A*T = b
b(array): RHS, of the equation A*t = b
"""
n = (nx)*(ny) #number of unknowns
d = np.ones(n) # diagonals
b = np.zeros(n) #RHS
d0 = d.copy()*-4
d1_lower = d.copy()[0:-1]
d1_upper = d1_lower.copy()
dnx_lower = d.copy()[0:-nx]
dnx_upper = dnx_lower.copy()
d1_lower[nx-1::nx] = 0 # every nx element on first diagonal is zero; starting from the nx-th elem
d1_upper[nx-1::nx] = 0
d1_upper[::nx] = 2 # every nx element on first upper diagonal is two; stating from the first elem
# this correspond to all equations on border (x=0, y)
dnx_upper[0:nx] = 2 # the first nx elements in the nx-th upper diagonal is two;
# This correspond to all equations on border (x, y=0)
b[-nx:] = -Ttop
b[nx-1::nx] += -Tright
A = scipy.sparse.diags([d0, d1_upper, d1_lower, dnx_upper, dnx_lower], [0, 1, -1, nx, -nx], forma
return A, b
if __name__ == ’__main__’:
from Visualization import plot_SurfaceNeumann_xy
# Main program
# Set temperature at the top
Ttop=1
Tright = 0.0
xmax=1.0
ymax=1.
Nx = 10
h=xmax/Nx
Ny = int(ymax/h)
Temp = scipy.sparse.linalg.spsolve(A, b)
∞
X
T (x, y) = An cosh(λn y) · cos(λn x), (6.33)
n=1
π (−1)n−1
der λn = (2n − 1) · , An = 2 , n = 1, 2, . . . (6.34)
2 λn cosh(λn )
The solution for the problem illustrated Figure 6.7 is computed and visualized
the the python code above. The solution is illustrated in Figure 6.10.
Marit 2: Har ikke endret figuren
Figure 6.10: Solution of the Laplace equation with Neuman boundary conditions.
Jacobi’s method
1
xm+1
1 = xm
1 + [b1 − (a11 xm m m
1 + a12 x2 + a13 x3 )] (6.38)
a11
1
xm+1
2 = xm
2 + [b2 − (a21 xm m m
1 + a22 x2 + a23 x3 )] (6.39)
a22
1
xm+1
3 = xm
3 + [b3 − (a31 xm m m
1 + a32 x2 + a33 x3 )] (6.40)
a33
xm+1
i = xmi + δxi (6.41)
n
1 X
δxi = bi − aij xm
j
, i = 1, 2, . . . , n, m = 0, 1, . . . (6.42)
aii j=1
Gauss-Seidel’s method
1
xm+1
1 = xm
1 + [b1 − (a11 xm m m
1 + a12 x2 + a13 x3 )] (6.43)
a11
1
xm+1
2 = xm
2 + [b2 − (a21 xm+1
1 + a22 xm m
2 + a23 x3 )] (6.44)
a22
1
xm+1
3 = xm
3 + [b3 − (a31 xm+1
1 + a32 xm+1
2 + a33 xm3 )] (6.45)
a33
SOR method
ω
xm+1
1 = xm
1 + [b1 − (a11 xm m m
1 + a12 x2 + a13 x3 )]
a11
ω
xm+1
2 = xm
2 + [b2 − (a21 xm+1
1 + a22 xm m
2 + a23 x3 )] (6.46)
a22
ω
xm+1
3 = xm
3 + [b3 − (a31 xm+1
1 + a32 xm+1
2 + a33 xm3 )]
a33
xm+1
i = xmi + δxi (6.47)
" i−1 n
!#
ω X
m+1
X
m
δxi = bi − aik xk + aik xk , i = 1, 2, . . . , n, (6.48)
aii
k=1 k=1
∂2u ∂2u
+ 2 = f (x, y) (6.49)
∂x2 ∂y
which after discretization with central differences, with ∆x = ∆y = h results
in the following difference equation:
xi = x0 + i · h, i = 0, 1, 2, . . . , nx
yj = y0 + j · h, j = 0, 1, 2, . . . , ny (6.51)
where nx and ny denote the number of grid cells in the x− and y-direction,
respectively (see 6.3).
By using the general SOR method (6.48) on (6.50) and (6.51) we get the
following iterative scheme:
um+1
i,j = umi,j + δui,j (6.52)
ω m+1
+ um+1 m m m 2
δui,j = u i,j−1 + ui+1,j + ui,j+1 − 4ui,j − h · fi,j (6.53)
4 i−1,j
CHAPTER 6. ELLIPTIC PDES 250
ω
um+1
i,j = um
i,j + Ri,j (6.55)
4
We will now solve the example in Figure 6.4, 6.3, with the iterative SOR-
scheme in (6.53).
y
T2,7 T3,7 T4,7 T5,7
T1,7 T5,6
m
Ti,j+1
T1,6
m+1
Ti-1,j m m
Ti,j Ti+1,j
T2,3
T1,3
T2,2 T3,2 Tm+1
T1,2 i,j-1
Figure 6.11: Rectangular domain as in Figure 6.4 but boundary nodes are
unknown due to Neumann boundary conditions.
T[-1,:] = Ttop
T[0,:] = Tbottom
T[:,0] = Tleft
T[:,-1] = Tright
In this first simple program we use ω = 1.5 and perform a fixed number of
20 iterations as shown in the program laplace_sor.py below with h = 0.25:
CHAPTER 6. ELLIPTIC PDES 251
# src-ch7/laplace_Diriclhet2.py
import numpy as np
import scipy
import scipy.linalg
import scipy.sparse
import scipy.sparse.linalg
import matplotlib.pylab as plt
import time
from math import sinh
from astropy.units import dT
xmax=1.0
ymax=1.5
# Set simulation parameters
#need hx=(1/nx)=hy=(1.5/ny)
Nx = 40
h=xmax/Nx
Ny = int(ymax/h)
nx = Nx-1
ny = Ny-1
n = (nx)*(ny) #number of unknowns
# surfaceplot:
x = np.linspace(0, xmax, Nx + 1)
y = np.linspace(0, ymax, Ny + 1)
X, Y = np.meshgrid(x, y)
T[-1,:] = Ttop
T[0,:] = Tbottom
T[:,0] = Tleft
T[:,-1] = Tright
tic=time.clock()
omega = 1.5
for iteration in range(20):
for j in range(1,ny+1):
for i in range(1, nx + 1):
R = (T[j,i-1]+T[j-1,i]+T[j,i+1]+T[j+1,i]-4.0*T[j,i])
dT = 0.25*omega*R
T[j,i]+=dT
CHAPTER 6. ELLIPTIC PDES 252
toc=time.clock()
print ’GS solver time:’,toc-tic
1 XX 1 X X δTi,j
|δTi,j | < tola , < tolr , |Ti,j | =
6 0 (6.56)
N i j N i j Ti,j
300
250
h = 0.03125
200
iterasjoner
150
h = 0.0625
100
h = 0.125
50
h = 0.25
0
1 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2
ω
import scipy.sparse.linalg
import matplotlib.pylab as plt
import time
from math import sinh
from astropy.units import dT
# Change some default values to make plots more readable on the screen
LNWDT=2; FNT=15
plt.rcParams[’lines.linewidth’] = LNWDT; plt.rcParams[’font.size’] = FNT
# Set temperature at the top
Ttop=10
Tbottom=0.0
Tleft=0.0
Tright=0.0
xmax=1.0
ymax=1.5
nx = Nx-1
ny = Ny-1
n = (nx)*(ny) #number of unknowns
# surfaceplot:
x = np.linspace(0, xmax, Nx + 1)
y = np.linspace(0, ymax, Ny + 1)
X, Y = np.meshgrid(x, y)
T = np.zeros_like(X)
CHAPTER 6. ELLIPTIC PDES 254
T2 = T.copy()
reltol=1.0e-3
omega = 1.5
iteration = 0
rel_res=1.0
iteration = 0
rel_res=1.0
# Jacobi iterative solution
tic=time.clock()
while (rel_res > reltol):
R2 = (T2[1:-1,0:-2]+T2[0:-2,1:-1]+T2[1:-1,2:]+T2[2:,1:-1]-4.0*T2[1:-1,1:-1])
dT2 = 0.25*R2
T2[1:-1,1:-1]+=dT2
rel_res=np.max(dT2)/np.max(T2)
iteration+=1
toc=time.clock()
print "Jacobi solver time:\t\t{0:0.2f}. \t Nr. iterations {1}".format(toc-tic,iteration)
1
ρ= [cos(π/nx ) + cos(π/ny )] (6.58)
2
2
ω= p (6.59)
1 + 1 − ρ2
If the interval length h is different in x- og y-directions with h = hx in
x-direction and h = hy in y-direction, instead of (6.59), the following holds:
% program lapsor2
clear
net = 1;
h = 0.25;
hn = h/2^(net -1);
nx = 1/hn; ny = nx;
imax = nx + 1; % points in x-direction
jmax = ny + 1; % points in y-direction
T = 0.5*ones(imax,jmax); % temperatures
% --- Compute optimal omega ---
ro = cos(pi/nx);
omega = 2/(1 + sqrt(1 - ro^2));
T(1:imax,jmax) = 1; % boundary values along y = 1
T(imax,1:jmax-1) = 0;% boundary values along x = 1
reltol = 1.0e-5; % relative iteration error
relres = 1.0; it = 0;
% --- Start iteration ---
while relres > reltol
it = it + 1;
Tsum = 0.0; dTsum = 0.0;
% --- boundary values along y = 0 ---
for i = 2: imax - 1
resid = 2*T(i,2) + T(i-1,1) + T(i+1,1) - 4*T(i,1);
dT = 0.25*omega*resid;
dTsum = dTsum + abs(dT);
T(i,1) = T(i,1) + dT;
Tsum = Tsum + abs(T(i,1));
end
% --- boundary values along x = 0 ---
for j = 2: jmax - 1
resid = 2*T(2,j) + T(1,j-1) + T(1,j+1) - 4*T(1,j);
dT = 0.25*omega*resid;
CHAPTER 6. ELLIPTIC PDES 257
h (6.59) (6.61)
0.25 1.17 1.14
0.125 1.45 1.45
0.0625 1.67 1.68
0.03125 1.82 1.83
We see that the values reported in the table match values shown in Figure 6.14
when the initial guess for the iterative process is 0.5.
CHAPTER 6. ELLIPTIC PDES 258
2
h = 0.03125
1.9
h = 0.0625
1.8
1.7 h = 0.125
1.6
h = 0.25
ωopt
1.5
1.4
1.3
1.2
1.1
1
0.5 0.502 0.504 0.506 0.508 0.51 0.512 0.514 0.516 0.518 0.52
startverdier
Figure 6.14: Effect of initial guesses on the number of iterations for a problem
with sub-optimal ω.
∂2u ∂2u
+ 2 + u2 = −1 (6.63)
∂x2 ∂y
with Dirichlet boundary conditions, i.e precribed values u = 0 on all bound-
aries (see Figure 6.15).
y
u=0
1.0
Figure 6.15: Solution domain and boundary conditions for a non-linear Poisson
equation.
CHAPTER 6. ELLIPTIC PDES 259
The PDE in (6.63) is only weakly non-linear, so-called semi-linear, but the
approach for how to solve a non-linear elliptic PDE is still illustrated.
We discretize (6.63) with central differences over the domain in Figure 6.15
by a constant stepsize h in both physical directions and get the following system
of equations when the source term on the right hands side has been moved to
the left hand side:
1
[ui+1,j + ui−1,j + ui,j−1 + ui,j‘1 − 4ui,j ] + u2i,j + 1 = 0
h2
or equivalently with by introducing the function fi,j :
y
u1,5 = 0 u2,5 = 0 u3,5 = 0 u4,5 = 0 u5,5 = 0
um+1
i,j = um
i,j + δui,j (6.65)
f (uk,l )
δui,j = −ω (6.66)
∂f (uk,l )
∂ui,j
CHAPTER 6. ELLIPTIC PDES 260
where:
uk,l = um+1
k,l for k < i, l < j (6.67)
uk,l = um
k,l ellers (6.68)
and
∂f f
= −4 + 2h2 · ui,j and δui,j = ω (6.69)
∂ui,j 4 − 2h2 ui,j
We have implemented (6.64) and (6.66) to (6.69) in the python code nonlin_poisson
below:
# Python Gauss-Seidel method
tic=time.clock()
while (rel_res > reltol):
du_max=0.0
for j in range(1,ny+1):
for i in range(1, nx + 1):
R = (U[j,i-1]+U[j-1,i]+U[j,i+1]+U[j+1,i]-4.0*U[j,i]) + h**2*(U[j,i]**2+1.0)
df=4-2*h**2*U[j,i]
dU = omega*R/df
U[j,i]+=dU
du_max=np.max([np.abs(dU),du_max])
rel_res=du_max/np.max(np.abs(U))
iteration+=1
toc=time.clock()
print "Python Gauss-Seidel CPU-time:\t{0:0.2f}. \t Nr. iterations {1}".format(toc-tic,iteration)
We observe relatively good agreement between the two ω-values for a range of
grid sizes h, even though the PDE is weakly non-linear.
CHAPTER 6. ELLIPTIC PDES 261
toc=time.clock()
print "Jacobi CPU-time:\t\t{0:0.2f}. \t Nr. iterations {1}".format(toc-tic,iteration)
Note with this implementation all values on the right hand side are at the
previous iteration, and thus the method must me denoted a Jacobian algorithm.
Finally, we have implemented a third method the Gauss-Seidel method (6.66)
with Cython. Cython is an optimising static compiler (based on Pyrex) for
both the Python programming language and the extended Cython programming
language. The ambition is to makes the writing of computationally superior C
extensions for Python as easy as Python itself.
The expensive triple loop is implemented in a typed Cython function which
looks very much like the Python implementation, save for the type declarations.
import cython
cimport cython
import numpy as np
cimport numpy as np
DTYPE = np.float64
ctypedef np.float64_t DTYPE_t
@cython.boundscheck(False)
@cython.wraparound(False)
@cython.nonecheck(False)
itmax=100
while ((rel_res>reltol) and (it<=itmax)):
CHAPTER 6. ELLIPTIC PDES 262
dU_max=0.0
for j in range(1,rows-2):
for i in range(1,cols-2):
R = (U[j,i-1]+U[j-1,i]+U[j,i+1]+U[j+1,i]-4.0*U[j,i]) + h**2*(U[j,i]**2+1.0)
df=4.0-2*h**2*U[j,i]
dU = omega*R/df
U[j,i]+=dU
dU_max=np.max([np.abs(dU),dU_max])
rel_res=dU_max/np.max(np.abs(U[:,:]))
# print ’rel_res’, rel_res
it+=1
if (it>=itmax): print ’Terminated after max iterations’
return U, rel_res, it
After compilation the Cython module is easily imported into our python-code
which allows for comparison with the methods above as illustrated in the code
below:
# Python Gauss-Seidel method
tic=time.clock()
while (rel_res > reltol):
du_max=0.0
for j in range(1,ny+1):
for i in range(1, nx + 1):
R = (U[j,i-1]+U[j-1,i]+U[j,i+1]+U[j+1,i]-4.0*U[j,i]) + h**2*(U[j,i]**2+1.0)
df=4-2*h**2*U[j,i]
dU = omega*R/df
U[j,i]+=dU
du_max=np.max([np.abs(dU),du_max])
rel_res=du_max/np.max(np.abs(U))
iteration+=1
toc=time.clock()
print "Python Gauss-Seidel CPU-time:\t{0:0.2f}. \t Nr. iterations {1}".format(toc-tic,iteration)
iteration = 0
rel_res=1.0
# Second method
# Jacobi iterative solution
tic=time.clock()
while (rel_res > reltol):
R2 = (U2[1:-1,0:-2]+U2[0:-2,1:-1]+U2[1:-1,2:]+U2[2:,1:-1]-4.0*U2[1:-1,1:-1]) + h**2*(U2[1:-1,1:-1
df=4-2*h**2*U2[1:-1,1:-1]
dU2 = R2/df
U2[1:-1,1:-1]+=dU2
rel_res=np.max(dU2)/np.max(U2)
iteration+=1
toc=time.clock()
print "Jacobi CPU-time:\t\t{0:0.2f}. \t Nr. iterations {1}".format(toc-tic,iteration)
# Third method
# Cython Gauss-Seidel method
rel_res=1.0
CHAPTER 6. ELLIPTIC PDES 263
tic=time.clock()
U3, relreturn, itsused=gs.gauss(U3,reltol,h, omega)
toc=time.clock()
print "Cython Gauss-Seidel CPU-time:\t{0:0.2f}. \t Nr. iterations {1}".format(toc-tic,itsused)
Diffusjonsproblemer
7.1 Introduction
A one-dimensional diffusion equation takes the canonical form:
∂u ∂2u
=α 2 (7.1)
∂t ∂x
where t is an evolutionary variable, which might be both a time-coordinaten
and a spatial coordinate. Some classical diffusion problems are listed below:
• Heat conduction
∂T ∂2T
=α 2
∂t ∂x
• Unsteady boundary layers (Stokes’ problem):
∂u ∂2u
=ν 2
∂t ∂y
• Linearized boundary layer equation with x as an evolutionary variable:
∂u ν ∂2u
=
∂x U0 ∂y 2
• Flow in porous media:
∂u ∂2u
=c 2
∂t ∂x
Our model equation (7.1) may be classified according to (5.18):
264
CHAPTER 7. DIFFUSJONSPROBLEMER 265
dt = 0, B 2 − 4AC = 0
And we find that (7.1) is a parabolic PDE with the characteristics given by
t = constant. By dividing dt with dx we get:
dt dx
=0→ =∞ (7.5)
dx dt
which corresponds to an infinite propagation speed along the characteristic
curve t = constant.
Y b
U0
Figure 7.1: Confined, unsteady Couette flow or channel flow. The channel with
is b
In this section we will look at the problem of unsteady Couette flow, confined
by two walls, which has the following governing equation:
∂U ∂2U
=ν , 0<Y <b (7.6)
∂τ ∂Y 2
with the following boundary conditions, representing the presence of the two
walls or channel if you like:
U (0, τ ) = U0
=τ ≥0 (7.7)
U (b, τ ) = 0
CHAPTER 7. DIFFUSJONSPROBLEMER 266
Futher, the parabolic problem also needs initial conditions to be solved and
we assume:
∂u ∂2u
= , 0<y<1 (7.10)
∂t ∂y 2
with the corresponding boundary conditions:
u(0, t) = 1
, t≥0 (7.11)
u(1, t) = 0
and initial conditions:
un+1
n
∂u j − unj
≈ (7.14)
∂t j ∆t
and central differences for the spatial coordinate y:
where:
tn = n · ∆t, n = 0, 1, 2, . . . , yj = j · ∆y, j = 0, 1, 2, . . .
Substitution of (7.14) in (7.10) results in the following difference equation:
un+1
j = D (unj+1 + unj−1 ) + (1 − 2D) unj (7.16)
CHAPTER 7. DIFFUSJONSPROBLEMER 267
∆t ∆τ
D= =ν (7.17)
(∆y)2 (∆Y )2
A scheme with Forward differences for the Time (evolutionary) variable and
Central differences for the Space variable, is commonly referred to as a FTCS
(Forward Time Central Space). For a FTCS-scheme we normally mean a scheme
which is first order in t og and second order in y. Another common name for
this scheme is the Euler-scheme.
In section 7.6 we show that for D = 1/6, the scheme (7.16)) is second order
in t og and fourth order in y. Further, in fluid mechanics it is customary to write
unj rather than uj,n , such that index for the evolutionary variable has a super
index. We seek to adopt this convention in general.
n+1
In Figure 7.2 we try to illustrate that the scheme is explicitt, meaning that
the unknown value at time n + 1 can be found explicitly from the formula without
having to solve an equation system. In other words, the unknown value at time
n + 1 is not implicitly dependent on other values at other spatial locations at
time n + 1.
The above example is implemented in the code below. Download the code
and experiment using different diffusion numbers. The FTCS-scheme is explicit
and thus has a stability constraint. We will look further into stability in the
next sections, but as we will see in the animations displayed below, the stability
limit in this example is D = 12 .
# src-ch5/couette_FTCS.py;Visualization.py @ git@lrhgit/tkt4140/src/src-ch5/Visualization.py;
#plt.get_current_fig_manager().window.raise_()
import numpy as np
from math import exp, sin, pi
""" Method that calculates the analytical solution to the differential equation:
du/dt = d^2(u)/dx^2 , u = u(y,t), 0 < y < 1
Boundary conditions: u(0, t) = 1, u(1, t) = 0
Initial condition: u(t, 0) = 0 t<0, u(t, 0) = 1 t>0
Args:
y(np.array): radial coordinat
t(float): time
N(int): truncation integer. Truncate sumation after N elements
Returns:
w(float): velocity, us - ur
"""
sumValue = 0
for n in range(1,N+1):
temp = np.exp(-t*(n*np.pi)**2)*np.sin(n*np.pi*y)/n
sumValue += temp
u = 1 - y - (2/pi)*sumValue
return u
def solveNextTimestepFTCS(Uold, D, U_b=1, U_t=0):
""" Method that solves the transient couetteflow using the FTCS-scheme..
At time t=t0 the plate starts moving at y=0
The method solves only for the next time-step.
The Governing equation is:
Args:
uold(array): solution from previous iteration
D(float): Numerical diffusion number
Returns:
unew(array): solution at time t^n+1
"""
Unew = np.zeros_like(Uold)
Uold_plus = Uold[2:]
Uold_minus = Uold[:-2]
Uold_mid = Uold[1:-1]
if __name__ == ’__main__’:
CHAPTER 7. DIFFUSJONSPROBLEMER 269
import numpy as np
from Visualization import createAnimation
N = 20
y = np.linspace(0, 1, N + 1)
h = y[1] - y[0]
dt = D*h**2
T = 0.4 # simulation time
time = np.arange(0, T + dt, dt)
# Spatial BC
U_bottom = 1.0 # Must be 1 for analytical solution
U_top = 0.0 # Must be 0 for analytical solution
# solution matrices:
U = np.zeros((len(time), N + 1))
U[0, 0] = U_bottom # no slip condition at the plate boundary
U[0,-1] = U_top
#
Uanalytic = np.zeros((len(time), N + 1))
Uanalytic[0, 0] = U[0,0]
for n, t in enumerate(time[1:]):
Uold = U[n, :]
Uanalytic[n + 1, :] = analyticSolution(y,t)
U_Visualization = np.zeros((1, len(time), N + 1))
U_Visualization[0, :, :] = U
createAnimation(U_Visualization, Uanalytic, ["FTCS"], y, time, symmetric=False)
s = a1 x1 + a2 x2 + · · · + ak xk (7.18)
Note, that the above is valid only when a1 , a2 , . . . , ak are all positive. In the
following we will consider two cases:
Case 1: a1 + a2 + · · · + ak = 1
In this case (7.20) simplifies to:
un+1
j = D (unj+1 + unj−1 ) + (1 − 2 D) unj
In this case the coefficients are a1 = D, a2 = D, a3 = 1 − 2D such that the
sum of all coefficents is: a1 + a2 + a3 = 1.
From (7.21) we get:
∂T ∂2T
= α 2 + bT, b = konstant, t < tmaks
∂t ∂x
which may be discretized with the FTCS-scheme to yield:
∆t
Tjn+1 = D (Tj+1
n n
+ Tj−1 ) + (1 − 2D + ∆t · b)Tjn , D=α
(∆x)2
and with the following coefficients a1 = a2 = D og a3 = 1 − 2D + ∆t · b we
get:
a1 + a2 + a3 = 1 + ∆t · b ≤ 1
only for negative b-values.
The condition of positive coefficients then becomes: 1 − 2D + ∆t · b > 0 which
corresponds to
1 ∆t · b
0<D< +
2 2
where b < 0.
In this situation the criterion implies that the T − values from the difference
equation will not increase or be unstable for a negative b. This result agrees well
the physics, as a a negative b corresponds to a heat sink.
Note that (7.21) and (7.23) provide limits within which the solution is
bounded, and provides a sufficient criteria to prevent the occurrence of unstable
ocillations in the solution. This criteria may be far more restrictive that what
is necessary for a stable solution. However, in many situations we may be
satisfied with such a criteria. The PC-criterion is used frequently on difference
equations for which a more exact analysis is difficult to pursue. Note that the
PC-criterion may only be applied for explicit schemes if no extra information is
provided. For parabolic equations we often have such extra information by means
of max/min principles (see (7.5.3)). Further, the criterion must be modified in
case of increasing amplitudes.
CHAPTER 7. DIFFUSJONSPROBLEMER 272
One would of course hope for the existence of a necessary and sufficient
condition for numerical stability. However, for general difference equations we
have no such condition, which is hardly surprising. But a method which often
leads to sufficient, and in some cases necessary, conditions for stabily, is von
Neumann’s method. This method involves Fourier-analysis of the linearized
difference equation and may be applied for both explicit and implicit numerical
schemes. We will present this method in 7.4.
∂u ∂2u
= (7.24)
∂t ∂x2
To motivate the rationale for the basis of the von Neumann analysis, we will
start by a revisit on the analytical solution of (7.24) by the method of separation
of variables. The aim of the method is to simplify the PDE to two ODEs which
has analytical solutions. With this approach we assume that the solution may
constructed by means of separation of variables as u(x, t), i.e. as a product of to
f (t) og g(x), which each are only functions of time and space, repsectively:
df (t) d2 g(x)
g(x) = f (t) (7.27)
dt dx2
or more conveniently:
df (t) 1 d2 g(x) 1
= (7.28)
dt f (t) dx2 g(x)
CHAPTER 7. DIFFUSJONSPROBLEMER 273
Observe that the left hand side of (7.28) is a function of t only, whereas the
right hand side is a function of x only. As the both sides of (7.28) must be
satisfied for arbitrary values of t and x, the only possible solution is that both
sides of the equation equals a constant, which we for convenience denote −β 2 ,
thus our original PDE in (7.24) has been transformed to two ODEs:
df (t) 1 df (t)
= −β 2 → + β 2 f (t) = 0 (7.29)
dt f (t) dt
d2 g(x) 1 d2 g(x)
2
= −β 2 → + β 2 g(x) = 0 (7.30)
dx g(x) dx2
The first ODE (7.29) is of first order and has the solution (verify by susbti-
tution):
2
f (t) = e−β t
(7.31)
whereas the second ODE is of second order with solution (7.30):
2
u(x, t) = e−β t
[A sin(βx) + B cos(βx)] (7.33)
and since (7.25) is a linear PDE, the sum or superposition of solutions like
(7.33) will also represent a solution:
m=∞
X 2
u(x, t) = e−βm t [Am sin(βm x) + Bm cos(βm x)]
m=0
2 2 2
u(xj , tn ) = e−β tn
ei βxj = e−β n ∆t iβxj
e = (e−β ∆t n iβxj
) e (7.37)
2 2 2
u(xj , tn+1 ) = e−β tn+1
ei βxj = e−β (n+1) ∆t iβxj
e = (e−β ∆t n+1 iβxj
) e (7.38)
If we divide (7.37) with (7.38), the spatial dependency vanishes and a we get
a spatially independent expression for how the solution is amplified (or damped):
u(xj , tn+1 ) 2
Ga = = e−β ∆t (7.39)
u(xj , tn )
For this reason, Ga is commonly denoted the analytical amplificaiton factor.
(See section 1.6.1 i Numeriske Beregninger). In our particular case we find that
Ga < 1. Having introduced the the amplification factor Ga we may rewrite
(7.37) as:
∆t
un+1
j = D (unj+1 + unj−1 ) + (1 − 2D)unj , D= (7.41)
(∆x)2
Clearly, the solution of the difference equation (7.41) will deviate from the
analytical solution to the differential equation for finite values of ∆x and ∆t.
The deviation/error will increase for increasing values of ∆x and ∆t and we
have seen that when D > 12 , the difference equation becomes unstable, with
constantly increasing amplitudes and alternating signs. As (7.40) is a solution of
the differential equuation, we introuce a simpliar expression for the difference
equation (7.41):
Ejn+1
G= (7.43)
Ejn
which may be complex and is a function og ∆t and β. From (7.43) we may
relate the error Ejn at the n-th timestep with the initial error Ej0 :
|G| ≤ 1 (7.45)
Ejn+1 = D (Ej+1
n n
+ Ej−1 ) + (1 − 2D) Ejn (7.46)
We have previously (see (7.42)) established how the error Ejn is related with
the numerical amplification factor: Ejn = Gn ei β yj which upon substitution in
(7.46) yields:
δ = βh (7.48)
By using terminology for periodical functions, β may be thought of as a
wavenumber (angular frequency) and δ as a phase angle. (See appendix A.3 in
Numeriske Beregninger)
For further simplification we introduce some standard trigonometric formulas:
δ
2
−1 ≤ G ≤ 1 or − 1 ≤ 1 − 4D sin ≤1 (7.51)
2
The right hand side of (7.4.1) is always true as D ≥ 0. For the left hand side
of (7.4.1) we have
CHAPTER 7. DIFFUSJONSPROBLEMER 277
1
D≤
2 sin2 ( 2δ )
which is true for all δ ( −π ≤ δ ≤ π) when D ≤ 21 . A von Neumann condition
for stability of (7.16) may then be presented as:
1
0<D< (7.52)
2
∆t
where D = (∆y) 2 from (7.17).
−1 ≤ 1 − 4D ≤ 1
The right hand side of the inequality will always be satisfied for positive D,
whereas the left hand side yields D ≤ 12 as before.
In many situations we will find that δ = ±π are critical values, and it may
therefore be wise to assess these values, but remember it might not be sufficent
in order to prove stability. On the other hand these values might sufficient to
prove instabilities for those values, as the condition |G| ≤ 1 must be satisfied for
all δ-values in the range [−π, π].
CHAPTER 7. DIFFUSJONSPROBLEMER 278
Ga = exp(−δ 2 D) (7.53)
And from (7.50) we have an expression for the numerical amplification factor:
δ
G = 1 − 2D (1 − cos(δ)) = 1 − 4D sin2 (7.54)
2
In figure 7.3 we plot G and Ga as functions of δ ∈ [0, π] for selected values
of D. For small values of δ we observe small differences between G and Ga , with
slightly larger values of G than for Ga , with progressively increasing differences
as a function of δ.
1.0
0.5
G
0.0
Ga (D = 0.25)
G (D = 0.25)
Ga (D = 0.35)
−0.5
G (D = 0.35)
Ga (D = 0.5)
G (D = 0.5)
−1.0
0 20 40 60 80 100 120 140 160 180
[degrees]
Further, we observe large errors for δ ∈ [90◦ , 180◦ ] and that the amplification
factor G even has the wrong sign, which will lead to unpysical oscillations in the
numerical solution.
The reason why the solution may still be usable is due to that the analytical
solution has an amplitude Ga which diminishes strongly with increasing frequency
(see the analytical solution in (7.13)). Such a smoothing effect is typical for
parbolic PDEs. Yet the effect is noticable as we in the current example have
a discontinuity at y = 0, such that the solution contains many high frequent
components.
Errors in the amplitude is commonly quantified with εD = GGa and denoted
diffusion error or dissipation error. No diffusion error corresponds to εD = 1. The
term diffusive scheme will normally refer to a numerical scheme with decreasing
CHAPTER 7. DIFFUSJONSPROBLEMER 279
amplitude with increasing t. For our FTCS-scheme applied for the diffusion
equation we have:
εD = 1 − D2 δ 4 /2 + Dδ 4 /12 + O(δ 6 )
which confirms that the dissipation error is smal for low frequencies when
D ≤ 1/2.
un+1 − un−1
n
∂u j j
≈
∂t j 2∆t
which results in:
n+1
n-1
j-1 j j+1
Stability analysis
CHAPTER 7. DIFFUSJONSPROBLEMER 280
Let us first try with the criterion of positive coefficients introduced in (7.21).
This condition can not be fulfilled for the coefficient in front of unj for D > 0.
Next, we try to gain insight on the stability of the scheme by using von Neumann’s
method. (7.42) inserted in (7.56) gives:
= 1 − 8GD · sin2 2δ
where we have used (7.48) and (7.49). We have obtained a 2nd order equation
because we are dealing with a 3-level scheme:
G2 = 1 + 4DG
· (cos(δ) −dG
1),
2G dG
dδ = 4D (cos(δ) − 1) dδ − G sin(δ) ,
that with dG
dδ = 0 gives max-min for δ = 0, δ = ±π, as for the FTCS scheme.
For δ = 0 we have G1,2 = ±1, while δ = ±π, resulting in
p
G1,2 = −4D ± 1 + (4D)2
with instability for |G2 | > 1 as before.
n+1
n-1
j-1 j j+1
that D ≤ 21 for a stable scheme. The stability analysis here is slightly more
complicated as with previous cases because we have to discuss what happens
when G takes complex values.
iδj
Inserting (7.42) in (7.58) and division by Gn−1 ee yields:
1 1
G2 = (1 − 2r) + 2r · G · (eiδ + e−iδ ) =
[(1 − 2r) + 4r · G · cos(δ)]
1 + 2r 1 + 2r
which gives the following 2nd order equation:
p p
2. Complex roots: 1−4D2 sin2 (δ) < 0 → 1 − 4D2 sin2 (δ) = i· 4D2 sin2 (δ) − 1
1 n+1 1 n
un+1
j−1 − 2(1 + )u + un+1 n
j+1 = −uj−1 + 2(1 − )u − unj+1 (7.61)
D j D j
∆t
with D = .
(δx)2
The stencil of the resulting numerical scheme is shown in Figure 7.6.
From (7.61) and Figure 7.6 we see that the scheme is implicit. This in turn
implies that the algebraic equations resulting from the discretization of the
original differential problem are coupled and, differently from an explicit scheme,
we have to solve them as a system. In this case the system is tridiagonal, so
we can use the Thomas’ algorithm, for which the code 4.3 was introduce in
Section 4.3.
Before proceeding with a stability analysis for (7.61), we will re-write it a
more general form, by introducing the parameter θ:
n+1
n+½
j-1 j j+1
Figure 7.6: 2-level stencil of the Crank-Nicolson scheme. Note that the scheme
does not require any evaluation of the solution at time level n + 21 .
1 − 4D(1 − θ) sin2 ( 2δ )
G= (7.63)
1 + 4Dθ sin2 ( 2δ )
The stability condition is |G| ≤ 1 or, since G is real: −1 ≤ G ≤ 1. Since we
have that 0 ≤ θ ≤ 1 , the condition of the right side is satisfied for D ≥ 0. For
the left side of the inequality we have that:
δ
2D sin2 (1 − 2θ) ≤ 1 (7.64)
2
or
1 δ
D(1 − 2θ) ≤ da sin2 ≤1 (7.65)
2 2
For 12 ≤ θ ≤ 1, stability is given for all D ≥ 0, ie.: the scheme is uncondition-
ally stable.
CHAPTER 7. DIFFUSJONSPROBLEMER 284
dG
With = 0 we get the max-min for δ = 0, δ = ±π (δ = 0 gives G = 1
dδ
1 − 4D(1 − θ)
as expected) δ = ±δ gives G = which is identical to (7.63) for
1 + 4Dθ
δ ◦ 1 − 4D(1 − θ)
2 = 90 . The condition |G| ≤ 1 becomes −1 ≤ 1 + 4Dθ
≤ 1 with the
same results as before.
Accuracy
Let us now look at the accuracy of the scheme presented in (7.62).
To do so, we write a simple Maple-program:
> eq1:= u(x+h,t+k) - 2*u(x,t+k) + u(x-h,t+k):
> eq2:= u(x+h,t) - 2*u(x,t) + u(x-h,t):
> eq:= (u(x,t+k) - u(x,t))/k -(theta*eq1 + (1-theta)*eq2)/h^2:
> Tnj:= mtaylor(eq,[h,k]):
> Tnj:= simplify(Tnj):
> Tnj:= convert(Tnj,diff);
4
∂6u
1 1 ∂ u 1
Tjn = − θ · ∆t − (∆x)2 · 4
+ (∆t)2 (1 − 3θ) · + . . . (7.67)
2 12 ∂x 6 ∂x6
Figure 7.7: Thin aluminum rod for one-dimensional heat equation example.
Figure 7.7 shows a thin aluminum rod of length equal to 300 mm.
The one-dimensional heat equation is given by:
∂T ∂2T
= α 2 , T = T (x, t)
∂t ∂x
Boundary conditions are:
α = 100mm2 /s
The numerical Fourier number is:
∆t
D=α
(∆x)2
√
Furthermore, we set ∆t = 0.25 s so that ∆x = 5/ D. By selecting D = 4,
we get that ∆x = 2.5 mm. Figures 7.8 and 7.9 show computational results with
θ = 12 and with θ = 1; the Crank-Nicolson and Laasonen schemes, respectively.
We see that the solution obtained using the C-N-scheme contains strong
oscillations at discontinuities present in the initial conditions at x = 100 of
x = 200. These oscillation will be dampen by the numerical scheme, in this
CHAPTER 7. DIFFUSJONSPROBLEMER 286
Figure 7.8: Solution for the one-dimensional heat equation problem using
Crank-Nicolson scheme.
Figure 7.9: Solution for the one-dimensional heat equation problem using
Laasonen scheme.
case they tend to disappear for t = 4 s. The solution delivered by the Laasonen-
scheme presents no oscillations, even around t = 0. To explain the behavior of
these solutions we have to go back to equation (7.63):
1 − 4D(1 − θ) sin2 ( 2δ )
G=
1 + 4D sin2 ( 2δ )
1
For θ = 2 we get:
CHAPTER 7. DIFFUSJONSPROBLEMER 287
1 − 2D sin2 ( 2δ )
G=
1 + 2D sin2 ( 2δ )
For δ near π, G will have a value close to −1 for large values of D. This can
1 − 2D
be clearly seen if one sets δ = π = β · h, which results in G = .
1 + 2D
From (7.44) we have:
1
un+1 θD(un+1 n+1 n n
j = j−1 + uj+1 ) + (1 − θ)D(uj−1 + uj+1 ) + 1 − (1 − θ)2D
1 + 2θD
(7.68)
By summing the coefficients on the right-hand side we find that the sum
is equal to 1. Next, we check for which conditions the coefficients are positive,
which is equivalent to require that 0 < θ < 1 and 1 − (1 − θ) · 2D > 0. The last
inequality is met for D · (1 − θ) < 12 . By setting θ = 0 and θ = 1, we find that
CHAPTER 7. DIFFUSJONSPROBLEMER 288
the sum is equal to 1 for these values, so that we get the following condition for
fulfilling the PC-criterion:
1
D · (1 − θ) ≤ (7.69)
2
By the von Neumann-analysis we found that the following relation should
hold (see (7.66)):
1
D · (1 − 2θ) ≤ (7.70)
2
We see that (7.69) is significantly stricter than (7.70). While the θ-scheme is
unconditionally stable for θ = 12 according to the von Neumann stability analysis,
we must have D ≤ 1 according to the PC-criterion. The PC-criterion provides
a sufficient condition which ensures that the physical max-min criterion is also
fulfilled by the solution delivered by the numerical scheme. The example shown
above confirms the prediction provided by the PC-criterion. Now, is there a
criterion that is both necessary and sufficient for this simple model equation?
Kraaijevanger found the following necessary and sufficient criterion in 1992:
2−θ
D · (1 − θ) ≤ (7.71)
4(1 − θ)
We see that for θ = 12 this criterion implies the condition D ≤ 32 . For θ = 34
it gives (7.71) D ≤ 5, while the PC-criterion yields D ≤ 2.
The purpose of allowing for large D values is related to the fact that one
might be interested in simulations where a large time step is set to let reach
a steady state. But on must not forget about accuracy. Moreover, we should
remember that this is a simple one-dimensional partial differential equation with
constant coefficients, and as such it can be solver very quickly with a modern
PC with for any reasonable ∆t and ∆x, as long as we stay within the stability
area. However, three-dimensional non-linear problems will in general result in
very computational demanding simulations.
∂T ∂2T
= α 2 + bT, b = const., t < tmax (7.72)
∂t ∂x
b < 0 represents a heat sink, while b > 0 is said to be a heat source. In the
first case we can apply the von Neumann strong criterion, whereas in the later
case it is necessary to allow for |G| > 1.
CHAPTER 7. DIFFUSJONSPROBLEMER 289
T (xj , tn+1 ) 2
= exp (b − αβ 2 ) · tn+1 − (b − αβ 2 ) · tn = eb∆t · e−αβ ∆t
Ga =
T (xj , tn )
(7.74)
We see that the term eb·∆t causes increases of amplitude for positive b.
A series expansion for small ∆t:
b2
eb∆t = 1 + b · ∆t + (∆t)2 + . . . (7.75)
2
If we use the FTCS-scheme we obtain:
Tjn+1 = D(Tj+1
n n
+ Tj−1 ) + (1 − 2D)Tjn + ∆tbTjn (7.76)
with
∆t
D=α (7.77)
(∆x)2
If we use the PC-criterion we find that the sum of coefficients is equal to
1 + b · ∆t, which implies that this criterion can only be used for b < 0.
On the other hand, using the von Neumann’s method yields (7.76):
δ
G = 1 − 4D sin2 + ∆t · b (7.78)
2
By setting D = 12 , we have:
δ
|G| ≤ 1 − 2 sin2
+ |∆t · b| ≤ 1 + ∆t · b, b > 0
2
If we compare this expression with (7.75), we see that the agreement between
the analytical and the numerical growth factor for this case.
We can now introduce the generalized von Neumann’s stability condition:
|G| ≤ 1 + K · ∆t (7.79)
with K being a positive constant.
This means that we allow the amplitude to grow exponentially for t < tmax .
In this case we can use the strong stability condition if we reason this way: since
the source term in (7.72) doe not contain any derivative term, we can ignore
this term in the stability analysis. We have the same kind of problem in Section
1.6 in the Numeriske Beregninger, where stiff ordinary differential equations are
discussed (See for example equation (1.6.8) in Section 1.6.1 of the Numeriske
Beregninger).
CHAPTER 7. DIFFUSJONSPROBLEMER 290
For a growing amplitude , we must decrease the step lenth of the independent
variable if we are to achieve a prescribed accuracy. For a decreasing amplitude,
however, we must keep it under a maximum step lenth to get a stable calculation.
Let us look at another example using the FTCS-scheme.
∂u ∂2u ∂u
= α 2 + a0 , α > 0 (7.80)
∂t ∂x ∂x
This equation is called the advection-diffusion equation and is a parabolic
equation according to the classification scheme discussed in Section 4.3 of the
Numeriske Beregninger.
∂u
Using the FTCS scheme for (7.80) with central differences for :
∂x
∆t n ∆t
un+1 = unj + D · (unj+1 − 2unj + unj−1 ) + a0 (u − unj−1 ), D = α
j
2∆x j+1 (∆x)2
δ 2 ∆t 2 2 a2 · D
2 δ
2
|G| = 1 − 4D sin 2
+ a0 sin(δ) = 1 − 4D sin + 0 ·∆t sin2 (δ)
2 ∆x 2 α
Setting now D = 12 :
s
δ 2
a20 a2
|G| = 1 − 2 sin2 · ∆t · sin2 (δ) ≤ 1 + 0 · ∆t
+
2 2α 2α
p
Here we have used the inequality x2 + y 2 ≤ |x| + |y|
a2
With K = 2α0 , we see that the generalized condition (7.79) is met.
The advection-diffusion equation is discussed in detail in Section6.10 of the
Numeriske Beregninger.
∂U ∂2U
L(U ) = − =0
∂t ∂x2
Uin+1 − Uin U n − 2Uin + Ui+1
n
Tin = F (Uin ) = − i−1 (7.86)
k h2
Replacing (7.84) in (7.86):
n n n
∂2U k ∂2U h2 ∂ 4 U k 2 ∂ 3 U
∂U
Tin = − + − + + O(k 3 , h4 )
∂t ∂x2 i 2 ∂t2 12 ∂x4 i 6 ∂t3 i
∂U ∂2U
Da − =0
∂t ∂x2
2 n
k∂ U h2 ∂ 4 U
Tin = − + higher order terms (7.87)
2 ∂t2 12 ∂x4 i
(7.87) shows that Tin = O(k) + O(h2 ), as expected.
(7.87) can also be written as:
n
h2 k ∂2U ∂4U
n
Ti = · 6 2 2 − + O(k 2 ) + O(h4 )
12 h ∂t ∂x4 i
k 1
By choosing D = 2 = , we get:
h 6
Tin = O(k 2 ) + O(h4 ) (7.88)
∆t becomes very small for D = 1/6, but with today’s computers this should
not be a problem, except for the possible accumulation of rounding errors.
CHAPTER 7. DIFFUSJONSPROBLEMER 292
7.6.2 Consistency
We say that a the discretization of a differential equation, i.e. the numerical
scheme, is consistent with the original differential if the local truncation error
Tin → 0 when ∆x and ∆t → 0 independent of each other.
" 2 2 # n 2 3 n
∂2U h2 ∂ 4 U
4
∂U k ∂ U k ∂ U k
Tin = − + + − +O 4
,k ,h 4
∂t ∂x2 h ∂t2 6 ∂t3 12 ∂x4 i h2
i
(7.89)
2
k
Due to the factor it is important to specify how k and h → 0. The
h
scheme is not necessarily consistent with the underlying differential equation.
Such schemes are normally called conditionally consistent.
Case 1
k
Set r0 = → k = r0 · h, and let r0 = be a positive constant.
h
Inserting r0 in (7.89):
n
∂2U 2
n ∂U 2 ∂ U
Ti = − + r0 · + O(h2 )
∂t ∂x2 ∂t2 i
For h → 0, we see that the DuFort-Frankel scheme is consistent with the
∂U ∂2U ∂2U
hyperbolic equation + r02 2 = and not with the original diffusuion
∂t ∂t ∂x2
equation.
Case 2
k
Setting r0 = 2 → k = r0 · h2 . Inserting r0 in (7.89):
h
CHAPTER 7. DIFFUSJONSPROBLEMER 293
h in h in
∂U ∂2U 2
k2 ∂ 3 U h2 ∂ 4 U k4
Tin = ∂t − ∂x2 + r02 h2 ∂∂tU2 + 3 − 12 ∂x4+O 4
h2 , k , h
4
h 2
i
2 3
in6 ∂t i
h2 ∂ 4 U
= r02 h2 ∂∂tU2 + k6 ∂∂tU3 −
12 ∂x4 + O r04 h6 , k 4 , h4
i
n
∂2U
∂U
da − =0
∂T ∂x2 i
We see that in this case Tin → 0 for h and t → 0 with Tin = O(k 2 ) + O(h2 ).
The scheme is now consistent with the diffusion equation. Therefore, the
DuFort-Frankel scheme can be used with k = r0 · h2 . However, this poses a
restriction in ∆t, arising from consistency constrains and not from stability
considerations. Non-consistent schemes usually arise when we change the scheme
after we have made the Taylor expansions in the usual way.
7.6.5 Convergence
Lucas 5: this must be considerably improved. No concepts given to understand
convergence properly
It is generally difficult to prove the convergence of a difference scheme.
Therefore, many attempts have been made to replace the above definition with
conditions that are easier to prove individually but which together are sufficient
for convergence.
A very important result in this direction is given by the following theorem
See Section 4.3 in the Numeriske Beregninger for more details. When we see
all the conditions that must be fulfilled in order for Lax’s theorem to be used,
we understand the difficulties of proving convergence in more general issues.
∂u ∂ 2 u 1 ∂u
= +
∂t ∂r2 r ∂r
Sphere:
CHAPTER 7. DIFFUSJONSPROBLEMER 294
∂u ∂ 2 u 2 ∂u
= +
∂t ∂r2 r ∂r
Both equations can be written as:
∂u ∂ 2 u λ ∂u
= + , λ = 0, 1, 2 (7.90)
∂t ∂r2 r ∂r
λ = 0 with r → x gives the well-known Cartesian case.
(7.90) is a partial differential equation with variable coefficients. We will now
perform a von Neumann stability analysis for this equation for the θ-scheme
from Section (7.5.3).
Stability analysis using θ -scheme for radius r > 0
∆t
Setting rj = ∆r · j, j = 0, 1, . . . and introducing D = (∆r) 2
For r > 0:
n+1 n+1
+ λD n n
2j θ(uj+1 − uj−1 ) + (1 − θ)(uj+1 − uj−1 )
(7.91)
We proceed with the von Neumann analysis by inserting Ejn = Gn · ei·βrj =
Gn ei·δ·j with δ = β · ∆r and using the usual formulas (7.49) and (7.50).
We get:
δ δ (1 − θ)λD
θλD
G· 1 + 4θD sin2 −i· sin(δ) = 1−4(1−θ)D·sin2 +i sin(δ)
2 j 2 j
δ δ
which, using the formula sin(δ) = 2 sin cos and the condition |G| ≤
2 2
1, becomes:
λ2 λ2
2 δ
D · (1 − 2θ) · sin · 4 − 2 + 2 ≤ 2, j ≥ 1 (7.92)
2 j j
It is not difficult to see that the term in parenthesis has its largest value for
δ
2
sin = 1; i.e. for δ = π. (This can also be found by deriving the term with
2
λ2
respect to δ, which has its maximum for δ = π). Factors 2 fall then out.
j
We get:
D · (1 − 2θ) · 2 ≤ 1 (7.93)
CHAPTER 7. DIFFUSJONSPROBLEMER 295
D · (2θ − 1) · 2 ≥ −1
This condition is always satisfied for the specified range of θ, so that in such
cases the scheme is unconditionally stable.
In other words, we have obtain the same stability condition as for the equation
∂u ∂2u
with constant coefficients: = , where we have the FTCS scheme for
∂t ∂r2
θ = 0, the Crank-Nicolson scheme for θ = 1/2, and the Laasonen scheme for
θ = 1.
Note that this analysis is only valid for r > 0.
We now have a look at the equation for r = 0.
λ ∂u
The term must be treated with special care for r = 0.
r ∂r
L’Hopital’s rule:
λ ∂u ∂2u ∂u ∂2u
lim =λ 2 → = (1 + λ) 2 for r = 0 (7.95)
r→0 r ∂r ∂r ∂t ∂r
We have found that using the FTCS scheme requires the usual constrain
D ≤ 1/2. Now we will check if boundary conditions pose some constrains when
using the FTCS scheme.
Version 1
Discretize (7.95) for r = 0 and use the symmetry condition ∂u
∂r (0) = 0:
un+1
0 = [1 − 2(1 + λ)D] · un0 + 2(1 + λ)D · un1 (7.96)
If we use the PC-criterion on (7.96), we get:
1
D≤ (7.97)
2(1 + λ)
For λ = 0 we get the well-known condition D ≤ 1/2, whereas for the cylinder
(λ = 1) we get D ≤ 1/4 and for the sphere, (λ = 2) we have D ≤ 1/6. The
question to be answered now is whether this conditions are for cylinder and
sphere are necessary. We know that D ≤ 1/2 is necessary and sufficient for
λ = 0.
CHAPTER 7. DIFFUSJONSPROBLEMER 296
R0
R
τ R U Us R02 dp
t=ν , r = , u = , us = der k = − (7.100)
R02 R0 k k 4µ dz
which introduced in (7.99) yield:
∂u ∂ 2 u 1 ∂u
=4+ 2 + (7.101)
∂t ∂r r ∂r
Boundary conditions are:
∂u
u(±1, t) = 0,(0, t) = 0 (7.102)
∂r
The later is a symmetry condition. Finding a stationary solution requires
that ∂u
∂t = 0:
d2 us
1 dus 1 d dus
+ = −4 → r = −4 som gir:
dr2 r dr r dr dr
dus C1 dus (0)
= −2r + med C1 = 0 da =0
dr r dr
After a new integration and application of boundary conditions we obtain
the familiar parabolic velocity profile:
us = 1 − r2 (7.103)
We assume now that we have a fully developed profile as given in (7.103)
and that suddenly we remove the pressure gradient. From (7.99) we see that
this results in a simpler equation. The velocity ω(r, t) for this case is given by:
W
ω(r, t) = us − u(r, t) med ω = (7.104)
k
We will now solve the following problem:
∂ω ∂ 2 ω 1 ∂ω
= + (7.105)
∂t ∂r2 r ∂r
with boundary conditions:
∂ω
ω(±1, t) = 0, (0, t) = 0 (7.106)
∂r
and initial conditions:
ω(r, 0) = us = 1 − r2 (7.107)
The original problem now reads:
D
ωjn+1 = ωjn + D · (ωj+1
n
− 2ωjn + ωj−1
n
)+ n
· (ωj+1 n
− ωj−1 ) (7.109)
2j
∆r D
0.02 0.413
0.05 0.414
0.1 0.413
0.2 0.402
0.25 0.394
0.5 0.341
For satisfactory accuracy, we should have that ∆r ≤ 0.1. From the table
above we see that in terms of stabilty, D < 0.4 is sufficient. In order words, a
sort of mean value between D = 12 og D = 14 .
The trouble causing equation is (7.110). We can avoid this problem by using
the following equation instead of (7.98):
1
ω0n = (4ω1n − ω2n ), n = 0, 1, . . . (7.111)
3
A new stability analysis shows then that the new stability condition for the
entire system is now 0 < D ≤ 12 for the FTCS scheme. Figure 7.11 shows the
velocity profile u for D = 0.45 with ∆r = 0.1 after 60 time increments using
(7.110). The presence of instabilities for r = 0 can be clearly observed.
Marit 6: Har ikke endret noe her
Programming of the θ-scheme:
Boundary conditions given in (7.110)
We set rj = ∆r · (j), ∆r = N1 , j = 0, 1, 2, . . . , N as shown in Fig. 7.12. From
Eq. (7.91) and (7.95) we get:
For j = 0:
Figure 7.11: Velocity profile for start-up flow in a tube for different time step
obtained using the FTCS scheme. Stability development can be observed.
0.0 1.0
r
0 1 2 i N-1 N N+1
For j = 1, . . . , N − 1:
n+1
−Dθ · 1 − 1
· ωj−1
2j + (1 + 2Dθ) · ωjn+1 − Dθ · 1 + 1
2j
n+1
· ωj+1
1 n
= D(1 − θ) · 1 − 2j · ωj−1 + [1 − 2D(1 − θ)] · ωjn (7.113)
1 n
+D(1 − θ) · 1 + 2j · ωj+1
Initial values:
1 + 4Dθ −4Dθ 0 0 0 0 0
−Dθ(1 − 0.5 ) 1 + 2Dθ −Dθ(1 + 0.5
1 1 ) 0 0 0 0
0.5 0.5
0 −Dθ(1 − 2 ) 1 + 2Dθ −Dθ(1 + 2 ) 0 0 0
0 0 · · · 0 0
0 0 0 · · · 0
−Dθ(1 − N0.5
0 0 0 0 −2 ) 1 + 2Dθ −Dθ(1 +
0 0 0 0 0 −Dθ(1 − N0.5
−1 ) 1 + 2D
(7.115)
Note that we can also use a linalg-solver for θ = 0 (FTCS-scheme). In
this case the elements in the first upper- and lower diagonal are all 0. The
determinant of the matrix is now the product of the elements on the main
diagonal. The criterion for non singualar matrix is then that all the elements on
the main diagonal are 6= 0, which is satisfied.
The python function thetaSchemeNumpyV1 show how one could imple-
ment the algorithm for solving wn+1 . The function is part of the script/module
startup.py which may be downloaded in your LiClipse workspace.
# Theta-scheme and using L’hopital for r=0
def thetaSchemeNumpyV1(theta, D, N, wOld):
""" Algorithm for solving w^(n+1) for the startup of pipeflow
using the theta-schemes. L’hopitals method is used on the
governing differential equation for r=0.
Args:
theta(float): number between 0 and 1. 0->FTCS, 1/2->Crank, 1->Laasonen
D(float): Numerical diffusion number [dt/(dr**2)]
N(int): number of parts, or dr-spaces. In this case equal to the number of unknowns
wOld(array): The entire solution vector for the previous timestep, n.
Returns:
wNew(array): solution at timestep n+1
"""
superDiag = np.zeros(N - 1)
subDiag = np.zeros(N - 1)
mainDiag = np.zeros(N)
RHS = np.zeros(N)
j_array = np.linspace(0, N, N + 1)
tmp = D*(1. - theta)
RHS[1:] = a + b + c
CHAPTER 7. DIFFUSJONSPROBLEMER 301
superDiag[0] = -4*D*theta
mainDiag[0] = 1 + 4*D*theta
RHS[0] = (1 - 4*tmp)*wOld[0] + 4*tmp*wOld[1]
return wNew
n+1
−Dθ · 1 − 1
2j · ωj−1 + (1 + 2Dθ) · ωjn+1 − Dθ · 1 + 1
2j
n+1
· ωj+1
1 n
= D(1 − θ) · 1 − 2j · ωj−1 + [1 − 2D(1 − θ)] · ωjn (7.117)
1 n
+D(1 − θ) · 1 + 2j · ωj+1
Now inserting Eq. (7.116) into Eq. (7.117) and collecting terms we get the
following difference equation for j = 1:
4 4 4 4
(1 + Dθ) · ω1n+1 − Dθω2n+1 = [1 − D(1 − θ)] · ω1n + D(1 − θ)ω2n (7.118)
3 3 3 3
When ω1 , ω2 , . . . are found, we can calculate ω0 from (7.116).
The initial values are as in Eq. (7.114), repeated here for convonience:
1 + 34 Dθ − 43 Dθ
0 0 0 0 0
0.5
−Dθ(1 − 2 ) 1 + 2Dθ −Dθ(1 + 0.5
2 ) 0 0 0 0
−Dθ(1 − 0.5 0.5
0 3 ) 1 + 2Dθ −Dθ(1 + 3 ) 0 0 0
0 0 · · · 0 0
0 0 0 · · · 0
−Dθ(1 − N0.5
0 0 0 0 −2 ) 1 + 2Dθ −Dθ(1 +
0 0 0 0 0 −Dθ(1 − N0.5
−1 ) 1 + 2D
(7.120)
CHAPTER 7. DIFFUSJONSPROBLEMER 302
Args:
theta(float): number between 0 and 1. 0->FTCS, 1/2->Crank, 1->Laasonen
D(float): Numerical diffusion number [dt/(dr**2)]
N(int): number of parts, or dr-spaces.
wOld(array): The entire solution vector for the previous timestep, n.
Returns:
wNew(array): solution at timestep n+1
"""
superDiag = np.zeros(N - 2)
subDiag = np.zeros(N - 2)
mainDiag = np.zeros(N-1)
RHS = np.zeros(N - 1)
j_array = np.linspace(0, N, N + 1)
tmp = D*(1. - theta)
a = tmp*(1 - 1./(2*j_array[2:-1]))*wOld[1:-2]
b = (1 - 2*tmp)*wOld[2:-1]
c = tmp*(1 + 1/(2*j_array[2:-1]))*wOld[3:]
RHS[1:] = a + b + c
superDiag[0] = -(4./3)*D*theta
mainDiag[0] = 1 + (4./3)*D*theta
RHS[0] = (1 - (4./3)*tmp)*wOld[1] + (4./3)*tmp*wOld[2]
return wNew
CHAPTER 7. DIFFUSJONSPROBLEMER 303
radius [-]
0.8
D = 0.4 0.6
0.4
0.2
0.0
1.0
radius [-]
0.8
D = 0.45 0.6
0.4
0.2
0.0
1.0
radius [-]
0.8
D = 0.5 0.6
0.4
0.2
0.0
1.0
radius [-]
0.8
D = 0.53 0.6
0.4
0.2
0.0
1.0
radius [-]
0.8
D=1 0.6
0.4
0.2
0.0
0.0 0.2 0.4 0.6 0.8 1.0 0.0 0.2 0.4 0.6 0.8 1.0 0.0 0.2 0.4 0.6 0.8 1.0 0.0 0.2 0.4 0.6 0.8 1.0
Velocity [-] Velocity [-] Velocity [-] Velocity [-]
Figure 7.13: Results for stability tests performed for the two versions of the
FTCS scheme, the Crank-Nicolson and the Laasonen schemes. Note that version
1 of FTCS scheme develops instabilities before D = 0.5, and that instabilities
start at r = 0. For version 2 of FTCS scheme instability occurs for D = 0.5 and
for all values of r, as expected.
Lucas 7: I do not see the point of this video, one should use a spatial
resolution that somehow illustrates differences in accuracy, if all results are mesh
independent you can not make any interesting observation ...
h̄ · b
We have chosen the above reported coefficients so that = 1, since this
k
choice leads to a simple analytical solution. Moreover, such values correspond to
characteristic values found for nickel alloys.
CHAPTER 7. DIFFUSJONSPROBLEMER 304
Tv
b
Tk
T (r, 0) = Tk (7.124)
In this example we use the FTCS scheme, but the derivation can be easily
extended to the θ-scheme as shown in Example 7.7.1. With rj = ∆r · j, ∆r =
1 ∆t
N +1 , j = 0, 1, . . . , N + 1 and D = α (∆r)2 from (7.91) we get that when
u(r, t) → T (r, t), θ = 0 and λ = 2:
2) From (7.98):
1
T0n = (4T1n − T2n ), alle n (7.127)
3
Boundary conditions for r = b.
Discretizing (7.122) using 2nd order backward differences:
n
3TN +1 − 4TNn + TNn −1
k· = h̄ · (Tv − Tb )
2 · ∆r
solving for TNn +1 yields:
4TNn − TNn −1 + 2δ · Tv
TNn +1 = (7.128)
3 + 2δ
with
∆r · h̄
δ= (7.129)
k
We have previously shown that we must have D < 1/3 when we use boundary
condition (7.126). In Example 7.7.1 for a cylindrical domain, we found that the
stability limit of D increased when we reduced ∆r using the FTCS scheme. On
the other hand, for the spherical case it appears that the condition D < 1/3 is
independent of ∆r. The reason for this can be understood by writing (7.125) for
j = 1:
Below we show the printout resulting from running program kule for a
computation where we used D = 0.4 and ∆r = 0.1cm. Tk = 300◦ C, Tv = 20◦ C
and a simulation time of 10 minutes, with time steps of 1 second. The results for
the analytical solution are computed using the program function kanalyt. We
see that there is a good agreement between analytical and numerical values (the
analytical solution is derived in Appendix G.9 of the Numeriske Beregninger.)
r(cm) T (◦ C) Ta r(cm) T (◦ C) Ta
0.00 53.38 53.37 2.60 49.79 49.78
0.10 53.37 53.36 2.70 49.52 49.51
0.20 53.36 53.35 2.80 49.24 49.23
0.30 53.33 53.32 2.90 48.95 48.94
0.40 53.29 53.28 3.00 48.65 48.64
0.50 53.24 53.23 3.10 48.35 48.34
0.60 53.18 53.17 3.20 48.03 48.03
0.70 53.11 53.10 3.30 47.71 47.71
0.80 53.03 53.02 3.40 47.38 47.38
0.90 52.93 52.93 3.50 47.05 47.04
1.00 52.83 52.82 3.60 46.70 46.70
1.10 52.72 52.71 3.70 46.35 46.35
1.20 52.59 52.58 3.80 46.00 45.99
1.30 52.46 52.45 3.90 45.63 45.63
1.40 52.31 52.30 4.00 45.26 45.26
1.50 52.16 52.15 4.10 44.89 44.88
1.60 51.99 51.98 4.20 44.50 44.50
1.70 51.81 51.81 4.30 44.11 44.11
1.80 51.63 51.62 4.40 43.72 43.71
1.90 51.43 51.42 4.50 43.32 43.31
2.00 51.22 51.22 4.60 42.92 42.91
2.10 51.01 51.00 4.70 42.51 42.50
2.20 50.78 50.78 4.80 42.09 42.09
2.30 50.55 50.54 4.90 41.67 41.67
2.40 50.30 50.30 5.00 41.25 41.24
2.50 50.05 50.04
307
CHAPTER 8. HYPERBOLIC PDES 308
∂u un+1
j − unj ∂u unj+1 − unj−1
≈ , ≈
∂t ∆t ∂x 2∆x
and we may substitute the approximations (8.2) into the advection equation
(8.1) to yield:
C
un+1
j = unj − (unj+1 − unj−1 ) (8.4)
2
For convenience we have introduced the non-dimensional Courant-Friedrich-Lewy
number (or CFL-number or Courant-number for short):
∆t
C = a0 (8.5)
∆x
The scheme in (8.4) is first order in time and second order in space (i.e. (O(∆t)+
O(∆x2 ))), and explicit in time as can bee seen both from Figure 8.1 and (8.4).
n+1
j-1 j j+1
Figure 8.1: Illustration of the first order in time central in space scheme.
We will try to solve model equation (8.1) with the scheme (8.4) and initial
conditions illustrated in Fig (8.2) with the mathematical representation:
Solutions for three CFL-numbers: C=0.25, 0.5 and 1.0 are illustrated in
Figure 8.3. Large oscillations are observed for all values of the CFL-number,
even though they seem to be sligtly reduced for smaller C-values,; thus we
CHAPTER 8. HYPERBOLIC PDES 309
1.0
C = 1.0
4
u
0
-2
-4
0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1
2.5
2 C = 0.5
u1.5
1
0.5
-0.5
-1
0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1
C = 0.25
1.5
u
1
0.5
0
0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1
x
Figure 8.3: Computed solutions with the (8.4). Dotted line: analytical solution,
solid line: computed soultion.
∂u unj − unj−1
= + O(∆x) (8.10)
∂x ∆x
CHAPTER 8. HYPERBOLIC PDES 311
a0
n+1
j-1 j
Figure 8.4: Stencil of the upwind scheme for the linear advection equation with
characteristic speed a0 .
∂u a0 ∆t
Replacing (8.10) in (8.1) and using a forward difference for ∂t and C = ∆x
yields:
un+1
j = unj − C · (unj − unj−1 ) = (1 − C)unj + C · unj−1 (8.11)
(8.11) has an a truncation error of order O(∆t) + O(∆x)
If we set C = 1 into (8.11), we get that un+1 j = unj−1 , which in turn is the
exact solution of the partial differential equation ∂t + a0 ∂u
∂u
∂x = 0 ( see Section 5.2).
For C < 1 the wavefront of the numerical solution is smoothed, indicating that
the scheme introduces a so called numerical viscosity for these values of C. We
will come back to the term numerical viscosity later on. Computational results
shown in Figure 8.5 indicate that the scheme should be stable for C ≤ 1. In
fact, the PC criterion shows that the scheme should be stable for that range of
C. Let us use the von Neumann stability analysis to see if the stability range
extends beyond that range. Insert (8.11) from (7.42) in Chapter 7:
q p
|G| = [1 + C cos(δ − 1)]2 + C 2 sin2 (δ) = 1 − 2C(1 − cos(δ)) · (1 − C)
(8.12)
Enforcing the stability criterion |G| ≤ 1 results in the following:
1 − 2C · (1 − cos(δ)) · (1 − C) ≤ 1 ⇒ C · (1 − cos(δ)) · (1 − C) ≥ 0
eller
δ
C · sin2 (1 − C) ≥ 0
2
Stability interval:
0<C≤1 (8.13)
(8.13) also applies when we set C = |a0 | · ∆t/∆x. All stable explicit 2-
level schemes for the advection equation have a restriction on the admissible
Courant-number .
Marit 9: Har ikke endret figur
CHAPTER 8. HYPERBOLIC PDES 313
1.5
C = 1.0
1
u
0.5
-0.5
0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1
1.5
C = 0.5
1 C = 0.5
u
0.5
-0.5
0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1
1.5
C = 0.25
1
u
0.5
-0.5
0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1
Figure 8.5: Numerical solution for the linear advection equation using the
upwind scheme and different values of Courant-number C.
CHAPTER 8. HYPERBOLIC PDES 314
un+1
j − unj unj+1 − unj−1
+ a0 =0 (8.14)
k 2h
Inserting in (8.14) the Taylor expansions introduced in Section 7.6, such as
(7.84), we obtain:
i n i n
1 h 2
a h
k 0 h2 h3
u + kut + utt + · · · − unj + u + hux + 2 uxx + 6 uxxx + ···
k 2 2h
j j
i n
a0 h h2 h3
− u − hux + 2 uxx − 6 uxxx + ··· = 0
2h
j
Reordering:
∂u ∂u k ∂ 2 u a0 h2 ∂ 3 u
+ a0 =− − + ··· (8.15)
∂t ∂x 2 ∂t2 6 ∂x3
∂u
We now use the differential equation ∂t + a0 ∂u
∂x = 0 to transform the term
k ∂2u
− 2 ∂t2 in a spatial derivative.
The resulting differential equation is:
∂u ∂u
= −a0 (8.16)
∂t ∂x
Deriving with respect to t:
∂2u ∂2u
2
= −a0
∂t ∂t∂x
and then we derive (8.16) with respect to x:
∂2u 2
2∂ u
= a0 (8.17)
∂t2 ∂x2
Inserting (8.17) in (8.15):
CHAPTER 8. HYPERBOLIC PDES 315
∂u ∂u ka2 ∂ 2 u a0 h2 ∂ 3 u
+ a0 =− 0 2 − + ···
∂t ∂x 2 ∂x 6 ∂x3
a0 k
which, when introducing the Courant-number C = h gives:
∂u ∂u Cha20 ∂ 2 u a0 h2 ∂ 3 u
+ a0 =− − + ··· (8.18)
∂t ∂x 2 ∂x2 6 ∂x3
(8.18) clearly shows why the scheme in (8.14) is unstable. In computational
fluid mechanics the term νN = − Cha 2
0
is often called numerical viscosity in
analogy to the physical viscosity that accompanies the diffusive term of advection-
∂2u
diffusion equations like ∂u
∂t + a0 ∂u∂x = ν ∂x2 . When using the scheme given in
(8.14), this results in solving the advection-diffusion equation with a negative
viscosity coefficient , which is certainly unstable. (8.18) provides information
on why oscillations in Figure 8.3 decrease for decreasing C. Equation (8.18) is
called the modified equation for a given numerical scheme, since it shows which
is the differential problem that the scheme is actually solving and it therefore
provides a deep insight in the behavior that the numerical solution will exhibit.
Now we will perform the same procedure as above but for the upwind scheme
presented in Section 8.3. The scheme can be written as:
un+1
j − unj (unj − unj−1 )
+ a0 =0 (8.19)
k h
After introducing Taylor expansions as before and reordering we get:
∂u ∂u k ∂ 2 u a0 h ∂ 2 u
= a0 =− + + ··· (8.20)
∂t ∂x 2 ∂t2 2 ∂x2
Since the differential equation is the same as for the FTCS scheme, we use
the same procedure to replace time derivative terms with spatial derivative ones,
yielding:
∂u ∂u a0 h ∂2u
+ a0 = (1 − C) 2 + · · · (8.21)
∂t ∂x 2 ∂x
The coefficient νN = a02h (1 − C) is the so called numerical viscosity.
For C > 1 this term becomes negative and this in turn means that the
scheme turns unstable. The necessary condition for stability is then C ≤ 1, in
agreement with what seen before in terms of stability analysis. Note that the
numerical viscosity increases with decreasing C, meaning that sharp fronts will
be smoothed more for lower Courant-numbers, as shown in Figure 8.5.
Remark: We have used the original differential equation for the derivation
2 2
of both modified equations (8.18) og (8.21), i.e. ∂∂t2u = a20 ∂∂xu2 . This procedure
is called the Cauchy-Kowalewsky procedure. The problem is that the modified
equation does not generally corresponds to the original equation, as expected.
This means that instead of deriving the original equation, we have to derive
(8.15) and (8.20). This procedure is called the Warming-Hyett procedure and
must be generally adopted. For the above made computations, where we stop
CHAPTER 8. HYPERBOLIC PDES 316
after one iteration of this procedure, both procedures are equivalent. For more
complex differential equations the use of symbolic calculus programs such as
Maple or Maxima becomes mandatory. The Maple-program below illustrate how
to implement such procedure.
> restart:
> EQ:= (u(x,t+k)-u(x,t))/k + a*(u(x+h,t)-u(x-h,t))/(2*h):
> EQT:= mtaylor(EQ,[h,k]):
> MDE:=EQT:
> ELIM:=proc(i::integer,j::integer)
local DE,uxt,UXT:
global EQT,MDE:
DE:=convert(diff(EQT,x$i,t$j-1,D):
uxt:=convert(diff(u(x,t),x$i,t$j),D):
UXT:=solve(DE = 0,uxt):
subs(uxt = UXT,MDE):
end:
> MDE:=ELIM(0,2):
> MDE:=ELIM(1,1):
> MDE:=ELIM(0,3):
> MDE:=ELIM(1,2):
> MDE:=ELIM(2,1):
> # Substitute the Courant number C = a*k/h
> MDE:=expand(subs(k=C*h/a,MDE)):
> u2x:=convert(diff(u(x,t),x$2),D):
> u3x:=convert(diff(u(x,t),x$3),D):
> collect(MDE,[u2x,u3x]):
> RHSMDE:=-coeff(MDE,u2x)*convert(u2x,diff)
-coeff(MDE,u3x)*convert(u3x,diff);
Note that in this case we have obtained the term 13 C 2 h2 a, which was missing
in (8.18). The technique of the modified equation can be also applied to non-
linear equations. Moreover, it can be shown that the method relates to von
Neumann stability method. Hirsch [7] and Anderson [14] use the modified
equation to study fundamental properties of finite difference schemes.
|G|
εD = (8.24)
|Ga |
where |Ga | is the amplituden of analytical amplification factor Ga , i.e. we
have no dissipative error when εD = 1 .
For problems and models related with convection we also need to consider
the error related with timing or phase, and introduce a measure for this kind
error as the dispersion error εφ :
φ
εφ = (8.25)
φa
where φa is the phase for the analytical amplification factor. And again
εφ = 1 corresponds to no dispersion error. Note for parabolic problems with
φa = 0 , it is common to use εφ = φ.
∆t
φa = ω · ∆t = a0 β · ∆t = a0β · ∆x = C · δ (8.28)
∆x
C is the Courant-number and δ = β · ∆x as usual.
From (8.28) we also get:
φa
a0 = (8.29)
β · ∆t
CHAPTER 8. HYPERBOLIC PDES 318
1.2 1.4
1.2
1.0
1.0
0.8
0.8
|G|
<G
0.6
0.6
0.4
CFL = 0.25 0.4
CFL = 0.5
0.2
CFL = 0.8 0.2
CFL = 1.0
0.0 0.0
0 20 40 60 80 100 120 140 160 180
[deg]
Figure 8.6: Diffusion error εD (left y-axis) and dispersion error εφ (right y-axis)
for the upwind scheme as a function of frequency for the upwind scheme. The
dotted lines of εφ correspond to same CFL-numbers as solid lines of εD with the
same color.
but does not guarantees that the scheme is monotone. On the other hand, the
PC criterion ensures that no oscillations can occur. In fact, this criterion can not
be verified for the Lax-Wendroff scheme applied to the linear advection equation.
In fact, monotone schemes for the linear advection equation can be at most first
order accurate.
Lucas 11: we could say something about Godunov’s theorem here! Lucas
10: axes labels are wrong, also legend is not consistent with text for denoting
the Courant-number
1.2 1.4
1.2
1.0
1.0
0.8
0.8
|G|
<G
0.6
0.6
0.4
CFL = 0.25 0.4
CFL = 0.5
0.2
CFL = 0.8 0.2
CFL = 1.0
0.0 0.0
0 20 40 60 80 100 120 140 160 180
[deg]
Figure 8.7: Diffusion error εD (left y-axis) and dispersion error εφ (right y-axis)
as a function of frequency for the Lax-Wendroff scheme. The dotted lines of εφ
correspond to same CFL-numbers as solid lines of εD with the same color.
1.2
C= 1
1
C =0.8
0.8
C = 0.25
C = 0.5
0.6
0.4
0.2
0
0 20 40 60 80 100 120 140 160 180
δ (grader)
0.8
0.6
0.4
0.2
0
0
0.5
1 1
1.5 0.8
0.6
δ(rad.) 2
0.4
2.5
0.2 C
3
0
un+1
j − 21 (unj+1 + unj−1 ) n
Fj+1 n
− Fj−1
=− (8.38)
∆t 2∆x
The Lax-Friedrich scheme is the obtained by isolation un+1
j at the right hand
side:
1 ∆t
un+1 = (unj+1 + unj−1 ) − (F n − Fj−1
n
) (8.39)
j
2 2∆x j+1
By assuming a linear flux F = a0 u it may be shown that the Lax-Friedrich
scheme takes the form:
1 n C
un+1 = (u + unj−1 ) − (unj+1 − unj−1 ) (8.40)
j
2 j+1 2
where we have introduced the CFL-number as given by (8.5) and have the simple
python-implementation:
def lax_friedrich(u):
u[1:-1] = (u[:-2] +u[2:])/2.0 - c*(u[2:] - u[:-2])/2.0
return u[1:-1]
∆t
un+1 = unj +
j Fj−1/2 − Fj+1/2 (8.41)
∆x
C n C2 n
un+1 = unj − uj+1 − unj−1 + uj+1 − 2unj + unj−1
j (8.45)
2 2
with the local truncation error Tjn :
n
∂3u ∂3u
1
Tjn = · (∆t)2 3 + a0 (∆x)2 3 = O[(∆t)2 , (∆x)2 ] (8.46)
6 ∂t ∂x j
C C
un+1
j = (1 + C)unj−1 + (1 − C 2 )unj − (1 − C)unj+1 (8.47)
2 2
The explicit Lax-Wendroff stenticl is illustrated in Figure 8.10
An example of how to implement the Lax-Wendroff scheme is given as
follows:
def lax_wendroff(u):
u[1:-1] = c/2.0*(1+c)*u[:-2] + (1-c**2)*u[1:-1] - c/2.0*(1-c)*u[2:]
return u[1:-1]
un+1
j
n+1
j-1 j j+1
n+1/2 1 n 1 ∆t
uj + unj+1 + Fjn − Fj+1
n
uj+1/2 = (8.48)
2 2 ∆x
n+1/2
Fj+1/2 = F (uj+1/2 ) (8.49)
n+1/2 1 n ∆t
+ unj − F (unj+1 ) − F (unj )
uj+1/2 = u (8.50)
2 j+1 2∆x
n+1/2 1 n ∆t
uj + unj−1 − F (unj ) − F (unj−1 )
uj−1/2 = (8.51)
2 2∆x
Second step:
∆t n+1/2 n+1/2
un+1
j = unj − F (uj+1/2 ) − F (uj−1/2 ) (8.52)
∆x
Notice that for a linear flux F = a0 u, the two-step Lax-Wendroff method
((8.51) and (8.52)) may be shown to reduce to the one-step Lax-Wendroff method
outlined in (8.45) or (8.47).
∆t
upj = unj + Fjn − Fj+1
n
∆x
(8.53)
1 n 1 ∆t
un+1 uj + upj + p
− Fjp
j = Fj−1
2 2 ∆x
where we have introduced the convention Fjp = F (upj ).
Note that in the predictor step we employ the conservative formula (8.41)
n
for a time ∆t with forward differencing, i.e. . Fj+1/2 = Fj+1 = F (unj+1 ). The
corrector step may be interpreted as using (8.41) for a time ∆t/2 with initial
condition 12 unj + upj+1 and backward differencing.
Another MacCormack scheme may be obtained by reversing the predictor
and corrector steps. Note that the MacCormack scheme (8.53) is not written in
conservative form (8.41). However, it easy to express the scheme in conservative
form by expressing the flux in (8.41) as:
1
m
Fjp + Fj+1
n
Fj+1 = (8.54)
2
For a linear flux F (u) = a0 u, one may show that the MacCormack scheme
in (8.53) reduces to a two-step scheme:
# src-ch6/advection_schemes.py
import numpy as np
from matplotlib import animation
from scipy import interpolate
from numpy import where
from math import sin
LNWDT=2; FNT=15
plt.rcParams[’lines.linewidth’] = LNWDT; plt.rcParams[’font.size’] = FNT
# Lax-Wendroff
def lax_wendroff(u):
u[1:-1] = c/2.0*(1+c)*u[:-2] + (1-c**2)*u[1:-1] - c/2.0*(1-c)*u[2:]
return u[1:-1]
# Lax-Friedrich Flux formulation
def lax_friedrich_Flux(u):
u[1:-1] = (u[:-2] +u[2:])/2.0 - dt*(F(u[2:])-F(u[:-2]))/(2.0*dx)
return u[1:-1]
# Lax-Friedrich Advection
def lax_friedrich(u):
u[1:-1] = (u[:-2] +u[2:])/2.0 - c*(u[2:] - u[:-2])/2.0
return u[1:-1]
# Discretize
x = np.linspace(xmin, xmax, Nx+1) # discretization of space
dx = float((xmax-xmin)/Nx) # spatial step size
dt = c/a*dx # stable time step calculated from stability requirement
Nt = int((tmax-tmin)/dt) # number of time steps
time = np.linspace(tmin, tmax, Nt) # discretization of time
solvers = [ftbs,lax_wendroff,lax_friedrich,macCormack]
#solvers = [ftbs,lax_wendroff,macCormack]
#solvers = [ftbs,lax_wendroff]
#solvers = [ftbs]
u_solutions=np.zeros((len(solvers),len(time),len(x)))
uanalytical = np.zeros((len(time), len(x))) # holds the analytical solution
for i, t in enumerate(time[1:]):
if k==0:
uanalytical[i,:] = f(x-a*t) # compute analytical solution for this time step
### Animation
# First set up the figure, the axis, and the plot element we want to animate
fig = plt.figure()
ax = plt.axes(xlim=(xmin,xmax), ylim=(np.min(un), np.max(un)*1.1))
lines=[] # list for plot lines for solvers and analytical solutions
legends=[] # list for legends for solvers and analytical solutions
for solver in solvers:
line, = ax.plot([], [])
lines.append(line)
CHAPTER 8. HYPERBOLIC PDES 328
legends.append(solver.func_name)
line, = ax.plot([], []) #add extra plot line for analytical solution
lines.append(line)
legends.append(’Analytical’)
plt.xlabel(’x-coordinate [-]’)
plt.ylabel(’Amplitude [-]’)
plt.legend(legends, loc=3, frameon=False)
# initialization function: plot the background of each frame
def init():
for line in lines:
line.set_data([], [])
return lines,
def animate_alt(i):
for k, line in enumerate(lines):
if (k==len(lines)-1):
line.set_data(x, uanalytical[i,:])
else:
line.set_data(x, u_solutions[k,i,:])
return lines,
# call the animator. blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate_alt, init_func=init, frames=Nt, interval=100, blit=False)
plt.show()
discretization error will be of order min(p, q). Where p is the spatial order,
and q the temporal order. Thus we could say that ftbs, and Lax-Friedrich are
both first order, and Lax-Wendroff is of second order. In order to determine the
observed (dominating) order of accuracy of our schemes we could adapt the same
procedure outlined in Example 2.8.1, where we determined the observed order
of accuracy of ODEschemes. For the schemes solving the Advection equation
the discretization error will be a combination of ∆x and ∆t, however since
∆t = O (∆x), we may still assume the following expression for the discretization
error:
≈ Chp (8.57)
To determine the observed discretization error we will use the root mean square
error of all our discrete soultions:
v
u
u1 X N 2
E=t fˆ − f
N i=1
# Discretize
x = np.linspace(xmin, xmax, Nx + 1) # discretization of space
CHAPTER 8. HYPERBOLIC PDES 330
−1
¶
2
−2
¢
f-f
¡
−3 ftbs
N
1
µq
lax_friedrich
−4
log10
lax_wendroff
−5
160320 640 1280 2560
Nx
2
log2( n-1
n
)
1, 2 2, 3 3, 4 4, 5
( n-1, n)
Figure 8.11: The root mean square error E for the various advection schemes
as a function of the number of spatial nodes (top), and corresponding observed
convergence rates (bottom).
init_funcs = [init_step, init_sine4] # Select stair case function (0) or sin^4 function (1)
f = init_funcs[1]
samplePoints += len(u)
error = sqrt(error/samplePoints) # calculate rms-error
errorDict[solver.func_name].append(error)
if n>0:
previousError = errorDict[solver.func_name][n-1]
orderDict[solver.func_name].append(log(previousError/error)/log(2))
print " finished iteration {0} of {1}, dx = {2}, dt = {3}, tsample = {4}".format(n+1, Ntd
# refine grid and dt:
Nx *= 2
x = np.linspace(xmin, xmax, Nx+1) # new x-array, twice as big as the previous
dx = float((xmax-xmin)/Nx) # new spatial step size, half the value of the previous
dt = c/a*dx # new stable time step
time = np.arange(tmin, tmax + dt, dt) # discretization of time
N = Nx/2**(Ntds + 1)
N_list = [N*2**i for i in range(1, Ntds+1)]
N_list = np.asarray(N_list)
epsilonN = [i for i in range(1, Ntds)]
epsilonlist = [’$\epsilon_{0} , \epsilon_{1}$’.format(str(i), str(i+1)) for i in range(1, Ntd
for k, solver in enumerate(solvers):
axarr[0][0].plot(N_list, np.log10(np.asarray(errorDict[solver.func_name])),lstyle[k])
axarr[1][0].plot(epsilonN, orderDict[solver.func_name],lstyle[k])
legendList.append(solver.func_name)
where Cx , and Ct are constants, hx and ht are the spatial- and temporal step
lengths and p and q are the spatial- and temporal orders respectively. We are
interested in confirming that p is close to the theoretical spatial order of the
scheme, and that q is close to the theoretical temporal order of the scheme. The
method of doing this is not necessarily straight forward, especially since ht and hx
are coupled through the cfl-constraint condition. In chapter one we showed that
CHAPTER 8. HYPERBOLIC PDES 332
we were able to verify C and p in the case of only one step-length dependency
(time or space), by solving two nonlinear equations for two unknowns using a
Newton-Rhapson solver. To expand this method would now involve solving four
nonlinear algebraic equations for the four unknowns Cx , Ct , p, q. However since
it is unlikely that the observed discretization error match the expression in (8.58)
exactly, we now sugest a method based on optimization and curve-fitting. From
the previous code example we calculated the root mean square error E(hx , ht )
of the schemes by succecively refining hx and ht by a ratio r. We now assume
that E is related to Cx , Ct , p, q as in (8.58). In other words we would like to find
the parameters Cx , Ct , p, q, so that the difference between our caluclated errors
E(hx, ht), and the function (hx , ht ; Cx , p, Ct , q) = Cx hpx + Ct hqt is as small as
possible. This may be done by minimizing the sum (S) of squared residuals of
E and :
N
X
S= ri2 , ri = Ei − (hx , ht ; Cx , p, Ct , q)i
i=1
The python module scipy.optimize has many methods for parameter optimization
and curve-fitting. In the code example below we use scipy.optimize.curve_fit
which fits a function "f(x;params)" to a set of data "y-data" using a Levenberg-
Marquardt algorithmwith a least square minimization criteria. In the code
example below we start by loading the calculated root mean square errors
E (hx , ht ) of the schemes from "advection_scheme_errors.txt", which where
calculated in the same manner as in the previous example. As can be seen
by Figure 8.11 the ftbs, and Lax Friedriech scheme takes a while before they
are in their asymptotic range (area where they converge at a constant rate).
In "advection_scheme_errors.txt" we have computed E (hx , ht ) up to Nx =
89120 in which all schemes should be close to their asymptotic range. This
procedure is demonstrated in the code example below in which the following
expressions for the errors are obtained:
Args:
errorList(array): array of calculated numerical discretization errors E
hxList(array): array of spatial step lengths corresponding to errorList
CHAPTER 8. HYPERBOLIC PDES 333
Returns:
Cx0(float): the optimized values of Cx
Ct0(float): the optimized values of Ct
"""
Args:
errorList(array): array of calculated numerical discretization errors E
hxList(array): array of spatial step lengths corresponding to errorList
htList(array): array of temporal step lengths corresponding to errorList
Cx0 (float): initial guessed value of Cx
p (float): initial guessed value of p
Ct0 (float): initial guessed value of Ct
q (float): initial guessed value of q
Returns:
Cx(float): the optimized values of Cx
p (float): the optimized values of p
Ct(float): the optimized values of Ct
q (float): the optimized values of q
"""
x0 = np.array([Cx0, p0, Ct0, q0]) # initial guessed values for Cx, p, Ct and q
xdata = np.array([hxList, htList]) # independent variables
ydata = errorList # data to be matched with expression in func
gx, p, gt, q = curve_fit(func, xdata, ydata, x0)[0] # call scipy optimization tool curvefit
gx, p, gt, q = round(gx,2), round(p, 2), round(gt,2), round(q, 2)
CHAPTER 8. HYPERBOLIC PDES 334
E_ftbs = []
E_lax_friedrich = []
E_lax_wendroff = []
lineNumber = 1
with the first line containing these headers, and the next lines containing
the corresponding values.
"""
# iterate all lines in FILENAME:
for line in FILENAME:
if lineNumber ==1:
# skip first line which contain headers
lineNumber += 1
else:
lineList = line.split() # sort each line in a list: lineList = [hx, ht, E_ftbs, E_lax_fri
E_ftbs.append(float(lineList[2]))
E_lax_friedrich.append(float(lineList[3]))
E_lax_wendroff.append(float(lineList[4]))
lineNumber += 1
FILENAME.close()
E_ftbs = np.asarray(E_ftbs)
E_lax_friedrich = np.asarray(E_lax_friedrich)
E_lax_wendroff = np.asarray(E_lax_wendroff)
# optimize using only last 5 values of E and h for the scheme, as the first values may be outside
print errorExpr
errorExprHx = Cx*h_x**p
errorExprHt = Ct*h_t**q
ax[0][0].plot(XtickList, np.log10(E),lstyle[k])
ax[0][0].plot(XtickList, np.log10(errorFunc(hxList, htList)),lstyle[k] + ’--’)
ax[1][0].plot(XtickList[-5:], E[-5:],lstyle[k])
ax[1][0].plot(XtickList[-5:], errorFunc(hxList, htList)[-5:],lstyle[k] + ’--’)
ax[1][0].plot(XtickList[-5:], errorFuncHx(hxList[-5:]), lstyle[k] + ’-.’)
ax[1][0].plot(XtickList[-5:], errorFuncHt(htList[-5:]),lstyle[k] + ’:’)
−1.0
ftbs −1.0
lax_friedrich −1
lax_wendroff
=E =E =E
−1.5 −1.5 −2
0.98
=6.45 hx + 1.32 ht0.98 =-1483.49 hx
1.87
+ 25.85 ht1.0 =364.05 hx
2.0
+ -148.59 ht2.0
−2.0 −3
−2.0
log10( )
log10( )
log10( )
−2.5 −4
−2.5
−3.0 −5
−3.5 −3.0 −6
−4.0 −3.5 −7
1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9
(hx ,ht )n (hx ,ht )n (hx ,ht )n
0.0035 0.00006
=E 0.008 =E 0.00005 =E
0.0030
0.98
0.0025 =6.45 hx + 1.32 ht0.98 0.006 =-1483.49 hx
1.87
+ 25.85 ht1.0 0.00004 =364.05 hx
2.0
+ -148.59 ht2.0
0.98 1.87 0.00003 2.0
0.0020 =6.45 hx =-1483.49 hx =364.05 hx
0.98 0.004 1.0 0.00002 2.0
0.0015 =1.32 ht =25.85 ht =-148.59 ht
0.00001
0.0010 0.002
0.00000
0.0005 −0.00001
0.000
0.0000 −0.00002
5 6 7 8 9 5 6 7 8 9 5 6 7 8 9
(hx ,ht )n (hx ,ht )n (hx ,ht )n
Figure 8.12: Optimized error expressions for all schemes. Test using doconce
combine images
CHAPTER 8. HYPERBOLIC PDES 336
Figure 8.13: The solutions at time t=T=1 for different grids corresponding
to the code-example above. A sin4 wave with period T = 0.4 travelling with
wavespeed a = 1. The solutions were calculated with a cfl-number of 0.8
The idea of Flux limiters is to combine the best features of high and low order
schemes. In general a flux limiter method can be obtained by combining any
low order flux Fl , and high order flux Fh . Further it is convenient/required to
CHAPTER 8. HYPERBOLIC PDES 338
write the schemes in the so the so-called conservative form as in (8.41) repeated
here for convenience
∆t
un+1 = unj +
j Fj−1/2 − Fj+1/2 (8.62)
∆x
Fredrik 17: do we need clarifying more about conservative methods and
Fluxes calculated at midpoints and halftime?
The general flux limiter method solves (8.62) with the following definitions
of the fluxes Fi−1/2 and Fi+1/2
Fj−1/2 = Fl (j − 1/2) + φ(rj−1/2 ) Fh (j − 1/2) − Fl (j − 1/2) (8.63)
Fj+1/2 = Fl (j + 1/2) + φ(rj+1/2 ) Fh (j + 1/2) − Fl (j + 1/2) (8.64)
1 ∆t
FLW (j − 1/2) = FLW (uj−1 , uj ) = a uj−1 + a 1 − a uj − uj−1
2 ∆x
(8.66)
1 ∆t
FLW (j + 1/2) = FLW (uj , uj+1 ) = a uj + a 1 − a uj+1 − uj
2 ∆x
(8.67)
1
F (j − 1/2) = a uj−1 + φ rj−1/2 a (1 − c) uj − uj−1 (8.68)
2
1
F (j + 1/2) = a uj + φ rj+1/2 a (1 − c) uj+1 − uj (8.69)
2
When φ = 0 the scheme is the upwind scheme, and when φ = 1 the scheme is
the Lax-Wendroff scheme. Many different limiter functions have been proposed,
the optimal function however is dependent on the solution. Sweby (Fredrik
18: Cite sweby ) showed that in order for the Flux limiting scheme to possess
the wanted properties of a low order scheme; TVD, or monotonically varying in
regions where the solution should be monotone, the following equality must hold
Fredrik 19: definition of TVD? more theory backing these statements?
φ(r)
0≤ , φ(r) ≤ 2 (8.70)
r
Where we require
3
Warming and Beam, =r
Lax-Wendroff, =1
1
0 1 2 3
r
Figure 8.15: TVD region for flux limiters (shaded), and the limiters for the
second order schemes; Lax-Wendroff, and Warming and Beam
For the scheme Fredrik 20: maybe add only using uj−2 , uj−1 uj uj+1 to
be second order accurate whenever possible, the limiter must be an arithmetic
average of the limiter of Lax-Wendroff (φ = 1) and that of Warming and
CHAPTER 8. HYPERBOLIC PDES 340
0 1 2 3
r
Figure 8.16: Second orderTVD region for flux limiters; sweby diagram Fredrik
22: cite sweby?
Note that φ(0) = 0, meaning that second order accuracy must be lost at
extrema. All schemes pass through the point φ(1) = 1, which is a general
requirement for second order schemes. Many limiters that pass the above
constraints have been proposed. Here we will only consider a few:
The limiters given in (8.72) -(8.74) are showed in Figure 8.17. Superbee traverses
the upper bound of the second order TVD region, whereas minmod traverses
the lower bound of the region. Keeping in mind that in our scheme, φ regulates
the anti diffusive flux, superbee is thus the least diffusive scheme of these.
In addition we will consider the two base cases:
upwind : φ(r) = 0 (8.76)
Lax − W endrof f : φ(r) = 1 (8.77)
in which upwind, is TVD, but first order, and Lax-Wendroff is second order, but
not TVD.
CHAPTER 8. HYPERBOLIC PDES 341
Superbee Van-Leer
Min-Mod
0 1 2 3
r
1.0
0.8
0.6
u
0.4
0.2
0.0
0.0 0.2 0.4 0.6 0.8 1.0
x
3.0
2.5
2.0
1.5
r
1.0
0.5
0.0
0.0 0.2 0.4 0.6 0.8 1.0
u
2.0
1.5 superbee
1.0
0.5
van-Leer
0.0 minmod
0.0 0.2 0.4 0.6 0.8 1.0
r
∂ u2
∂u
+ =0 (8.79)
∂t ∂x 2
u2
F (u) = (8.80)
2
the conservative formulation of the Burger’s equation may be represented by
a generic transport equation:
∂u ∂F (u)
+ =0 (8.81)
∂t ∂x
∆t
un+1 = unj − F (unj+1 ) − F (unj )
j (8.82)
∆x
where we have assumed a forward propagating wave (a(u) = F 0 (u) > 0, i.e. u > 0
for the burger equation). In the opposite case ∂F∂x(u) will be approximated by
1 n n
∆x F (uj ) − F (uj−1 )
8.10.2 Lax-Friedrich
The Lax-Friedrich conservation equation take the form as given in (8.38), repeated
here for convinience:
n n
∆t n Fj+1 − Fj−1
un+1
j = (uj+1 + unj−1 ) − (8.83)
2 2∆x
CHAPTER 8. HYPERBOLIC PDES 343
8.10.3 Lax-Wendroff
As outlined in ((8.7.1)), the general Lax-Wendroff two step method takes the
form as given in (8.51) and (8.52) repeated here for convinience:
First step:
n+1/2 1 n ∆t
uj+1 + unj − F (unj+1 ) − F (unj )
uj+1/2 = (8.84)
2 2∆x
n+1/2 1 n ∆t
uj + unj−1 − F (unj ) − F (unj−1 )
uj−1/2 = (8.85)
2 2∆x
∆t n+1/2 n+1/2
un+1
j = unj − F (uj+1/2 ) − F (uj−1/2 ) (8.86)
∆x
In the previous section ((8.7.1)) we showed how the two step Lax-Wendroff
method could be condensed to a one step method. The same procedure may be
applied to a the general transport equation given by (8.81). However for the
nonlinear case (8.43) no longer holds. This may be overcome be rewriting (8.43):
∂ ∂F∂t(u) n
n n n n
∂u ∂F ∂ 2 u ∂ 2 F (u)
=− and =− =−
∂t ∂x ∂t2 ∂t∂x ∂x
j j j j j
∂F (u) ∂u n n (8.87)
∂ ∂F
∂u ∂t
∂ a(u) ∂x
=− =
∂x ∂x
j j
∆t ∆t2 h i
un+1
j = unj − (Fj+1 − Fj−1 )+ aj+1/2 (Fj+1 − Fj )−aj−1/2 (Fj − Fj−1 )
2∆x 2∆x2
(8.89)
where a(u) is the wavespeed, or the Jacobian of F, F 0 (u), which is u for
the burger equation. As indicated a(u) has to be approximated at the indice
(j + 1/2) and (j − 1/2). This may simply be done by averaging the neighboring
values:
1 n
uj + unj+1
aj+1/2 = (8.90)
2
for the burger equation. Another method that assure conservation is to use the
following approximation
( F n −F n
j+1 j
un −un if uj+1 6= uj
aj+1/2 = j+1 j (8.91)
uj otherwise
CHAPTER 8. HYPERBOLIC PDES 344
8.10.4 MacCormack
The MacCormack scheme was discussed in ((8.7.1)) and is given by (8.94)
repeated her for convinience
∆t
upj = unj + Fjn − Fj+1
n
(8.92)
∆x
1 n 1 ∆t
un+1 uj + upj + p
− Fjp
j = Fj−1 (8.93)
2 2 ∆x
(8.94)
ftbs or upwind:
def ftbs(u, t):
"""method that solves u(n+1), for the scalar conservation equation with source term:
du/dt + dF/dx = RHS,
where F = 0.5u^2 for the burger equation
with use of the forward in time backward in space (upwind) scheme
Args:
CHAPTER 8. HYPERBOLIC PDES 345
Lax-Friedrichs:
def lax_friedrich_Flux(u, t):
"""method that solves u(n+1), for the scalar conservation equation with source term:
du/dt + dF/dx = RHS,
where F = 0.5u^2 for the burger equation
with use of the lax-friedrich scheme
Args:
u(array): an array containg the previous solution of u, u(n). (RHS)
t(float): an array
Returns:
u[1:-1](array): the solution of the interior nodes for the next timestep, u(n+1).
"""
u[1:-1] = (u[:-2] +u[2:])/2.0 - dt*(F(u[2:])-F(u[:-2]))/(2.0*dx) + dt*(RHS(t, x[:-2]) + RHS(t, x
return u[1:-1]
Lax-Wendroff-Two-step:
def Lax_W_Two_Step(u, t):
"""method that solves u(n+1), for the scalar conservation equation with source term:
du/dt + dF/dx = RHS,
where F = 0.5u^2 for the burger equation
with use of the Two-step Lax-Wendroff scheme
Args:
u(array): an array containg the previous solution of u, u(n).
t(float): time at t(n+1)
Returns:
u[1:-1](array): the solution of the interior nodes for the next timestep, u(n+1).
"""
ujm = u[:-2].copy() #u(j-1)
uj = u[1:-1].copy() #u(j)
ujp = u[2:].copy() #u(j+1)
up_m = 0.5*(ujm + uj) - 0.5*(dt/dx)*(F(uj)-F(ujm)) + 0.5*dt*RHS(t-0.5*dt, x[1:-1] - 0.5*dx) #u(n+
up_p = 0.5*(uj + ujp) - 0.5*(dt/dx)*(F(ujp)-F(uj)) + 0.5*dt*RHS(t-0.5*dt, x[1:-1] + 0.5*dx)#u(n+0
u[1:-1] = uj -(dt/dx)*(F(up_p) - F(up_m)) + dt*RHS(t-0.5*dt, x[1:-1])
return u[1:-1]
macCormack:
def macCormack(u, t):
"""method that solves u(n+1), for the scalar conservation equation with source term:
du/dt + dF/dx = RHS,
where F = 0.5u^2 for the burger equation
with use of the MacCormack scheme
Args:
u(array): an array containg the previous solution of u, u(n). (RHS)
t(float): an array
Returns:
u[1:-1](array): the solution of the interior nodes for the next timestep, u(n+1).
CHAPTER 8. HYPERBOLIC PDES 346
"""
up = u.copy()
up[:-1] = u[:-1] - (dt/dx)*(F(u[1:]) - F(u[:-1])) + dt*RHS(t-0.5*dt, x[:-1])
u[1:] = .5*(u[1:] + up[1:] - (dt/dx)*(F(up[1:]) - F(up[:-1])) + dt*RHS(t-0.5*dt, x[1:]))
return u[1:-1]
PEP 8, and Google have python style guides which we generally try to follow,
though we have made some different choices in some respect.
• https://www.python.org/dev/peps/pep-0008/
• https://google-styleguide.googlecode.com/svn/trunk/pyguide.html
347
CHAPTER 9. PYTHON STYLE GUIDE 348
9.2 Summary
• Have clear names and use proper namespacing in your code
• Remember that all code you write will end up being treated as a "black
box" sooner or later. So make sure that it actually works like one, with
clean input-output dichotomy.
• Exceptions should crash your program, unless you have very specific reasons
why it should not.
• If you absolutely want to stop exceptions, you should not use "except",
as this will catch the system exit and keyboard interrupts. If you want
catch-all use "except Exception as e"
• You should catch, append and re-raise the exception at each level of the
code, so that an informative traceback will appear when the program dies.
• Unit Testing is desirable, and you should test the crucial behavior of each
feature you wish to push to the main project. Also, test that the code
fails the way it should. Few things are as hard to track down as code that
passes wrong data onto other parts in the program
• Keep your Unit tests. Write them well. Systematize them. Make sure
they´re thorough, independent of each other, and fast.
10.1 Introduction
SymPy is a Python library for symbolic mathematics, with the ambition to
offer a full-featured computer algebra system (CAS). The library design makes
SymPy ideally suited to make symbolic mathematical computations integrated
in numerical Python applications.
Symbols. SymPy introduces the class symbols (or Symbol) to represent math-
ematical symbols as Python objects. An instance of type symbols has a set of
attributes to hold the its properties and methods to operate on those properties.
Such symbols may be used to represent and manipulate algebraic expressions.
Unlike many symbolic manipulation systems, variables in SymPy must be defined
before they are used (for justification see sympy.org)
As an example, let us define a symbolic expression, representing the mathe-
matical expression x2 + xy − y
349
CHAPTER 10. SYMPOLIC COMPUTATION WITH SYMPY 350
import sympy
x, y = sympy.symbols(’x y’)
expr = x**2 + x*y -y
expr
Note that we wrote the expression as if "x" and "y" were ordinary Python
variables, but instead of being evaluated the expression remains unaltered.
To make the output look nicer we may invoke the pretty print feature of
SymPy by:
sympy.init_printing(use_latex=True)
expr
and
expr + y
x**2 + x*y
Note that the result of the above is not x2 + xy − y + y but rather x2 + xy,
i.e. the −y and the +y are added and found to cancel automatically by SymPy
and a simplified expression is outputted accordingly. Appart from rather obvious
simplifications
√ like discarding subexpression that add up to zero (e.g. y − y or
9 = 3), most simplifications are not performed automatically by SymPy.
expr2 = x**2 + 2*x + 1
expr3 = sympy.factor(expr2)
expr3
Matrices. Matrices in SymPy are implemented with the Matrix class and are
constructed by providing a list of row the vectors in the following manner:
sympy.init_printing(use_latex=’mathjax’)
M = sympy.Matrix([[1, -1], [2, 1], [4, 4]])
M
The matrices may naturally be manipulated like any other object in SymPy
or Python. To illustrate this we introduce another 2x2-matrix
n1, n2, n3, n4 =sympy.symbols(’n1 n2 n3 n4’)
N=sympy.Matrix([[n1, n2],[n3, n4]])
N
The two matrices may then be added, subtracted, multiplied, and inverted
by the following simple statements
CHAPTER 10. SYMPOLIC COMPUTATION WITH SYMPY 351
Diagonaliztion:
L, D = M.diagonalize()
L, D
Q = sympy.factor(Q)
Q
sin(x)
limits. perform limx→0 x
from sympy import limit
limit(sin(x)/x, x, 0)
[1] P.W. Bearman and J.K. Harvey. Golf ball aerodynamics. Aeronaut Q, 27(pt
2):112–122, 1976. cited By 119.
[4] J. Evett and C. Liu. 2,500 Solved Problems in Fluid Mechanics and Hy-
draulics. Schaum’s Solved Problems Series. McGraw-Hill Education, 1989.
[5] C. A. J. Fletcher. Computational Techniques for Fluid Dynamics. 1. ,
Fundamental and General Techniques. Springer series in computational
physics. Springer-Verlag, Berlin, Paris, 1991.
[6] Ernst Hairer, Syvert Paul Norsett, and Gerhard Wanner. Solving Ordinary
Differential Equations I: Nonstiff Problems, volume 1. Springer Science &
Business, 2008.
[7] C. Hirsch. Numerical Computation of Internal and External Flows. Elsevier,
2007.
[8] George A. Hool and Nathan Clarke Johnson. Elements of Structural Theory-
Definitions. Handbook of Building Construction. New York. McGraw-Hill.
Google Books, 1920. p.2.
[9] Robert Johansson. Numerical Python. a Practical Techniques Approach for
Industry. Springer, 2015.
352
BIBLIOGRAPHY 353
[12] Randall J. LeVeque. Finite Difference Methods for Ordinary and Partial
Differential Equations: Steady-State and Time-Dependent Problems. Society
for Industrial and Applied Mathematics, Philadelphia, PA, 2007. OCLC:
ocm86110147.
[13] R. W. MacCormack. The effect of viscosity in hypervelocity impact cratering.
Astronautics, AIAA, 69:354, 1969.
[14] John. C. Tannehil, Dale A. Anderson, and Richard H. Pletcher. Computa-
tional Fluid Mechanics and Heat Transfer. Taylor & Francis, 1997.
[15] F.M. White. Viscous Fluid Flow. WCB/McGraw-Hill, 1991.