25) Chebyshev Polynomials and Piecewise Interpolation#
Last time:#
Interpolation using polynomials and function approximation
Lagrange Interpolating Polynomials
Newton polynomials
Choice of points and choice of bases
Runge phenomenon as a manifestation of ill-conditioning
Today:#
Chebyshev polynomials
1.1 Chebyshev polynomials are well-conditionedIntroduction to Piecewise Interpolation
using LinearAlgebra
using Plots
using LaTeXStrings
default(linewidth=4, legendfontsize=12)
function vander(x, k=nothing)
if isnothing(k)
k = length(x)
end
m = length(x)
V = ones(m, k)
for j in 2:k
V[:, j] = V[:, j-1] .* x
end
V
end
function vander_newton(x, abscissa=nothing)
if isnothing(abscissa)
abscissa = x
end
n = length(abscissa)
A = ones(length(x), n)
for i in 2:n
A[:,i] = A[:,i-1] .* (x .- abscissa[i-1])
end
A
end
CosRange(a, b, n) = (a + b)/2 .+ (b - a)/2 * cos.(LinRange(-pi, 0, n))
ArgumentError: Package LaTeXStrings not found in current path.
- Run `import Pkg; Pkg.add("LaTeXStrings")` to install the LaTeXStrings package.
Stacktrace:
[1] macro expansion
@ ./loading.jl:1842 [inlined]
[2] macro expansion
@ ./lock.jl:267 [inlined]
[3] __require(into::Module, mod::Symbol)
@ Base ./loading.jl:1823
[4] #invoke_in_world#3
@ ./essentials.jl:926 [inlined]
[5] invoke_in_world
@ ./essentials.jl:923 [inlined]
[6] require(into::Module, mod::Symbol)
@ Base ./loading.jl:1816
So far, we have seen different strategies for Interpolation.
Note
Interpolation \(\neq\) Function Approximation.
But can we use interpolation to accurately approximate continuous functions?
Suppose the interpolation data come from samples of a continuous function \(f\) on \([a, b] \in \R\).
Then we’d like the interpolant to be “close to” \(f\) on \([a, b]\).
1. Chebyshev polynomials#
We saw that interpolating a set of data points using the monomial basis leads to the Vandermonde matrix. We noticed that when using the monomial basis for \(P_n\), that is, \({1, x, x^2, \dots, x^n}\), these monomial basis functions become increasingly similar. This means that the Vandermonde matrix columns become nearly linearly-dependent, i.e., it is an ill-conditioned matrix (hence, it is very sensitive to perturbations in the data).
A well-conditioned polynomial basis:#
Let’s construct the Vandermonde matrix, but instead of using the canonical basis for polynomials, we’ll use the Legendre polynomials.
function vander_legendre(x, k=nothing)
if isnothing(k)
k = length(x) # Square by default
end
m = length(x)
Q = ones(m, k)
Q[:, 2] = x
for n in 1:k-2
Q[:, n+2] = ((2*n + 1) * x .* Q[:, n+1] - n * Q[:, n]) / (n + 1) # definition of Legendre's polynomials via the generating function
end
Q
end
vcond(mat, points, nmax) = [cond(mat(points(-1, 1, n))) for n in 2:nmax]
plot([vcond(vander_legendre, CosRange, 20)], yscale=:log10, label = "cond of vander_legendre with cos nodes")
A different set of points: Chebyshev nodes#
CosRange(a, b, n) = (a + b)/2 .+ (b - a)/2 * cos.(LinRange(-pi, 0, n))
plot([vcond(vander, LinRange, 20)], yscale=:log10, size=(1000, 600), label = "vander LinRange")
plot!([vcond(vander, CosRange, 20)], yscale=:log10, label = "vander CosRange")
plot!([vcond(vander_legendre, LinRange, 20)], yscale=:log10, label = "vander_legendre LinRange")
plot!([vcond(vander_legendre, CosRange, 20)], yscale=:log10, label = "vander_legendre CosRange", legend = :topleft)
What’s wrong with ill-conditioning?#
runge(x) = 1 / (1 + 10*x^2)
x_lin = LinRange(-1, 1, 20)
x_cos = CosRange(-1, 1, 20)
y_lin = runge.(x_lin)
y_cos = runge.(x_cos)
plot(runge, xlims=(-1, 1))
scatter!(x_lin, y_lin)
plot(runge, xlims=(-1, 1))
scatter!(x_cos, y_cos)
ourvander = vander_legendre
p = ourvander(x_cos) \ y_cos
scatter(x_cos, y_cos, label = L"(x_i,y_i)")
s = LinRange(-1, 1, 100)
plot!(s, runge.(s), label = "runge")
plot!(s, ourvander(s, length(x_cos)) * p, label = "vander_legendre")
# Let's check conditioning with the Chebyshev nodes:
svdvals(ourvander(s, length(x_cos)) / ourvander(x_cos))
cond(ourvander(x_cos))
Definition of Chebyshev polynomials#
Define
Recall
Yet another basis:#
Let’s construct again the Vandermonde matrix, but instead of using the canonical basis for polynomials, we’ll use the Chebyshev polynomials.
function vander_chebyshev(x, n=nothing)
if isnothing(n)
n = length(x) # Square by default
end
m = length(x)
T = ones(m, n)
if n > 1
T[:, 2] = x
end
for k in 3:n
T[:, k] = 2 * x .* T[:,k-1] - T[:, k-2]
end
T
end
x = LinRange(-1, 1, 50)
plot(x, vander_chebyshev(x, 5), title="Chebyshev polynomials")
1.1 Chebyshev polynomials are well-conditioned#
plot_vcond(mat, points) = plot!([
cond(mat(points(-1, 1, n)))
for n in 2:30], label="$mat/$points", marker=:auto, yscale=:log10)
plot(title="Vandermonde condition numbers", legend=:topleft, size=(1000, 600))
plot_vcond(vander, LinRange)
plot_vcond(vander, CosRange)
plot_vcond(vander_chebyshev, LinRange)
plot_vcond(vander_chebyshev, CosRange)
Lagrange interpolating polynomials revisited#
Let’s re-examine the Lagrange polynomials as we vary the points.
When using Chebyshev nodes in a Vandermonde matrix, the corresponding Lagrange basis functions are constructed using these nodes, allowing for a well-conditioned interpolation process.
x_cos = CosRange(-1, 1, 11)
x_lin = LinRange(-1, 1, 100)
A = vander_chebyshev(x_lin, length(x_cos)) /
vander_chebyshev(x_cos)
plot(x_lin, A[:,1:5], size=(800, 600))
scatter!(x_cos[1:5], ones(5), color=:black, label=nothing)
scatter!(x_cos, zero(x_cos), color=:black, label=nothing)
y = abs.(x_cos) # not a smooth function
plot(x_lin, [abs.(x_lin) A * y])
Are there artifacts?
2. Introduction to Piecewise Interpolation#
Main idea: Break the domain into subdomains, apply polynomial interpolation on each subdomain (interpolation points/nodes now called “knots”).
The simplest piecewise interpolation is the piecewise constant interpolation.
function interp_nearest(x, s)
A = zeros(length(s), length(x))
for (i, t) in enumerate(s)
loc = nothing
dist = Inf
for (j, u) in enumerate(x)
if abs(t - u) < dist
loc = j
dist = abs(t - u)
end
end
A[i, loc] = 1
end
A
end
interp_nearest(LinRange(-1, 1, 3), LinRange(0, 1, 4))
A = interp_nearest(x_cos, x_lin)
plot(x_lin, A[:, 1:5])
scatter!(x_cos, ones(length(x_cos)))
Are there any visual artifacts?
x = LinRange(-1, 1, 40)
A = interp_nearest(x, x_lin)
cond(A)
plot(x_lin, A * runge.(x))
plot!(x_lin, runge.(x_lin))
scatter!(x, runge.(x), title = "Function approximation with piecewise constant polynomials", size=(800, 600))
function interp_chebyshev(x, xx)
vander_chebyshev(xx, length(x)) * inv(vander_chebyshev(x))
end
function interp_monomial(x, xx)
vander(xx, length(x)) * inv(vander(x))
end
function interp_error(ieval, x, xx, test)
"""Compute norm of interpolation error for function test
using method interp_and_eval from points x to points xx.
"""
A = ieval(x, xx)
y = test.(x)
yy = test.(xx)
norm(A * y - yy, Inf)
end
function plot_convergence(ievals, ptspaces; xscale=:log10, yscale=:log10, maxpts=40)
"""Plot convergence rates for an interpolation scheme applied
to a set of tests.
"""
xx = LinRange(-1, 1, 100)
ns = 2:maxpts
fig = plot(title="Convergence",
xlabel="Number of points",
ylabel="Interpolation error",
xscale=xscale,
yscale=yscale,
legend=:bottomleft,
size=(1000, 700))
for ieval in ievals
for ptspace in ptspaces
for test in [runge]
errors = [interp_error(ieval, ptspace(-1, 1, n), xx, test)
for n in ns]
plot!(ns, errors, marker=:circle, label="$ieval/$ptspace")
end
end
end
for k in [1, 2, 3]
plot!(ns, ns .^ (-1.0*k), color=:black, label="\$n^{-$k}\$")
end
fig
end
So how accurate is it?#
plot_convergence([interp_monomial, interp_chebyshev, interp_nearest], [LinRange, CosRange])
Note that this convergence plot is specifically for the Runge function example given above, i.e., \(f(x) = \frac{1}{1+x^2}\). In general, convergence rates depend on smoothness of \(f\).
In general, a smoother f \(\Rightarrow\) faster convergence.