Interpolation

We have three different implementation methodologies to deal with interpolation problems:

  • A procedural mode that computes a set of data points (in the form of ndarray with the required dimension) representing the actual solution.
  • In a few special cases, a functional mode that provides us with numpy functions representing the solutions.
  • An object-oriented mode that creates classes for interpolation problems. Different classes have different methods, depending on the operations that the particular kinds of interpolants enjoy. The advantage of this mode is that, through these methods, we can request more information from the solutions: not only evaluation or representation, but also relevant operations like searching for roots, computing derivatives and antiderivatives, error checking, and calculating coefficients and knots.

The choice of mode to represent our interpolants is up to us, depending mostly on how much accuracy we require, and the information/operations that we need afterwards.

Implementation details

There is not much more to add to the implementation details on the procedural mode. For each interpolation problem, we choose a routine to which we feed nodes xi, the values yi of the target function (and possibly its derivatives) on those nodes, and the domain x where the interpolant is to be evaluated. In some cases, if the interpolant requires more structure, we feed extra information.

Functional implementations are even simpler: when they are available, they only require the values of nodes xi and evaluation at those nodes, yi.

There are several generic object-oriented classes for interpolation. We seldom tamper with them and resort to using routines that create and manipulate internally more appropriate subclasses instead. Let us go over these objects in brief:

  • For generic univariate interpolation, we have the _Interpolator1D class. It may be initialized with the set of nodes xi, together with the value of the target function on those nodes, yi. If necessary, we may force the data type of yi as well, with the._set_dtype class method. In case we need to deal with the derivatives of an interpolator, we have the _Interpolator1DWithDerivatives subclass, with the extra class method .derivatives to compute the evaluations of the differentiations.
  • For univariate interpolation with splines of a degree less than or equal to 5, we have the InterpolatedUnivariateSpline class, which is in turn a subclass of the UnivariateSpline class. They are both very rich classes, with plenty of methods not only to evaluate a spline or any of its derivatives, but also to compute the spline representations of its derivatives and antiderivatives. We have methods to compute a definite integral between two points, as well. There are also methods that return the position of the knots, the spline coefficients, residuals, or even the roots. We initialize objects in the UnivariateSpline class at least with the nodes xi and the values to fit on those nodes, yi. We may optionally initialize the object with the degrees of the spline.
  • For bivariate interpolation with unstructured nodes (nodes not necessarily on a rectangular grid), one option is the interp2d class, which implements interpolation in two dimensions with bivariate splines of orders 1, 3, or 5. This class is initialized with the nodes and their evaluation.
  • For bivariate spline interpolation with nodes on a rectangular grid, we have the RectBivariateSpline class (when used with the s = 0 parameter), which is a subclass of the BivariateSpline class. In turn, BivariateSpline is a subclass of the base class _BivariateSplineBase. As its univariate counterpart, this is a very rich class with many methods for evaluation, extraction of nodes and coefficients, and computation of volume integrals or residuals.
  • For multivariate interpolation, there is the NDInterpolatorBase class, with three subclasses: NearestNDInterpolator (for nearest-neighbors interpolation), LinearNDInterpolator (for piecewise linear interpolation), and CloughTocher2DInterpolator (that implements a piecewise cubic, C1 smooth, curvature-minimizing interpolant in two dimensions).
  • For interpolation on a set of nodes on a rectangular grid over the surface of a sphere, there is the RectSphereBivariateSpline subclass of the SphereBivariateSpline class. We initialize it with the angles (theta and phi) representing the location of the nodes on the sphere, together with the corresponding evaluations.
  • For multivariate interpolation with radial functions, we have the Rbf class. It is rather dry, since it only allows an evaluation method. It is initialized with nodes and evaluations.

Univariate interpolation

The following table summarizes the different univariate interpolation modes coded in SciPy, together with the processes that we may use to resolve them:

Interpolation mode

Object-oriented implementation

Procedural implementation

Nearest-neighbors

interp1d(,kind='nearest')

 

Lagrange polynom.

BarycentricInterpolator

barycentric_interpolate

Hermite polynom.

KroghInterpolator

krogh_interpolate

Piecewise polynom.

PiecewisePolynomial

piecewise_polynomial_interpolate

Piecewise linear

interp1d(,kind='linear')

 

Generic spline interpolation

InterpolatedUnivariateSpline

splrep

Zero-order spline

interp1d(,kind='zero')

 

Linear spline

interp1d(,kind='slinear')

 

Quadratic spline

interp1d(,kind='quadratic')

 

Cubic spline

interp1d(,kind='cubic')

 

PCHIP

PchipInterpolator

pchip_interpolate

Nearest-neighbors interpolation

