cmlabs.interpolate.CubicSpline

class cmlabs.interpolate.CubicSpline(xvals, yvals, bc_type='not-a-knot')[source]

Cubic spline data interpolator.

\[\begin{split}\begin{gather} S(x) = c_i + d_i (x - x_i) + a_i (x - x_i)^2 + b_i (x - x_i)^3, \\ S(x) \in P_{3i}, \quad S(x) \in C^2[a, b], \quad S(x_i) = y_i \end{gather}\end{split}\]

where \(P_{3i}\) is the set of cubic polynomials, \(C^2[a, b]\) is the set of twice continuously differentiable functions, and \(y_i\) is the interpolated value at \(x_i\).

Parameters:
  • xvals (array-like) – The x-coordinates of the data points.

  • yvals (array-like) – The y-coordinates of the data points.

  • bc_type (str, optional) –

    Boundary condition type. Two additional equations, given by the boundary conditions, are required to determine all coefficients of polynomials on each segment.

    • ’clamped’: The first derivative at curves ends are equal, i.e., \(S'(a) = f'(a)\) and \(S'(b) = f'(b)\).

    • ’second’: The second derivative at curves ends are equal, i.e., \(S''(a) = f''(a)\) and \(S''(b) = f''(b)\).

    • ’periodic’: The interpolated functions is assumed to be periodic of period, i.e., \(S^i(a) = S^i(b)\) for \(i = 0, 1, 2\).

    • ’not-a-knot’ (default): The first and second segment at a curve end are the same polynomial. It is a good default when there is no information on boundary conditions, i.e., \(S'''(x_1 - 0) = S'''(x_1 + 0)\) and \(S'''(x_{n-1} - 0) = S'''(x_{n-1} + 0)\).

Notes

Сonsider the construction of a cubic spline interpolant based on given data points. The spline will be represented piecewise on each subinterval.

Assume that \(S'(x_i) = y'_i = m_i\) - first derivative of the interpolated function at the node \(x_i\). So the polynomial looks like this:

\[\begin{split}\begin{gather} S(x) = y_i + m_i (x - x_i) + a_i \frac{(x - x_i)^2}{2} + b_i \frac{(x - x_i)^3}{6}, \\ a_i = \frac{6}{h_{i+1}} \left(\frac{y_{i+1} - y_i}{h_{i+1}} - \frac{m_{i+1} + 2m_i}{3}\right), \quad b_i = \frac{12}{h_{i+1}^2} \left(\frac{m_{i+1} + m_i}{2} - \frac{y_{i+1} - y_i}{h_{i+1}}\right) \end{gather}\end{split}\]

where \(h_{i+1} = x_{i+1} - x_i\) is the distance between the nodes.

Using second condition \(S''(x_i - 0) = S''(x_i + 0)\) we can set up a system of equations for the unknown slopes \(m_i\):

\[\begin{gather} \mu_i m_{i-1} + 2 m_i + \lambda_i m_{i+1} = 3 \left(\lambda_i \frac{y_{i+1} - y_i}{h_{i+1}} + \mu_i \frac{y_i - y_{i-1}}{h_i}\right), \quad i = \overline{2, N-2} \end{gather}\]

where \(\lambda_i = \frac{h_{i+1}}{h_i + h_{i+1}}\) and \(\mu_i = \frac{h_i}{h_i + h_{i+1}}\) are the coefficients of the system. The first and last equations are given by the boundary conditions.

We impose the not-a-knot condition, which requires that the third derivative be continuous at the second and penultimate nodes. The not-a-knot condition effectively treats the first two segments and the last two segments as parts of the same cubic polynomial:

\[\begin{split}\begin{gather} S'''(x_1 - 0) = S'''(x_1 + 0), \quad S'''(x_{n-1} - 0) = S'''(x_{n-1} + 0) \\ b_0 = b_1, \quad b_{n-2} = b_{n-1} \\ (1 + \gamma_1) m_1 + \gamma_1 m_2 = \lambda_1 (3 + 2 \gamma_1) \frac{y_2 - y_1}{h_2} + \mu_1 \frac{y_1 - y_0}{h_1} \\ \gamma_N m_{N-2} + (1 + \gamma_N) m_{N-1} = \mu_{N-1} (3 + 2 \gamma_N) \frac{y_{N-1} - y_{N-2}}{h_{N-1}} + \lambda_{N-1} \frac{y_N - y_{N-1}}{h_N} \\ \end{gather}\end{split}\]

