Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
restrepo
GitHub Repository: restrepo/ComputationalMethods
Path: blob/master/material/interpolation_details.ipynb
934 views
Kernel: Python 3 (ipykernel)

Open In Colab

Appendix

Thecnical details of interpolation functions: interpolation_details.ipynb Open In Colab

Bibligraphy

Divided Differences

In spite of the good precision achieved by the Lagrange interpolating polynomials, analytical manipulation of such an expressions is rather complicated. Furthermore, when applying other polynomials-based techniques like Hermite polynomials, the algorithms present very different ways to achieve the final interpolation, making a comparison unclear.

Divided differences is a way to standardize the notation for interpolating polynomials. Suppose a polynomial Pn(x)P_n(x) and write it in the next form:

Pn(x)=a0+a1(xx0)+a2(xx0)(xx1)++an(xx0)(xxn1)P_n(x) = a_0 + a_1(x-x_0)+ a_2 (x-x_0)(x-x_1)+\cdots + a_n(x-x_0)\cdots (x-x_{n-1})

where aia_i are a set of constants to be determined from the given data (xi,yi)(x_i, y_i).

  • Obtain a0a_0
    Note that due to the definition of an interpolant function, previous expression should satisfy:

Pn(x0)=a0=f(x0)=y0P_{n}\left(x_{0}\right)=a_{0}=f\left(x_{0}\right)=y_{0}
  • Obtain a1a_1 Pn(x)a0xx0=Pn(x)y0xx0=a1+a2(xx1)++an(xx1)(xxn)\frac{P_{n}(x)-a_{0}}{x-x_{0}}=\frac{P_{n}(x)-y_{0}}{x-x_{0}}=a_{1}+a_{2}\left(x-x_{1}\right)+\cdots+a_{n}\left(x-x_{1}\right) \cdots\left(x-x_{n}\right)

Pn(x1)y0x1x0=a1=y1y0x1x0\frac{P_{n}\left(x_{1}\right)-y_{0}}{x_1-x_{0}}=a_{1}=\frac{y_{1}-y_{0}}{x_1-x_{0}}
  • Obtain a2a_2 1xx1(Pn(x)y0xx0y1y0x1x0)=a2+a3(xx2)++an(xx2)(xxn) \frac{1}{x-x_{1}}\left(\frac{P_{n}(x)-y_{0}}{x-x_{0}}-\frac{y_{1}-y_{0}}{x_{1}-x_{0}}\right)=a_{2}+a_{3}\left(x-x_{2}\right)+\cdots+a_{n}\left(x-x_{2}\right) \cdots\left(x-x_{n}\right) evaluating in x=x2x=x_2 a2=1x2x1(y2y0x2x0y1y0x1x0), a_{2}=\frac{1}{x_{2}-x_{1}}\left(\frac{y_{2}-y_{0}}{x_{2}-x_{0}}-\frac{y_{1}-y_{0}}{x_{1}-x_{0}}\right), that we can rewrite like a2=y2y1x2x1y1y0x1x0x2x0.a_{2}=\frac{\frac{y_{2}-y_{1}}{x_{2}-x_{1}}-\frac{y_{1}-y_{0}}{x_{1}-x_{0}}}{x_{2}-x_{0}}.

In summary a0=f[x0]y0a1=f[x0,x1]y1y0x1x0a2=f[x0,x1,x2]y2y1x2x1y1y0x1x0x2x0\begin{align} a_{0}=& f\left[x_{0}\right] \equiv y_{0} \\ a_{1}=& f\left[x_{0}, x_{1}\right] \equiv \frac{y_{1}-y_{0}}{x_{1}-x_{0}}\\ a_{2}=&f\left[x_{0}, x_{1}, x_{2}\right] \equiv \frac{\frac{y_{2}-y_{1}}{x_{2}-x_{1}}-\frac{y_{1}-y_{0}}{x_{1}-x_{0}}}{x_{2}-x_{0}} \end{align}

The key observation, by using the symmetry f[x0,x1]=f[x1,x0]f\left[x_{0}, x_{1}\right]=f\left[x_{1}, x_{0}\right], is f[x0,x1,x2]=f[x1,x2]f[x0,x1]x2x0f\left[x_{0}, x_{1}, x_{2}\right]=\frac{f\left[x_{1}, x_{2}\right]-f\left[x_{0}, x_{1}\right]}{x_{2}-x_{0}}

Allow us to definine the zeroth divided difference, k=0k=0, of xix_i like

D0[xi]f[xi]f(xi)=yiD_0[x_i] \equiv f[x_i] \equiv f(x_i) = y_i

the first divided difference, k=1k=1 of xix_i like