In the context of one-dimensional functions, nearest-neighbors interpolation provides a solution that is constant around each node, on each subinterval defined by two consecutive midpoints of the node set. To calculate the required interpolants, we call the generic scipy.interpolate.interp1d function with the kind='nearest' option. It produces an instance of the _Interpolator1D class with only the evaluation method available.

The following example shows its result on a simple trigonometric function f(x) = sin(3*x) on the interval from 0 to 1:

In [1]: import numpy as np, matplotlib.pyplot as plt; 
   ...: from scipy.interpolate import interp1d
In [2]: nodes = np.linspace(0, 1, 5); 
   ...: print nodes
[ 0.    0.25  0.5   0.75  1.  ]
In [3]: def f(t): return np.sin(3 * t)
In [4]: x = np.linspace(0, 1, 100)         # the domain
In [5]: interpolant = interp1d(nodes, f(nodes), kind='nearest')
In [6]: plt.rc('text', usetex=True)
   ...: plt.figure(); 
   ...: plt.axes().set_aspect('equal'); 
   ...: plt.plot(nodes, f(nodes), 'ro', label='Nodes'); 
   ...: plt.plot(x, f(x), 'r-', label=r'f(x)=sin(3x)'); 
   ...: plt.plot(x, interpolant(x), 'b--', label='Interpolation'); 
   ...: plt.title("Nearest-neighbor approximation"); 
   ...: plt.ylim(-0.05, 1.05); 
   ...: plt.xlim(-0.5, 1.05); 
   ...: plt.show()

This produces the following graph:

Nearest-neighbors interpolation

Lagrange interpolation

In Lagrange interpolation, we seek a polynomial that agrees with a target function at the set of nodes. In the scipy.interpolate module, we have three ways to solve this problem:

  • The BarycentricInterpolator subclass of _Interpolator1D implements a very stable algorithm based upon approximation by rational functions. This class has an evaluation method, plus two methods to add/update nodes of the fly: .add_xi and .set_yi.
  • A procedural scheme barycentric_interpolate is syntactic sugar for the previous class, with the evaluation method applied on a prescribed domain.
  • A numerically unstable functional scheme, lagrange, computes a numpy.poly1d instance of the interpolating polynomial. If the nodes are few and wisely chosen, this method allows us to deal with derivative, integration, and root-solving problems associated with the target function, somewhat reliably.

Let us experiment with this interpolation mode on the infamous Runge example: Find an interpolation polynomial for the function f(x) = 1/(1+x2) in the interval from -5 to 5, with two sets of equally distributed nodes:

In [7]: from scipy.interpolate import BarycentricInterpolator, 
   ...: barycentric_interpolate, lagrange
In [8]: nodes = np.linspace(-5, 5, 11); 
   ...: x = np.linspace(-5,5,1000); 
   ...: print nodes
[-5. -4. -3. -2. -1.  0.  1.  2.  3.  4.  5.]
In [9]: def f(t): return 1. / (1. + t**2)
In [10]: interpolant = BarycentricInterpolator(nodes, f(nodes))
In [11]: plt.figure(); 
   ....: plt.subplot(121, aspect='auto'); 
   ....: plt.plot(x, interpolant(x), 'b--', 
   ....:          label="Lagrange Interpolation"); 
   ....: plt.plot(nodes, f(nodes), 'ro', label='nodes'); 
   ....: plt.plot(x, f(x), 'r-', label="original"); 
   ....: plt.legend(loc=9); 
   ....: plt.title("11 equally distributed nodes")
Out[11]: <matplotlib.text.Text at 0x10a5fbe50>

The BarycentricInterpolator class allows adding the extra nodes and updating interpolant in an optimal way, without the need to recalculate from scratch:

In [12]: newnodes = np.linspace(-4.5, 4.5, 10); 
   ....: print newnodes
[-4.5 -3.5 -2.5 -1.5 -0.5  0.5  1.5  2.5  3.5  4.5]
In [13]: interpolant.add_xi(newnodes, f(newnodes))
In [14]: plt.subplot(122, aspect='auto'); 
   ....: plt.plot(x, interpolant(x), 'b--', 
   ....:          label="Lagrange Interpolation"); 
   ....: plt.plot(nodes, f(nodes), 'ro', label='nodes'); 
   ....: plt.plot(x, f(x), 'r-', label="original"); 
   ....: plt.legend(loc=8); 
   ....: plt.title("21 equally spaced nodes"); 
   ....: plt.show()

We obtain the following results:

Lagrange interpolation

The Runge example shows one of the shortcomings of very simple interpolation. Although the interpolant accurately approximates the function in the interior of the interval, it shows a very large deviation at the endpoints.

