using Plots, ComplexPhasePortrait, Interact, ApproxFun, Statistics, SpecialFunctions, LinearAlgebra
gr();
periodic_rule(n) = 2π/n*(0:(n-1)), 2π/n*ones(n)
function circle_rule(n, r)
θ = periodic_rule(n)[1]
r*exp.(im*θ), 2π*im*r/n*exp.(im*θ)
end
function ellipse_rule(n, a, b)
θ = periodic_rule(n)[1]
a*cos.(θ) + b*im*sin.(θ), 2π/n*(-a*sin.(θ) + im*b*cos.(θ))
end
ellipse_rule (generic function with 1 method)
Dr. Sheehan Olver
This lecture we cover
Quadrature rules are pairs of nodes x0,…,xn−1 and weights w0,…,wn−1 to approximate integrals ∫baf(x)dx≈n−1∑j=0wjf(xj)
The trapezium rule gives an easy approximation to integrals. On [0,2π) for periodic f(θ), we have a simplified form:
Definition (Periodic trapezium rule) The periodic trapezium rule is the approximation
∫2π0f(θ)dθ≈2πnn−1∑j=0f(θk)for θj=2πjn.
The periodic trapezium rule is amazingly accurate for smooth, periodic functions:
f = θ -> 1/(2 + cos(θ))
errs = [((x, w) = periodic_rule(n); abs(sum(w.*f.(x)) - sum(Fun(f, 0 .. 2π)))) for n = 1:45];
plot(errs.+eps(); yscale=:log10, title="exponential convergence of n-point trapezium rule", legend=false, xlabel="n")
The accuracy in integration is remarkable as the trapezoidal interpolant does not accurately approximate f: we can see clear differences between the functions here:
n=20
(x, w) = periodic_rule(n)
plot(Fun(f, 0 .. 2π); label = "integrand")
plot!(x, f.(x); label = "trapezium approximation")
We can use the map that defines a contour to construct an approximation to integrals over closed contour γ:
Definition (Complex trapezium rule) The complex trapezium rule on a contour γ (mapped from [0,2π)) is the approximation
∮γf(z)dz≈n−1∑j=0wjf(zj)for
zj=γ(θj)andwj=2πnγ′(θj)Example (Circle trapezium rule) On a circle {reiθ:0≤θ<2π}, we have
∮γf(z)dz≈n−1∑j=0wjf(zj)for zj=reiθj and wj=2πirneiθj.
Here we plot the quadrature points:
ζ, w = circle_rule(20, 1.0)
scatter(ζ; title="quadrature points", legend=false, ratio=1.0)
The Circle trapezium rule is surprisingly accurate for analytic functions:
ζ, w = circle_rule(20, 1.0)
f = z -> cos(z)
z = 0.1+0.2im
sum(f.(ζ)./(ζ .- z).*w)/(2π*im) - f(z)
-9.814371537686384e-14 - 1.3111040031432708e-14im
Calculating high-order derivatives using limits is numerically unstable. Here is a demonstration using finite-difference: making h small does not increase the accuracy after a certain point:
f = z -> gamma(z)
fp = z -> gamma(z)polygamma(0,z) # exact derivative
x = 1.2
fp_fd = [(h=2.0^(-n); (f(x+h)-f(x))/h) for n = 1:50]
plot(abs.(fp_fd .- fp(x)); yscale=:log10, legend=false, title = "error of finite-difference with h=2^(-n)", xlabel="n")
But the previous formula tells us that we can reduce a derivative to a contour integral. The example above shows that it's still numerically unstable, but we can deform the integration contour, to make it stable!
trap_fp = [((ζ, w) = circle_rule(n, 0.5);
ζ .+= x; # circle around x
sum(f.(ζ)./(ζ .- x).^2 .*w)/(2π*im)) for n=1:50]
plot(abs.(trap_fp .- fp.(x)); yscale=:log10,
title="Error of trapezium rule applied to Cauchy integral formula", xlabel="n", legend=false)
We can take things further and use this to calculate the higher order derivatives, with some care taken for choosing the radius:
k=100
r = 1.0k
g = Fun( ζ -> exp(ζ)/(ζ - 0.1)^(k+1), Circle(0.1,r))
factorial(1.0k)/(2π*im) * sum(g) - exp(0.1)
-7.993605777301127e-15 + 3.675487826103639e-16im
Bournemann 2011 investigates this further and optimizes the radius.
The exponential convergence of the complex trapezium rule is a consequence of f(γ(t)) being 2π-periodic, as we will see later in a few lectures:
θ = periodic_rule(100)[1]
plot(θ, real(f.(0.6exp.(im*θ) .+ x)./(0.5exp.(im*θ))))
Example (Ellipse trapezium rule) On an ellipse {acosθ+bisinθ:0≤θ<2π} we have ∮γf(z)dz≈n−1∑j=0wjf(zj)
We can use the ellipse trapezium rule in place of the circle trapzium rule and still achieve accurate results. This gives us flexibility in avoiding singularities. Consider f(z)=1/(25z2+1)
scatter([1/5im,-1/5im]; label="singularities")
θ = range(0; stop=2π, length=2000)
a = 2; b= 0.1
plot!(a * cos.(θ) + im*b * sin.(θ); label="ellipse")
Thus we can still use Cauchy's integral formula:
x = 0.1
f = z -> 1/(25z^2 + 1)
f_ellipse = [((z, w) = ellipse_rule(n, a, b); sum(f.(z)./(z.-x).*w)/(2π*im)) for n=1:1000]
plot(abs.(f_ellipse .- f(x)); yscale=:log10, title="convergence of n-point ellipse approximation", legend=false, xlabel="n")
The Taylor series gives a polynomial approximation to f. The Cauchy's integral formula discretisation gives a rational approximation, which is more adaptiple and does not require knowning the derivatives of f:
f = z -> sqrt(z)
function sqrtⁿ(n,z,z₀)
ret = sqrt(z₀)
c = 0.5/ret*(z-z₀)
for k=1:n
ret += c
c *= -(2k-1)/(2*(k+1)*z₀)*(z-z₀)
end
ret
end
z₀ = 0.3
n = 40
phaseplot(-2..2, -2..2, z -> sqrtⁿ.(n,z,z₀))
Here we see that the approximation is vallid on the expected ellipse:
(ζ, w) = ellipse_rule(20, 4.0, 1.0);
ζ .= ζ .+ 4.5;
f_c = z -> sum(f.(ζ)./(ζ.-z).*w)/(2π*im)
phaseplot(-2..10, -2 .. 2, f_c)
(ζ, w) = ellipse_rule(100, 4.0, 1.0);
ζ .= ζ .+ 4.5;
f_c = z -> sum(f.(ζ)./(ζ.-z).*w)/(2π*im)
f_c(5.3) - sqrt(5.3)
-1.3987033753437572e-11 + 2.459960261444439e-16im
Let A∈Cd×d, u0∈Cd and consider the constant coefficient linear ODE
u′(t)=Au(t)andu(0)=u0(0)The solution to this is given by the matrix exponential u(t)=exp(At)u0
Theorem (Cauchy integral representation for matrix exponential) Let A∈Cn×n be a diagonalizable matrix: A=VΛV−1forΛ=(λ1⋱λd)
Proof Note by definition exp(A)=∞∑k=0Akk!=V∞∑k=0Λkk!V−1=V(eλ1⋱eλd)V−1=12πiV(∮γeζζ−λ1dζ⋱∮γeζζ−λddζ)V−1
We now take out the integration from the matrix, the easiest way to see this is to apply the Trapezium rule approximation: V(∮γeζζ−λ1dζ⋱∮γeζζ−λddζ)V−1=Vlimn→∞(∑nj=1wjeζjζj−λ1⋱∑nj=1wjeζjζj−λd)V−1=limn→∞Vn∑j=1wjeζj(1ζj−λ1⋱1ζj−λd)V−1=limn→∞n∑j=1wjeζjV(Iζj−Λ)−1V−1=∮γeζV(Iζ−Λ)−1V−1dζ=∮γeζ(Iζ−VΛV−1)−1dζ=∮γeζ(Iζ−A)−1dζ
■
Demonstration we use this formula alongside the complex trapezium rule to calculate matrix exponentials. Begin by creating a random symmetric matrix (which only has real eigenvalues):
A = randn(5,5)
A = A + A'
λ = eigvals(A)
5-element Array{Float64,1}: -1.1701131879499997 -0.8307847323168219 -0.2011088438314149 1.7390356067088892 2.1354582101565316
We can now by hand create a circle that surrounds all the eigenvalues:
z,w = circle_rule(100,maximum(abs.(λ))+1)
plot(z)
scatter!(λ,zeros(5); label = "eigenvalues of A")
Here we wrap this up into a function circle_exp
that calculates the matrix exponential:
function circle_exp(A, n, z₀, r)
z,w = circle_rule(n,r)
z .+= z₀
ret = zero(A)
for j=1:n
ret += w[j]*exp(z[j])*inv(z[j]*I - A)
end
ret/(2π*im)
end
circle_exp(A, 100, 0, 8.0) -exp(A) |>norm
4.240422855934442e-13
In this case, it is beneficial to use an ellipse:
function ellipse_exp(A, n, z₀, a, b)
z,w = ellipse_rule(n,a,b)
z .+= z₀
ret = zero(A)
for j=1:n
ret += w[j]*exp(z[j])*inv(z[j]*I - A)
end
ret/(2π*im)
end
ellipse_exp(A, 50, 0, 8.0, 5.0) -exp(A) |>norm
1.3594516845940306e-13
For matrices with large negative eigenvalues (For example, discretisations of the Laplacian), complex quadrature can lead to much better accuracy than Taylor series:
function taylor_exp(A,n)
ret = Matrix(I, size(A))
for k=1:n
ret += A^k/factorial(1.0k)
end
ret
end
B = A - 20I
taylor_exp(B, 200) -exp(B) |>norm
4.813209010780333e-8
We can use an ellpise to surround the spectrum:
scatter(complex.(eigvals(B)))
plot!(ellipse_rule(50,8,5)[1] .- 20)
norm(ellipse_exp(B, 50, -20.0, 8.0, 5.0) - exp(B))
7.24987088647028e-22
If we only know A, how do we know how big to make the contour? Gershgorin's circle theorem gives the answer:
Theorem (Gershgorin) Let A∈Cd×d and define Rk=d∑j=1j≠k|akj|
Proof
■
Demonstration Here we apply this to a particular matrix:
A = [1 2 3; 1 5 2; -4 1 6]
3×3 Array{Int64,2}: 1 2 3 1 5 2 -4 1 6
The following calculates the row sums:
R = sum(abs.(A - Diagonal(diag(A))),dims=2)
3×1 Array{Int64,2}: 5 3 5
Gershgorin's theorem tells us that the spectrum lies in the union of the circles surrounding the diagonals:
drawcircle!(z0, R) = plot!(θ-> real(z0) + R[1]*cos(θ), θ-> imag(z0) + R[1]*sin(θ), 0, 2π, fill=(0,:red), α = 0.2, legend=false)
λ = eigvals(A)
p = plot()
for k = 1:size(A,1)
drawcircle!(A[k,k], R[k])
end
scatter!(complex.(λ); label="eigenvalues")
scatter!(complex.(diag(A)); label="diagonals")
p
We can therefore use this to choose a contour big enough to surround all the circles. Here's a fairly simplistic construction for our case where everything is real:
z₀ = (maximum(diag(A) .+ R) + minimum(diag(A) .- R)) /2 # average edges of circle
r = max(abs.(diag(A) .- R .- z₀)..., abs.(diag(A) .+ R .- z₀)...)
plot!(Circle(z₀, r))
Definition (Fourier series) On [−π,π), Fourier series is an expansion of the form g(θ)=∞∑k=−∞gkeikθ
Definition (Laurent series) In the complex plane, Laurent series around z0 is an expansion of the form f(z)=∞∑k=−∞fk(z−z0)k
Lemma (Fourier series = Laurent series) On a circle in the complex plane γr={z0+reiθ:−π≤θ<π},
Proof This follows immediately from the change of variables z=reiθ+z0. ■
Interestingly, analytic properties of f can be used to show decaying properties in g:
Theorem (Decay in Fourier/Laurent coefficients) Suppose f(z) is analytic in a closed annulus Ar,R around 0: Ar(z0)={z:r≤|z|≤R}
Proof This is a simple application of the ML lemma: |fk|=12π|∮γ1f(ζ)ζk+1dζ|=12π|∮γrf(ζ)ζk+1dζ|≤supζ∈γr|f(ζ)|R−k≤MR−k
Demonstration The Fourier coefficients of g(θ)=12−cosθ
g =Fun(θ -> 1/(2-cos(θ)), Laurent(-π .. π))
g₊ = g.coefficients[1:2:end]
scatter(abs.(g₊); yscale=:log10, label="|g_k|", legend=:bottomleft)
R = 1.1
scatter!(2/(4-R-inv(R))*R.^(-(0:length(g₊))), label = "R = $R")
R = 3.5
scatter!(2/(4-R-inv(R))*R.^(-(0:length(g₊))), label = "R = $R")
R = 2+sqrt(3)-0.1
scatter!(2/(4-R-inv(R))*R.^(-(0:length(g₊))), label = "R = $R")