B-splines

### 17.2.3 B-splines

##### Finding B-splines from control points.

The command bspline finds a B-spline with a given list of control points and, optionally, a list of breakpoints.

- bspline takes one mandatory argument and up to three optional argument(s):
  - cpts, a list of m control points in ℝd which may be given either as vectors of (Cartesian) coordinates, 2D or 3D points (graphic objects), or (complex) affixes of 2D points.
  - Optionally, var, a variable specification which can be one of:
    - x, an identifier (by default x).
    - x=a..b, where a and b are real numbers such that a<b (by default, a=0 and b=1).
    - x=[x0,x1,…,xn], where n=m−p+1 and xi are real numbers for i=0,1,…,n given in strictly ascending order (by default, xk=a+k/n(b−a) for k=0,1,…,n).
  - Optionally, p, a positive integer corresponding to the spline degree (by default, p=3, which produces a cubic B-spline).
  - Optionally, piecewise, the symbol.
- bspline(cpts ⟨,var,p ⟩ ⟨,piecewise ⟩) returns a matrix with n−1 rows and d columns in which the kth row represents a parametric definition of the B-spline with parameter x such that x∈[xk,xk+1⟩, or, if piecewise is given, a parametric representation of the entire spline in which each component is piecewise-defined.
- The points x0,x1,…,xn are called breakpoints. B-spline is a piecewise parametric function with parts joining at these points. If no breakpoints are given, a uniform subdivision of the specified segment [a,b] (by default [0,1]) is used. Note that the relation 2≤ n=m−p+1 must hold, which implies m≥ p+1.
- B-splines are useful for curve-fitting and numerical differentiation of data.

#### Example

To define a sequence of control points, enter:

c:=2i,1-2i,5-i,6-4i,8+2i,5+i:;

Now compute the B-spline defined by the above points and uniform knots, with parameter t∈[0,1]:

bs:=bspline([c],t)

| ⎡ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎣ |
− |
99 t3
2
| +27 t2+9 t
− |
567 t3
4
| + |
243 t2
2
| −36 t+2
63 t3
2
| −54 t2+36 t−3
297 t3
4
| − |
189 t2
2
| +36 t−6
−117 t3+243 t2−162 t+41 | −162 t3+378 t2−279 t+64
| ⎤ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎦

To plot the result with alternating red-blue color for spline pieces and green control points, enter:

polygon(c,open); plotparam(bs[0],t=0..1/3,display=red+line_width_2); plotparam(bs[1],t=1/3..2/3,display=blue+line_width_2); plotparam(bs[2],t=2/3..1,display=red+line_width_2); point(c,display=point_point+point_width_3+green);

Sometimes it is useful to return the result in a piecewise form. Enter:

bs:=bspline([c],t,piecewise)

| ⎡ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎣ |
⎧ ⎪ ⎪ ⎪ ⎪ ⎨ ⎪ ⎪ ⎪ ⎪ ⎩ |
− |
99
2
| t3+27 t2+9 t,
t≥ 0∧ |
1
3
| >t
63
2
| t3−54 t2+36 t−3,
2
3
| >t
−117 t3+243 t2−162 t+41, | 1≥ t
| , |
⎧ ⎪ ⎪ ⎪ ⎪ ⎨ ⎪ ⎪ ⎪ ⎪ ⎩ |
− |
567
4
| t3+ |
243
2
| t2−36 t+2,
t≥ 0∧ |
1
3
| >t
297
4
| t3− |
189
2
| t2+36 t−6,
2
3
| >t
−162 t3+378 t2−279 t+64, | 1≥ t
| ⎤ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎦

The result is a list of components of the spline function. Now, for example, the command line

plotparam(bs,t=0..1)

draws the entire spline.

##### Fitting B-splines to data.

The fitspline command fits B-splines to (multidimensional) time-stamped data in the least-squares sense.

- fitspline takes two mandatory arguments and a sequence of optional arguments:
  - data, a list of time-stamped locations in ℝd given either as [tk,xk1,xk2,…,xkd] or as [tk,[xk1,xk2,…,xkd]] for k=1,2,…,m, where m≥ 2 and d≥ 1.
  - var, which is either a variable or a vector of real numbers.
  - Optionally, opts, a sequence of options each of which may be one of:
    - brkpts, which is either a positive integer n specifying the number of spline pieces (by default, n=⌊√m+1/2⌋), or breakpoint=[b0,b2,…,bn] which specifies the breakpoints b0,…,bn∈ℝ explicitly (and not necessarily in an ascending order). Note that minibi≤ t1<tm≤ maxjbj must hold. If brkpts=n, then n+1 uniformly spaces breakpoints are automatically generated in the segment [t1,tm].
    - degree=p, where p is a positive integer (spline degree, by default p=3).
    - piecewise, the symbol.
- fitspline(data,var ⟨,opts ⟩) returns
  - the list of spline points at elements of var if the latter is a vector,
  - the spline as returned by bspline for the variable var and optionally piecewise, if var is an identifier.
  The resulting spline (of degree p) is computed from a sequence of n+1 breakpoints (either automatically generated or explicitly given) and n+p control points obtained by minimizing squared distances between spline points at values tk and data points (xk1,xk2,…,xkd) for k=1,2,…,m.
- The number m of data samples must be larger than the number n+p of control points. Furthermore, it is recommended that n+p≤ m/2 is satisfied when uniformly spaced breakpoints are used; otherwise the spline could easily overfit on the noise resulting in large oscillations (this is essentially a Nyquist-frequency argument). If the above inequality does not hold, a warning is printed.
- You should specify the breakpoints explicitly in cases when data has significant changes in curvature; it is a good idea to provide more resp. less breakpoints for intervals in which the curvature is large resp. small.
- Note that fitspline operates exclusively in floating-point arithmetic. If var is a vector, then GSL fitting routines are used if available.

#### Examples

To generate some synthetic data with noise, enter:

x:=linspace(0,15,200):; y:=apply(t->cos(t)*exp(-0.1*t)*(1+randnorm(0,0.1)),x):;

The following command line computes y-values of a B-spline fit:

ys:=fitspline(tran([x,y]),x)]):;

Essentially, ys is the result of “snapping” y to a smooth curve. Now visualize the result by entering:

scatterplot(x,y,color=blue); listplot(tran([x,ys]))

You can use B-spline to trace a sequence of points in time. For example, generate a list of 2D locations by entering:

t:=linspace(0,10,50):; x:=apply(u->(u^2*sin(u)+u-1)/(1+u*sqrt(u))+randnorm(0,0.1),t):; y:=apply(u->(u^2*cos(u)-u+1)/(1+u*sqrt(u))+randnorm(0,0.1),t):;

The underlying smooth path of these “footsteps” can be approximated like this:

t1:=linspace(0,10,500):; listplot(fitspline(tran([t,x,y]),t1,20)),scatterplot(x,y,color=blue)

Such approximation is useful for estimating the total distance traveled or the velocity at a certain point in time, assuming that data represents a discretized trajectory of a moving object, such as e.g. a series of GPS positions would.

By setting the number of spline pieces (which is 20 in the above graph) to a value that is close to 50 (the number of samples), the resulting spline exhibits large oscillations near the beginning and end of the curve, reminiscent of Runge’s phenomenon:

listplot(fitspline(tran([t,x,y]),t1,45)),scatterplot(x,y,color=blue)

Warning: too many control points

Note that using Chebyshev nodes as breakpoints, which would be beneficial in the case of polynomial interpolation, does not make much difference here.