CUBIC_SPLINE

Overview

The CUBIC_SPLINE function performs piecewise cubic polynomial interpolation through a set of data points. Unlike fitting a single high-degree polynomial to all data points (which can cause Runge’s phenomenon), cubic spline interpolation fits individual cubic polynomials between each pair of consecutive points while ensuring the overall curve is C² smooth—meaning the function, its first derivative, and second derivative are all continuous across segment boundaries.

This implementation uses the SciPy CubicSpline class from the scipy.interpolate module. The algorithm constructs n cubic polynomials q_i(x) for n+1 data points, where each polynomial connects adjacent knots (x_{i-1}, y_{i-1}) and (x_i, y_i). The continuity requirements at interior knots generate a tridiagonal system of linear equations that is solved efficiently to determine the spline coefficients.

The function supports four boundary condition types that specify behavior at the endpoints:

  • not-a-knot (default): The first and second polynomial segments share the same cubic; similarly for the last two segments. This is a good choice when no boundary information is available.
  • natural: The second derivative at both endpoints is zero (q''(x_0) = q''(x_n) = 0), simulating a physical elastic ruler that can pivot freely at its ends.
  • clamped: The first derivative at both endpoints is zero, fixing the slope at the boundaries.
  • periodic: For periodic functions where y_0 = y_n, this ensures y'(x_0) = y'(x_n) and y''(x_0) = y''(x_n).

The extrapolate parameter controls whether the spline extends beyond the original data range or returns errors for out-of-bounds queries. For further mathematical background, see the Spline interpolation article on Wikipedia and Carl de Boor’s reference text A Practical Guide to Splines (Springer-Verlag, 1978).

This example function is provided as-is without any representation of accuracy.

Excel Usage

=CUBIC_SPLINE(x, y, x_new, bc_type, extrapolate)
  • x (list[list], required): The x-coordinates of the data points.
  • y (list[list], required): The y-coordinates of the data points.
  • x_new (list[list], required): The x-coordinates at which to evaluate the interpolated values.
  • bc_type (str, optional, default: “not-a-knot”): Boundary condition type.
  • extrapolate (bool, optional, default: true): If True, extrapolate out-of-bounds points.

Returns (list[list]): A 2D list of interpolated values, or an error message (str) if invalid.

Examples

Example 1: Demo case 1

Inputs:

x y x_new
0 0 0.5
1 1 1.5
2 4 2.5
3 9

Excel formula:

=CUBIC_SPLINE({0;1;2;3}, {0;1;4;9}, {0.5;1.5;2.5})

Expected output:

Result
0.25
2.25
6.25

Example 2: Demo case 2

Inputs:

x y x_new bc_type
0 0 0.5 natural
1 1 1.5
2 0 2.5
3 1 3.5
4 0

Excel formula:

=CUBIC_SPLINE({0;1;2;3;4}, {0;1;0;1;0}, {0.5;1.5;2.5;3.5}, "natural")

Expected output:

Result
0.7679
0.4464
0.4464
0.7679

Example 3: Demo case 3

Inputs:

x y x_new bc_type
1 1 1.5 clamped
2 4 2.5
3 9 3.5
4 16

Excel formula:

=CUBIC_SPLINE({1;2;3;4}, {1;4;9;16}, {1.5;2.5;3.5}, "clamped")

Expected output:

Result
2
6
13.5

Example 4: Demo case 4

Inputs:

x y x_new bc_type extrapolate
0 0 0.5 not-a-knot true
1 1 1.5
2 4 2.5

Excel formula:

=CUBIC_SPLINE({0;1;2}, {0;1;4}, {0.5;1.5;2.5}, "not-a-knot", TRUE)

Expected output:

Result
0.25
2.25
6.25

Python Code

import math
from scipy.interpolate import CubicSpline as scipy_CubicSpline

