scijit.interpolate.CubicSpline

scijit.interpolate.CubicSpline(x, y, axis=0, bc_type='not-a-knot', extrapolate=None)

C2 cubic spline interpolator over piecewise-cubic segments.

The interpolant is twice continuously differentiable, and the boundary condition fixes the two remaining degrees of freedom.

Parameters:
x1-D float64 ndarray, length n >= 2

Breakpoints, strictly increasing. A non-increasing pair raises ValueError.

yfloat64 ndarray of rank 1, 2 or 3

Values at the breakpoints. Its length along axis must be n. Every position on the other axes is an independent series over the same x. Rank 4 and above raise ValueError.

axisint, optional

Which axis of y the interpolation runs along, default 0. Negative values count from the end. Ignored for a 1-D y, which has only one axis. ev(xs) returns the shape of y with this axis replaced by len(xs); ev_one and integral return the shape of y with this axis removed.

bc_typestr or 2-element sequence, optional

Boundary condition. A single string applies the same condition at BOTH ends:

'not-a-knot'   the default
'natural'      second derivative zero at the ends
'clamped'      first derivative zero at the ends
'periodic'     the spline repeats with period ``x[-1] - x[0]``;
               requires ``y[0] == y[-1]``, and evaluation folds
               the query into that period rather than
               extrapolating

Otherwise a 2-element sequence giving the ends separately. Each element is independently one of the first three strings above or an (order, value) pair fixing S^(order) to value at that end, with order 1 or 2. So ((1, 0.0), (1, 0.0)) is 'clamped', ((2, 1.5), (1, -2.0)) is a curvature at the left and a slope at the right, and ('not-a-knot', (1, 3.0)) mixes the two forms. 'periodic' names a condition on the pair rather than on one end and is legal only as the single string; in the 2-element form it raises ValueError.

An unrecognised string raises ValueError; a shape that is neither is refused while compiling. A string may be a runtime value, not only a literal. The default applies inside @njit as well as from Python.

extrapolateNone, bool or str, optional

What a query outside [x[0], x[-1]] gives:

True           the end segment polynomial, continued
False          NaN
'periodic'     the query folded into ``[x[0], x[-1]]`` first
None           the default: ``'periodic'`` when
               ``bc_type='periodic'``, otherwise True

A string other than 'periodic' raises ValueError. 0 and 1 are accepted for False and True. May be a runtime value, not only a literal.

Attributes:
x1-D float64 ndarray

The breakpoints, as float64.

c(4, n-1) float64 ndarray, or (4, n-1, T) for an N-D y

PPoly coefficients: c[k, i] multiplies (x - x[i]) ** (3 - k) on segment [x[i], x[i+1]]. For an N-D y the trailing axes are flattened into one; get_coeffs unflattens them.

nint

Number of breakpoints.

bc_typeint

Which condition built the spline: 0 not-a-knot, 1 natural, 2 clamped, 3 periodic, 4 any other derivative pair.

extrapolateint

The resolved extrapolation mode: 0 NaN outside the data, 1 the end segment continued, 2 the query folded into the base period.

Methods

ev(xs), ev_one(x), __getitem__(xs), derivative_ev(xs, nu),

integral(a, b), get_knots(), get_coeffs()

Returns:
_CubicSpline

A callable jitclass instance: cs(xs) runs ev and cs(x) runs ev_one. A rank-2 or rank-3 y gives _CubicSplineND1 or _CubicSplineND2, which carry the same methods.

See also

scipy.interpolate.CubicSpline

The scipy routine this mirrors.

Notes

Deviations from scipy. extrapolate reads back as the resolved int code rather than the caller’s value, and an unrecognised string raises where scipy extrapolates. 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, where scipy takes any rank and promotes a complex y to complex128; a jitclass field has a fixed rank and a fixed dtype, so each supported rank is a separate class. A rank-2 x raises TypingError where scipy raises ValueError('`x` must be 1-dimensional.'); both refuse and only the class differs. Evaluation is ev(xs) and derivative_ev(xs, nu), where scipy spells both as cs(xs, nu); a two-argument call raises on arity here. There is no .derivative() or .antiderivative() returning a new object, and no solve, roots, extend, construct_fast, from_spline or from_bernstein_basis.

CubicSpline is an @njit factory over the jitclass _CubicSpline, so bc_type and extrapolate may be omitted from Python AND from inside @njit, and the strings resolve there, before the jitclass is constructed. Method defaults such as derivative_ev’s nu=1 work in both too.

Measured against scipy 1.18 on 5 nodes spanning [0, 1.4], queried at 2.0 and -1.0: extrapolate=None gives 13.1204501 and -13.35208741 on both sides, extrapolate=False NaN on both, extrapolate='periodic' 0.80812133 and 0.96731898, and bc_type='periodic' with extrapolate=None 0.83153846 and 1.00846154.

Accuracy vs scipy.interpolate.CubicSpline on 15 random nodes over 211 in-range points, for each of the three bc types: values 4.6e-15 to 5.4e-15, first derivative 7.1e-15 to 1.4e-14, second derivative 2.8e-13 to 4.0e-13, integrate(x[0], x[-1]) 8.9e-16 to 2.2e-15, and PPoly coefficients 2.0e-13 (natural) to 4.7e-11 (not-a-knot) in absolute terms on coefficients whose magnitude reaches 5.0e3, i.e. about 1e-14 relative. On 9 nodes at five points, values, coefficients, first derivative and integral together: the per-end (order, value) pairs 2.220e-16, and 'periodic' 0.000e+00 at n=9 with 2.132e-13 on the coefficients at n=12.

Extrapolating 50 units past each end: 2.3e-10 absolute, 8.9e-16 relative. The n=2 not-a-knot case is exactly 0.0 and the n=3 parabola special case 4.4e-16.

prange-safe: yes – pure numpy, no module state, no callback. Both construction and evaluation may run inside a prange body.

Examples

>>> import numpy as np
>>> from numba import njit
>>> from scijit.interpolate import CubicSpline
>>> x = np.linspace(0.0, 1.0, 9)
>>> y = np.sin(3.0 * x)
>>> cs = CubicSpline(x, y)          # 'not-a-knot', extrapolating
>>> np.round(cs(np.array([0.25, 0.75])), 6)
array([0.681639, 0.778073])

Inside compiled code, the mean of the curve over its domain:

>>> @njit
... def mean(cs):
...     xs = np.linspace(0.0, 1.0, 201)
...     return np.mean(cs(xs))
>>> float(np.round(mean(cs), 6))
0.660382