We have three different implementation methodologies to deal with interpolation problems:
ndarray
with the required dimension) representing the actual solution.numpy
functions representing the solutions.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.
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:
_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.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.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.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.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).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.Rbf
class. It is rather dry, since it only allows an evaluation method. It is initialized with nodes and evaluations.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 |
| |
Lagrange polynom. |
|
|
Hermite polynom. |
|
|
Piecewise polynom. |
|
|
Piecewise linear |
| |
Generic spline interpolation |
|
|
Zero-order spline |
| |
Linear spline |
| |
Quadratic spline |
| |
Cubic spline |
| |
PCHIP |
|
|
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:
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:
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
.barycentric_interpolate
is syntactic sugar for the previous class, with the evaluation method applied on a prescribed domain.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:
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
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:
_Interpolator1DWithDerivatives
, KroghInterpolator
, which has the .derivative
method to compute a representation for any derivative of the interpolant and the .derivatives
method to evaluate it.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.
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()
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:
_Interpolator1DWithDerivatives
, the PiecewisePolynomial
class, with methods for evaluating interpolants and its derivatives, or appending new nodesinterp1d
utility creates an instance of the _Interpolator1D
class with only the evaluation methodpiecewise_polynomial_interpolate
function, which is syntactic sugar for the PiecewisePolynomial
class, with the evaluation method applied on a prescribed domainLet 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:
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)]))
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:
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:
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 |
|
|
|
Reporting knots of spline |
|
| |
Reporting spline coefficients |
|
|
|
Evaluation of spline |
|
|
|
Derivative |
|
| |
Evaluation of derivatives |
|
|
|
Antiderivative |
|
| |
Definite integral |
|
|
|
Roots (for cubic 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
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:
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!
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
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()
This gives an actual interpolation now:
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:
In [17]: interpolant.degrees Out[17]: (1, 1)
In [18]: interpolant.fp Out[18]: 0.0 In [19]: interpolant.get_residual() Out[19]: 0.0
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]
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:
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()
This gives the following diagram:
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:
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:
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:
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.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.13.58.209.201