where \(\gamma_1 = \frac{h_1}{h_2}\) and \(\gamma_N = \frac{h_N}{h_{N-1}}\).

The system of equations can be solved using the Thomas algorithm. The tridiagonal system is represented in the form:

\[\begin{split}\left( \begin{array}{cccccc} 1 + \gamma_1 & \gamma_1 & 0 & \cdots & 0 & 0 \\ \mu_2 & 2 & \lambda_2 & \ddots & \vdots & \vdots \\ 0 & \mu_3 & 2 & \ddots & 0 & 0 \\ \vdots & \ddots & \ddots & \ddots & \lambda_{N-3} & 0 \\ 0 & \cdots & 0 & \mu_{N-2} & 2 & \lambda_{N-2} \\ 0 & \cdots & 0 & 0 & \gamma_N & 1 + \gamma_N \end{array} \right) \left( \begin{array}{c} m_1 \\ m_2 \\ m_3 \\ \vdots \\ m_{N-2} \\ m_{N-1} \end{array} \right) = \left( \begin{array}{c} \hat{g}_1 \\ g_2 \\ g_3 \\ \vdots \\ g_{N-2} \\ \hat{g}_{N-1} \end{array} \right)\end{split}\]

where

\[\begin{split}\begin{gather} \hat{g}_1 = \lambda_1 (3 + 2 \gamma_1) \frac{y_2 - y_1}{h_2} + \mu_1 \frac{y_1 - y_0}{h_1} \\ g_i = 3 \left(\lambda_i \frac{y_{i+1} - y_i}{h_{i+1}} + \mu_i \frac{y_i - y_{i-1}}{h_i}\right), \quad i = \overline{2, N-2} \\ \hat{g}_{N-1} = \mu_{N-1} (3 + 2 \gamma_N) \frac{y_{N-1} - y_{N-2}} {h_{N-1}} + \lambda_{N-1} \frac{y_N - y_{N-1}}{h_N} \end{gather}\end{split}\]

Now consider the second derivative of the spline:

\[\begin{split}\begin{gather} S'(x) = m_i + \frac{6(x - x_i)}{h_{i+1}} \left(\frac{y_{i+1} - y_i} {h_{i+1}} - \frac{m_{i+1} + 2m_i}{3}\right) + \\ + \frac{6(x - x_i)^2}{h_{i+1}^2} \left(\frac{m_{i+1} + m_i}{2} - \frac{y_{i+1} - y_i}{h_{i+1}}\right) \\ S''(x) = \frac{6}{h_{i+1}} \left(\frac{y_{i+1} - y_i}{h_{i+1}} - \frac{m_{i+1} + 2m_i}{3}\right) + \frac{12(x - x_i)}{h_{i+1}^2} \left(\frac{m_{i+1} + m_i}{2} - \frac{y_{i+1} - y_i}{h_{i+1}}\right) \end{gather}\end{split}\]

Examples

>>> import numpy as np
>>> from cmlabs.interpolate import CubicSpline
>>> x = np.linspace(0, 10, 5)
>>> y = np.sin(x)
>>> cs = CubicSpline(x, y)
>>> cs.interpolate(5)
-0.9589242746631386
__init__(xvals, yvals, bc_type='not-a-knot')[source]

Initialize the cubic spline with given x and y data points.

Parameters:
  • xvals (array-like) – The x-coordinates of the data points.

  • yvlas (array-like) – The y-coordinates of the data points.

  • bc_type (str, optional) – Boundary condition type. Options are ‘clamped’, ‘second’, ‘periodic’, and ‘not-a-knot’. Default is ‘not-a-knot’.

Methods

__init__(xvals, yvals[, bc_type])

Initialize the cubic spline with given x and y data points.

derivative(x[, order])

Calculate the derivative of the cubic spline at a given point.

interpolate(x)

Interpolate the \(S(x)\) using cubic spline interpolation.