The same methods to initialize our interpolants can be called to request information about them. The following short session illustrates this point:

In [15]: print interpolant.xi
[-5.  -4.  -3.  -2.  -1.   0.   1.   2.   3.   4.   5.  -4.5 -3.5
 –2.5 -1.5 -0.5  0.5  1.5  2.5  3.5  4.5]
In [16]: print interpolant.yi.squeeze()
[ 0.04  0.06  0.1   0.2   0.5   1.    0.5   0.2   0.1   0.06  0.04
  0.05  0.08  0.14  0.31  0.8   0.8   0.31  0.14  0.08  0.05]

The procedural scheme has a simpler syntax, but lacks the flexibility to update nodes on the fly:

In [17]: y = barycentric_interpolate(nodes, f(nodes), x)

The functional scheme also enjoys a simple syntax:

In [18]: g = lagrange(nodes, f(nodes)); 
   ....: print g
           10             9            8             7           6
3.858e-05 x  + 6.268e-19 x - 0.002149 x + 3.207e-17 x + 0.04109 x
              5          4            3         2
 + 5.117e-17 x - 0.3302 x - 2.88e-16 x + 1.291 x - 1.804e-16 x

Hermite interpolation

The goal of Hermite interpolation is the computation of a polynomial that agrees with a target function and some of its derivatives in a finite set of nodes. We accomplish this task numerically with two schemes:

  • A subclass of _Interpolator1DWithDerivatives, KroghInterpolator, which has the .derivative method to compute a representation for any derivative of the interpolant and the .derivatives method to evaluate it.
  • A krogh_interpolate function, which is syntactic sugar for the previous class with the evaluation method applied on a prescribed domain.

Let us showcase these routines with Bernstein's example: Compute the Hermite interpolation to the absolute value function in the interval from -1 to 1 with ten equally distributed nodes, providing one derivative on each node.

Tip

The nodes need to be fed in an increasing order. For every node in which we present derivatives, we repeat the node as many times as necessary. For each occurrence of a node in xi, we place on yi the evaluation of the function and its derivatives, at the same entry levels.

In [19]: from scipy.interpolate import KroghInterpolator
In [20]: nodes = np.linspace(-1, 1, 10); 
   ....: x = np.linspace(-1, 1, 1000)
In [21]: np.set_printoptions(precision=3, suppress=True)
In [22]: xi = np.repeat(nodes, 2); 
   ....: print xi; 
   ....: yi = np.ravel(np.dstack((np.abs(nodes), np.sign(nodes)))); 
   ....: print yi
[-1.    -1.    -0.778 -0.778 -0.556 -0.556 -0.333 -0.333 -0.111
 -0.111  0.111  0.111  0.333  0.333  0.556  0.556  0.778  0.778 
  1.     1.   ]
[ 1.    -1.     0.778 -1.     0.556 -1.     0.333 -1.     0.111
 -1.     0.111  1.     0.333  1.     0.556  1.     0.778  1.    
  1.     1.   ]
In [23]: interpolant = KroghInterpolator(xi, yi)
In [24]: plt.figure(); 
   ....: plt.axes().set_aspect('equal'); 
   ....: plt.plot(x, interpolant(x), 'b--', 
   ....:          label='Hermite Interpolation'); 
   ....: plt.plot(nodes, np.abs(nodes), 'ro'); 
   ....: plt.plot(x, np.abs(x), 'r-', label='original'); 
   ....: plt.legend(loc=9); 
   ....: plt.title('Bernstein example'); 
   ....: plt.show()

This gives the following diagram:

Hermite interpolation

Piecewise polynomial interpolation

By prescribing the degrees of several polynomials and a finite set of nodes, we can construct an interpolator that has on each subinterval between two consecutive nodes, a polynomial arc with the required order. We can construct interpolants of this characteristic with the following procedures:

  • A subclass of _Interpolator1DWithDerivatives, the PiecewisePolynomial class, with methods for evaluating interpolants and its derivatives, or appending new nodes
  • For the special case of piecewise linear interpolation, the interp1d utility creates an instance of the _Interpolator1D class with only the evaluation method
  • A piecewise_polynomial_interpolate function, which is syntactic sugar for the PiecewisePolynomial class, with the evaluation method applied on a prescribed domain

Let us revisit the first example in this section. First, we try piecewise linear interpolation with interp1d. Second, we apply piecewise quadratic interpolation (all pieces have order 2) with the correct derivatives at every node by using PiecewisePolynomial.

In [25]: from scipy.interpolate import PiecewisePolynomial
In [26]: nodes = np.linspace(0, 1, 5); 
   ....: x = np.linspace(0, 1, 100)
