Main Content

How to Construct Splines

This example shows how to construct splines in various ways using the spline functions in Curve Fitting Toolbox™.

Interpolation

You can construct a cubic spline interpolant that matches the cosine function at the following sites x, using the csapi command.

x = 2*pi*[0 1 .1:.2:.9];
y = cos(x);
cs = csapi(x,y);

You can then view the interpolating spline by using fnplt.

fnplt(cs,2);
axis([-1 7 -1.2 1.2])
hold on
plot(x,y,'o')
hold off

Checking the Interpolant

The cosine function is 2*pi-periodic. How well does our cubic spline interpolant do in that regard? One way to check is to compute the difference in the first derivative at the two endpoints.

diff( fnval( fnder(cs), [0 2*pi] ) )
ans = -0.1375

To enforce periodicity, use csape instead of csapi.

csp = csape( x, y, 'periodic' );
hold on
fnplt(csp,'g')
hold off

Now the check gives

diff( fnval( fnder(csp), [0 2*pi] ) )
ans = -2.2806e-17

Even the second derivative now matches at the endpoints.

diff( fnval( fnder(csp, 2), [0 2*pi] ) )
ans = -2.2204e-16

The piecewise linear interpolant to the same data is available via spapi. Here we add it to the previous plot, in red.

pl = spapi(2, x, y);
hold on
fnplt(pl, 'r', 2)
hold off

Smoothing

If the data are noisy, you usually want to approximate rather than interpolate. For example, with these data

x = linspace(0,2*pi,51);
noisy_y = cos(x) + .2*(rand(size(x))-.5);
plot(x,noisy_y,'x')
axis([-1 7 -1.2 1.2])

interpolation would give the wiggly interpolant shown below in blue.

hold on
fnplt( csapi(x, noisy_y) )
hold off

In contrast, smoothing with a proper tolerance

tol = (.05)^2*(2*pi)
tol = 0.0157

gives a smoothed approximation, shown below in red.

hold on
fnplt( spaps(x, noisy_y,  tol), 'r', 2 )
hold off

The approximation is much worse near the ends of the interval, and is far from periodic. To enforce periodicity, approximate to periodically extended data, then restrict the approximation to the original interval.

noisy_y([1 end]) = mean( noisy_y([1 end]) );
lx = length(x);
lx2 = round(lx/2);
range = [lx2:lx 2:lx 2:lx2];
sps = spaps([x(lx2:lx)-2*pi x(2:lx) x(2:lx2)+2*pi],noisy_y(range),2*tol);

This gives the more nearly periodic approximation, shown in black.

hold on
fnplt(sps, [0 2*pi], 'k', 2)
hold off

Least-Squares Approximation

Alternatively, you could use least-squares approximation to the noisy data by a spline with few degrees of freedom.

For example, you might try a cubic spline with just four pieces.

spl2 = spap2(4, 4, x, noisy_y);
fnplt(spl2,'b',2);
axis([-1 7 -1.2 1.2])
hold on
plot(x,noisy_y,'x')
hold off

Knot Selection

When using spapi or spap2, you usually have to specify a particular spline space. This is done by specifying a knot sequence and an order, and this may be a bit of a problem. However, when doing spline interpolation to x,y data using a spline of order k, you can use the function optknt to supply a good knot sequence, as in the following example.

k = 5;   % order 5, i.e., we are working with quartic splines
x = 2*pi*sort([0 1 rand(1,10)]);
y = cos(x);
sp = spapi( optknt(x,k), x, y );
fnplt(sp,2,'g');
hold on
plot(x,y,'o')
hold off
axis([-1 7 -1.1 1.1])

When doing least-squares approximation, you can use the current approximation to determine a possibly better knot selection with the aid of newknt. For example, the following approximation to the exponential function isn't all that good, as can be seen from its error, plotted in red.

x = linspace(0,10,101);
y = exp(x);
sp0 = spap2( augknt(0:2:10,4), 4, x, y );
plot(x,y-fnval(sp0,x),'r','LineWidth',2)

However, you can use that initial approximation to create another one with the same number of knots, but which are better distributed. Its error is plotted in black.

sp1 = spap2( newknt(sp0), 4, x, y );
hold on
plot(x,y-fnval(sp1,x),'k','LineWidth',2)
hold off

Gridded Data

All the spline interpolation and approximation commands in the Curve Fitting Toolbox can also handle gridded data, in any number of variables.

