The first three derivatives of Runge’s function from uniformly spaced samples: convinterp’s analytic derivatives of its kernels, against the derivatives of SciPy’s cubic spline and of Chebyshev interpolation. The boundary condition for convinterp is "poly" throughout.
Packages and the test function
import numpy as np # arrays and Chebyshev evaluationimport matplotlib.pyplot as plt # plottingfrom scipy.fft import dct # fast cosine transform, for Chebyshev coefficientsfrom scipy.interpolate import CubicSpline # cubic spline referencefrom convinterp import convolution_interpolation # the package under testdef runge(x):# Runge's function: smooth, but hard for polynomial interpolation on uniform gridsreturn1.0/ (1.0+25.0* x**2)def runge_derivative(x, k):# the exact k-th derivative of Runge's function, for k = 1, 2, 3 s =1.0+25.0* x**2# the denominator's baseif k ==1:return-50.0* x / s**2if k ==2:return50.0* (75.0* x**2-1.0) / s**3if k ==3:return15000.0* x * (1.0-25.0* x**2) / s**4raiseValueError("k must be 1, 2 or 3")def chebyshev_coefficients(f, n):# coefficients of the interpolant of f at the n Chebyshev points of the first kind,# by a type-II DCT (stable and O(n log n)) j = np.arange(n) # node indices 0 … n−1 nodes = np.cos(np.pi * (j +0.5) / n) # Chebyshev points of the first kind coefs = dct(f(nodes), type=2) / n # c_k = (2/n) Σ_j f(x_j) cos(πk(j + ½)/n) coefs[0] /=2# the constant term has half weightreturn coefs
Accuracy
Figure 1 shows the maximum error of each derivative over a dense set of test points as the number of samples grows.
Code
def derivative_study(orders=(1, 2, 3)): x_test = np.linspace(-1.0, 1.0, 11_001) # dense test points ns = np.unique(np.logspace(1, 4, 100).astype(int)) # 10 … 10000 samples, log-spaced kernels = ["b5", "b7", "b9", "b11", "b13"] # all support derivatives up to 3 fig, axes = plt.subplots(len(orders), 1, figsize=(10, 6*len(orders))) # panels stacked verticallyfor ax, k inzip(axes, orders): exact = runge_derivative(x_test, k) # the exact k-th derivative errors = {kern: [] for kern in kernels} # max error per kernel, per n spline_errors, cheb_errors = [], [] # the two referencesfor n in ns: x = np.linspace(-1.0, 1.0, n) # uniform grid with n samples y = runge(x) # the data on itfor kern in kernels: d_itp = convolution_interpolation(x, y, kernel=kern, bc="poly", derivative=k) errors[kern].append(np.max(np.abs(d_itp(x_test) - exact))) spline = CubicSpline(x, y) # not-a-knot cubic spline spline_errors.append(np.max(np.abs(spline(x_test, nu=k) - exact))) # its k-th derivative coefs = np.polynomial.chebyshev.chebder(chebyshev_coefficients(runge, n), m=k) # differentiate k times cheb_errors.append(np.max(np.abs(np.polynomial.chebyshev.chebval(x_test, coefs) - exact))) ax.loglog(ns, spline_errors, lw=2.5, color="orange", label="Cubic spline (SciPy)")for kern in kernels: ax.loglog(ns, errors[kern], lw=2, label=f'"{kern}"') # one line per convinterp kernel ax.loglog(ns, cheb_errors, lw=2.5, color="blue", label="Chebyshev") i100 = np.argmin(np.abs(ns -100)) # anchor the slope line near n = 100 anchor =10.0* errors["b7"][i100] # a decade above the b7 curve there ax.loglog(ns, anchor * (ns[i100] / ns) ** (7- k), "k--", lw=2, label=rf"$\mathcal{{O}}(h^{{{7- k}}})$") # the expected order: 7 − k ax.set_xlim(10, 1e4) ax.set_title(rf"derivative order {k}") ax.set_xlabel("Number of sample points") ax.set_ylabel("Maximum absolute error") # every panel has its own scale ax.grid(True, which="both", alpha=0.3) # light grid for reading slopes ax.legend(loc="lower left") fig.tight_layout()return figfig = derivative_study()plt.show()
Figure 1: Maximum error of the first three derivatives of Runge’s function. The b-series kernels lose one order of convergence per derivative (dashed lines for “b7” to “b13”), and their rounding floor rises slowly. Chebyshev differentiation converges fastest but amplifies rounding by about n² per derivative order.
Each derivative costs the b-series kernels one order of convergence: for "b7" to "b13", 6th order for the first derivative, 5th for the second and 4th for the third, and one order less for "b5". Their rounding floor rises slowly with the number of samples, roughly in proportion to n^k for the k-th derivative, which is unavoidable when differentiating sampled data. Chebyshev differentiation reaches its best accuracy with fewer samples, but its rounding grows about n^2 per derivative order: for the third derivative it becomes worse than no interpolation at all. The cubic spline converges at 4 - k for the k-th derivative, so its third derivative is piecewise constant.