In [27]: def f(t): return np.sin(3 * t)
In [28]: interpolant = interp1d(nodes, f(nodes), kind='linear')
In [29]: plt.figure(); 
   ....: plt.subplot(121, aspect='equal'); 
   ....: plt.plot(x, interpolant(x), 'b--', label="interpolation"); 
   ....: plt.plot(nodes, f(nodes), 'ro'); 
   ....: plt.plot(x, f(x), 'r-', label="original"); 
   ....: plt.legend(loc=8); 
   ....: plt.title("Piecewise Linear Interpolation")
Out[29]: <matplotlib.text.Text at 0x107be0390>
In [30]: yi = np.zeros((len(nodes), 2)); 
   ....: yi[:,0] = f(nodes); 
   ....: yi[:,1] = 3 * np.cos(3 * nodes); 
   ....: print yi
[[ 0.     3.   ]
 [ 0.682  2.195]
 [ 0.997  0.212]
 [ 0.778 -1.885]
 [ 0.141 -2.97 ]]
In [31]: interpolant = PiecewisePolynomial(nodes, yi, orders=2)
In [32]: plt.subplot(122, aspect='equal'); 
   ....: plt.plot(x, interpolant(x), 'b--', label="interpolation"); 
   ....: plt.plot(nodes, f(nodes), 'ro'); 
   ....: plt.plot(x, f(x), 'r-', label="original"); 
   ....: plt.legend(loc=8); 
   ....: plt.title("Piecewise Quadratic interpolation"); 
   ....: plt.show()

This gives the following diagram:

Piecewise polynomial interpolation

In this image, the piecewise quadratic interpolation and the original function are virtually indistinguishable. We need to go to the computation of the absolute values of differences (of the function together with its first and second derivatives) to actually realize the error of computation. The following is a crude computation that approximates these errors and illustrates the use of the .derivatives method:

In [33]: np.abs(f(x) - interpolant(x)).max()
Out[33]: 0.0093371930045896279
In [34]: f1prime = lambda t: 3 * np.cos(3 * t); 
   ....: np.abs(f1prime(x) - interpolant.derivatives(x)).max()
Out[34]: 10.589218385920123
In [35]: f2prime = lambda t: -9 * np.sin(3 * x); 
   ....: np.abs(f2prime(x) - interpolant.derivatives(x,der=2)).max()
Out[35]: 9.9980773091170505

A great advantage of piecewise polynomial approximation is the flexibility of using polynomials of different degrees on different subintervals. For instance, with the same set of nodes, we can use lines in the first and last subintervals and cubics for the others:

In [36]: interpolant = PiecewisePolynomial(nodes, yi, 
   ....:                                   orders=[1,3,3,1])

The other great advantage of the implementation of this interpolation scheme is the ease with which we may add new nodes, without the need to recalculate from scratch. For example, to add a new node after the last one, we issue:

In [37]: interpolant.append(1.25, np.array([f(1.25)]))

Spline interpolation

Univariate splines are a special case of piecewise polynomials. They possess a high degree of smoothness at places where the polynomial pieces connect. These functions can be written as linear combinations of basic splines with minimal support with respect to a given degree, smoothness, and set of nodes.

Univariate spline interpolations using splines of the order of up to five may be carried out by the interp1d function with the appropriate kind option. This function creates instances of _Interpolator1DWithDerivatives, with the corresponding class methods. The computations are performed through calls to routines in the Fortran library FITPACK. The following example shows the different possibilities:

In [38]: splines = ['zero', 'slinear', 'quadratic', 'cubic', 4, 5]; 
   ....: g = KroghInterpolator([0,0,0,1,1,1,2,2,2,3,3,3], 
   ....:                       [10,0,0,1,0,0,0.25,0,0,0.125,0,0]); 
   ....: f = lambda t: np.log1p(g(t)); 
   ....: x = np.linspace(0,3,100); 
   ....: nodes = np.linspace(0,3,11)
In [39]: plt.figure()
In [40]: for k in xrange(6):
   ....:     interpolant = interp1d(nodes, f(nodes), 
   ....:                            kind = splines[k])
   ....:     plt.subplot(2,3,k+1, aspect='equal')
   ....:     plt.plot(nodes, f(nodes), 'ro')
   ....:     plt.plot(x, f(x), 'r-', label='original')
   ....:     plt.plot(x, interpolant(x), 'b--', 
   ....:              label='interpolation')
   ....:     plt.title('{0} spline'.format(splines[k]))
   ....:     plt.legend()
In [41]: plt.show()

This gives the following diagram:

Spline interpolation

Note

The zero spline is very similar to the nearest-neighbors approximation, although in this case the interpolant is constant between each choice of two consecutive nodes. The slinear spline is exactly the same as the piecewise linear interpolation. However, the algorithm that performs this interpolation through splines is slower.

