CARMA C++
spline_template.h
1 /*
2  * This file contains the type-independant implementation of
3  * spline functions. It is included by spline.c with SP_OBJ
4  * set to the typedef name of the spline container datatype,
5  * and SP_TYPE set to the type of spline data (float or double) to
6  * compile for.
7  */
8 
9 /* Define a container to hold spline interpolation state info and data */
10 
11 struct SP_OBJ {
12  SP_TYPE *memory; /* Memory for 4*nmax elements to be shared between the */
13  /* x,y,y2 and wrk arrays */
14  int nmax; /* The allocated dimensions of x,y,y2 and wrk[] */
15  SP_TYPE *x; /* The array of monotonically increasing X-axis values */
16  SP_TYPE *y; /* The array of Y-axis values being interpolated vs x */
17  SP_TYPE *y2; /* The array of spline 2nd derivatives */
18  SP_TYPE *wrk; /* An intermediate array used in decomposition */
19  int npts; /* The number of points currently used in each of the */
20  /* above arrays */
21  int below; /* This is the lower index of the two neighboring points */
22  /* in x[] that bracketted the last x-axis position */
23  /* requested from interpolate_spline(). */
24 };
25 
26 /*
27  * Create an SP_PASTE macro that concatenates two tokens.
28  * A two level macro is used so that if suffix is itself a
29  * macro, it will be macro-expanded before concatenation.
30  */
31 #undef SP_PASTE
32 #undef SP_REAL_PASTE
33 #define SP_REAL_PASTE(prefix,suffix) prefix##suffix
34 #define SP_PASTE(prefix,suffix) SP_REAL_PASTE(prefix,suffix)
35 
36 /*
37  * Create a macro that encloses its arguments in quotes.
38  * A two level macro is used so that the argument is
39  * macro-expanded before being enclosed in quotes.
40  */
41 #undef SP_REAL_STRINGIZE
42 #undef SP_STRINGIZE
43 #define SP_REAL_STRINGIZE(s) #s
44 #define SP_STRINGIZE(s) SP_REAL_STRINGIZE(s)
45 
46 /*
47  * Create a SPLINE_FN(name) macro that appends the spline object type
48  * name to a given function prefix.
49  */
50 #undef SPLINE_FN
51 #define SPLINE_FN(name) SP_PASTE(name, SP_OBJ)
52 
53 static int SPLINE_FN(expand_)(SP_OBJ *sp, int nmax);
54 
55 /*.......................................................................
56  * Create a container for subsequent cubic spline computations.
57  *
58  * Input:
59  * npts int Zero, or the initial number of points to allocate
60  * data and work arrays for. If create_spline() is
61  * called with npts greater than this, the arrays will
62  * be expanded via calls to realloc().
63  * Output:
64  * return Spline * The spline container.
65  */
66 SP_OBJ *SPLINE_FN(new_)(int npts)
67 {
68  SP_OBJ *sp;
69 /*
70  * Allocate the container.
71  */
72  sp = (SP_OBJ *) malloc(sizeof(SP_OBJ));
73  if(sp==NULL) {
74  lprintf(stderr, SP_STRINGIZE(SPLINE_FN(new_)) ": Insufficient memory.\n");
75  return sp;
76  };
77 /*
78  * Before attempting any operation that might fail initialize the
79  * container at least to the point at which it can safely be passed
80  * to del_Spline().
81  */
82  sp->memory = NULL;
83  sp->nmax = 0;
84  sp->npts = 0;
85  sp->x = NULL;
86  sp->y = NULL;
87  sp->y2 = NULL;
88  sp->wrk = NULL;
89  sp->below = 0;
90 /*
91  * Allocate initial arrays?
92  */
93  if(npts > 0 && SPLINE_FN(expand_)(sp, npts))
94  return SPLINE_FN(del_)(sp);
95  return sp;
96 }
97 
98 /*.......................................................................
99  * Delete a Spline container and the implementation parts of its
100  * contents.
101  *
102  * Input:
103  * sp Spline * The pointer to the container to be deleted.
104  * Output:
105  * return Spline * Allways NULL. Use like sp=del_Spline(sp);
106  */
107 SP_OBJ *SPLINE_FN(del_)(SP_OBJ *sp)
108 {
109  if(sp) {
110  if(sp->memory)
111  free(sp->memory);
112  sp->nmax = 0;
113  sp->memory = NULL;
114  sp->npts = 0;
115  sp->x = sp->y = sp->y2 = sp->wrk = NULL;
116  free(sp);
117  };
118  return NULL;
119 }
120 
121 /*.......................................................................
122  * Expand the arrays of a spline container to accomodate more points.
123  *
124  * Input:
125  * sp Spline * The spline container to modify.
126  * nmax int The number of points to allow for.
127  * Output:
128  * return int 0 - OK.
129  * 1 - Error.
130  */
131 static int SPLINE_FN(expand_)(SP_OBJ *sp, int nmax)
132 {
133 /*
134  * Are the current arrays too small?
135  */
136  if(nmax > sp->nmax) {
137 /*
138  * We need to allocate space for 4 arrays of nmax doubles.
139  */
140  size_t needed = 4 * nmax * sizeof(SP_TYPE);
141 /*
142  * Attempt to resize the block of memory that is shared between the
143  * arrays.
144  */
145  SP_TYPE *memory = (SP_TYPE* )(sp->memory ? realloc(sp->memory, needed) : malloc(needed));
146  if(!memory) {
147  lprintf(stderr, SP_STRINGIZE(SPLINE_FN(expand_)) ": Insufficient memory.\n");
148  return 1;
149  };
150  sp->memory = memory;
151  sp->nmax = nmax;
152 /*
153  * Share the new memory between the arrays.
154  */
155  sp->wrk = (sp->y2 = (sp->y = (sp->x = memory) + nmax) + nmax) + nmax;
156  };
157 /*
158  * Any points that were recorded before have now been lost.
159  * We could recover them, but expand_spline() is only called when
160  * evaluating a new spline, or by new_Spline() so this would be pointless.
161  */
162  sp->npts = 0;
163  sp->below = 0;
164  return 0;
165 }
166 
167 /*.......................................................................
168  * Compute the 2nd derivative coefficients of the natural cubic spline
169  * that interpolates a given set of points.
170  *
171  * References:
172  * 1. "Computational Mathematics - an introduction to numerical
173  * approximation" by T.R.F. Nonweiler.
174  * ISBN 0-85312-586-4 (Ellis Horwood Limited) or
175  * ISBN 0-470-27472-7 (Halsted Press - division of John Wiley)
176  * See section 3.4 - Interpolation by splines.
177  * 2. "Numerical Recipes" - First edition ...
178  * See sections on LU decomposition (2.3), Triagonal Systems of
179  * equations (2.6) and Cubic Spline Interpolation (3.3).
180  *
181  * Input:
182  * sp Spline * A spline container returned by new_Spline().
183  * On return this will contain the coefficients
184  * needed by interpolate_spline().
185  * x double * An array of 'npts' monotonically increasing X-coords.
186  * y double * An array of 'npts' Y-coords to be interpolated.
187  * npts int The number of points in each of the above arrays.
188  * Output:
189  * return int 0 - OK.
190  * 1 - Error.
191  */
192 int SPLINE_FN(create_)(SP_OBJ *sp, SP_TYPE *x, SP_TYPE *y, int npts)
193 {
194  SP_TYPE betajj; /* Diagonal term in upper triangle of LU decomposed matrix */
195  SP_TYPE *betaij;/* Pointer into 'sp->wrk[]'. This is used to hold the */
196  /* diagonal vector that is above and to the right of the */
197  /* central diagonal */
198  SP_TYPE *y2; /* Pointer into the second derivative array, sp->y2[]. */
199  SP_TYPE lambda; /* Defined under eqn 3.4.8 in ref 1 */
200  int j;
201 /*
202  * Expand the spline arrays if necessary.
203  */
204  if(SPLINE_FN(expand_)(sp, npts))
205  return 1;
206 /*
207  * Copy the new X and Y axis data into the spline container.
208  */
209  memcpy(sp->x, x, npts * sizeof(SP_TYPE));
210  memcpy(sp->y, y, npts * sizeof(SP_TYPE));
211 /*
212  * In the standard cubic spline equation there are two more degrees of
213  * freedom than simultaneous equations, so we apply the constraint that
214  * the second derivative of the spline curve at the first and last points
215  * be roughly zero. This implies that the gradient of the spline remains
216  * the same on either side of these two points. To acheive this we set the
217  * first and last terms of the Y column vector (right-hand-side of
218  * equation 3.4.8 in ref 1) to zero since these are estimates of the
219  * second derivatives of the original curve.
220  *
221  * We will attempt to solve the tridiagonal matrix eqn 3.4.8 in ref 1, using
222  * the LU decomposition recipe given in section 2.3 in ref 2.
223  * In the following, beta refers to the upper LU decomposed matrix terms
224  * and alpha the lower half as in eqn 2.3.2 (ref 2). a,b,c will refer to
225  * the diagonal vectors of the three non-zero diagonals as in section 2.6
226  * (ref 2).
227  *
228  * Note that since the matrix is tridiagonal only alpha(i,i)=1 and
229  * alpha(i,i-1)=a(i)/beta(i-1) are non zero. Also only beta(i,i) and
230  * beta(i-1,i)=c(i-1) are non zero and beta(i,i) follows a recurrence
231  * relation: beta(i,i) = b(i) - a(i)*c(i-1)/beta(i,i-1) and beta(0,0) = b(0).
232  */
233  betajj = 2.0f; /* beta(0,0)=b(0)= top-left-hand-corner element of matrix */
234  lambda = 0.0f; /* This is the lambda_0 in eqn 3.4.8 and is defined to be */
235  /* zero (see text below matrix eqn) ref 1. */
236 /*
237  * In the following loop we solve for arrays of beta(j-1,j) and of the
238  * lower triangle solution. Both will be stored divided through by
239  * betajj since this is required later in the back-substitution.
240  */
241  betaij = &sp->wrk[1]; /* Array of beta(j-1,j) */
242  sp->y2[0] = 0.0; /* Y(0)/beta(0,0) -> 0/b(0)=0 */
243  y2 = &sp->y2[1];
244  x++; y++;
245  for(j=1; j<npts-1; j++,x++,y++,y2++,betaij++) {
246  SP_TYPE yval; /* Value in result matrix on rhs of eqn 3.4.8 in ref 1. */
247  SP_TYPE xspan; /* Distance between x(i-1) and x(i+1) */
248 /*
249  * beta(j-1,j)=c(j-1) and c(j-1)=(1-lambda(j-1)).
250  */
251  *betaij = (1.0f-lambda)/betajj;
252 /*
253  * Determine lambda(j).
254  */
255  xspan = x[1] - x[-1];
256  lambda = (x[0] - x[-1])/xspan;
257 /*
258  * Use it to determine beta(j,j).
259  */
260  betajj = 2.0f - lambda * *betaij;
261 /*
262  * Now calculate Y(j) (the jth result matrix element in eqn 2.6.1 ref 2).
263  */
264  yval = 6.0f * ((y[0]-y[1])/(x[0]-x[1]) - (y[-1]-y[0])/(x[-1]-x[0]))/xspan;
265 /*
266  * Now use recurrence relation to do latest iteration of the forward
267  * substitution for the solution to the lower triangle.
268  */
269  *y2 = (yval - lambda * y2[-1])/betajj;
270  };
271 /*
272  * Perform the iteration for the last point - setting Y(N) to be
273  * zero to request a zero second derivative in the spline.
274  * Note that a(n)=lambda(n)=1, b=2 and c=0 (see text below eqn 3.4.8 ref 1).
275  * This gives *betaij = (1-1)/betajj.
276  * Place the required second derivative in the last element in 'y2'.
277  */
278  *betaij = 0.0f;
279  *y2 = 0.0f;
280 /*
281  * Now perform back-substitution to find the second derivatives of the
282  * spline.
283  */
284  for(j=npts-2; j>0; j--,y2--,betaij--)
285  y2[-1] -= *betaij * *y2;
286 /*
287  * Success.
288  */
289  sp->npts = npts;
290  sp->below = 0;
291  return 0;
292 }
293 
294 /*.......................................................................
295  * Return the spline interpolated Y-value corresponding to a given
296  * X-axis position.
297  *
298  * Input:
299  * sp Spline * A spline container returned by new_Spline().
300  * xpos double The X-position for which a Y-value is required.
301  * sequential int If true assume that calls are made for monotonically
302  * increasing xpos values. If false a binary search
303  * will be made for xpos within the X-axis array.
304  * Input/Output:
305  * yval double * On return *yval will contain the interpolated
306  * y-axis value for x-axis position 'xpos'.
307  * Output:
308  * return int 0 - OK.
309  * 1 - Error.
310  */
311 int SPLINE_FN(eval_)(SP_OBJ *sp, SP_TYPE xpos, int sequential,
312  SP_TYPE *yval)
313 {
314  SP_TYPE *x = sp->x; /* Local copy of sp->x */
315  SP_TYPE *y = sp->y; /* Local copy of sp->y */
316  SP_TYPE *y2 = sp->y2; /* Local copy of sp->y2 */
317  int npts = sp->npts; /* Local copy of sp->npts */
318  int below, above; /* Indexes of points on either side of xpos in */
319  /* sp->x[] */
320 /*
321  * Range check.
322  */
323  if(xpos < x[0] || xpos > x[npts-1]) {
324  lprintf(stderr, SP_STRINGIZE(SPLINE_FN(eval_)) ": X value (%g) not in range %g -> %g.\n",
325  xpos, x[0], x[npts-1]);
326  return 1;
327  };
328 /*
329  * Assume the point is near the point selected on the last call?
330  */
331  if(sequential) {
332  if(xpos > x[sp->below]) {
333  for(above=sp->below; xpos > x[above]; above++);
334  below = above-1;
335  } else {
336  for(below=sp->below; xpos < x[below]; below--);
337  above = below+1;
338  };
339  } else {
340 /*
341  * Use a binary search to locate the bracketing segment.
342  */
343  int mid; /* The index at which to bisect x[] */
344  below = 0;
345  above = sp->npts-1;
346  while(above-below > 1) {
347  mid = (above + below)/2;
348  if(x[mid] > xpos)
349  above = mid;
350  else
351  below = mid;
352  };
353  };
354 /*
355  * Keep a record of the start index of the bracketing segment for use in
356  * a subsequent call to this function.
357  */
358  sp->below = below;
359 /*
360  * Perform the interpolation.
361  */
362  if(yval) {
363  SP_TYPE h = x[above] - x[below];
364  SP_TYPE a = (x[above] - xpos)/h;
365  SP_TYPE b = (xpos - x[below])/h;
366  *yval = a*y[below] + b*y[above] + h * h *
367  (a*(a*a-1.0)*y2[below] + b*(b*b-1.0)*y2[above]) / 6.0f;
368  };
369  return 0;
370 }