def cubic_spline(x, y, x_new, bc_type='not-a-knot', extrapolate=True):
    """
    Cubic spline data interpolator.

    See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.CubicSpline.html

    This example function is provided as-is without any representation of accuracy.

    Args:
        x (list[list]): The x-coordinates of the data points.
        y (list[list]): The y-coordinates of the data points.
        x_new (list[list]): The x-coordinates at which to evaluate the interpolated values.
        bc_type (str, optional): Boundary condition type. Valid options: not-a-knot, periodic, clamped, natural. Default is 'not-a-knot'.
        extrapolate (bool, optional): If True, extrapolate out-of-bounds points. Default is True.

    Returns:
        list[list]: A 2D list of interpolated values, or an error message (str) if invalid.
    """
    def to2d(val):
        """Convert scalar to 2D list if needed."""
        return [[val]] if not isinstance(val, list) else val

    def flatten(arr):
        """Flatten a 2D list to 1D."""
        result = []
        for sublist in arr:
            if isinstance(sublist, list):
                for item in sublist:
                    result.append(item)
            else:
                result.append(sublist)
        return result

    def validate_numeric_array(arr, name):
        """Validate that array contains only numeric values."""
        for i, row in enumerate(arr):
            if not isinstance(row, list):
                return f"Invalid input: {name} must be a 2D list."
            for j, val in enumerate(row):
                if not isinstance(val, (int, float, bool)):
                    return f"Invalid input: {name}[{i}][{j}] must be numeric."
                if isinstance(val, bool):
                    continue
                num_val = float(val)
                if math.isnan(num_val) or math.isinf(num_val):
                    return f"Invalid input: {name}[{i}][{j}] must be finite."
        return None

    # Validate bc_type
    valid_bc_types = ['not-a-knot', 'periodic', 'clamped', 'natural']
    if not isinstance(bc_type, str):
        return "Invalid input: bc_type must be a string."
    if bc_type not in valid_bc_types:
        return f"Invalid input: bc_type must be one of {valid_bc_types}."

    # Validate extrapolate
    if not isinstance(extrapolate, bool):
        return "Invalid input: extrapolate must be a boolean."

    # Normalize inputs to 2D lists
    x = to2d(x)
    y = to2d(y)
    x_new = to2d(x_new)

    # Validate inputs
    error = validate_numeric_array(x, 'x')
    if error:
        return error
    error = validate_numeric_array(y, 'y')
    if error:
        return error
    error = validate_numeric_array(x_new, 'x_new')
    if error:
        return error

    # Flatten arrays
    try:
        x_flat = flatten(x)
        y_flat = flatten(y)
        x_new_flat = flatten(x_new)
    except Exception as exc:
        return f"Invalid input: unable to flatten arrays: {exc}"

    # Check that x and y have the same length
    if len(x_flat) != len(y_flat):
        return "Invalid input: x and y must have the same number of elements."

    # Check minimum number of points
    if len(x_flat) < 2:
        return "Invalid input: at least 2 data points are required."

    # Check if x is strictly increasing
    for i in range(len(x_flat) - 1):
        if x_flat[i] >= x_flat[i+1]:
             return "Invalid input: x must be strictly increasing."

    # Perform cubic spline interpolation
    try:
        interp = scipy_CubicSpline(x_flat, y_flat, bc_type=bc_type, extrapolate=extrapolate)
        result = interp(x_new_flat)
    except Exception as exc:
        return f"scipy.interpolate.CubicSpline error: {exc}"

    # Convert result to 2D list
    try:
        result_2d = [[float(val)] for val in result]
    except Exception as exc:
        return f"Error converting result to 2D list: {exc}"

    # Validate result values
    for i, row in enumerate(result_2d):
        for j, val in enumerate(row):
            if math.isnan(val) or math.isinf(val):
                if extrapolate is False:
                    continue
                return f"scipy.interpolate.CubicSpline error: result contains non-finite value at index {i}."

    return result_2d

Online Calculator