0% found this document useful (0 votes)
5 views39 pages

Finite-Difference Schemes for Diffusion

Uploaded by

Tayeb Chelirem
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views39 pages

Finite-Difference Schemes for Diffusion

Uploaded by

Tayeb Chelirem
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Computer Lab 2:

Implicit Finite-Difference Schemes for the


Diffusion Equation with Smooth
Initial Conditions

Schemes Investigated
In this session we continue a comparsion the accuracy of various difference schemes for solving the
diffusion equation. This is the equation that arises when the Black-Scholes differential equation is trans-
formed into a form suitable for treatment by finite-difference methods. We compare (a was done last time)

(a) explicit finite-difference, with 400 time-steps (equivalent to the use of a binomial model, but on a grid
rather than a tree);

(b) fully implicit, also with 400 time-steps;

(c) Crank-Nicholson, with 40 time-steps;

(d) Douglas, with 40 time-steps.

The solution method for type (a) is a simple updating rule, while (b), (c), (d) require the solution of tridiago-
nal systems of equations.

A Simple Test Problem with Smooth Initial Conditions


We consider the diffusion equation

¶∂ u ¶∂2 u
= (1)
¶∂ t ¶∂ x2
on the region defined by
on the region defined by

-2 § x § 2 t¥0 (2)

The initial condition is


px
uHx, 0L = sin O (3)
2
and the boundary conditions are

uH2, tL = uH-2, tL = 0 (4)

This has the exact solution

px p2 t
-
uHx, tL = sin O‰ 4 (5)
2
So it is a simple matter to test various difference schemes by comparing with this known exact solution. It
should be emphasized that this type of smooth initial data, which also joins continuously onto the boundary
conditions, is rather atypical of option-pricing problems. Our purpose here is to simplify matters to get a
general feel for the relative merits of explicit and implicit scheme.

A = LU Decomposition for Tridiagonal Systems


Our implicit schemes involve the solution of a matrix problem A.x = r where A has the tridiagonal form :

A = 88b0 , c0 , 0, …, …, 0, 0<, 8a1 , b1 , c1 , 0, …, …, 0<,


80, a2 , b2 , c2 , …, …, 0<, 80, …, ¸⋱, ¸⋱, ¸⋱, ¸⋱, 0<,
80, …, …, 0, a"N-2" , b"N-2" , c"N-2" <,
80, …, …, …, 0, a"N-1" , b"N-1" <<;

MatrixForm@AD

b0 c0 0 … … 0 0
a1 b1 c1 0 … … 0
0 a2 b2 c2 … … 0
0 … ¸⋱ ¸⋱ ¸⋱ ¸⋱ 0
0 … … 0 aN-2 bN-2 cN-2
0 … … … 0 aN-1 bN-1

We can write down all sorts of fancy notation for solving this problem, but it is really very easy. We can
perform row operations to reduce the problem to upper triangular form, then solve the resulting problem
by back substitution starting from the bottom. This amounts to writing A = L.U where L is lower triangu-
lar and U is upper triangular, or indeed doing Gaussian elimination. The L matrix is given by
We can write down all sorts of fancy notation for solving this problem, but it is really very easy. We can
perform row operations to reduce the problem to upper triangular form, then solve the resulting problem
by back substitution starting from the bottom. This amounts to writing A = L.U where L is lower triangu-
lar and U is upper triangular, or indeed doing Gaussian elimination. The L matrix is given by

TraditionalForm@
MatrixForm@881, 0, 0, …, …, 0, 0<, 8l1 , 1, 0, 0, …, …, 0<,
80, l2 , 1, 0, …, …, 0<, 80, …, ¸⋱, ¸⋱, ¸⋱, ¸⋱, 0<,
80, …, …, 0, l"N-2" , 1, 0<, 80, …, …, …, 0, l"N-1" , 1<<DD

1 0 0 … … 0 0
l1 1 0 0 … … 0
0 l2 1 0 … … 0
0 … ¸⋱ ¸⋱ ¸⋱ ¸⋱ 0
0 … … 0 lN-2 1 0
0 … … … 0 lN-1 1

The U matrix is given by

TraditionalForm@
MatrixForm@88d0 , u0 , 0, …, …, 0, 0<, 80, d1 , u1 , 0, …, …, 0<,
80, 0, d2 , u2 , …, …, 0<, 80, …, ¸⋱, ¸⋱, ¸⋱, ¸⋱, 0<,
80, …, …, 0, 0, d"N-2" , u"N-2" <, 80, …, …, …, 0, 0, d"N-1" <<DD

d0 u0 0 … … 0 0
0 d1 u1 0 … … 0
0 0 d2 u2 … … 0
0 … ¸⋱ ¸⋱ ¸⋱ ¸⋱ 0
0 … … 0 0 dN-2 uN-2
0 … … … 0 0 dN-1

If we can solve first L.z = r, then solve U.x = z, we have solved the problem A.x = r, because

A.x = L.U.x = [Link] = L.z = r (6)

