The interpolant is a sum of polynomial pieces, so its derivatives are polynomial pieces too, and both packages evaluate them analytically: the derivative keyword gives an interpolant of a derivative instead of the data, built from the differentiated kernel. There is no finite difference and no step size to tune, and the result is exact to rounding for the interpolant that is being differentiated.
x =range(0, 2π, length =40) # 40 uniformly spaced samplesy =sin.(x) # the values at the samplesd1 =convolution_interpolation(x, y; derivative =1) # the first derivative of the interpolantd2 =convolution_interpolation(x, y; derivative =2) # the second derivatived1(1.0), d2(1.0) # cos(1) and −sin(1), approximately
(0.5403022965038063, -0.8414703517312542)
import numpy as npfrom convinterp import convolution_interpolationx = np.linspace(0.0, 2* np.pi, 40) # 40 uniformly spaced samplesy = np.sin(x) # the values at the samplesd1 = convolution_interpolation(x, y, derivative=1) # the first derivative of the interpolantd2 = convolution_interpolation(x, y, derivative=2) # the second derivatived1(1.0), d2(1.0) # cos(1) and −sin(1), approximately
f′(1)
f″(1)
interpolant
0.540302296504
-0.841470351731
exact
0.540302305868
-0.841470984808
Which orders a kernel provides
Each kernel supports derivatives up to a fixed order (the table on the interpolation page): one for :a3 to :a7, three for :b5, five for :b7, six for :b9 and seven for :b11 and :b13. Asking for more is an error. The limit is set by accuracy, not by the polynomials: a kernel of degree p has non-trivial derivatives up to order p, but each order multiplies the rounding error by 1/h, and beyond the listed order that amplification exceeds what the derivative is worth in double precision.
Each order costs one order of convergence
Differentiating the interpolant costs about one order of convergence per derivative order: a 7th-order kernel gives a 6th-order first derivative and a 5th-order second derivative. Runge’s function again, with :b7, and the largest error of the interpolant and its first three derivatives against the spacing:
The convergence study
runge(x) =1/ (1+25x^2) # Runge's functiondrunge = (x ->-50x / (1+25x^2)^2, # its first derivative x -> (3750x^2-50) / (1+25x^2)^3, # its second derivative x -> (15000x -375000x^3) / (1+25x^2)^4) # its third derivativexr =range(-1, 1, length =2001) # fine points for measuring the errorns = [41, 81, 161, 321, 641, 1281] # the numbers of sampleshs =2./ (ns .-1) # the corresponding spacingsorders =0:3# the interpolant and three derivativesexact = (runge, drunge...) # the function and its derivativeserrors = [begin xn =range(-1, 1, length = n) itp_m =convolution_interpolation(xn, runge.(xn); kernel =:b7, derivative = m)maximum(abs.(itp_m.(xr) .- exact[m+1].(xr)))end for n in ns, m in orders]plot(xscale =:log10, yscale =:log10, xlabel ="spacing h", ylabel ="largest error", legend =:bottomleft, xflip =true)for m in ordersplot!(hs, errors[:, m+1], marker =:circle, label ="derivative $m")end
derivative 0
derivative 1
derivative 2
derivative 3
observed convergence order
7.62
6.04
4.99
3.94
Derivatives in more dimensions
In more dimensions derivative takes one order per axis. A single number applies to every axis, which gives the mixed partial derivative; a tuple gives each axis its own order, with zero for the axes that are only interpolated:
The kernel can also be chosen per axis, kernel = (:b9, :b5), when one axis needs higher derivatives than another and the wider stencil is only worth paying for there.
Nonuniform grids
Derivatives work on nonuniform grids too, in the Julia package, with the nonuniform kernels’ own differentiated weights; the nonuniform page has the details.