Back to November Calendar            ←Previous Entry                             Next Entry→

November 16, 2005

Spline Representations VIII

 

 

Here’s a run-through of the c++ code I finagled for fitting cubic splines.  First, here’s the main routine which sets up the 9 control points and shows their coordinates on the console with showvec calls.  The first call to spline with ic = 1 computes the coefficients c in  while subsequent calls (in the for loop)  with ic = 0 compute actual spline y-values. 

 

Note:

  • Variable y is a function of x here, instead of x a function of t, as previously formulated.  We may want to go back to parameterizing x, y and z in terms of t, but, for this instance of code, we’ve got y = f(x).
  • Lower case x and y contain control point data while X, Y contain both control point data and spline data.
  • I commented out the graphmm call to the MatLab m-file because, well, this programs runs independent of MatLab.  It would be fairly simple to plot the curve with OpenGL.

:

 

// do the spline thing

 

#include <vector>

#include <iostream>

#include <cmath>

 

using namespace std;

 

void showvec(char *, vector<double>, int);

double spline(double, vector<double> &, vector<double> &, vector<double> &, int, int);

double tridec(vector<vector<double> > &, vector<double> &, int, vector<double> &);

double      epsilon(void);

int MIN(int, int);

 

int  main (void) {

            /* This is hardwired to fit the function 1/(1+x^2)

            to samples corresponding to x = -4, -3, -2, ... , 4.

             n = number of sample to fit polynomial on

             m = number of samples in the finer resolution of fitting polynomial

             i and j are just counters--can't use 'em in MatLab

             d = the distance from 0 of the extreme x values

             a = arbitrary x value */

 

   int  n = 9, m = 10*n, i, j;

   double   d = 4, a;        

   /* x and y are intial samples.  c are linear term coefficients X and Y are

      2x90 vectors containing all info about coordinates to be plotted    

   create x, y and c zero-vectors of dimension n */

   vector<double>  x(n,0.), y(n,0.), c(n,0.);

   vector<vector<double> > X, Y;

   vector<double> dummy(m,0.);

   X.push_back(dummy); X.push_back(dummy);

   Y.push_back(dummy); Y.push_back(dummy);

 

   cout << "\nCubic Spline Fit\n";

   for (i = 0; i < n; i++)    {

      a = 1-d + 2*d*(i-1)/(n-1);

      x[i] = a;

      y[i] = 1/(1 + a*a);

   }

   cout << "\n\nInput/output control points coordinates are:";

   showvec ("x",x,n);

   showvec ("y",y,n);

 

   spline (0,x,y,c,n,1);

   for (i = 0; i < m; i++)    {

      a = x[0] + (i)*(x[n-1] - x[0])/(m-1);

        X[0][i] = a; 

        Y[0][i] = spline (a,x,y,c,n,0);

      j = MIN (i,n-1);

      X[1][i] = x[j];

      Y[1][i] = y[j];

   }

   //graphmm (X,Y,2,m,"Cubic Spline Fit","x","y","fig442",1);

   char ch; cin >> ch;

   return 0;

}

 

Then the spline function, changed from the previous incarnation to be consistent with STL vectors instead of NLib/MatLab vectors, is shown below.  The main difference is that the index of the first element of a vector is now 0, instead of 1.  The constructors are also quite different in syntax and I changed the vector variables to pass-by-reference. 

 

Also, there is code to print out the coefficient parameters for the fitting cubics which we'll look at again in a moment.

 

/* -------------------------------------------------------------------- */

double spline(double a, vector<double> &x, vector<double> &y, vector<double> &c, int n, int ic)