Finding L and U is easy (multiply them out to see how). Then solving L.z = r is forward subsitutation.
U.x = z is then back substitution. In general we supply three vectors for the three diagonals and the RHS,
solve for the LHS.

Details
Details
To see how it works out, let N = 4

L = 881, 0, 0, 0, 0<, 8l1 , 1, 0, 0, 0<, 80, l2 , 1, 0, 0<,


80, 0, l3 , 1, 0<, 80, 0, 0, l4 , 1<<;
MatrixForm@LD

1 0 0 0 0
l1 1 0 0 0
0 l2 1 0 0
0 0 l3 1 0
0 0 0 l4 1

U = 88d0 , u0 , 0, 0, 0<, 80, d1 , u1 , 0, 0<, 80, 0, d2 , u2 , 0<,


80, 0, 0, d3 , u3 <, 80, 0, 0, 0, d4 <<;
MatrixForm@UD

d0 u0 0 0 0
0 d1 u1 0 0
0 0 d2 u2 0
0 0 0 d3 u3
0 0 0 0 d4

MatrixForm@[Link]

d0 u0 0 0 0
d0 l1 d1 + l1 u0 u1 0 0
0 d1 l2 d2 + l2 u1 u2 0
0 0 d2 l3 d3 + l3 u2 u3
0 0 0 d3 l4 d4 + l4 u3

and this must equal


A = 88b0 , c0 , 0, 0, 0<, 8a1 , b1 , c1 , 0, 0<, 80, a2 , b2 , c2 , 0<,
80, 0, a3 , b3 , c3 <, 80, 0, 0, a4 , b4 <<;
MatrixForm@AD

b0 c0 0 0 0
a1 b1 c1 0 0
0 a2 b2 c2 0
0 0 a3 b3 c3
0 0 0 a4 b4

We can now read off that

ui = ci , i = 0, … , N - 1

d0 = b0

li = ai ê di-1 , i = 1, …, N

di = bi - li ui-1 , i = 1, …, N

So having worked out those cofficients of L and U, we then solve the easy pair of linear systems. Solving
L.z = r gives us

z0 = r0

zi = ri - li zi-1 , i = 1, … , N - 1

Solving U.x = z gives us, working backwards

dN-1 xN-1 = zN-1

xj = Izj - uj xj+1 M ë dj , j = N - 2, …, 1, 0

Compact versions of the algorithm can be given by reusing/overwriting the arrays. E.g. the Mathematica
one below and tridiag in the NR class. We shall give a more explicit C++ code that follows the above very
closely.
//Tridiagonal matrix solver

#include "nrutil_nr.h"

//Uses expanded algorithm in course notes and PD NR vectors.

//There are more compact versions

//This version could be modified to output L and U as well as soln

void tridiagsolve(NRVec<double> &a, NRVec<double> &b, NRVec<double> &c, NRVec<double> &r, NRVec<dou-
ble> &x)