For any given problem setup, there are many different possible spline interpolations with the same degrees, nodes, and evaluations. The output also depends on the position and the number of knots, for example. Unfortunately, the interp1d function only allows the control of nodes and values; the algorithm uses the simplest possible settings in terms of knot computation.

Note for instance that the cubic spline interpolation in the previous example does not preserve the monotonicity of the target function. It is possible to force the monotonicity of the interpolant in this case by carefully imposing restrictions on derivatives or the location of knots. We have a special function that achieves this task for us by using piecewise monotonic cubic Hermite interpolation (PCHIP), implemented through the Fritsch-Carlson algorithm. This simple algorithm is carried out either by the PchipInterpolator subclass of _Interpolator1DWithDerivatives, or through its equivalent procedural function, pchip_interpolate.

In [42]: from scipy.interpolate import PchipInterpolator
In [43]: interpolant = PchipInterpolator(nodes, f(nodes))
In [44]: plt.figure(); 
   ....: plt.axes().set_aspect('equal'); 
   ....: plt.plot(nodes, f(nodes), 'ro'); 
   ....: plt.plot(x, f(x), 'r-', label='original'); 
   ....: plt.plot(x, interpolant(x), 'b--', label='interpolation'); 
   ....: plt.title('PCHIP interpolation'); 
   ....: plt.legend(); 
   ....: plt.show()

This gives the following graph:

Spline interpolation

Generic spline interpolation, where we have actual control over all the different parameters that affect the quality of splines, is handled by the InterpolatedUnivariateSpline class. In this case, all computations are carried out by wrappers of routines from the Fortran library FITPACK. It is possible to access these wrappers in a procedural way, through a set of functions in the scipy.interpolate module. The following table shows a match between class methods, the corresponding procedural functions, and the FITPACK routines that they call:

Operation

Object-oriented implementation

Procedural

FITPACK

Instantiation of interpolant

InterpolatedUnivariateSpline

splrep

CURFIT

Reporting knots of spline

object.get_knots()

splrep

 

Reporting spline coefficients

object.get_coeffs()

splrep

CURFIT

Evaluation of spline

object()

splev

SPLEV

Derivative

object.derivative()

splder

 

Evaluation of derivatives

object.derivatives()

splev, spalde

SPLDER, SPALDE

Antiderivative

object.antiderivative()

splantider

 

Definite integral

object.integral()

splint

SPLINT

Roots (for cubic splines)

object.roots()

sproot

SPROOT

Tip

The values obtained by the.get_coeffs method are the coefficients of the spline as a linear combination of B-splines.

Let us show how to approximate the area under the graph of the target function by computing the integral of the corresponding interpolation spline of order 5.

In [45]: from scipy.interpolate import InterpolatedUnivariateSpline 
   ....: as IUS
In [46]: interpolant = IUS(nodes, f(nodes), k=5)
In [47]: area = interpolant.integral(0,3); 
   ....: print area
2.14931665485

Multivariate interpolation

Bivariate interpolation with splines can be performed with interp2d in the scipy.interpolate module. This is a very simple class that allows only the evaluation method and has three basic spline interpolation modes coded: linear, cubic, and quintic. It offers no control over knots or weights. To create a representation of the bivariate spline, the interp2d function calls the Fortran routine SURFIT from the library FITPACK (which, sadly, is not actually meant to perform interpolation!). To evaluate the spline numerically, the module calls the routine BISPEV.

Let us show by example the usage of interp2d. We first construct an interesting bivariate function to interpolate a random choice of 100 nodes on its domain and present a visualization:

In [1]: import numpy as np, matplotlib.pyplot as plt; 
   ...: from mpl_toolkits.mplot3d.axes3d import Axes3D
In [2]: def f(x, y): return np.sin(x) + np.sin(y)
In [3]: t = np.linspace(-3, 3, 100); 
   ...: domain = np.meshgrid(t, t); 
   ...: X, Y = domain; 
   ...: Z = f(*domain)
In [4]: fig = plt.figure(); 
   ...: ax1 = plt.subplot2grid((2,2), (0,0), aspect='equal'); 
   ...: p = ax1.pcolor(X, Y, Z); 
   ...: fig.colorbar(p); 
   ...: CP = ax1.contour(X, Y, Z, colors='k'); 
   ...: ax1.clabel(CP); 
   ...: ax1.set_title('Contour plot')
In [5]: nodes = 6 * np.random.rand(100, 2) - 3; 
   ...: xi = nodes[:, 0]; 
   ...: yi = nodes[:, 1]; 
   ...: zi = f(xi, yi)
