You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#%config InlineBackend.figure_format = 'svg'
from pylab import *
from scipy.interpolate import barycentric_interpolate
Polynomials are easy to evluate since they require only basic arithmetic operations: $-,+,\div$. They can provide a good approximation to continuous functions as shown by Weirstrass Theorem.
that satisfies the interpolation conditions $$p(x_i) = y_i, \qquad i=0,1,\ldots, N$$ This is a linear system of $N+1$ equations that can be written in matrix form
This has a unique solution if the Vandermonde matrix $V$ is non-singular. Since
$$
\det V = \prod_{j=0}^N \prod_{k=j+1}^N (x_k - x_j)
$$
we can solve the problem provided the points ${ x_i }$ are distinct.
$V$ is non-singular.
We can show this without computing the determinant. Assume that the ${ x_i }$ are distinct. It is enough to show that the only solution of $Va=0$ is $a=0$. Note that the set of $N+1$ equations $Va=0$ is of the form $$p(x_i) = 0, \qquad i=0,1,\ldots,N$$ which implies that $p(x)$ has $N+1$ distinct roots. But since $p$ is a polynomial of degree $N$, this implies that $p(x) \equiv 0$ and hence each $a_i = 0$; the matrix $V$ is non-singular.
+++
Condition number of $V$
If we want to solve the interpolation problem by solving the matrix problem, then the condition number of the matrix becomes important. The condition number of a square matrix wrt some matrix norm is defined as
$$
\kappa(A) = \norm{A} \cdot \norm{A^{-1}}
$$
Matrices with large condition numbers cannot be solved accurately on a computer by Gaussian elimination due to possible growth of round-off errors.
+++
:::{prf:example}
Take $(x_0,x_1,x_2) = (100,101,102)$, then with $p(x) = a_0 + a_1 x + a_2 x^2$
The condition number can be improved by scaling and/or shifting the variables.
:::
+++
:::{prf:remark}
It is usually better to map the data to the interval $[-1,+1]$ or $[0,1]$ and then solve the interpolation problem on the mapped interval.
Assuming that $x_0 < x_1 < \ldots
x_N$, define
$$
\xi = \frac{x - x_0}{x_N - x_0}
$$
and find a polynomial of the form
$$
p(\xi) = a_0 + a_1 \xi + \ldots + a_N \xi^N
$$
But still the condition number increases rapidly with $N$. For $N=20$ we have a condition number of $8 \times 10^8$ even when using the interval $[-1,+1]$.
We will use the numpy.linalg.cond function to compute the condition number. By default, it uses the 2-norm.
Nvalues, Cvalues = [], []
for N in range(1,30):
x = linspace(-1.0,+1.0,N+1)
V = zeros((N+1,N+1))
for j in range(0,N+1):
V[:,j] = x**j
Nvalues.append(N), Cvalues.append(cond(V))
semilogy(Nvalues, Cvalues, 'o-')
xlabel('N'), ylabel('cond(V)'), grid(True);
The condition number is large for even moderate value of $N=30$.
:::
+++
Interpolating polynomials
:::{prf:theorem}
Suppose $f(x)$ is a polynomial of degree $\le N$ and we interpolate this
at $N+1$ distinct points to construct a polynomial $p(x)$ of degree
$N$. Then $f(x) \equiv p(x)$.
:::
:::{prf:proof}
Define
$$
r(x) = f(x) - p(x)
$$
which is of degree $\le N$. $r(x)$ vanishes at $N+1$ distinct nodes, the interpolation points, and hence it must be zero polynomial. Thus $p(x) \equiv f(x)$.
:::
+++
:::{prf:example}
If $f(x) = a + b x$ and we interpolate this with $p(x) = a_0 + a_1 x + a_2 x^2$ at three distinct points, then the solution will be
$$
a_0 = a, \qquad a_1 = b, \qquad a_2 = 0
$$
:::
+++
:::{exercise}
If $f(x)$ is a polynomial of degree $m$ and $p(x)$ interpolates it at $n+1$ distinct points with $n > m$, then show that $p(x)$ is actually a polynomial of degree $m$.
:::
+++
Lagrange interpolation
Lagrange interpolation provides the solution without having to solve a
matrix problem. Define
Then consider the polynomial of degree $N$ given by
$$
p_N(x) = \sum_{j=0}^N y_j \ell_j(x)
$$
By construction this satisfies
$$
p_N(x_i) = y_i, \qquad i=0,1,\ldots,N
$$
and hence is the solution to the interpolation problem.
+++
:::{prf:example}
Initially, let us use some python functions to compute the interpolating polynomial. In particular, we use scipy.interpolate.barycentric_interpolate function which we will explain in the next chapter. We will demonstrate this for the function
$$
f(x) = \cos(4\pi x), \qquad x \in [0,1]
$$
Define the function
xmin, xmax = 0.0, 1.0
f = lambda x: cos(4*pi*x)
Make a grid and evaluate function at those points.
N = 8 # is the degree, we need N+1 points
x = linspace(xmin, xmax, N+1)
y = f(x)
Now we evaluate this on larger grid for better visualization
M = 100
xe = linspace(xmin, xmax, M)
ye = f(xe) # exact function
yp = barycentric_interpolate(x, y, xe)
plot(x,y,'o',xe,ye,'--',xe,yp,'-')
legend(('Data points','Exact function','Polynomial'))
grid(True), title('Degree '+str(N)+' interpolation');
:::
+++
:::{prf:example}
Interpolate the following functions on uniformly spaced points
$$
f(x) = \cos(x), \qquad x \in [0,2\pi]
$$
for $N=2,4,6,\ldots,12$.
xmin, xmax = 0.0, 2.0*pi
fun = lambda x: cos(x)
xx = linspace(xmin,xmax,100);
ye = fun(xx);
figure(figsize=(9,8))
for i in range(1,7):
N = 2*i;
subplot(3,2,i)
x = linspace(xmin,xmax,N+1);
y = fun(x);
yy = barycentric_interpolate(x,y,xx);
plot(x,y,'o',xx,ye,'--',xx,yy);
axis([xmin, xmax, -1.1, +1.1])
text(3.0,0.0,'N='+str(N),ha='center');
The interpolating polynomials seem to converge to the true function as $N$ increases.
:::
+++
Error estimate
:::{prf:theorem}
Let $p_N(x)$ be the degree $N$ polynomial which interpolates the given
data $(x_i,y_i)$, $i=0,1,\ldots,N$. Then the error
:::{prf:theorem}
Assume that $|f^{(n)}(x)| \le M$ for all $x$ and $n$. Then the error of
polynomial interpolation on uniformly spaced points is bounded by
$$
|f(x) - p(x)| \le \frac{M h^{N+1}}{4(N+1)}
$$
and the error goes to zero as $N \to \infty$.
:::
:::{prf:proof}
The error bound follows from the two previous theorems. As $N$ increases, $h$ becomes less than one at some point, and beyond this, the right hand side in the error bound will converge to zero.
:::
+++
:::{prf:remark}
Functions like $\cos x$, $\sin x$, $\exp(x)$ satisfy the conditions of the above theorem. These conditions are quite strong and can be relaxed considerably. E.g., if $f(x) = \sin(\alpha x)$ then $|f^{(n)}(x)| \le |\alpha|^n$ and if $|\alpha| > 1$, the derivatives can increase with $n$. If the derivatives satisfy
$$
|f^{(n)}(x)| \le C \alpha^n, \qquad \alpha > 0
$$
As $N$ increases, we will satisfy $\alpha h < 1$ and beyond this point, the right hand side goes to zero.
:::
+++
:::{exercise}
Modify previous code to apply it to $f(x) = \cos(4x)$ for $x \in [0,2\pi]$ and observe convergence for moderate values of $N$ and uniformly spaced points. For very high degrees, you should see the error start to grow.
:::
+++
:::{prf:example} Runge phenomenon
Consider interpolating the following two functions on $[-1,1]$
The two functions look visually similar and both are infinitely differentiable.
def interp(f,points):
xx = linspace(xmin,xmax,100,True);
ye = f(xx);
figure(figsize=(9,8))
for i in range(1,7):
N = 2*i
subplot(3,2,i)
if points == 'uniform':
x = linspace(xmin,xmax,N+1,True)
else:
theta = linspace(0,pi,N+1, True)
x = cos(theta)
y = f(x);
yy = barycentric_interpolate(x,y,xx);
plot(x,y,'o',xx,ye,'--',xx,yy)
axis([xmin, xmax, -1.0, +1.1])
text(-0.1,0.0,'N = '+str(N));
Interpolate $f_1(x)$ on uniformly spaced points.
interp(f1,'uniform')
Interpolate $f_2(x)$ on uniformly spaced points.
interp(f2,'uniform')
The above results are not good. Let us try $f_2(x)$ on Chebyshev points.
interp(f2,'chebyshev')
What about interpolating $f_1(x)$ on Chebyshev points ?
interp(f1,'chebyshev')
This also seems fine. So uniform points works for one function, Chebyshev points work for both.
:::
+++
Difficulty of polynomial interpolation
Do the polynomial approximations $p_N$ converge to the true function $f$ as $N \to \infty$ ? The error formula seems to suggest so, due to the factor $\frac{1}{(N+1)!}$ provided
higher order derivatives of $f$ are small
the function $\omega_N(x)$ which depends on point distribution is small
Size of derivatives
On uniformly spaced points, we have seen the interpolants of $\cos(x)$ converge but those of the rational function $\frac{1}{1+16x^2}$ do not. This must be related to the behaviour of the derivatives of these functions.
+++
:::{prf:example}
Consider $f(x)=\ln(x)$. Then its derivatives are
Even though the curve $y=\ln(x)$ looks smooth near any
value of $x$, as $n$ gets large, the derivatives become very large in
size, and tend to behave like $n!$ or worse.
:::
+++
This is the general situation; for most functions, some higher order
derivatives tend to grow as $n!$. Even for a polynomial $p_N(x)$, the
derivatives grow in size until the $N$'th one, which is $a_N N!$, after
which they suddenly all become zero.
\begin{align}
\max_{x_1 \le x \le x_2}|\omega_3(x)| &= \frac{9}{16}h^4 \approx 0.56 h^4 \
\max_{x_0 \le x \le x_3}|\omega_3(x)| &= h^4
\end{align}
In this case, the error near the endpoints can be twice as large as the error near the middle.
Case $N = 6$.
The behaviour exhibited for $N=3$ is accentuated for larger degree. For
$N=6$,
$$
\max_{x_2 \le x \le x_4}|\omega_6(x)| \approx 12.36 h^7, \qquad
\max_{x_0 \le x \le x_6}|\omega_6(x)| \approx 95.8 h^7
$$
and the error near the ends can be almost 8 times that near the center.
The next functions evaluate $\omega_N(x)$ and plot it.
# x = (x0,x1,x2,...,xN)
# xp = array of points where to evaluate
def omega(x,xp):
fp = ones_like(xp)
for xi in x:
fp = fp * (xp - xi)
return fp
def plot_omega(x):
M = 1000
xx = linspace(-1.0,1.0,M)
f = omega(x, xx)
plot(xx,f,'b-',x,0*x,'o')
title("N = "+str(N)), grid(True);
+++
:::{prf:example} $\omega_N(x)$ on uniformly spaced points
For a given set of points $x_0, x_1, \ldots, x_N \in [-1,+1]$ we plot the function
Near the end points, the function $\omega_N$ does not go to zero as fast as near the middle. For the Runge example, we observed convergence near the middle but not at the ends.
:::
For a given function $f(x)$, we cannot do anything about the derivative term in the error estimate. For uniformly spaced data points, $\omega_N$ has large value near the end points which is also where the Runge phenomenon is observed. But we can try to minimize the magnitude of $\omega_N(x)$ by choosing a different set of nodes for interpolation. For the following discussion, let us assume that the $x_i$ are ordered and contained in the interval $[-1,+1]$.
:::{hint} Question
Given $N$, can we choose $N+1$ distinct nodes ${x_i}$ in $[-1,1]$ so that
In Python, we can use function recursion to compute the Chebyshev polynomials.
def chebyshev(n, x):
if n == 0:
y = ones_like(x)
elif n == 1:
y = x.copy()
else:
y = 2 * x * chebyshev(n-1,x) - chebyshev(n-2,x)
return y
The first few of these are shown below.
:tag: hide-input
N = 200
x = linspace(-1.0,1.0,N)
figure(figsize=(8,6))
for n in range(0,6):
y = chebyshev(n,x)
plot(x,y,label='n='+str(n))
legend(), grid(True), xlabel('x'), ylabel('$T_n(x)$');
We can write $x \in [-1,+1]$ in terms of an angle $\theta \in [0,\pi]$
:::{prf:definition} Monic polynomial
A polynomial whose term of highest degree has coefficient one is called a monic polynomial.
:::
+++
:::{prf:remark}
In $T_n(x)$, the coefficient of $x^n$ is $2^{n-1}$ for $n \ge 1$ which can be observed from the recursion relation. Hence $2^{1-n} T_n(x)$ is monic polynomial of degree $n$.
:::
+++
:::{prf:theorem}
If $p : [-1,1] \to \re$ is a monic polynomial of degree $n$, then
The roots are shown below for $n=10,11,\ldots,19$.
:tags: hide-input
c = 1
for n in range(10,20):
j = linspace(0,n-1,n)
theta = (2*j+1)*pi/(2*n)
x = cos(theta)
y = 0*x
subplot(10,1,c)
plot(x,y,'.')
xticks([]); yticks([])
ylabel(str(n))
c += 1
Note that the roots are clustered near the end points and are contained in $(-1,1)$;
the endpoints of $[-1,+1]$ are not nodes. The nodes are ordered as $x_0 > x_1 > \ldots > x_N$. We can reorder them by defining the $x_i$ as in .
:::
+++
:::{prf:theorem}
If the nodes ${x_i}$ are the $N+1$ roots of the Chebyshev polynomial
$T_{N+1}$, then the error formula for polynomial interpolation in the
interval $[-1,+1]$ is
:::{prf:remark} Chebyshev points of second kind
In practice, we dont use the Chebyshev nodes as derived above. The important point is how the points are clustered near the ends of the interval. This type of clustering can be achieved by other node sets. If we want $N+1$ nodes, then divide $[0,\pi]$ into $N$ equal intervals so that
These are called Chebyshev points of second kind. In Python they can be obtained as
theta=linspace(0,pi,N+1)
x=-cos(theta)
which returns them in the order
$$
-1 = x_0 < x_1 < \ldots < x_N = +1
$$
They can also be obtained as projections of uniformly spaced points on the unit circle onto the $x$-axis.
:tags: hide-input
t = linspace(0,pi,1000)
xx, yy = cos(t), sin(t)
plot(xx,yy)
n = 10
theta = linspace(0,pi,n)
plot(cos(theta),sin(theta),'o')
plot(cos(theta),zeros(n),'sr',label='Chebyshev')
for i in range(n):
x1 = [cos(theta[i]), cos(theta[i])]
y1 = [0.0, sin(theta[i])]
plot(x1,y1,'k--')
plot([-1.1,1.1],[0,0],'-')
legend(), xlabel('x')
axis([-1.1, 1.1, 0.0, 1.1])
axis('equal'), title(str(n)+' Chebyshev points');
Below, we compare the polynomial $\omega_N(x)$ for uniform and Chebyshev points for $N=16$.
:tags: hide-input
M = 1000
xx = linspace(-1.0,1.0,M)
N = 16
xu = linspace(-1.0,1.0,N+1) # uniform points
xc = cos(linspace(0.0,pi,N+1)) # chebyshev points
fu = omega(xu,xx)
fc = omega(xc,xx)
plot(xx,fu,'b-',xx,fc,'r-')
legend(("Uniform","Chebyshev"))
grid(True), title("Degree N = "+str(N));
With Chebyshev points, this function is of similar size throughout the interval.
:::
+++
:::{exercise}
Plot the function $\omega_N(x)$ for $N=16$ and for Chebyshev points of first and second kind. Write Python code to produce a plot like this.
:tags: remove-input
N = 16
# First kind
theta1 = pi*(2*arange(0,N+1) + 1)/(2*N + 2)
x1 = cos(theta1)
# Second kind
theta2 = linspace(0,pi,N+1)
x2 = cos(theta2)
x = linspace(-1,1,1000)
f1 = omega(x1,x)
f2 = omega(x2,x)
figure(figsize=(10,8))
plot(x,f1,label='First kind')
plot(x,f2,label='Second kind')
plot([-1,1],[2**(-N),2**(-N)],'--',label='$y=2^{-N}$')
plot([-1,1],[-2**(-N),-2**(-N)],'--',label='$y=-2^{-N}$')
legend(), grid(True), xlabel('x'), ylabel('$\\omega_N(x)$')
title('Degree N = ' + str(N));