{
//Tridiagonal matrix solver

#include "nrutil_nr.h"

//Uses expanded algorithm in course notes and PD NR vectors.

//There are more compact versions

//This version could be modified to output L and U as well as soln

void tridiagsolve(NRVec<double> &a, NRVec<double> &b, NRVec<double> &c, NRVec<double> &r, NRVec<dou-
ble> &x)

int j;

int P=[Link]();

NRVec<double> u(P);

NRVec<double> d(P);

NRVec<double> l(P);

NRVec<double> z(P);

u[0]=c[0];

d[0]=b[0];

z[0]=r[0];

// Do LU decomposition and forward subsititution as you go

for (j=1;j<P;j++) {

u[j]=c[j];

l[j]=a[j]/d[j-1];

d[j]=b[j]-l[j]*u[j-1];

z[j] = r[j]-l[j]*z[j-1];

//follow with back substitution

x[P-1] = z[P-1]/d[P-1];

for (j=(P-2);j>=0;j--){

x[j] = (z[j] - u[j]*x[j+1])/d[j];

Implicit Scheme Tridiagonal Solver


Implicit Scheme Tridiagonal Solver
Here is a compacted Mathematica version

CompTridiagSolve =
Compile@88a, _Real, 1<, 8b, _Real, 1<, 8c, _Real, 1<,
8r, _Real, 1<<,
Module@8len = Length@rD, solution = r, aux = 1 ê Hb@@1DDL,
aux1 = r, a1 = Prepend@a, 0.0D, iter<,
solution@@1DD = aux * r@@1DD;
Do@aux1@@iterDD = c@@iter - 1DD aux;

aux = 1 ê Hb@@iterDD - a1@@iterDD * aux1@@iterDDL;


solution@@iterDD =
Hr@@iterDD - a1@@iterDD solution@@iter - 1DDL
aux,
8iter, 2, len<D;

Do@solution@@iterDD -= aux1@@iter + 1DD solution@@iter + 1DD,


8iter, len - 1, 1, - 1<D;
solutionDD;

A = 883, 1, 0, 0, 0<, 81, 3, 1, 0, 0<, 80, 1, 3, 1, 0<,


80, 0, 1, 3, 1<, 80, 0, 0, 1, 3<<;
MatrixForm@AD

3 1 0 0 0
1 3 1 0 0
0 1 3 1 0
0 0 1 3 1
0 0 0 1 3
8v, w, x, y, z< ê. Solve@A.8v, w, x, y, z< ã 81, 2, 3, 4, 5<,
8v, w, x, y, z<D êê N

880.208333, 0.375, 0.666667, 0.625, 1.45833<<

CompTridiagSolve@81, 1, 1, 1<, 83, 3, 3, 3, 3<, 81, 1, 1, 1<,


81, 2, 3, 4, 5<D

80.208333, 0.375, 0.666667, 0.625, 1.45833<

A.%

81., 2., 3., 4., 5.<

Comparison with C++ code


tridiagsolve is a function defined and tested with same example in [Link]. Go check it!

Fully Implicit Scheme


Here is the matrix involved in the computations:

FullyImpCMatrix[alpha_, nminus_, nplus_] :=


Sequence[Table[-alpha, {nplus+nminus-2}],
Table[1+2*alpha, {nplus+nminus-1}],
Table[-alpha, {nplus+nminus-2}]]

Here are the parameters of this particular model:


M=400;
nminus = 80;
nplus = 80;
dx = 0.025;
dtau = 0.00025;
alpha = dtau/dx^2

0.4

Initial and boundary conditions, and problem initialization:

initial = Table@N@Sin@Pi * Hk - 1 - nminusL ê nminusD D,


8k, 1, nminus + nplus + 1<D;
lower = Table@0, 8m, 1, M + 1<D;
upper = Table@0, 8m, 1, M + 1<D;
wold = initial;
wvold = wold;
wnew = wold;

The solution:

CMat = FullyImpCMatrix[alpha,nminus,nplus];

For[m=2, m<=M+1, m++,


(wvold = wold; wold = wnew;
rhs = Take[wold, {2, -2}]+
Table[Which[k==1, alpha*lower[[m]],
k== nplus + nminus-1, alpha*upper[[m]], True, 0],
{k, 1, nplus + nminus-1}];
temp = CompTridiagSolve[CMat, rhs];
wnew = Join[{lower[[m]]}, temp, {upper[[m]]}])]

Interpolating the answer:

interpoldata =
Table[{(k-nminus-1)*dx ,wnew[[k]]}, {k, 1, nminus+nplus+1}];
ufunc = Interpolation[interpoldata, InterpolationOrder -> 3];
Now we plot the error:

px 1
J-p2 N 0.1`
PlotBufunc@xD - SinB F ‰4 , 8x, - 2, 2<, PlotPoints Ø 50F
2
0.00005

-2 -1 1 2

- 0.00005

Finally we tabulate the numerical result, the exact result and the error in the numerical scheme.

samples = TableForm[Join[{{"x", "Implicit FD", "Exact", "Error"}},


Table[Map[PaddedForm[N[Chop[#1]],{5,6}]&,
N[{x, ufunc[x], Sin[Pi*x/2]*Exp[-(Pi^2 0.1)/4],
ufunc[x]- Sin[Pi*x/2]*Exp[-(Pi^2 0.1)/4]},5]],
{x, -2, 2, 0.25}]]]
x Implicit FD Exact Error
-2.000000 0.000000 0.000000 0.000000
-1.750000 -0.299040 -0.299010 -0.000032
-1.500000 -0.552550 -0.552490 -0.000060
-1.250000 -0.721950 -0.721870 -0.000078
-1.000000 -0.781430 -0.781340 -0.000084
-0.750000 -0.721950 -0.721870 -0.000078
-0.500000 -0.552550 -0.552490 -0.000060
-0.250000 -0.299040 -0.299010 -0.000032
0.000000 0.000000 0.000000 0.000000
0.250000 0.299040 0.299010 0.000032
0.500000 0.552550 0.552490 0.000060
0.750000 0.721950 0.721870 0.000078
1.000000 0.781430 0.781340 0.000084
1.250000 0.721950 0.721870 0.000078
1.500000 0.552550 0.552490 0.000060
1.750000 0.299040 0.299010 0.000032
2.000000 0.000000 0.000000 0.000000

Note that we obtain no improvement in accuracy over the explicit scheme (the maximum error is about
twice in fact). The only advantage of the fully implicit scheme over the explicit scheme is the fact that we
can increase a, that is, the time-step for a given price-step, without the system going unstable.

C++ Code for Fully Implicit Method

M=400;
nminus = 80;
nplus = 80;
dx = 0.025;
dtau = 0.00025;
alpha = dtau/dx^2

#include <cmath>

#include <iostream>

#include <fstream>

#include "nrutil_nr.h" //allow access to public domain Numerical


Recipes matrix routines

using namespace std;

void tridiagsolve(NRVec<double> &a, NRVec<double> &b, NRVec<double>


&c, NRVec<double> &r, NRVec<double> &x)

int j;

int P=[Link]();

NRVec<double> u(P);
#include <cmath>

#include <iostream>

#include <fstream>

#include "nrutil_nr.h" //allow access to public domain Numerical


Recipes matrix routines

using namespace std;

void tridiagsolve(NRVec<double> &a, NRVec<double> &b, NRVec<double>


&c, NRVec<double> &r, NRVec<double> &x)

int j;

int P=[Link]();

NRVec<double> u(P);

NRVec<double> d(P);

NRVec<double> l(P);

NRVec<double> z(P);

u[0]=c[0];

d[0]=b[0];

z[0]=r[0];

// Do LU decomposition and forward subsititution as you go

for (j=1;j<P;j++) {

u[j]=c[j];

l[j]=a[j]/d[j-1];

d[j]=b[j]-l[j]*u[j-1];

z[j] = r[j]-l[j]*z[j-1];

//follow with back substitution

x[P-1] = z[P-1]/d[P-1];

for (j=(P-2);j>=0;j--){

x[j] = (z[j] - u[j]*x[j+1])/d[j];

void ImplicitDiffusion(int N, int M)

int i, j;

double dt = 0.1/M ;

double dx = 2.0/N;;

const double PI = 3.141592653589793;


//follow with back substitution

x[P-1] = z[P-1]/d[P-1];

for (j=(P-2);j>=0;j--){

x[j] = (z[j] - u[j]*x[j+1])/d[j];

void ImplicitDiffusion(int N, int M)

int i, j;

double dt = 0.1/M ;

double dx = 2.0/N;;

const double PI = 3.141592653589793;

NRMat<double> solvals(0.0,M+1,2*N+1); //NR dynamic matrix

double alpha = 0.0; // key diffusion parameter

NRVec<double> r(0.0,2*N-1); // create vectors for implicit


solver

NRVec<double> x(0.0,2*N-1);

NRVec<double> a(0.0,2*N-1);

NRVec<double> b(0.0,2*N-1);

NRVec<double> c(0.0,2*N-1);

alpha = dt/(dx*dx);

//output dt

cout << "dt = " << dt << "\n";

//output dt

cout << "dx = " << dx << "\n";

// output alpha

cout << "alpha = " << alpha << "\n";

// initialize initital data

for (j = 0; j <= 2*N; j++)

solvals[0][j] = sin(PI*(j-N)*dx/2);

// initialize boundary conditions

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

solvals[i][0] = 0;
cout << "alpha = " << alpha << "\n";

// initialize initital data

for (j = 0; j <= 2*N; j++)

solvals[0][j] = sin(PI*(j-N)*dx/2);

// initialize boundary conditions

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

solvals[i][0] = 0;

solvals[i][2*N] = 0;

// Run the fully implicit algorithm

// First fill the arrays to feed to implicit solver

// Note form for fully implicit scheme

for (j=0; j<=2*N-2;j++)

a[j] = -alpha;

b[j] = 1.0+2.0*alpha;

c[j] = -alpha;

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

for (j = 1; j<= 2*N-1; j++)

r[j-1] = solvals[i-1][j];

}// The boundary conditions are zero so no end effects

tridiagsolve(a, b, c, r, x);

for (j = 1; j<= 2*N-1; j++)

solvals[i][j] = x[j-1];

}
{

for (j = 1; j<= 2*N-1; j++)

r[j-1] = solvals[i-1][j];

}// The boundary conditions are zero so no end effects

tridiagsolve(a, b, c, r, x);

for (j = 1; j<= 2*N-1; j++)

solvals[i][j] = x[j-1];

ofstream out("[Link]");

for (j=0; j<= 2*N; j++)

{ [Link](15);

out << solvals[M][j] << "\n";

return ;

#include <iostream>

#include <cmath>

using namespace std;

int main()

double result;

char name[20];

int N;

int M;

cout << "Implicit Diffusion Example \n ";

cout << "Enter number of time steps (M) \n ";

cin >> M;

cout << M << " time steps \n" ;

cout << "Enter space step parameter (N) \n ";

cin >> N;

cout << 2*N << " space steps \n" ;

cout << " Result being written to [Link] \n " << endl;
{

double result;

char name[20];

int N;

int M;

cout << "Implicit Diffusion Example \n ";

cout << "Enter number of time steps (M) \n ";

cin >> M;

cout << M << " time steps \n" ;

cout << "Enter space step parameter (N) \n ";

cin >> N;

cout << 2*N << " space steps \n" ;

cout << " Result being written to [Link] \n " << endl;

ImplicitDiffusion(N,M);

cout << "Hit any key+<RET> to finish \n ";

cin >> name;

return(0);

SetDirectory@
"C:\Documents and Settings\William Shaw\My
Documents\LGS0708\codes"D

C:\Documents and Settings\William


Shaw\My Documents\LGS0708\codes

FileNames@"*.txt"D

[Link], [Link], testtridiag_cpp.txt<

cppdata3 = ReadList@"[Link]", NumberD;


ListPlot@cppdata3D

0.5

50 100 150

- 0.5

cppinterpoldata =
Table[{(k-nminus-1)*dx ,cppdata3[[k]]}, {k, 1, nminus+nplus+1}];

cppfunc = Interpolation[cppinterpoldata, InterpolationOrder -> 3]

InterpolatingFunction@88- 2., 2.<<, <>D

Now we plot the error in the answer:

px 1
J-p2 N 0.1`
PlotBcppfunc@xD - SinB F ‰4 , 8x, - 2, 2<, PlotPoints Ø 50F
2
0.00005

-2 -1 1 2

- 0.00005

mmadata = Transpose@interpoldataD@@2DD;

mmacppdiff = mmadata - cppdata3;

Max@Abs@mmacppdiffDD

5.44009 µ 10-14

Which is close enough. Now rerun in C++ with half the number of time steps. (M=200, N=80, alpha =
0.8).
cppdata3 = ReadList@"[Link]", NumberD;

cppinterpoldata = Table@8Hk - nminus - 1L dx, cppdata3PkT<,


8k, 1, nminus + nplus + 1<D;
cppfunc = Interpolation@cppinterpoldata, InterpolationOrder Ø 3D;
px 1
J-p2 N 0.1`
PlotBcppfunc@xD - SinB F ‰4 , 8x, - 2, 2<, PlotPoints Ø 50F
2

0.00015

0.00010

0.00005

-2 -1 1 2

- 0.00005

- 0.00010

The error has grown, but not to order 10^100!!


Crank-Nicolson
The fully implicit analysis can be repeated with a Crank-Nicolson scheme by making some minor changes
to the difference algorithm. Note number of time steps is now 40:

CNCMatrix[alpha_, nminus_, nplus_] :=


Sequence[Table[-alpha/2, {nplus+nminus-2}],
Table[1+alpha, {nplus+nminus-1}],
Table[-alpha/2, {nplus+nminus-2}]];

CNDMatrix[alpha_, vec_List] := Module[{temp},


temp = (1 - alpha)*vec + (alpha/2)*(RotateRight[vec] + RotateLeft[vec]);
temp[[1]] = Simplify[First[temp] - alpha*Last[vec]/2];
temp[[-1]] = Simplify[Last[temp] - alpha*First[vec]/2];
temp];

M=40; nminus = 80; nplus = 80;


dx = 0.025; dtau = 0.0025; alpha = dtau/dx^2;
initial=Table[N[Sin[Pi*(k-1-nminus)/nminus] ], {k,1, nminus+nplus+1}];
lower=Table[0, {m, 1, M+1}]; upper=Table[0, {m, 1, M+1}];
wold = initial; wvold = wold; wnew = wold;
CMat = CNCMatrix[alpha,nminus,nplus];

For[m=2, m<=M+1, m++,


(wvold = wold; wold = wnew;
rhs = CNDMatrix[alpha, Take[wold, {2, -2}]]+
Table[Which[k==1, alpha*(lower[[m-1]] + lower[[m]])/2,
k== nplus + nminus-1, alpha*(upper[[m-1]] + upper[[m]])/2,
True, 0],
{k, 1, nplus + nminus-1}];
temp = CompTridiagSolve[CMat, rhs];
wnew = Join[{lower[[m]]}, temp, {upper[[m]]}])
];

interpoldata =
Table[{(k-nminus-1)*dx ,wnew[[k]]}, {k, 1, nminus+nplus+1}];

ufunca = Interpolation[interpoldata, InterpolationOrder -> 3];


px 1
J-p2 N 0.1`
PlotBufunca@xD - SinB F ‰4 , 8x, - 2, 2<, PlotPoints Ø 50F
2
0.00002

0.00001

-2 -1 1 2

- 0.00001

- 0.00002

samples = TableForm[Join[{{"x", "Crank-Nic", "Exact", "Error"}},


Table[Map[PaddedForm[N[Chop[#1]],{5,6}]&,
N[{x, ufunca[x], Sin[Pi*x/2]*Exp[-(Pi^2 0.1)/4],
ufunca[x]- Sin[Pi*x/2]*Exp[-(Pi^2 0.1)/4]},5]],{x, -2, 2, 0.25}]]]

x Crank-Nic Exact Error


-2.000000 0.000000 0.000000 0.000000
-1.750000 -0.299020 -0.299010 -9.246900 µ 10-6
-1.500000 -0.552510 -0.552490 -0.000017
-1.250000 -0.721890 -0.721870 -0.000022
-1.000000 -0.781370 -0.781340 -0.000024
-0.750000 -0.721890 -0.721870 -0.000022
-0.500000 -0.552510 -0.552490 -0.000017
-0.250000 -0.299020 -0.299010 -9.246900 µ 10-6
0.000000 0.000000 0.000000 0.000000
0.250000 0.299020 0.299010 9.246900 µ 10-6
0.500000 0.552510 0.552490 0.000017
0.750000 0.721890 0.721870 0.000022
1.000000 0.781370 0.781340 0.000024
1.250000 0.721890 0.721870 0.000022
1.500000 0.552510 0.552490 0.000017
1.750000 0.299020 0.299010 9.246900 µ 10-6
2.000000 0.000000 0.000000 0.000000

Note that we obtain comparable accuracy or better with a tenth the number of time-steps that where used
for the explicit case. This example nicely illustrates the power of the implicit approach and the Crank-
Nicholson scheme in particular. As we shall see, we can do rather better.
Note that we obtain comparable accuracy or better with a tenth the number of time-steps that where used
for the explicit case. This example nicely illustrates the power of the implicit approach and the Crank-
Nicholson scheme in particular. As we shall see, we can do rather better.

C++ Code
The C++ code is the same apart from the use of a different tridiagonal matrix and RHS:

for (j=0; j<=2*N-2;j++)


{
a[j] = -0.5*alpha;
b[j] = 1.0+alpha;
c[j] = -0.5*alpha;
}

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


{
for (j = 1; j<= 2*N-1; j++)
{
r[j-1]=(1.0-alpha)*solvals[i-1][j]+
0.5*alpha*(solvals[i-1][j+1]+solval-
s[i-1][j-1]);
}// The boundary conditions are zero so no
end effects
tridiagsolve(a, b, c, r, x);

cppdata4 = ReadList@"[Link]", NumberD;


ListPlot@cppdata4D

0.5

50 100 150

- 0.5

cppinterpoldata =
Table[{(k-nminus-1)*dx ,cppdata4[[k]]}, {k, 1, nminus+nplus+1}];
cppfunc = Interpolation[cppinterpoldata, InterpolationOrder -> 3]

InterpolatingFunction@88- 2., 2.<<, <>D

Now we plot the error in the answer:

px 1
J-p2 N 0.1`
PlotBcppfunc@xD - SinB F ‰4 , 8x, - 2, 2<, PlotPoints Ø 50F
2
0.00002

0.00001

-2 -1 1 2

- 0.00001

- 0.00002

mmadata = Transpose@interpoldataD@@2DD;
mmacppdiff = mmadata - cppdata4;
Max@Abs@mmacppdiffDD

1.05471 µ 10-14
Douglas
A further small change to the matrices takes us to the Douglas algorithm.

‡ Douglas Algorithm

DougCMatrix[alpha_, nminus_, nplus_] :=


Sequence[Table[1-6*alpha, {nplus+nminus-2}], Table[10+12*alpha, {nplus+nminus-1}],
Table[1-6*alpha, {nplus+nminus-2}]];

DougDMatrix[alpha_, vec_List] := Module[{temp},


temp = (10 - 12*alpha)*vec + (1+6*alpha)*(RotateRight[vec] + RotateLeft[vec]);
temp[[1]] = Simplify[First[temp] - (1+6*alpha)*Last[vec]];
temp[[-1]] = Simplify[Last[temp] - (1 + 6*alpha)*First[vec]];
temp];

M=40;nminus = 80;nplus = 80;dx = 0.025;


dtau = 0.0025;alpha = dtau/dx^2;
Print[alpha];
CMat = DougCMatrix[alpha,nminus,nplus];
initial = Table[N[Sin[Pi*(k-1-nminus)/nminus] ], {k,1, nminus+nplus+1}];
lower=Table[0, {m, 1, M+1}];
upper=Table[0, {m, 1, M+1}];
wold = initial; wvold = wold; wnew = wold;

For[m=2, m<=M+1, m++,


(wvold = wold;
wold = wnew;
rhs = DougDMatrix[alpha, Take[wold, {2, -2}]]+
Table[
Which[
k==1, (6*alpha+1)*lower[[m-1]] +(6 alpha - 1)*lower[[m]],
k== nplus + nminus-1,
(6*alpha+1)*upper[[m-1]] + (6 alpha - 1)*upper[[m]],
True, 0],
{k, 1, nplus + nminus-1}];
temp = CompTridiagSolve[CMat, rhs];
wnew = Join[{lower[[m]]}, temp, {upper[[m]]}])];
interpoldata =
Table[{(k-nminus-1)*dx ,wnew[[k]]}, {k, 1, nminus+nplus+1}];
ufunc = Interpolation[interpoldata, InterpolationOrder -> 3];
4.

Now we plot the error - note the vertical scale!

px 1
J-p2 N 0.1`
PlotBufunc@xD - SinB F ‰4 , 8x, - 2, 2<, PlotPoints Ø 50F
2

6. µ 10-7

4. µ 10-7

2. µ 10-7

-2 -1 1 2

- 2. µ 10-7

- 4. µ 10-7

- 6. µ 10-7

Finally we tabulate the x-value, the numerical result, the exact result and the error in the numerical scheme.

samples = TableForm[Join[{{"x", "Douglas", "Exact", "Error"}},


Table[Map[PaddedForm[N[Chop[#1]],{5,6}]&,
N[{x, ufunc[x], Sin[Pi*x/2]*Exp[-(Pi^2 0.1)/4],
ufunc[x]- Sin[Pi*x/2]*Exp[-(Pi^2 0.1)/4]},5]],
{x, -2, 2, 0.25}]]]
x Douglas Exact Error
-2.000000 0.000000 0.000000 0.000000
-1.750000 -0.299010 -0.299010 2.332100 µ 10-7
-1.500000 -0.552490 -0.552490 4.309100 µ 10-7
-1.250000 -0.721870 -0.721870 5.630100 µ 10-7
-1.000000 -0.781340 -0.781340 6.094000 µ 10-7
-0.750000 -0.721870 -0.721870 5.630100 µ 10-7
-0.500000 -0.552490 -0.552490 4.309100 µ 10-7
-0.250000 -0.299010 -0.299010 2.332100 µ 10-7
0.000000 0.000000 0.000000 0.000000
0.250000 0.299010 0.299010 -2.332100 µ 10-7
0.500000 0.552490 0.552490 -4.309100 µ 10-7
0.750000 0.721870 0.721870 -5.630100 µ 10-7
1.000000 0.781340 0.781340 -6.094000 µ 10-7
1.250000 0.721870 0.721870 -5.630100 µ 10-7
1.500000 0.552490 0.552490 -4.309100 µ 10-7
1.750000 0.299010 0.299010 -2.332100 µ 10-7
2.000000 0.000000 0.000000 0.000000

Note that we obtain errors of about 1/40 of those obtained with Crank-Nicholson, with identical time-step
parameters. Some oscillations have started to appear in our error plot, though it should be appreciated that
our plot is now given on a much finer scale than was the case for CN - if we plot the Douglas errors on the
same scale as the CN error plot the difference is clearer:

px 1
J-p2 N 0.1`
PlotBufunc@xD - SinB F ‰4 , 8x, - 2, 2<, PlotPoints Ø 50,
2
PlotRange Ø 8- 0.000025`, 0.000025`<F
0.00002

0.00001

-2 -1 1 2

- 0.00001

- 0.00002

C++ Version
The C++ code again just has a different matrix evolution block:

for (j=0; j<=2*N-2;j++)


{
a[j] = 1.0-6.0*alpha;
b[j] = 10.0+12.0*alpha;
c[j] = 1.0-6.0*alpha;
}

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


{
for (j = 1; j<= 2*N-1; j++)
{
r[j-1]=(10.0-12.0*alpha)*solvals[i-1][j]+

(1+6.0*alpha)*(solvals[i-1][j+1]+solvals[i-1][j-1]);
}// The boundary conditions are zero so no
end effects
tridiagsolve(a, b, c, r, x);
}

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


{
for (j = 1; j<= 2*N-1; j++)
{
r[j-1]=(10.0-12.0*alpha)*solvals[i-1][j]+

(1+6.0*alpha)*(solvals[i-1][j+1]+solvals[i-1][j-1]);
}// The boundary conditions are zero so no
end effects
tridiagsolve(a, b, c, r, x);

cppdata5 = ReadList@"[Link]", NumberD;


ListPlot@cppdata5D

0.5

50 100 150

- 0.5

cppinterpoldata =
Table[{(k-nminus-1)*dx ,cppdata5[[k]]}, {k, 1, nminus+nplus+1}];
cppfunc = Interpolation[cppinterpoldata, InterpolationOrder -> 3];

Now we plot the error in the answer:

px 1
J-p2 N 0.1`
PlotBcppfunc@xD - SinB F ‰4 , 8x, - 2, 2<, PlotPoints Ø 50F
2
6. µ 10-7

4. µ 10-7

2. µ 10-7

-2 -1 1 2

- 2. µ 10-7

- 4. µ 10-7

- 6. µ 10-7

mmadata = Transpose@interpoldataD@@2DD;
mmacppdiff = mmadata - cppdata5;
Max@Abs@mmacppdiffDD

2.10942 µ 10-15
Theta Method - C++ code
#include <cmath>

#include <iostream>

#include <fstream>

#include "nrutil_nr.h" //allow access to public domain Numerical


Recipes matrix routines

using namespace std;

void tridiagsolve(NRVec<double> &a, NRVec<double> &b, NRVec<double>


&c, NRVec<double> &r, NRVec<double> &x)

int j;

int P=[Link]();

NRVec<double> u(P);

NRVec<double> d(P);

NRVec<double> l(P);

NRVec<double> z(P);

u[0]=c[0];

d[0]=b[0];

z[0]=r[0];

// Do LU decomposition and forward subsititution as you go

for (j=1;j<P;j++) {

u[j]=c[j];

l[j]=a[j]/d[j-1];

d[j]=b[j]-l[j]*u[j-1];

z[j] = r[j]-l[j]*z[j-1];

//follow with back substitution

x[P-1] = z[P-1]/d[P-1];

for (j=(P-2);j>=0;j--){
d[0]=b[0];

z[0]=r[0];

// Do LU decomposition and forward subsititution as you go

for (j=1;j<P;j++) {

u[j]=c[j];

l[j]=a[j]/d[j-1];

d[j]=b[j]-l[j]*u[j-1];

z[j] = r[j]-l[j]*z[j-1];

//follow with back substitution

x[P-1] = z[P-1]/d[P-1];

for (j=(P-2);j>=0;j--){

x[j] = (z[j] - u[j]*x[j+1])/d[j];

void ThetaImplicitDiffusion(int N, int M, double theta)

int i, j;

double dt = 0.1/M ;

double dx = 2.0/N;;

const double PI = 3.141592653589793;

NRMat<double> solvals(0.0,M+1,2*N+1); //NR dynamic matrix

double alpha = 0.0; // key diffusion parameter

NRVec<double> r(0.0,2*N-1); // create vectors for implicit


solver

NRVec<double> x(0.0,2*N-1);

NRVec<double> a(0.0,2*N-1);

NRVec<double> b(0.0,2*N-1);

NRVec<double> c(0.0,2*N-1);

alpha = dt/(dx*dx);

//output dt

cout << "dt = " << dt << "\n";

//output dt

cout << "dx = " << dx << "\n";

// output alpha
NRVec<double> x(0.0,2*N-1);

NRVec<double> a(0.0,2*N-1);

NRVec<double> b(0.0,2*N-1);

NRVec<double> c(0.0,2*N-1);

alpha = dt/(dx*dx);

//output dt

cout << "dt = " << dt << "\n";

//output dt

cout << "dx = " << dx << "\n";

// output alpha

cout << "alpha = " << alpha << "\n";

// initialize initital data

for (j = 0; j <= 2*N; j++)

solvals[0][j] = sin(PI*(j-N)*dx/2);

// initialize boundary conditions

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

solvals[i][0] = 0;

solvals[i][2*N] = 0;

// Run the theta implicit algorithm

// First fill the arrays to feed to implicit solver

// Note form for theta implicit scheme

for (j=0; j<=2*N-2;j++)

a[j] = -alpha*theta;

b[j] = 1.0+2.0*alpha*theta;

c[j] = -alpha*theta;

}
solvals[i][0] = 0;

solvals[i][2*N] = 0;

// Run the theta implicit algorithm

// First fill the arrays to feed to implicit solver

// Note form for theta implicit scheme

for (j=0; j<=2*N-2;j++)

a[j] = -alpha*theta;

b[j] = 1.0+2.0*alpha*theta;

c[j] = -alpha*theta;

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

for (j = 1; j<= 2*N-1; j++)

r[j-1]=(1.0-2.0*alpha*(1-theta))*solvals[i-1][j]+al-
pha*(1-theta)*(solvals[i-1][j+1]+solvals[i-1][j-1]);

}// The boundary conditions are zero so no end effects

tridiagsolve(a, b, c, r, x);

// Note that for a grid with constant parameters as here

// It would be more efficient to make one call to get L and


U

// before entering the evolution and then

// only call the fwd and back substitution at each step

// The version here would be more appropriate for the case


when

// the matrix varied across the entire grid.

for (j = 1; j<= 2*N-1; j++)

solvals[i][j] = x[j-1];

ofstream out("[Link]");

for (j=0; j<= 2*N; j++)

{ [Link](15);
// before entering the evolution and then

// only call the fwd and back substitution at each step

// The version here would be more appropriate for the case


when

// the matrix varied across the entire grid.

for (j = 1; j<= 2*N-1; j++)

solvals[i][j] = x[j-1];

ofstream out("[Link]");

for (j=0; j<= 2*N; j++)

{ [Link](15);

out << solvals[M][j] << "\n";

return ;

#include <iostream>

#include <cmath>

using namespace std;

int main()

double result;

char name[20];

int N;

int M;

double theta;

cout << "Theta Method Implicit Diffusion Example \n ";

cout << "Enter number of time steps (M) \n ";

cin >> M;

cout << M << " time steps \n" ;

cout << "Enter space step parameter (N) \n ";

cin >> N;

cout << 2*N << " space steps \n" ;

cout << "Enter evolution parameter (theta) \n ";

cin >> theta;

cout << 2*N << " space steps \n" ;


int N;

int M;

double theta;

cout << "Theta Method Implicit Diffusion Example \n ";

cout << "Enter number of time steps (M) \n ";

cin >> M;

cout << M << " time steps \n" ;

cout << "Enter space step parameter (N) \n ";

cin >> N;

cout << 2*N << " space steps \n" ;

cout << "Enter evolution parameter (theta) \n ";

cin >> theta;

cout << 2*N << " space steps \n" ;

cout << " Result being written to [Link] \n " << endl;

ThetaImplicitDiffusion(N,M,theta);

cout << "Hit any key+<RET> to finish \n ";

cin >> name;

return(0);

You might also like