For example, here is a bicubic spline interpolant to the Mexican Hat function.

x =.0001+(-4:.2:4);
y = -3:.2:3;
[yy,xx] = meshgrid(y,x);
r = pi*sqrt(xx.^2+yy.^2);
z = sin(r)./r;
bcs = csapi({x,y}, z);
fnplt(bcs)
axis([-5 5 -5 5 -.5 1])

Here is the least-squares approximation to noisy values of that same function on the same grid.

knotsx = augknt(linspace(x(1), x(end), 21), 4);
knotsy = augknt(linspace(y(1), y(end), 15), 4);
bsp2 =  spap2({knotsx,knotsy},[4 4], {x,y},z+.02*(rand(size(z))-.5));
fnplt(bsp2)
axis([-5 5 -5 5 -.5 1])

Curves

Gridded data can be handled easily because Curve Fitting Toolbox can deal with vector-valued splines. This also makes it easy to work with parametric curves.

Here, for example, is an approximation to infinity, obtained by putting a cubic spline curve through the points marked in the following figure.

t = 0:8;
xy = [0 0;1 1; 1.7 0;1 -1;0 0; -1 1; -1.7 0; -1 -1; 0 0].';
infty = csape(t, xy, 'periodic');
fnplt(infty, 2)
axis([-2 2 -1.1 1.1])
hold on
plot(xy(1,:),xy(2,:),'o')
hold off

Here is the same curve, but with motion in a third dimension.

roller = csape( t , [ xy ;0 1/2 1 1/2 0 1/2 1 1/2 0], 'periodic');
fnplt( roller , 2, [0 4],'b' )
hold on
fnplt( roller, 2, [4 8], 'r')
plot3(0,0,0,'o')
hold off

The two halves of the curve are plotted in different colors and the origin is marked, as an aid to visualizing this two-winged space curve.

Surfaces

Bivariate tensor-product splines with values in R^3 give surfaces. For example, here is a good approximation to a torus.

x = 0:4;
y = -2:2;
R = 4;
r = 2;
v = zeros(3,5,5);
v(3,:,:) = [0 (R-r)/2 0 (r-R)/2 0].'*[1 1 1 1 1];
v(2,:,:) = [R (r+R)/2 r (r+R)/2 R].'*[0 1 0 -1 0];
v(1,:,:) = [R (r+R)/2 r (r+R)/2 R].'*[1 0 -1 0 1];
dough0 = csape({x,y},v,'periodic');
fnplt(dough0)
axis equal, axis off

Here is a crown of normals to that surface.

nx = 43;
xy = [ones(1,nx); linspace(2,-2,nx)];
points = fnval(dough0,xy)';
ders = fnval(fndir(dough0,eye(2)),xy);
normals = cross(ders(4:6,:),ders(1:3,:));
normals = (normals./repmat(sqrt(sum(normals.*normals)),3,1))';
pn = [points;points+normals];
hold on
for j=1:nx
   plot3(pn([j,j+nx],1),pn([j,j+nx],2),pn([j,j+nx],3))
end
hold off

Finally, here is its projection onto the (x,y)-plane.

fnplt(fncmb(dough0, [1 0 0; 0 1 0]))
axis([-5.25 5.25 -4.14 4.14]), axis off

Scattered Data

It is also possible to interpolate to values given at ungridded data sites in the plane. Consider, for example, the task of mapping the unit square smoothly to the unit disk. We construct the data values, marked as circles, and the corresponding data sites, marked as x's. Each data site is connected to its associated value by an arrow.

n = 64;
t = linspace(0,2*pi,n+1); t(end) = [];
values = [cos(t); sin(t)];
plot(values(1,:),values(2,:),'or')
axis equal, axis off

sites = values./repmat(max(abs(values)),2,1);
hold on
plot(sites(1,:),sites(2,:),'xk')
quiver(sites(1,:),sites(2,:), ...
       values(1,:)-sites(1,:), values(2,:)-sites(2,:))
hold off

Then use tpaps to construct a bivariate interpolating vector-valued thin-plate spline.

st = tpaps(sites, values, 1);

The spline does indeed map the unit square smoothly (approximately) to the unit disk, as its plot via fnplt indicates. The plot shows the image of a uniformly-spaced square grid under the spline map in st.

hold on
fnplt(st)
hold off