D1[xi]f[xi,xi+1]f[xi+1]f[xi]xi+1xiD_1[x_i] \equiv f[x_i, x_{i+1}] \equiv \frac{f[x_{i+1}]-f[x_i]}{x_{i+1}-x_i}D1[xi]=D0[xi+1]D0[xi]xi+1xiD_1[x_i] = \frac{D_{0}[x_{i+1}]-D_{0}[x_{i}]}{x_{i+1}-x_i}

the second divided difference, k=2k=2 of xix_i like

D2[xi]f[xi,xi+1,xi+2]f[xi+1,xi+2]f[xi,xi+1]xi+2xiD_2[x_i]\equiv f[x_i,x_{i+1},x_{i+2}] \equiv \frac{f[x_{i+1},x_{i+2}]-f[x_i,x_{i+1}]}{x_{i+2}-x_{i}}D2[xi]=D1[xi+1]D1[xi]xi+2xiD_2[x_i]=\frac{D_1[x_{i+1}]-D_1[x_i]}{x_{i+2}-x_{i}}

successively until the kth divided difference

Dk[xi]f[xi,xi+1,,xi+k1,xi+k]f[xi+1,xi+2,xi+k]f[xi,xi+1,,xi+k1]xi+kxiD_k[x_i] \equiv f[x_i, x_{i+1},\cdots, x_{i+k-1},x_{i+k}] \equiv \frac{f[x_{i+1},x_{i+2}\cdots, x_{i+k}]-f[x_i, x_{i+1},\cdots, x_{i+k-1}]}{x_{i+k}-x_i}Dk[xi]=Dk1[xi+1]Dk1[xi]xi+kxiD_k[x_i] = \frac{D_{k-1}[x_{i+1}]-D_{k-1}[x_{i}]}{x_{i+k}-x_i}

These expressions are the fundamental bricks for any interpolating method.

#Construction of a kth divided difference (recursive code) def D( i, k, Xn, Yn ): #If k+i>N if i+k>=len(Xn): return 0 #Zeroth divided difference elif k == 0: return Yn[i] #If higher divided difference else: return (D(i+1, k-1, Xn, Yn)-D(i, k-1, Xn, Yn))/(Xn[i+k]-Xn[i])

Example 2

As an example, Lagrange interpolation can be also derived by using divided differences, which is reached through the next equation:

Pn(x)=D0[x0]+k=1nDk[x0](xx0)(xxk1)P_n(x) = D_0[x_0] + \sum_{k=1}^n D_k[x_0] (x-x_0) \cdots (x-x_{k-1})

Note this expression is by far easier to be manipulated analytically as we can know the coefficients of each order.

Activity

Using the previous expression and the defined function for divided differences, show both methods to calculate Lagrange interpolators are equivalents.

Hermite Interpolation

From calculus we know that Taylor polynomials expand a function at a specific point xix_i, being both functions (the original one and the Taylor function) exactly equal at any derivative-order at that point. Also, as mentioned before, a Lagrange polynomial, given a set of data points, passes through all those points at the same time. However if those points come from an unknown underlying function f(x)f(x), the interpolant polynomial might (surely) differ from the real function at any superior derivative-order. So we have:

  • Taylor polynomials are exact at any order, but that only remains true at a specific point.

  • Lagrange polynomials pass through all points of a give dataset, but only at zeroth-order. Derivatives are not longer equal.

Once established these differences, we can introduce Hermite polynomials just as a generalization of both, Taylor and Lagrange polynomials.

At first, Hermite polynomials can be approximated at any desired order at all the points, as long as one has all these information. However, for the sake of simplicity and without loss of generality, we shall assume Hermite polynomials equal to the real function at zeroth and first-derivative order. For that, we also need to know the points associated to the first-derivate.