{

/* -------------------------------------------------------------------- */

 

/* Description: Evaluate a cubic spline function at x.

 

   On entry:    a  = evaluation point

                x  = n by 1 vector of strictly increasing independent

                     variables

                y  = n by 1 vector of dependent variables

 

                c  = n by 1 vector containing the spline coefficients,

                n  = number of sample points (n >= 3)

                ic = initial condition code defined as follows:

 

                    0  =>  use previously computed c

                    1  =>  compute vector c

 

   On exit:    spline = natural cubic spline function evaluated at a

 

   Notes:       1. spline is initialized using ic <> 0 and then

                   subsequent calls use ic = 1.

                2. spline uses the function tridec in linear.c to

                   compute the cubic spline coefficients. */  

           

   int k = 0, i;

   double   s, b0, b1, p;

   vector<double> h, w, v;

   vector<vector<double> > T;

 

   if (ic)

   {

      /* Compute spline coefficients */

      h.assign( n-1, 0. );

      w.assign( n-1, 0. );

      v.assign( n, 0. );

       

      T.push_back(v);

      T.push_back(v);

      T.push_back(v);

 

      /* Initialize right-hand side v */

      for (i = 0; i < n-1; i++)     {

         h[i] = x[i+1] - x[i];

         if (h[i] > 0)

            w[i] = (y[i+1] - y[i])/h[i];

         else   {

            cout <<  "\nThe independent variables in spline must be";

            cout <<  "\nstrictly increasing.\n";

            return 0;

         }

      }

      v[0] = 0.;

      v[n-1] = 0.;

      for (i = 1; i < n-1; i++)

         v[i] = 6*(w[i] - w[i-1]);

     

      /* Solve equations */

      for (i = 0; i < n-1; i++)

      {

             if (i > 0)

         {

                T[0][i] = h[i];

                T[1][i] = 2*(h[i-1] + h[i]);

         }

         if (i < n-2)

            T[2][i] = h[i];

      }

      T[1][0] = T[1][n-1] = 1;

 

      /*  TRIDEC !!!!!!!!!!!!!!!!!!!!!!  */

      tridec (T,v,n,c);  

      showvec("c",c,n);

      return 0.;

   }

 

   /* Evaluate spline function at a */

   if (ic == 0) {

      while ((a > x[k+1]) && (k < n-1)) k++; /* go to segment k */

      p = x[k+1] - x[k];                 

      b0 = (6*y[k+1] - p*p*c[k+1])/(6*p);  // coefficients of linear terms

      b1 = (6*y[k]   - p*p*c[k])/(6*p);

      s = b0*(a - x[k]) + b1*(x[k+1] - a);

      s += (double) (pow(a-x[k],3)*c[k+1] + pow(x[k+1]-a,3)*c[k])/(6*p);

      cout << "\nb0 = " << b0 << "\tb1 = " << b1

           << "\tc[" << k+1 << "] = " << c[k+1]

           << "\tc[" << k << "] = " << c[k];

      return s;

   }

}

 

 

The call to tridec is used to solve the big matrix equation for the coefficients cTridec is optimized for solving a tridiagonal matrix system.

 

/* ------------------------------------------------------------------- */

double tridec (vector<vector<double> > &T, vector<double> &b, int n, vector<double> &x)

{

/* ------------------------------------------------------------------- */

 

/* Descripton: Solve the tridiagonal linear algebraic system Ax = b

               using the LU decomposition method.

 

   On entry:   T = 3 by n matrix containing the nonzero entries of A:

 

                   row                  contents

                   ---------------------------------------------------

                    0     A[0][1], A[1][2], ... , A[n-2][n-1],   ---   

                    1     A[0][0], A[1][1], ... , A[n-2][n-2], A[n-1][n-1]   

                    2     A[1][0], A[2][1], ... , A[n-1][n-2],   ---   

                   ---------------------------------------------------

 

               b = n by 1 right-hand side vector

               n = number of unknowns (n >= 3)

 

   On exit:    x = n by 1 solution vector (cubic coefficients for splines)

 

   Returned:   The determinant of A.  If it is nonzero, then

               x has been successfully computed. */

 

   int k;  

   double   eps = epsilon()/100, delta = 1;

   vector<double> y;

   vector<vector<double> >  Q;

 

   /* Initilialize */

 

   y.assign( n, 0. );

   Q.push_back(y);

   Q.push_back(y);

   Q.push_back(y);

  

   /* Factor T into L and U */

 

   Q[2].assign( T[2].begin(), T[2].end() );

   Q[1][0] = T[1][0];

 

   for (k = 0; k < n-1; k++) {

      delta *= Q[1][k];

      if (fabs(Q[1][k]) < eps)

         return 0;

      else {

         Q[0][k] = T[0][k]/Q[1][k];

         Q[1][k+1] = T[1][k+1] - T[2][k]*Q[0][k];

      }

   }

   delta *= Q[1][n];

  

   /* Perform forward substitution to find y */

 

   for (k = 1; k < n; k++)

      y[k] = (b[k] - Q[2][k-1]*y[k-1])/Q[1][k];

   y[0] = b[0]/Q[1][0];

   showvec("tridec y",y,n); //cin >> ch;

   

   /* Perform back substitution to find x */

 

   x[n-1] = y[n-1];

   for (k = n-2; k >= 0; k--) {

      x[k] = y[k] - Q[0][k]*x[k+1];

   }

   showvec("x",x,n);

   return delta;

}

 

 

Assuming that tridec has returned a non-zero determinant, the vector x (named c from the calling function) will contain the cubic coefficients for the splines.  The calls to spline in the for-loop of the main function then compute the 90 y-coordinates of the splines.

 

There are a couple of other little functions involved, which may be of interest: MIN, epsilon and showvec.

 

