Differentiation matrices Tobin A. Driscoll Richard J. Braun
In Finite differences we used finite differences to turn a discrete collection of function values into an estimate of the derivative of the function at a point. Just as with differentiation in elementary calculus, we can generalize differences at a point into an operation that maps discretized functions to discretized derivatives.
The derivative of a function is a unique result. The same is not true for our finite-dimensional approximation of the derivative, though.
10.3.1 Matrices for finite differences ¶ In Chapter 5 and Chapter 9 we used the notation t k t_k t k for nodes to more clearly distinguish them from the continuous variable x x x . Since we will soon have problems that discretize both x x x and t t t variables, though, we now switch to using the same letter for nodes as the continuous variable.
We first discretize the interval x ∈ [ a , b ] x\in[a,b] x ∈ [ a , b ] into equal pieces of length h = ( b − a ) / n h=(b-a)/n h = ( b − a ) / n , leading to the nodes
x i = a + i h , i = 0 , … , n , h = b − a n . x_i=a + i h, \quad i=0,\ldots,n, \qquad h=\frac{b-a}{n}. x i = a + ih , i = 0 , … , n , h = n b − a . Note again that our node indexing scheme starts at zero. Our goal is to find a vector g \mathbf{g} g such that g i ≈ f ′ ( x i ) g_i \approx f'(x_i) g i ≈ f ′ ( x i ) for i = 0 , … , n i=0,\ldots,n i = 0 , … , n . Our first try is the forward difference formula (5.4.2) ,
g i = f i + 1 − f i h , t = 0 , … , n − 1. g_i = \frac{f_{i+1}-f_i}{h}, \qquad t=0,\ldots,n-1. g i = h f i + 1 − f i , t = 0 , … , n − 1. However, this leaves g n g_n g n undefined, because the formula would refer to the unavailable value f n + 1 f_{n+1} f n + 1 . For g n g_n g n we could resort to the backward difference
g n = f n − f n − 1 h . g_n = \frac{f_{n}-f_{n-1}}{h}. g n = h f n − f n − 1 . We can summarize the entire set of formulas by defining
f = [ f ( x 0 ) f ( x 1 ) ⋮ f ( x n − 1 ) f ( x n ) ] , \mathbf{f} =
\begin{bmatrix}
f(x_0) \\[1mm] f(x_1) \\[1mm] \vdots \\[1mm] f(x_{n-1}) \\[1mm] f(x_n)
\end{bmatrix}, \quad f = ⎣ ⎡ f ( x 0 ) f ( x 1 ) ⋮ f ( x n − 1 ) f ( x n ) ⎦ ⎤ , and then the vector equation
[ f ′ ( x 0 ) f ′ ( x 1 ) ⋮ f ′ ( x n − 1 ) f ′ ( x n ) ] ≈ D x f , D x = 1 h [ − 1 1 − 1 1 ⋱ ⋱ − 1 1 − 1 1 ] . \begin{bmatrix}
f'(x_0) \\[1mm] f'(x_1) \\[1mm] \vdots \\[1mm] f'(x_{n-1}) \\[1mm] f'(x_n)
\end{bmatrix}
\approx
\mathbf{D}_x \mathbf{f}, \qquad
\mathbf{D}_x
= \frac{1}{h}
\begin{bmatrix}
-1 & 1 & & & \\[1mm]
& -1 & 1 & & \\[1mm]
& & \ddots & \ddots & \\[1mm]
& & & -1 & 1 \\[1mm]
& & & -1 & 1
\end{bmatrix}. ⎣ ⎡ f ′ ( x 0 ) f ′ ( x 1 ) ⋮ f ′ ( x n − 1 ) f ′ ( x n ) ⎦ ⎤ ≈ D x f , D x = h 1 ⎣ ⎡ − 1 1 − 1 1 ⋱ ⋱ − 1 − 1 1 1 ⎦ ⎤ . The matrix D x \mathbf{D}_x D x in (10.3.5) is a finite-difference differentiation matrix. Here as elsewhere, elements of D x \mathbf{D}_x D x that are not shown are zero. Each row of D x \mathbf{D}_x D x gives the weights of a finite-difference formula being used at one of the nodes.
The differentiation matrix D x \mathbf{D}_x D x in (10.3.5) is not a unique choice. In fact, it’s about the least accurate choice possible, as explained in Convergence of finite differences . We are theoretically free to use whatever finite-difference formulas we want in each row, such as those in Table 5.4.1 and Table 5.4.2 , although it makes sense to choose rows that are as similar as possible. For instance, using second-order centered differences where possible and second-order one-sided formulas at the boundary points leads to
to be applied to a vector of function values at the nodes (10.3.1) .
Example 10.3.1 (Differentiation matrix for finite differences)
Let’s use (10.3.6) to compute the derivative of f ( x ) = sin ( π x ) f(x) = \sin(\pi x) f ( x ) = sin ( π x ) over the interval [ − 1 , 0 ] [-1, 0] [ − 1 , 0 ] with n = 4 n=4 n = 4 . This gives h = 1 / 4 h = 1 / 4 h = 1/4 and the nodes x i = − 1 + i / 4 x_i = -1 + i/4 x i = − 1 + i /4 for i = 0 , … , 4 i=0,\ldots,4 i = 0 , … , 4 . The matrix D x \mathbf{D}_x D x is
D x = 4 [ − 3 / 2 2 − 1 / 2 − 1 / 2 0 1 / 2 − 1 / 2 0 1 / 2 − 1 / 2 0 1 / 2 1 / 2 − 2 3 / 2 ] . \mathbf{D}_x = 4
\begin{bmatrix}
-3/2 & 2 & -1/2 & & \\
-1/2 & 0 & 1/2 & & \\
& -1/2 & 0 & 1/2 & \\
& & -1/2 & 0 & 1/2 \\
& & 1/2 & -2 & 3/2
\end{bmatrix}. D x = 4 ⎣ ⎡ − 3/2 − 1/2 2 0 − 1/2 − 1/2 1/2 0 − 1/2 1/2 1/2 0 − 2 1/2 3/2 ⎦ ⎤ . The discrete approximation to f ′ ( x ) = π cos ( π x ) f'(x) = \pi \cos(\pi x) f ′ ( x ) = π cos ( π x ) is
[ − 6 8 − 2 − 2 0 2 − 2 0 2 − 2 0 2 2 − 8 6 ] [ sin ( − π ) sin ( − 3 π / 4 ) sin ( − π / 2 ) sin ( − π / 4 ) sin ( 0 ) ] = [ − 6 8 − 2 − 2 0 2 − 2 0 2 − 2 0 2 2 − 8 6 ] [ 0 − 1 / 2 − 1 − 1 / 2 0 ] = [ 2 − 8 2 + 2 − 2 0 2 − 2 + 8 / 2 ] ≈ [ − 3.657 − 2 0 2 3.657 ] . \begin{align*}
&
\begin{bmatrix}
-6 & 8 & -2 & & \\
-2 & 0 & 2 & & \\
& -2 & 0 & 2 & \\
& & -2 & 0 & 2 \\
& & 2 & -8 & 6
\end{bmatrix}
\begin{bmatrix}
\sin(-\pi) \\[1mm] \sin(-3\pi/4) \\[1mm] \sin(-\pi/2) \\[1mm] \sin(-\pi/4) \\[1mm] \sin(0)
\end{bmatrix} \\
& =
\begin{bmatrix}
-6 & 8 & -2 & & \\
-2 & 0 & 2 & & \\
& -2 & 0 & 2 & \\
& & -2 & 0 & 2 \\
& & 2 & -8 & 6
\end{bmatrix}
\begin{bmatrix}
0 \\ -1/\sqrt{2} \\ -1 \\ -1/\sqrt{2} \\ 0
\end{bmatrix} \\
& =
\begin{bmatrix}
2 - 8\sqrt{2} + 2\\ -2 \\ 0 \\ 2 \\ -2 + 8/\sqrt{2}
\end{bmatrix} \\
& \approx
\begin{bmatrix}
-3.657 \\ -2 \\ 0 \\ 2 \\ 3.657
\end{bmatrix}.
\end{align*} ⎣ ⎡ − 6 − 2 8 0 − 2 − 2 2 0 − 2 2 2 0 − 8 2 6 ⎦ ⎤ ⎣ ⎡ sin ( − π ) sin ( − 3 π /4 ) sin ( − π /2 ) sin ( − π /4 ) sin ( 0 ) ⎦ ⎤ = ⎣ ⎡ − 6 − 2 8 0 − 2 − 2 2 0 − 2 2 2 0 − 8 2 6 ⎦ ⎤ ⎣ ⎡ 0 − 1/ 2 − 1 − 1/ 2 0 ⎦ ⎤ = ⎣ ⎡ 2 − 8 2 + 2 − 2 0 2 − 2 + 8/ 2 ⎦ ⎤ ≈ ⎣ ⎡ − 3.657 − 2 0 2 3.657 ⎦ ⎤ . This result is not especially close to the exact values of f ′ f' f ′ at the nodes, which are [ − 3.142 , − 2.221 , 0 , 2.221 , 3.142 ] \bigl[ -3.142, -2.221, 0, 2.221, 3.142 \bigr] [ − 3.142 , − 2.221 , 0 , 2.221 , 3.142 ] , but we should not expect it to be for such a small n n n .
10.3.2 Second derivative ¶ In a TPBVP , we will need to take the second derivative of the unknown solution. One option is to apply a first derivative twice, that is, to multiply the value vector f \mathbf{f} f on the left by D x 2 \mathbf{D}_x^2 D x 2 . This is usually not the best option, however (see Finite differences ). Instead, the following is usually a better choice:
10.3.3 Implementation ¶ Together, the matrices (10.3.6) and (10.3.10) give second-order approximations of the first and second derivatives at all nodes. These matrices, as well as the nodes x 0 , … , x n x_0,\ldots,x_n x 0 , … , x n , are returned by Function 10.3.1 .
Second-order differentiation matrices1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
"""
diffmat2(n, xspan)
Compute 2nd-order-accurate differentiation matrices on `n`+1 points
in the interval `xspan`. Returns a vector of nodes and the matrices
for the first and second derivatives.
"""
function diffmat2(n, xspan)
a, b = xspan
h = (b - a) / n
x = [a + i * h for i in 0:n] # nodes
# Define most of Dₓ by its diagonals.
dp = fill(0.5 / h, n) # superdiagonal
dm = fill(-0.5 / h, n) # subdiagonal
Dₓ = diagm(-1 => dm, 1 => dp)
# Fix first and last rows.
Dₓ[1, 1:3] = [-1.5, 2, -0.5] / h
Dₓ[n+1, n-1:n+1] = [0.5, -2, 1.5] / h
# Define most of Dₓₓ by its diagonals.
d0 = fill(-2 / h^2, n + 1) # main diagonal
dp = ones(n) / h^2 # super- and subdiagonal
Dₓₓ = diagm(-1 => dp, 0 => d0, 1 => dp)
# Fix first and last rows.
Dₓₓ[1, 1:4] = [2, -5, 4, -1] / h^2
Dₓₓ[n+1, n-2:n+1] = [-1, 4, -5, 2] / h^2
return x, Dₓ, Dₓₓ
end
Second-order differentiation matrices1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function [x, Dx, Dxx] = diffmat2(n, xspan)
%DIFFMAT2 Second-order accurate differentiation matrices.
% Input:
% n number of subintervals (one less than the number of nodes)
% xspan interval endpoints
% Output:
% x equispaced nodes
% Dx matrix for first derivative
% Dxx matrix for second derivative
a = xspan(1); b = xspan(2);
h = (b - a) / n;
x = a + h * (0:n)'; % nodes
% Define most of Dx by its diagonals.
dp = 0.5 * ones(n, 1) / h; % superdiagonal
dm = -0.5 * ones(n, 1) / h; % subdiagonal
Dx = diag(dm, -1) + diag(dp, 1);
% Fix first and last rows.
Dx(1, 1:3) = [-1.5, 2, -0.5] / h;
Dx(n+1, n-1:n+1) = [0.5, -2, 1.5] / h;
% Define most of Dxx by its diagonals.
d0 = -2 * ones(n+1, 1) / h^2; % main diagonal
dp = ones(n, 1) / h^2; % superdiagonal and subdiagonal
Dxx = diag(dp, -1) + diag(d0) + diag(dp, 1);
% Fix first and last rows.
Dxx(1, 1:4) = [2, -5, 4, -1] / h^2;
Dxx(n+1, n-2:n+1) = [-1, 4, -5, 2] / h^2;
end
Second-order differentiation matrices1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def diffmat2(n, xspan):
"""
diffmat2(n, xspan)
Compute 2nd-order-accurate differentiation matrices on n+1 points in the
interval xspan. Return a vector of nodes, and the matrices for the first
and second derivatives.
"""
a, b = xspan
h = (b - a) / n
x = np.linspace(a, b, n + 1) # nodes
# Define most of Dx by its diagonals.
dp = 0.5 / h * np.ones(n) # superdiagonal
dm = -0.5 / h * np.ones(n) # subdiagonal
Dx = np.diag(dm, -1) + np.diag(dp, 1)
# Fix first and last rows.
Dx[0, :3] = np.array([-1.5, 2, -0.5]) / h
Dx[-1, -3:] = np.array([0.5, -2, 1.5]) / h
# Define most of Dxx by its diagonals.
d0 = -2 / h**2 * np.ones(n + 1) # main diagonal
dp = np.ones(n) / h**2 # superdiagonal and subdiagonal
Dxx = np.diag(d0, 0) + np.diag(dp, -1) + np.diag(dp, 1)
# Fix first and last rows.
Dxx[0, :4] = np.array([2, -5, 4, -1]) / h**2
Dxx[-1, -4:] = np.array([-1, 4, -5, 2]) / h**2
return x, Dx, Dxx
Example 10.3.2 (Differentiation matrices)
Example 10.3.2 We test first-order and second-order differentiation matrices for the function x + exp ( sin 4 x ) x + \exp(\sin 4x) x + exp ( sin 4 x ) over [ − 1 , 1 ] [-1,1] [ − 1 , 1 ] .
f = x -> x + exp(sin(4 * x));
For reference, here are the exact first and second derivatives.
df_dx = x -> 1 + 4 * exp(sin(4x)) * cos(4x);
d2f_dx2 = x -> 4 * exp(sin(4x)) * (4 * cos(4x)^2 - 4 * sin(4x));
We discretize on equally spaced nodes and evaluate f f f at the nodes.
t, Dₓ, Dₓₓ = FNC.diffmat2(18, [-1, 1])
y = f.(t);
Then the first two derivatives of f f f each require one matrix-vector multiplication.
yₓ = Dₓ * y
yₓₓ = Dₓₓ * y;
The results show poor accuracy for this small value of n n n .
using Plots
plot(df_dx, -1, 1, layout = 2, xaxis = (L"x"), yaxis = (L"f'(x)"))
scatter!(t, yₓ, subplot = 1)
plot!(d2f_dx2, -1, 1, subplot = 2, xaxis = (L"x"), yaxis = (L"f''(x)"))
scatter!(t, yₓₓ, subplot = 2)
A convergence experiment confirms the order of accuracy. Because we expect an algebraic convergence rate, we use a log-log plot of the errors.
n = @. round(Int, 2^(4:0.5:11))
err = zeros(length(n), 2)
for (k, n) in enumerate(n)
t, Dₓ, Dₓₓ = FNC.diffmat2(n, [-1, 1])
y = f.(t)
err[k, 1] = norm(df_dx.(t) - Dₓ * y, Inf)
err[k, 2] = norm(d2f_dx2.(t) - Dₓₓ * y, Inf)
end
plot(n, err, m = :o, label = [L"f'" L"f''"])
plot!(n, 10 * 10 * n .^ (-2);
l = (:dash, :black),
label = "2nd order",
xaxis = (:log10, "n"),
yaxis = (:log10, "max error"),
title = "Convergence of finite differences")
Example 10.3.2 We test first-order and second-order differentiation matrices for the function x + exp ( sin 4 x ) x + \exp(\sin 4x) x + exp ( sin 4 x ) over [ − 1 , 1 ] [-1,1] [ − 1 , 1 ] .
f = @(x) x + exp(sin(4*x));
For reference, here are the exact first and second derivatives.
df_dx = @(x) 1 + 4 * exp(sin(4*x)) .* cos(4*x);
d2f_dx2 = @(x) 4 * exp(sin(4*x)) .* (4*cos(4*x).^2 - 4*sin(4*x));
We discretize on equally spaced nodes and evaluate f f f at the nodes.
[t, Dx, Dxx] = diffmat2(12, [-1 1]);
y = f(t);
Then the first two derivatives of f f f each require one matrix-vector multiplication.
yx = Dx * y;
yxx = Dxx * y;
The results show poor accuracy for this small value of n n n .
clf, subplot(2, 1, 1)
fplot(df_dx, [-1, 1]), hold on
plot(t, yx, 'ko')
xlabel('x'), ylabel('f''(x)')
subplot(2, 1, 2)
fplot(d2f_dx2, [-1, 1]), hold on
plot(t, yxx, 'ko')
xlabel('x'), ylabel('f''''(x)')
An convergence experiment confirms the order of accuracy. Because we expect an algebraic convergence rate, we use a log-log plot of the errors.
n = round( 2 .^ (4:.5:11)' );
err = zeros(length(n), 2);
for k = 1:length(n)
[t, Dx, Dxx] = diffmat2(n(k), [-1, 1]);
y = f(t);
err(k, 1) = norm(df_dx(t) - Dx * y, Inf);
err(k, 2) = norm(d2f_dx2(t) - Dxx * y, Inf);
end
clf
loglog(n, err, 'o-'), hold on
loglog(n, 100 * n.^(-2), 'k--')
legend("f'", "f''", '2nd order')
xlabel('n'), ylabel('max error')
title('Convergence of finite differences')
Example 10.3.2 We test first-order and second-order differentiation matrices for the function x + exp ( sin 4 x ) x + \exp(\sin 4x) x + exp ( sin 4 x ) over [ − 1 , 1 ] [-1,1] [ − 1 , 1 ] .
f = lambda x: x + exp( sin(4 * x) )
For reference, here are the exact first and second derivatives.
df_dx = lambda x: 1 + 4 * exp(sin(4 * x)) * cos(4 * x)
d2f_dx2 = lambda x: 4 * exp(sin(4 * x)) * (4 * cos(4 * x)**2 - 4 * sin(4 * x))
We discretize on equally spaced nodes and evaluate f f f at the nodes.
t, Dx, Dxx = FNC.diffmat2(12, [-1, 1])
y = f(t)
Then the first two derivatives of f f f each require one matrix-vector multiplication.
yx = Dx @ y
yxx = Dxx @ y
The results show poor accuracy for this small value of n n n .
x = linspace(-1, 1, 500)
subplot(2, 1, 1)
plot(x, df_dx(x))
plot(t, yx, "ko")
xlabel("$x$"), ylabel("$f'(x)$")
subplot(2, 1, 2)
plot(x, d2f_dx2(x))
plot(t, yxx, "ko")
xlabel("$x$"), ylabel("$f''(x)$");
A convergence experiment confirms the order of accuracy. Because we expect an algebraic convergence rate, we use a log-log plot of the errors.
N = array([int(2**k) for k in arange(4, 11.5, 0.5)])
err1 = zeros(len(N))
err2 = zeros(len(N))
for k, n in enumerate(N):
t, Dx, Dxx = FNC.diffmat2(n, [-1, 1])
y = f(t)
err1[k] = norm(df_dx(t) - Dx @ y, inf)
err2[k] = norm(d2f_dx2(t) - Dxx @ y, inf)
loglog(N, err1, "-o", label="$f'$")
loglog(N, err2, "-o", label="$f''$")
plot(N, 10 * 10 / N**2, "k--", label="2nd order")
xlabel("$n$"), ylabel("max error")
legend(loc="lower left")
title("Convergence of finite differences");
10.3.4 Spectral differentiation ¶ Recall that finite-difference formulas are derived in three steps:
Choose a node index set S S S near node i i i . Interpolate with a polynomial using the nodes in S S S . Differentiate the interpolant and evaluate at node i i i . We can modify this process by using a global interpolant, either polynomial or trigonometric, as in Chapter 9 . Rather than choosing a different index set for each node, we use all the nodes each time.
In a nonperiodic setting, we use Chebyshev second-kind points for stability:
x k = − cos ( k π n ) , k = 0 , … , n . x_k = -\cos\left(\frac{k \pi}{n}\right), \qquad k=0,\ldots,n. x k = − cos ( n kπ ) , k = 0 , … , n . Definition 10.3.4 (Chebyshev differentiation matrix)
Using a vector f \mathbf{f} f of function values at the Chebyshev nodes (10.3.11) , the vector D x f \mathbf{D}_x \mathbf{f} D x f will have approximate first derivative values at those nodes, where the entries of D x \mathbf{D}_x D x are given by
D 00 = 2 n 2 + 1 6 , D n n = − 2 n 2 + 1 6 , D i j = { − x i 2 ( 1 − x i 2 ) , i = j , c i c j ( − 1 ) i + j x i − x j , i ≠ j , \begin{gathered}
D_{00} = \dfrac{2n^2+1}{6}, \qquad D_{n n} = -\dfrac{2n^2+1}{6}, \\
D_{ij} =
\begin{cases}
-\dfrac{x_i}{2(1-x_i^2)}, & i=j, \\[4mm]
\dfrac{c_i}{c_j}\, \dfrac{(-1)^{i+j}}{x_i-x_j}, & i\neq j,
\end{cases}
\end{gathered} D 00 = 6 2 n 2 + 1 , D nn = − 6 2 n 2 + 1 , D ij = ⎩ ⎨ ⎧ − 2 ( 1 − x i 2 ) x i , c j c i x i − x j ( − 1 ) i + j , i = j , i = j , where c 0 = c n = 2 c_0=c_n=2 c 0 = c n = 2 and c i = 1 c_i=1 c i = 1 for i = 1 , … , n − 1 i=1,\ldots,n-1 i = 1 , … , n − 1 .
For the second derivative, D x x = D x 2 \mathbf{D}_{xx} = \mathbf{D}_x^2 D xx = D x 2 .
Function 10.3.2 returns the matrices from Definition 10.3.4 . This function uses a change of variable to transplant the standard [ − 1 , 1 ] [-1,1] [ − 1 , 1 ] for Chebyshev nodes to any [ a , b ] [a,b] [ a , b ] . It also takes a different approach to computing the diagonal elements of D x \mathbf{D}_x D x than the formulas in (10.3.12) (see Exercise 10.3.5 ).
Chebyshev differentiation matrices1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
"""
diffcheb(n, xspan)
Compute Chebyshev differentiation matrices on `n`+1 points in the
interval `xspan`. Returns a vector of nodes and the matrices for the
first and second derivatives.
"""
function diffcheb(n, xspan)
x = [-cos(k * π / n) for k in 0:n] # nodes in [-1,1]
# Off-diagonal entries.
c = [2; ones(n - 1); 2] # endpoint factors
dij = (i, j) -> (-1)^(i + j) * c[i+1] / (c[j+1] * (x[i+1] - x[j+1]))
Dₓ = [dij(i, j) for i in 0:n, j in 0:n]
# Diagonal entries.
Dₓ[isinf.(Dₓ)] .= 0 # fix divisions by zero on diagonal
s = sum(Dₓ, dims = 2)
Dₓ -= diagm(s[:, 1]) # "negative sum trick"
# Transplant to [a,b].
a, b = xspan
x = @. a + (b - a) * (x + 1) / 2
Dₓ = 2 * Dₓ / (b - a) # chain rule
# Second derivative.
Dₓₓ = Dₓ^2
return x, Dₓ, Dₓₓ
end
Chebyshev differentiation matrices1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function [x, Dx, Dxx] = diffcheb(n, xspan)
%DIFFCHEB Chebyshev differentiation matrices.
% Input:
% n number of subintervals (integer)
% xspan interval endpoints (vector)
% Output:
% x Chebyshev nodes in domain (length n+1)
% Dx matrix for first derivative (n+1 by n+1)
% Dxx matrix for second derivative (n+1 by n+1)
x = -cos( (0:n)' * pi / n ); % nodes in [-1,1]
Dx = zeros(n+1);
c = [2; ones(n-1, 1); 2]; % endpoint factors
i = (0:n)'; % row indices
% Off-diagonal entries
for j = 0:n
num = c(i+1) .* (-1).^(i + j);
den = c(j+1) * (x - x(j+1));
Dx(:, j+1) = num ./ den;
end
% Diagonal entries
Dx(isinf(Dx)) = 0; % fix divisions by zero on diagonal
Dx = Dx - diag(sum(Dx, 2)); % negative sum trick
% Transplant to [a,b]
a = xspan(1); b = xspan(2);
x = a + (b - a) * (x + 1) / 2;
Dx = 2 * Dx / (b - a);
% Second derivative
Dxx = Dx^2;
end
Chebyshev differentiation matrices1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def diffcheb(n, xspan):
"""
diffcheb(n, xspan)
Compute Chebyshev differentiation matrices on n+1 points in the
interval xspan. Return a vector of nodes, and the matrices for the first
and second derivatives.
"""
x = -np.cos(np.arange(n + 1) * np.pi / n) # nodes in [-1,1]
Dx = np.zeros([n + 1, n + 1])
c = np.hstack([2.0, np.ones(n - 1), 2.0]) # endpoint factors
# Off-diagonal entries
Dx = np.zeros([n + 1, n + 1])
for i in range(n + 1):
for j in range(n + 1):
if i != j:
Dx[i, j] = (-1) ** (i + j) * c[i] / (c[j] * (x[i] - x[j]))
# Diagonal entries by the "negative sum trick"
for i in range(n + 1):
Dx[i, i] = -np.sum([Dx[i, j] for j in range(n + 1) if j != i])
# Transplant to [a,b]
a, b = xspan
x = a + (b - a) * (x + 1) / 2
Dx = 2 * Dx / (b - a)
# Second derivative
Dxx = Dx @ Dx
return x, Dx, Dxx
Example 10.3.3 (Chebyshev differentiation matrices)
Example 10.3.3 Here is a 4 × 4 4\times 4 4 × 4 Chebyshev differentiation matrix.
t, Dₓ = FNC.diffcheb(3, [-1, 1])
Dₓ
4×4 Matrix{Float64}:
-3.16667 4.0 -1.33333 0.5
-1.0 0.333333 1.0 -0.333333
0.333333 -1.0 -0.333333 1.0
-0.5 1.33333 -4.0 3.16667
We again test the convergence rate.
f = x -> x + exp(sin(4 * x));
df_dx = x -> 1 + 4 * exp(sin(4 * x)) * cos(4 * x);
d2f_dx2 = x -> 4 * exp(sin(4 * x)) * (4 * cos(4 * x)^2 - 4 * sin(4 * x));
n = 5:5:70
err1 = zeros(size(n))
err2 = zeros(size(n))
for (k, n) in enumerate(n)
t, Dₓ, Dₓₓ = FNC.diffcheb(n, [-1, 1])
y = f.(t)
err1[k] = norm(df_dx.(t) - Dₓ * y, Inf)
err2[k] = norm(d2f_dx2.(t) - Dₓₓ * y, Inf)
end
Since we expect a spectral convergence rate, we use a semi-log plot for the error.
plot(n, [err1 err2]; m = :o,
label=[L"f'" L"f''"],
xaxis=(L"n"), yaxis = (:log10, "max error"),
title="Convergence of Chebyshev derivatives")
Example 10.3.3 Here is a 4 × 4 4\times 4 4 × 4 Chebyshev differentiation matrix.
[t, Dx] = diffcheb(3, [-1, 1]);
format rat
Dx
We again test the convergence rate.
f = @(x) x + exp(sin(4*x));
df_dx = @(x) 1 + 4 * exp(sin(4*x)) .* cos(4*x);
d2f_dx2 = @(x) 4 * exp(sin(4*x)) .* (4*cos(4*x).^2 - 4*sin(4*x));
n = 5:5:70;
err = zeros(length(n), 2);
for k = 1:length(n)
[t, Dx, Dxx] = diffcheb(n(k), [-1, 1]);
y = f(t);
err(k, 1) = norm(df_dx(t) - Dx * y, Inf);
err(k, 2) = norm(d2f_dx2(t) - Dxx * y, Inf);
end
Since we expect a spectral convergence rate, we use a semi-log plot for the error.
clf, format
semilogy(n, err, 'o-'), hold on
legend("f'", "f''")
xlabel('n'), ylabel('max error')
title('Convergence of finite differences')
Example 10.3.3 Here is a 4 × 4 4\times 4 4 × 4 Chebyshev differentiation matrix.
t, Dx, Dxx = FNC.diffcheb(3, [-1, 1])
print(Dx)
[[-3.16666667 4. -1.33333333 0.5 ]
[-1. 0.33333333 1. -0.33333333]
[ 0.33333333 -1. -0.33333333 1. ]
[-0.5 1.33333333 -4. 3.16666667]]
We again test the convergence rate.
f = lambda x: x + exp(sin(4 * x))
df_dx = lambda x: 1 + 4 * exp(sin(4 * x)) * cos(4 * x)
d2f_dx2 = lambda x: 4 * exp(sin(4 * x)) * (4 * cos(4 * x) ** 2 - 4 * sin(4 * x))
N = range(5, 75, 5)
err1 = zeros(len(N))
err2 = zeros(len(N))
err = zeros((len(N), 2))
for k, n in enumerate(N):
t, Dx, Dxx = FNC.diffcheb(n, [-1, 1])
y = f(t)
err[k, 0] = norm(df_dx(t) - Dx @ y, inf)
err[k, 1] = norm(d2f_dx2(t) - Dxx @ y, inf)
Since we expect a spectral convergence rate, we use a semi-log plot for the error.
semilogy(N, err, "-o")
xlabel("$n$"), ylabel("max error")
legend(["$f'$", "$f''$"], loc="lower left")
title("Convergence of Chebyshev derivatives");
According to Theorem 9.3.1 , the convergence of polynomial interpolation to f f f using Chebyshev nodes is spectral if f f f is analytic (at least having infinitely many derivatives) on the interval. The derivatives of f f f are also approximated with spectral accuracy.
10.3.5 Exercises ¶ (a) ✍ Using (10.3.5) to define D x \mathbf{D}_x D x , calculate D x 2 \mathbf{D}_x^2 D x 2 in the general case.
(b) ⌨ Repeat the convergence experiment in the second part of Example 10.3.2 , but using this version of D x 2 \mathbf{D}_x^2 D x 2 in place of D x x \mathbf{D}_{xx} D xx to estimate f ′ ′ f'' f ′′ . Why does it fail to converge as n → ∞ n\to \infty n → ∞ ?
(a) ✍ Find the derivative of f ( x ) = sign ( x ) x 2 f(x) =\operatorname{sign}(x)x^2 f ( x ) = sign ( x ) x 2 on the interval [ − 1 , 1 ] [-1,1] [ − 1 , 1 ] . (If this gives you trouble, use an equivalent piecewise definition of f f f .) What is special about this function at x = 0 x=0 x = 0 ?
(b) ⌨ Adapt Example 10.3.2 to operate on the function from part (a), computing only the first derivative. What is the observed order of accuracy?
(c) ✍ Show that for even values of n n n , there is only one node at which the error for computing f ′ f' f ′ in part (b) is nonzero.
⌨ To get a fourth-order accurate version of D x \mathbf{D}_x D x , five points per row are needed, including two special rows at each boundary. For a fourth-order D x x \mathbf{D}_{xx} D xx , five symmetric points per row are needed for interior rows and six points are needed for the rows near a boundary.
(a) Modify Function 10.3.1 to a function diffmat4
, which outputs fourth-order accurate differentiation matrices. You may want to use Function 5.4.1 .
(b) Repeat the experiment of Algorithm 10.3.1 , and compare observed errors to fourth-order accuracy.
✍ Explain in detail how lines 23-24 in Function 10.3.2 correctly change the interval from [ − 1 , 1 ] [-1,1] [ − 1 , 1 ] to [ a , b ] [a,b] [ a , b ] .
(a) ✍ What is the derivative of a constant function?
(b) ✍ Explain why for any reasonable differentiation matrix D \mathbf{D} D , we should find ∑ j = 0 n D i j = 0 \displaystyle \sum_{j=0}^nD_{ij}=0 j = 0 ∑ n D ij = 0 for all i i i .
(c) ✍ What does this have to do with Function 10.3.2 ? Refer to specific line(s) in the function for your answer.
Define the ( n + 1 ) × ( n + 1 ) (n+1)\times (n+1) ( n + 1 ) × ( n + 1 ) matrix T = [ 1 1 1 ⋮ ⋱ 1 1 ⋯ 1 ] \mathbf{T} = \displaystyle \begin{bmatrix}
1 & & & \\ 1 & 1 & & \\ \vdots & & \ddots & \\ 1 & 1 & \cdots & 1
\end{bmatrix} T = ⎣ ⎡ 1 1 ⋮ 1 1 1 ⋱ ⋯ 1 ⎦ ⎤ .
(a) ✍ Write out T u \mathbf{T}\mathbf{u} Tu for a generic vector u \mathbf{u} u (start with a zero index). How is this like integration?
(b) ✍ Find the inverse of T \mathbf{T} T for any n n n . (You can use a computer to find the pattern, but show that the result is correct in general.) What does this have to do with the inverse of integration?
Trefethen, L. N. (2000). Spectral Methods in MATLAB . SIAM.