Let's suppose a dataset {xi}i\{x_i\}_i for i=0,1,,ni = 0,1,\cdots,n with the respective values {f(xi)}i\{f(x_i)\}_i and {f(xi)}i\{f'(x_i)\}_i. If we assume two different polynomials to fit each set of data, i.e. a polynomial for {f(xi)}i\{f(x_i)\}_i and another for {f(xi)}i\{f'(x_i)\}_i, we obtain 2n+22n+2 coefficients, however zeroth-order coefficients can be put together so finally there are 2n+12n+1 independet coefficients to be determined. In this case, we assign the respective Hermite polynimial as H2n+1(x)H_{2n+1}(x).

Derivation in terms of divided differences

Remembering the divided differences expression for a Lagrange polynomial

Pn(x)=D0[x0]+k=1nDk[x0](xx0)(xxk1)P_n(x) = D_0[x_0] + \sum_{k=1}^n D_k[x_0] (x-x_0) \cdots (x-x_{k-1})

and by defining a new sequence {z0,z1,,z2n+1}\{z_0, z_1, \cdots, z_{2n+1}\} such that

For even ii z2i=xi for i=0,2,z_{2i} = x_i \mbox{ for } i = 0,2,\cdots while for odd ii z2i+1=xi for i=1,3,z_{2i+1} = x_i \mbox{ for } i = 1,3,\cdots

However, divided differences has to be modified in order to include first-order derivatives:

Note that f[z0,z1]f[z_0,z_1] sould be originally

f[z0,z1]=f[z1]f[z0]z1z0f[z_0,z_1] = \frac{f[z_1]-f[z_0]}{z_1-z_0}

but replacing z0=z1=x0z_0 = z_1 = x_0 this would lead an indetermination. In order to solve this issue, this indertemination can be readily approximated to the derivative at x0=z0x_0=z_0, so

f[z0,z1]=f(x0)f[z_0,z_1] = f'(x_0)

or using the previously defined notation

D1[z0]=f(x0)D_1[z_0] = f'(x_0)

Generally, for first-order divided differences we will have

even ii D1[z2i]=f(xi)D_1[z_{2i}] = f'(x_i) odd ii D1[z2i+1]=f(xi)D_1[z_{2i+1}] = f'(x_i)

Higher order divided differences are calculated as usual: Dk[zi]=Dk1[zi+1]Dk1[zi]zi+kziD_k[z_i] = \frac{D_{k-1}[z_{i+1}]-D_{k-1}[z_{i}]}{z_{i+k}-z_i}

Finally, the Hermite polynomial is built using the next expression

  • Order zero coefficient: D0[z0]D_0[z_0]

  • Hermite polynomials H2n+1(x)=D0[z0]+k=12n+1Dk[z0](xz0)(xzk1)H_{2n+1}(x) = D_0[z_0] + \sum_{k=1}^{2n+1} D_k[z_0] (x-z_0) \cdots (x-z_{k-1})

Example

%pylab inline
Populating the interactive namespace from numpy and matplotlib
/usr/local/lib/python3.9/dist-packages/IPython/core/magics/pylab.py:159: UserWarning: pylab import has clobbered these variables: ['f'] `%matplotlib` prevents importing * from pylab and numpy warn("pylab import has clobbered these variables: %s" % clobbered +
def f(x): ''' WARNING: all the parts of the function must be numpy arrays in order to get the element by element sum. ''' x=np.asarray(x) # Force x → array return np.exp(2*x)-x
def fp(x): ''' Derivative of f(x) ''' x=np.asarray(x) # Force x → array return 2*np.exp(2*x)-1
from scipy import interpolate
x=[1,1.25,1.6]
H=interpolate.CubicHermiteSpline(x,f(x),fp(x))
X=np.linspace( x[0],x[-1] ) plt.plot(X,f(X),'k-',lw=1,label="$f(x)$") plt.plot(x,f(x),'r*') plt.plot(X,H(X),'c:',lw=3,label='$H(x)$') plt.plot(X,fp(X),'r-',lw=1,label="$f'(x)$") plt.plot(x,fp(x),'r*') plt.plot(X,H.derivative()(X),'g:',lw=3,label="$H'(x)$") plt.grid() plt.xlabel('$x$',size=12) plt.legend()
<matplotlib.legend.Legend at 0x7f4af01582b0>
Image in a Jupyter notebook

Example 3

Define a routine to calculate divided differences for Hermite polynomials.

#Construction of a kth divided difference for Hermite polynomials (recursive code) def Dh( j, k, Zn, Yn, Ypn ): #If k+j>N if j+k>=len(Zn): return 0 #Zeroth divided difference elif k == 0: return Yn[j/2] #First order divided difference (even indexes) elif k == 1 and j%2 == 0: return Ypn[j/2] #If higher divided difference else: return (Dh(j+1, k-1, Zn, Yn, Ypn)-Dh(j, k-1, Zn, Yn, Ypn))/(Zn[j+k]-Zn[j])

Activity

Calculate a routine, using the previous program for divided differences, that computes the Hermite polynomial given a dataset.

Generate a set of NN points of the function sin2(x)\sin^2(x) between 00 and 2π2\pi, including an array of xx positions, y=f(x)y = f(x) and first derivative y=f(x)y' = f'(x).

Show which polynomial gives the best approximation to the real function, Hermite or Lagrange polynomial.

Solution:

nbviewer.ipython.org/github/sbustamante/ComputationalMethods/blob/master/activities/hermite-and-lagrange.ipynb