scijit.interpolate.make_interp_spline

scijit.interpolate.make_interp_spline(x, y, k=3, t=None, bc_type=None, axis=0, check_finite=True)

Interpolating B-spline through (x, y).

Parameters:
x1-D float64 ndarray, length n

Abscissae, strictly increasing. A non-increasing pair raises ValueError. Must satisfy n >= k + 1.

yfloat64 ndarray of rank 1, 2 or 3

Values to interpolate. Its length along axis must be n.

kint, optional

Spline degree, >= 0. Default 3. k = 0 gives the piecewise-constant spline and takes no boundary condition.

t1-D float64 ndarray, optional

Knot vector, len(t) == n + k + 1 when no derivative conditions accompany it, and longer by one per condition when they do. Default None, which builds the knot set bc_type implies. It must be non-decreasing and cover [x[0], x[-1]]; otherwise ValueError. bc_type='periodic' builds its own knots and refuses this with NotImplementedError. k = 0 refuses it with ValueError.

bc_typestr or pair of lists, optional

Boundary condition. Default None, which selects 'not-a-knot'. The strings are 'not-a-knot', which the default selects and which works for any k >= 1; 'natural', S'' = 0 at both ends; 'clamped', S' = 0 at both ends; and 'periodic', which makes the spline repeat with period x[-1] - x[0] and requires y[0] == y[-1].

Otherwise a pair of LISTS of (order, value) pairs, one list per end, fixing S^(order) to value there: ([(1, 0.0)], [(1, 0.0)]) is 'clamped' and ([(2, 0.0)], [(2, 0.0)]) is 'natural'. This shape is NOT CubicSpline’s, which takes a pair of PAIRS; each refuses the other’s shape.

The conditions must use up exactly the k - 1 coefficients the augmented knot vector leaves free, so a cubic takes one per end and a quintic two. An unrecognised string raises ValueError, and a shape that is neither is refused while compiling.

axisint, optional

Which axis of y the interpolation runs along, default 0. Negative values count from the end. Ignored for a 1-D y. ev(xs) returns the shape of y with this axis replaced by len(xs); a scalar query returns it with this axis removed.

check_finitebool, optional

Whether to refuse a non-finite x or y, default True. The check is worth having on: every comparison against NaN is False, so a NaN x passes the strictly-increasing loop and reaches the solve. With it off a NaN reaches np.linalg.solve, which raises LinAlgError of its own.

Returns:
splBSpline

The interpolating spline. extrapolate=True, except for bc_type='periodic', where a query is folded into [x[0], x[-1]] first.

See also

scipy.interpolate.make_interp_spline

The scipy routine this mirrors.

Notes

Deviations from scipy. A derivative value is one scalar per end and applies to every series of an N-D y, where scipy takes an array of the trailing shape. y is capped at rank 3 and is float64. A y of size zero raises here, where scipy returns a spline with zero-size coefficients. The returned spline goes through the BSpline constructor and is validated a second time, where scipy builds it with construct_fast and skips that; a knot vector this function produced that the constructor rejects therefore raises here and does not there. scipy has no weights argument either, so there is none to match.

k = 0 and k = 1 resolve before the periodic branch, in scipy and here, so bc_type='periodic' at those degrees gives the piecewise-constant and piecewise-linear splines with ordinary extrapolation. The ends are still required to match.

Unlike a jitclass constructor, this is a plain @njit FUNCTION, so its defaults work in both worlds – make_interp_spline(x, y) is verified to compile and run inside @njit – and it is where the bc_type string resolves.

Accuracy vs scipy.interpolate.make_interp_spline on 14 random nodes, evaluated at 101 points: not-a-knot gives exactly 0.0 in values, coefficients and knots for k = 2, 3, 4, 5, and 1.1e-16 in values and coefficients for k = 1 (knots exact). 'natural' and 'clamped' with k=3 are exactly 0.0 in both values and coefficients.

On 9 nodes at [0.07, 0.3, 0.55, 0.7, 0.94], values, coefficients and knots together: an explicit t 0.000e+00; the per-end (order, value) pairs 0.000e+00 at k=3 and 3.331e-16 with two conditions per end at k=5; 'periodic' 0.000e+00 at k=0 and k=1, and 2.220e-16, 3.331e-16, 3.331e-16 and 8.882e-16 at k = 2, 3, 4 and 5.

prange-safe: yes – pure numpy plus np.linalg.solve, no state.

Examples

>>> import numpy as np
>>> from numba import njit
>>> from scijit.interpolate import make_interp_spline
>>> x = np.linspace(0.0, 1.0, 11)
>>> y = np.sin(3.0 * x)
>>> spl = make_interp_spline(x, y, 3)     # build once
>>> np.round(spl(np.array([0.25, 0.75])), 6)
array([0.681629, 0.778063])

Inside compiled code, scanning the domain for the curve’s peak:

>>> @njit
... def peak(spl):
...     xs = np.linspace(0.0, 1.0, 201)
...     return np.max(spl(xs))
>>> float(np.round(peak(spl), 6))
0.999979