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