In [6]: ax2 = plt.subplot2grid((2,2), (0,1), aspect='equal'); 
   ...: p2 = ax2.pcolor(X, Y, Z); 
   ...: ax2.scatter(xi, yi, 25, zi) ; 
   ...: ax2.set_xlim(-3, 3); 
   ...: ax2.set_ylim(-3, 3); 
   ...: ax2.set_title('Node selection')
In [7]: ax3 = plt.subplot2grid((2,2), (1,0), projection='3d', 
   ...:                        colspan=2, rowspan=2); 
   ...: ax3.plot_surface(X, Y, Z, alpha=0.25); 
   ...: ax3.scatter(xi, yi, zi, s=25); 
   ...: cset = ax3.contour(X, Y, Z, zdir='z', offset=-4); 
   ...: cset = ax3.contour(X, Y, Z, zdir='x', offset=-5); 
   ...: ax3.set_xlim3d(-5, 3); 
   ...: ax3.set_ylim3d(-3, 5); 
   ...: ax3.set_zlim3d(-4, 2); 
   ...: ax3.set_title('Surface plot')
In [8]: fig.tight_layout(); 
   ...: plt.show()

We obtain the following diagram:

Multivariate interpolation

Piecewise linear interpolation with these nodes can be then performed as follows:

In [9]: from scipy.interpolate import interp2d
In [10]: interpolant = interp2d(xi, yi, zi, kind='linear')
In [11]: plt.figure(); 
   ....: plt.axes().set_aspect('equal'); 
   ....: plt.pcolor(X, Y, interpolant(t, t)); 
   ....: plt.scatter(xi, yi, 25, zi); 
   ....: CP = plt.contour(X, Y, interpolant(t, t), colors='k'); 
   ....: plt.clabel(CP); 
   ....: plt.xlim(-3, 3); 
   ....: plt.ylim(-3, 3); 
   ....: plt.title('Piecewise linear interpolation'); 
   ....: plt.show()

In spite of its name (interp2d), this process is not an actual interpolation but a crude approximation that tries to fit the data. In general, each time you run this code, you will obtain results of different quality. Luckily, this is not the case with the rest of the interpolation routines that follow!

Multivariate interpolation

Note

If the location of the nodes is not optimal, we are likely to obtain a warning:

Warning:     No more knots can be added because the number of B-spline coefficients
    already exceeds the number of data points m. Probably causes: either
    s or m too small. (fp>s)
  kx,ky=1,1 nx,ny=11,14 m=100 fp=0.002836 s=0.000000

Tip

Note that in the previous example, the evaluation of the interpolant is performed with a call to two one-dimensional arrays. In general, to evaluate an interpolant g computed with interp2d on a rectangular grid that can be realized as the Cartesian product of two one-dimensional arrays (tx of size m and ty of size n), we issue g(tx, ty); this gives us a two-dimensional array of size m x n.

The quality of the result is, of course, deeply linked to the density and structure of the nodes. Increasing their number or imposing their location on a rectangular grid improves matters. In the case of nodes forming a rectangular grid, an actual interpolation, with a much faster and accurate method is accomplished by means of the RectBivariateSpline class. This function is a wrapper to the Fortran routine REGRID in the FITPACK library.

Let us now choose 100 nodes on a rectangular grid and recalculate, as follows:

In [12]: ti = np.linspace(-3, 3, 10); 
   ....: xi, yi = np.meshgrid(ti, ti); 
   ....: zi = f(xi, yi)
