I was having a discussion about different approaches for quadratic spline interpolation and I introduced an example involving interpolation of points on a modulus function. This led to a deeper exploration of how altering the knot sequence of a B-Spline interpolation can substantially improve the results. I want to record this here.
The question that initially arose was about what methods were available for quadratic spline interpolation that were better than the usual introductory textbook approach of positioning the knots of the piecewise polynomial at the data points and forcing the first derivatives to be continuous at the joins. B-Splines allow the task to be achieved more flexibly, e.g., their localization at `difficult bits’ can be made substantially better sometimes by adjusting the knot sequence or by choosing collocation points according to certain criteria. I thought it might be interesting to illustrate the situation by experimenting with a ‘pathological’ function for quadratic spline interpolation, namely one that does not have continuous first derivatives at one or more of the knots when seeking to apply the introductory textbook approach (this can be expected to cause problems for the introductory textbook approach since it works by enforcing continuous first derivatives at the knots). I therefore used points on a modulus function (including a data point at the origin), and tried performing a quadratic spline interpolation using both the introductory textbook approach and an in-built scipy.interpolate.BSpline() method. I got the following results:

The picture shows the modulus function I used, with five data points one of which is at the origin (where there is a discontinuity in the first derivative). The orange curve is the result of the introductory textbook quadratic spline interpolation procedure, which does indeed perform quite badly with this pathological case. The green curve performs better and is the result of the in-built Python quadratic B-Spline method.
In producing the above picture, a default setting was used for the scipy.interpolate.BSpline() method and the question then arose as to whether the interpolation could be improved by adjusting the underlying B-Spline knot sequence. To experiment with this, I decided to replicate the in-built method using Python code which reveals more of the mathematical structure underlying B-Splines, and then to explore the effects of different knot settings.
B-Splines are a numerically convenient set of piecewise polynomial (pp) functions used as a basis for all other pp splines. Using standard notational conventions, a pp function of order
is defined for
as
if
, where
is a strictly increasing sequence of breakpoints and
is any sequence of polynomials of order
(i.e., of degree
). At each breakpoint other than
and
, the pp function is (arbitrarily) defined for computational purposes as taking the value from the right, i.e.,
for
. The collection of all pp functions of order
with breakpoint sequence
is a linear space of dimension
denoted by
.
In general, it is necessary to impose continuity conditions on pp functions and their derivatives, of the form
for and
, where the notation means the jump of the function across the site
, and
is a set of nonnegative integers with
counting the number of continuity conditions required at
. (Note that there is no need for elements
or
in this list as continuity conditions are only needed to govern how different pieces of the pp function `meet’ at interior breakpoints). For example,
means that both the function and the first derivative are required to be continuous at
, whereas
means that there are no continuity conditions at
. The subset of all
satisfying the continuity conditions is a linear subspace of
denoted by
. The dimension of
is
B-Splines originally emerged from the desire to have a numerically convenient basis for . To formally introduce B-Splines, let
be a nondecreasing sequence of numbers (these are called `knots’ in the context of splines, and can be viewed as an extension of the breakpoint sequence
defined earlier in the sense that
can incorporate the elements of a given
but does not have to be strictly increasing, and in principle it can be finite or infinite as required). Then the
-th normalised B-Spline of order
(i.e., of degree
) using knot sequence
is denoted by
, and its value at a site
is given by
This relation can be used to generate B-Splines by induction, starting from , which is given by
Note that the B-Spline is a piecewise polynomial of order
and has support
, so it is continuous from the right in accordance with the convention for pp functions stated earlier. By putting
into the recurrence relation, we obtain the B-Spline
which is a piecewise polynomial of order
with support
. B-Splines of higher order can be found via the recurrence relation in a convenient way using a tableau similar to the one commonly used to work out divided differences of functions.
A result known as the Curry-Schoenberg Theorem shows that the B-Splines as defined above constitute a basis for under certain conditions. Specifically, the theorem says that the sequence
is a basis for
if:
(i) is a strictly increasing sequence of breakpoints;
(ii) is a set of nonnegative integers with
for all
;
(iii) is a nondecreasing sequence with
;
(iv) for , the number
occurs exactly
times in
;
(v) and
.
These specifications provide the necessary information for generating a knot sequence from a given breakpoint sequence
with the desired amount of `smoothness’ (i.e., number of continuity conditions), and we can then construct a B-Spline basis using the recurrence relation alluded to above. The number of continuity conditions at a breakpoint
is determined by the number of times
appears in
, in the sense that each repetition of
reduces the number of continuity conditions at that breakpoint by one. If
appears
times in
, this corresponds to imposing no continuity conditions at
. If
appears
times, the function is continuous at
, but not its first or higher derivatives. If
appears
times, the function and its first derivative are continuous at
, but not its second and higher derivatives; and so on. Note that a convenient choice of knot sequence is to make the first
knot points equal to
, and the last
knot points equal to
, thus imposing no continuity conditions at
and
.
Using this structure, I managed to replicate what the in-built scipy.interpolate.BSpline() method did above by using a breakpoint sequence
which is of length so the parameter
is
. The corresponding knot sequence is
The order is (corresponding to a degree
for the polynomial pieces), and there are two continuity conditions for each of the interior points in the breakpoint sequence (the function and its first derivative are continuous) so using the standard notation we have
. The dimension of the subspace is thus
so we expect to have five B-Splines in the basis set. In the picture below, I plot on the left the five basis B-Splines corresponding to
and the above knot sequence, and on the right I show the resulting interpolation which is exactly the same as the one produced by the in-built scipy.interpolate.BSpline() method above:

However, this is not the best we can do. The result can be improved substantially simply by adjusting the knot sequence a little. For example, an obvious adjustment would be to put the two interior knots much closer to the origin. I tried this with a new knot sequence
We still have five B-Splines in the basis set, but, as can be seen in the picture on the left below, the middle B-SPline has now become more `peaked’. The resulting interpolation shown on the right-hand side of the picture is now much better than the one produced by default using the scipy.interpolate.BSpline() method above:

So, this is a worked example of B-Splines allowing more flexibility to ‘fiddle with the settings’ in order to achieve a better quadratic spline interpolation.