int MIN(int a, int b) {

      return ((a < b) ? a : b);

}

 

/* ------------------------------------------------------------------- */

double epsilon (void) {

/* ------------------------------------------------------------------- */

 

/* Description: Compute the machine epsilon for type float.

 

   Returns:  The smallest number e such that 1 + e > 1. */

 

   double e = 1, x;

   do {

      e /= 2;

      x = 1 + e;

   }

   while (x > 1);

   return 2*e;

}

 

/* -------------------------------------------------------------- */

void showvec(char * name, vector<double> v, int n) {

      cout << endl << name << " = [";

      for(int i = 0; i < n-1; ++i)

            cout << v[i] << ", ";

      cout << v[n-1] << "]" << endl;

}

 

Here’s the output of the program:

 

Cubic Spline Fit

 

 

Input/output control points coordinates are:

x = [-4, -3, -2, -1, 0, 1, 2, 3, 4]

 

y = [0.0588235, 0.1, 0.2, 0.5, 1, 0.5, 0.2, 0.1, 0.0588235]

 

tridec y = [0, 0.0882353, 0.296471, 0.242017, -1.6725, 0.769683, 0.115303, 0.063

675, 0]

 

x = [0, 0.063675, 0.0982414, 0.74336, -1.87168, 0.74336, 0.0982414, 0.063675, 0]

 

 

c = [0, 0.063675, 0.0982414, 0.74336, -1.87168, 0.74336, 0.0982414, 0.063675, 0]

 

 

b0 = 0.0893875  b1 = 0.0588235  c[1] = 0.063675   c[0] = 0

b0 = 0.183626   b1 = 0.0893875  c[2] = 0.0982414  c[1] = 0.063675

b0 = 0.376107   b1 = 0.183626   c[3] = 0.74336    c[2] = 0.0982414

b0 = 1.31195    b1 = 0.376107   c[4] = -1.87168   c[3] = 0.74336

b0 = 0.376107   b1 = 1.31195    c[5] = 0.74336    c[4] = -1.87168

b0 = 0.183626   b1 = 0.376107   c[6] = 0.0982414  c[5] = 0.74336

b0 = 0.0893875  b1 = 0.183626   c[7] = 0.063675   c[6] = 0.0982414

b0 = 0.0588235  b1 = 0.0893875  c[8] = 0          c[7] = 0.063675

 

It was a bit tedious, but I then entered these into a graphing program as

Y(x) = 0.0893875(x+4)+0.0588235(-3-x)+(0.063675(x+4)^3)/6

Y(x) = 0.183626(x+3)+0.0893875(-2-x)+ (0.0982414(x+3)^3+0.063675(-2-x)^3)/6

Y(x) = 0.376107(x+2)+0.183626(-1-x)+(0.74336(x+2)^3+.0982414(-1-x)^3)/6

Y(x) = 1.31195(x+1)+0.376107(-x)+(-1.87168(x+1)^3+.74336(-x)^3)/6

Y(x) = 1.31195(-x+1)+0.376107(x)+(-1.87168(-x+1)^3+.74336(x)^3)/6

Y(x) = 0.376107(-x+2)+0.183626(-1+x)+(0.74336(-x+2)^3+.0982414(-1+x)^3)/6

Y(x) = 0.183626(-x+3)+0.0893875(-2+x)+ (0.063675(-2+x)^3+.0982414(-x+3)^3)/6

 

Or, in pretty type, and rounding coefficients to 3-digits:

 

 

When you graph these without restricting to the spline domains, you get a graph like this:

Now to compute the sum of the spline curvatures

 

 

Using MatLab, I start by making an m-file for the cubic spline function in terms of the horizontal coordinates array (named u, here) and the linear and cubic coefficients array (named c.)

 

function y = spline02(a,c,k,u,x)

y = a(k+1).*(x-u(k))+a(k).*(u(k+1)-x)+(c(k+1).*(x-u(k)).^3+c(k).*(u(k+1)-x).^3)./6;

 

To be concise:

 

a = linear coefficients array

c = cubic coefficients array

k = index

u = array of control point inputs

x = domain variable

y =  range (output) variable

 

In the command window then you set everything up this way:

 

>> a = [0.0588235 0.0893875 0.183626 0.376107  1.31195]

a =

    0.0588    0.0894    0.1836    0.3761    1.3119

>> c=[0 0.063675  0.0982414 0.74336 -1.87168]

c =

         0    0.0637    0.0982    0.7434   -1.8717

>> u = -4:1:4

u =

    -4    -3    -2    -1     0     1     2     3     4

>> x1 = u(1):.05:u(2);

>> x2 = u(2):.05:u(3);