In [13]: from scipy.interpolate import RectBivariateSpline
In [14]: interpolant = RectBivariateSpline(ti, ti, zi, kx=1, ky=1)
In [15]: plt.figure(); 
   ....: plt.axes().set_aspect('equal'); 
   ....: plt.pcolor(X, Y, interpolant(t, t)); 
   ....: CP = plt.contour(X, Y, interpolant(t, t), colors='k'); 
   ....: plt.clabel(CP); 
   ....: plt.scatter(xi, yi, 25, zi); 
   ....: plt.xlim(-3, 3); 
   ....: plt.ylim(-3, 3); 
   ....: plt.title('Piecewise linear interpolation, 
   ....: rectangular grid'); 
   ....: plt.show()

Tip

As with the case of interp2d, to evaluate an interpolant g computed with RectBivariateSpline on a rectangular grid that can be realized as the Cartesian product of two one-dimensional arrays (tx of size m and ty of size n), we issue g(tx, ty); this gives us a two-dimensional array of size m x n.

This gives an actual interpolation now:

Multivariate interpolation

The volume integral under the graph is very accurate (the actual integral of the target function in the given domain is zero):

In [16]: interpolant.integral(-3, 3, -3, 3)
Out[16]: 2.636779683484747e-16

Let us examine some of the different pieces of information that we receive from this class, in this case:

  • The degrees of the interpolant:
    In [17]: interpolant.degrees
    Out[17]: (1, 1)
    
  • The sum of the squared residuals of the spline approximation returned:
    In [18]: interpolant.fp
    Out[18]: 0.0
    In [19]: interpolant.get_residual()
    Out[19]: 0.0
    
  • The coefficients of the interpolant:
    In [20]: np.set_printoptions(precision=5, suppress=True)
    In [21]: print interpolant.get_coeffs()
    [-0.28224 -0.86421 -1.13653 -0.98259 -0.46831  0.18607
      0.70035  0.85429  0.58197  0.      -0.86421 -1.44617
     -1.71849 -1.56456 -1.05028 -0.39589  0.11839  0.27232 
      0.      -0.58197 -1.13653 -1.71849 -1.99082 -1.83688
     -1.3226  -0.66821 -0.15394  0.      -0.27232 -0.85429
     -0.98259 -1.56456 -1.83688 -1.68294 -1.16867 -0.51428 
      0.       0.15394 -0.11839 -0.70035 -0.46831 -1.05028
     -1.3226  -1.16867 -0.65439 -0.       0.51428  0.66821
      0.39589 -0.18607  0.18607 -0.39589 -0.66821 -0.51428
     -0.       0.65439  1.16867  1.3226   1.05028  0.46831
      0.70035  0.11839 -0.15394  0.       0.51428  1.16867 
      1.68294  1.83688  1.56456  0.98259  0.85429  0.27232
      0.       0.15394  0.66821  1.3226   1.83688  1.99082
      1.71849  1.13653  0.58197  0.      -0.27232 -0.11839
      0.39589  1.05028  1.56456  1.71849  1.44617  0.86421 
      0.      -0.58197 -0.85429 -0.70035 -0.18607  0.46831
      0.98259  1.13653  0.86421  0.28224]
    
  • The location of the knots:
    In [22]: interpolant.get_knots()
    (array([-3. , -3. , -2.33333, -1.66667, -1. , -0.33333,
            0.33333,  1. ,  1.66667,  2.33333,  3. ,  3. ]),
     array([-3. , -3. , -2.33333, -1.66667, -1. , -0.33333,
            0.33333,  1. ,  1.66667,  2.33333,  3. ,  3. ]))
    

Smoother results can be obtained with piecewise cubic splines. In the previous example, we can accomplish this task by setting kx = 3 and ky = 3:

In [23]: interpolant = RectBivariateSpline(ti, ti, zi, kx=3, ky=3)
In [24]: fig = plt.figure(); 
   ....: ax = fig.add_subplot(121, projection='3d',aspect='equal'); 
   ....: ax.plot_surface(X, Y, interpolant(t, t), alpha=0.25, 
   ....:                 rstride=5, cstride=5); 
   ....: ax.scatter(xi, yi, zi, s=25); 
   ....: C = ax.contour(X, Y, interpolant(t, t), zdir='z', 
   ....:                offset=-4); 
   ....: C = ax.contour(X, Y, interpolant(t, t), zdir='x',
   ....:                offset=-5); 
   ....: ax.set_xlim3d(-5, 3); 
   ....: ax.set_ylim3d(-3, 5); 
   ....: ax.set_zlim3d(-4, 2); 
   ....: ax.set_title('Cubic interpolation, RectBivariateSpline')

Among all possible interpolations with cubic splines, there is a special case that minimizes the curvature. We have one implementation of this particular case, by means of a clever iterative algorithm that converges to the solution. It relies on the following three key concepts:

  • Delaunay triangulations of the domain using the nodes as vertices
  • Bezier cubic polynomials supported on each triangle using a Cough-Tocher scheme
  • Estimation and imposition of gradients to minimize curvature

An implementation is available through the CloughTocher2dInterpolator function in the scipy.interpolate module, or through the black-box function griddata in the same module with the method='cubic' option. Let us compare the outputs:

In [25]: from scipy.interpolate import CloughTocher2DInterpolator
In [26]: nodes = np.dstack((np.ravel(xi), np.ravel(yi))).squeeze(); 
   ....: zi = f(nodes[:, 0], nodes[:, 1])
In [27]: interpolant = CloughTocher2DInterpolator(nodes, zi)
In [28]: ax = fig.add_subplot(122, projection='3d', aspect='equal'); 
   ....: ax.plot_surface(X, Y, interpolant(X, Y), alpha=0.25, 
   ....:                 rstride=5, cstride=5); 
   ....: ax.scatter(xi, yi, zi, s=25); 
   ....: C = ax.contour(X, Y, interpolant(X, Y), zdir='z', 
   ....:                offset=-4); 
   ....: C = ax.contour(X, Y, interpolant(X, Y), zdir='x', 
   ....:                offset=-5); 
   ....: ax.set_xlim3d(-5, 3); 
   ....: ax.set_ylim3d(-3, 5); 
   ....: ax.set_zlim3d(-4, 2); 
   ....: ax.set_title('Cubic interpolation, 
   ....: CloughTocher2DInterpolator'); 
   ....: plt.show()

Tip

Unlike the cases of interp2d and RectBivariateSpline, to evaluate an interpolant g computed with CloughTocher2DInterpolator on a rectangular grid X, Y = domain, we issue g(X, Y) or g(*domain).

This gives the following diagram:

Multivariate interpolation

The black-box procedural function called griddata also allows us to access piecewise linear interpolation in multiple dimensions, as well as multidimensional nearest-neighbors interpolation.

In [29]: from scipy.interpolate import griddata
In [30]: Z = griddata(nodes, zi, (X, Y), method='nearest')
In [31]: plt.figure(); 
   ....: plt.axes().set_aspect('equal'); 
   ....: plt.pcolor(X, Y, Z); 
   ....: plt.colorbar(); 
   ....: plt.title('Nearest-neighbors'); 
   ....: plt.show()

This gives us the following not-too-impressive diagram:

Multivariate interpolation

There is one more interpolation mode to consider: radial basis function interpolation. The aim here is to interpolate with a linear combination of radial functions of the form fk(x,y) = g(sqrt((x-xk)**2 + (y-yk)**2)), centered at the points (xk, yk), for the same function g. We may choose among seven standard functions g (listed below), or even choose our own:

  • 'multiquadric': g(r) = sqrt((r/self.epsilon)**2 + 1)
  • 'inverse': g(r) = 1.0/sqrt((r/self.epsilon)**2 + 1)
  • 'gaussian': g(r) = exp(-(r/self.epsilon)**2)
  • 'linear': g(r) = r
  • 'cubic': g(r) = r**3
  • 'quintic': g(r) = r**5
  • 'thin_plate': g(r) = r**2 * log(r)

The implementation is performed through the Rbf class. It can be initialized as usual with nodes and their evaluations. We also need to include the choice of radial function, and if necessary, the value of the epsilon parameter affecting the size of the bumps.

Let us run a couple of interpolations: first, by means of radial Gaussians with standard deviation epsilon = 2.0, and then, with a radial function based on sinc. Let us also go back to random nodes:

In [32]: from scipy.interpolate import Rbf
In [33]: nodes = 6 * np.random.rand(100, 2) - 3; 
   ....: xi = nodes[:, 0]; 
   ....: yi = nodes[:, 1]; 
   ....: zi = f(xi, yi)
In [34]: interpolant = Rbf(xi, yi, zi, function='gaussian', 
   ....:                   epsilon=2.0)
In [35]: plt.figure(); 
   ....: plt.subplot(121, aspect='equal'); 
   ....: plt.pcolor(X, Y, interpolant(X, Y)); 
   ....: plt.scatter(xi, yi, 25, zi); 
   ....: plt.xlim(-3, 3); 
   ....: plt.ylim(-3, 3)
Out[35]: (-3, 3)
In [36]: interpolant = Rbf(xi, yi, zi, function = np.sinc)
In [37]: plt.subplot(122, aspect='equal'); 
   ....: plt.pcolor(X, Y, interpolant(X, Y)); 
   ....: plt.scatter(xi, yi, 25, zi); 
   ....: plt.xlim(-3, 3); 
   ....: plt.ylim(-3, 3); 
   ....: plt.show()

This gives two very accurate interpolations, in spite of the unstructured nodes:

Multivariate interpolation

The last case for consideration is bivariate spline interpolation over a rectangular grid on a sphere. We obtain this interpolation with the RectSphereBivariateSpline function, which instantiates a subclass of SphereBivariateSpline through calls to the FITPACK routines SPGRID (to compute the representation of the spline) and BISPEV (for evaluation).

The implementation and evaluation are reminiscent of the Fortran coding methodology:

  • To compute the spline representation, we issue the RectSphereBivariateSpline(u, v, data) command, where both u and v are one-dimensional arrays of strictly increasing positive values representing the angles (in radians) for the latitudes and longitudes (respectively) of the locations of the nodes.
  • At evaluation time, if we require a two-dimensional representation of the interpolant on a refined grid of size m x n, we issue object(theta, phi) where both theta and phi are one-dimensional and strictly increasing, and must be contained in the domains defined by u and v above. The output is (in spite of what your documentation says) an m x n array.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.58.209.201