scijit.interpolate.BSpline

scijit.interpolate.BSpline(t, c, k, extrapolate=True, axis=0)

Univariate spline in the B-spline basis.

S(x) = sum_j c[j] * B_{j,k;t}(x), evaluated with the de Boor recursion. This accepts an ARBITRARY knot vector, so hand-built or textbook knots work as well as a FITPACK tck.

Parameters:
t1-D float64 ndarray

Knot vector, non-decreasing and finite. Must hold at least 2*k+2 knots and at least two distinct values in t[k:n+1]; otherwise ValueError. A decreasing pair, a NaN or an inf also raises.

c1-D float64 ndarray

B-spline coefficients. Only the first n = len(t) - k - 1 entries are used and stored, so a FITPACK tck (whose c is padded to len(t)) can be passed straight in. len(c) < n raises ValueError. 1-D real only.

kint

Spline degree, >= 0. Negative raises ValueError.

extrapolatebool, str or None, optional

What a query outside the base interval [t[k], t[n]] gives.

True the first/last polynomial piece, extended False NaN ‘periodic’ the query folded into [t[k], t[n]] first None NaN

Default 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.

axisint, optional

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

Attributes:
t1-D float64 ndarray

The knot vector (a copy).

c1-D float64 ndarray, length n

The coefficients actually used (a copy, trailing entries dropped).

k, nint

Degree and number of coefficients, n = len(t) - k - 1.

extrapolatebool

Whether a query outside the base interval is evaluated at all.

periodicbool

Whether the query is folded into the base interval first.

Methods

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

antiderivative_ev(xs, nu), integral(a, b), get_knots(), get_coeffs()

Returns:
_BSpline

A callable jitclass instance: spl(xs) runs ev and spl(x) runs ev_one. A rank-2 or rank-3 c gives _BSplineND1 or _BSplineND2, which carry the same methods.

See also

scipy.interpolate.BSpline

The scipy routine this mirrors.

Notes

Deviations from scipy: no .derivative() / .antiderivative() returning new objects (use the _ev methods, or splder / splantider for the tck); no .roots(), .design_matrix, .basis_element or .from_power_basis. The resolved mode is split across the extrapolate and periodic attributes, where scipy keeps the caller’s value in one; an unrecognised string raises where scipy extrapolates. An axis naming an axis of c that does not exist raises numpy.exceptions.AxisError. c is float64, where scipy derives the coefficient dtype from c and supports complex128. Evaluation is ev(xs) and derivative_ev(xs, nu), where scipy spells both as spl(xs, nu); a two-argument call raises on arity here.

BSpline is an @njit factory over the jitclass _BSpline, so extrapolate may be omitted from Python AND from inside @njit. Method defaults such as derivative_ev’s nu=1 work in both too.

Measured against scipy 1.18 on a k=3 spline over [0, 1] queried at 1.3 and -0.2: extrapolate=True gives -10.21 and -1.672, extrapolate=False and extrapolate=None NaN, extrapolate='periodic' 1.018 and 1.74. integrate(-1, 2) under 'periodic' gives 3.5625, three times the 1.1875 of one period.

Accuracy vs scipy.interpolate.BSpline on random knots and coefficients, 137 points across the base interval, for every degree k = 0..5: values, first derivative, first antiderivative and integrate(t[k], t[n]) all EXACTLY 0.0. With extrapolate=False the NaN pattern matches scipy’s element for element and the finite values agree exactly.

prange-safe: yes – pure de Boor, no shared state, no library call.

Examples

>>> import numpy as np
>>> from numba import njit
>>> from scijit.interpolate import BSpline
>>> t = np.array([0., 0., 0., 0., 1., 2., 3., 3., 3., 3.])
>>> c = np.array([0., 1., -1., 2., 0., 1.])
>>> spl = BSpline(t, c, 3)     # build once from knots and coefficients
>>> np.round(spl(np.array([0.5, 1.5, 2.5])), 6)
array([0.375, 0.5  , 0.625])

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

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