>> x3 = u(3):.05:u(4);

>> x4 = u(4):.05:u(5);

>> y1 = spline02(a,c,1,u,x1);

>> y2 = spline02(a,c,2,u,x2);

>> y3 = spline02(a,c,3,u,x3);

>> y4 = spline02(a,c,4,u,x4);

>> plot(x1,y1,x2,y2,x3,y3,x4,y4,'LineWidth',2)

 

To produce this graph:

Note that we produced these splines before by NLib’s spline example, but we didn’t know the actual coefficients. 

 

Now that we have coefficient arrays, let’s see if we can develop a nice function for the total curvature

           

Substituting , this becomes

                                             

The indices are off by 1, as seems to be plaguing my work with MatLab…oh well. In MatLab, we’d maybe write a for loop, like so:

 

>> splineCurvTot = 0;

>> for k = 1:4

splineCurvTot = splineCurvTot + 2*(c(k)*c(k)+c(k)*c(k+1)+c(k+1)*c(k+1))/3

end

splineCurvTot =

    0.0027

splineCurvTot =

    0.0160

splineCurvTot =

    0.4395

splineCurvTot =

    2.2158

 

The factor of 2 doubles the curvature for the left side of the symmetric curve.  Provided all these  computations are good, and I think they are, the curvature of the spline is about 1% of the curvature of the single interpolating polynomial. 

 

I haven’t computed the more complicated formula measuring (perhaps more precisely) the curvature.  That’s a good exercise!  Oh, ok, I’ll do it.  Here’s my attempt, done all in the command window

 

>> a = [0.0588235 0.0893875 0.183626 0.376107  1.31195];

>> c = [0         0.063675  0.0982414 0.74336 -1.87168];

>> u = -4:1:4;

>> x1 = u(1):.05:u(2);

>> x2 = u(2):.05:u(3);

>> x3 = u(3):.05:u(4);

>> x4 = u(4):.05:u(5);

>> y1 = spline02(a,c,1,u,x1);

>> y2 = spline02(a,c,2,u,x2);

>> y3 = spline02(a,c,3,u,x3);

>> y4 = spline02(a,c,4,u,x4);

>> dydx1 = diff(y1)./diff(x1);

>> dydx2 = diff(y2)./diff(x2);

>> dydx3 = diff(y3)./diff(x3);

>> dydx4 = diff(y4)./diff(x4);

>> for k=1:20 x1mids(k) = (x1(k)+x1(k+1))/2; end

>> dy2dx21 = diff(dydx1)./diff(x1mids);

>> for k = 1:19 dydx1mids(k) = (dydx1(k)+dydx1(k+1))/2; end

>> for k=1:20 x2mids(k) = (x2(k)+x2(k+1))/2; end

>> for k=1:20 x3mids(k) = (x3(k)+x3(k+1))/2; end

>> for k=1:20 x4mids(k) = (x4(k)+x4(k+1))/2; end

>> for k = 1:19 dydx2mids(k) = (dydx2(k)+dydx2(k+1))/2; end

>> for k = 1:19 dydx3mids(k) = (dydx3(k)+dydx3(k+1))/2; end

>> for k = 1:19 dydx4mids(k) = (dydx4(k)+dydx4(k+1))/2; end

>> dy2dx22 = diff(dydx2)./diff(x2mids);

>> dy2dx23 = diff(dydx3)./diff(x3mids);

>> dy2dx24 = diff(dydx4)./diff(x4mids);

>> int1 = abs(dy2dx21)./(1+dydx1mids.*dydx1mids).^1.5;

>> int2 = abs(dy2dx22)./(1+dydx2mids.*dydx2mids).^1.5;

>> int3 = abs(dy2dx23)./(1+dydx3mids.*dydx3mids).^1.5;

>> int4 = abs(dy2dx24)./(1+dydx4mids.*dydx4mids).^1.5;

>> curve(1) = .05*sum(int1)+.025*(int1(1)+int1(19));

>> curve(2) = .05*sum(int2)+.025*(int2(1)+int2(19));

>> curve(3) = .05*sum(int3)+.025*(int3(1)+int3(19));

>> curve(4) = .05*sum(int4)+.025*(int4(1)+int4(19));

>> 2*sum(curve)

ans =

    2.1568

 

This is about  the curvature found using the same measure on the single interpolating polynomial: 8.54760869196934, as found yesterday.

 

http://www.utexas.edu/its/rc/tutorials/matlab/

http://dept.seattlecolleges.com/southengineering/introductiontomatlab.pdf

enamel 

http://www.applet-magic.com/gaussbf.htm

http://www.cs.berkeley.edu/~sequin/CS184/GuestLecture_05/SplineLect1.html