3 * Copyright (C) 2006, 2007, 2008 Patrick Alken
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or (at
8 * your option) any later version.
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include <gsl/gsl_errno.h>
22 #include <gsl/gsl_bspline.h>
25 * This module contains routines related to calculating B-splines.
26 * The algorithms used are described in
28 * [1] Carl de Boor, "A Practical Guide to Splines", Springer
32 static int bspline_eval_all(const double x, gsl_vector *B, size_t *idx,
33 gsl_bspline_workspace *w);
35 static inline size_t bspline_find_interval(const double x, int *flag,
36 gsl_bspline_workspace *w);
40 Allocate space for a bspline workspace. The size of the
41 workspace is O(5k + nbreak)
43 Inputs: k - spline order (cubic = 4)
44 nbreak - number of breakpoints
46 Return: pointer to workspace
49 gsl_bspline_workspace *
50 gsl_bspline_alloc(const size_t k, const size_t nbreak)
54 GSL_ERROR_NULL("k must be at least 1", GSL_EINVAL);
58 GSL_ERROR_NULL("nbreak must be at least 2", GSL_EINVAL);
62 gsl_bspline_workspace *w;
64 w = (gsl_bspline_workspace *)
65 calloc(1, sizeof(gsl_bspline_workspace));
68 GSL_ERROR_NULL("failed to allocate space for workspace", GSL_ENOMEM);
77 w->knots = gsl_vector_alloc(w->n + k);
81 GSL_ERROR_NULL("failed to allocate space for knots vector", GSL_ENOMEM);
84 w->deltal = gsl_vector_alloc(k);
85 w->deltar = gsl_vector_alloc(k);
86 if (!w->deltal || !w->deltar)
89 GSL_ERROR_NULL("failed to allocate space for delta vectors", GSL_ENOMEM);
92 w->B = gsl_vector_alloc(k);
96 GSL_ERROR_NULL("failed to allocate space for temporary spline vector", GSL_ENOMEM);
101 } /* gsl_bspline_alloc() */
103 /* Return number of coefficients */
105 gsl_bspline_ncoeffs (gsl_bspline_workspace * w)
112 gsl_bspline_order (gsl_bspline_workspace * w)
117 /* Return number of breakpoints */
119 gsl_bspline_nbreak (gsl_bspline_workspace * w)
125 gsl_bspline_breakpoint (size_t i, gsl_bspline_workspace * w)
127 size_t j = i + w->k - 1;
128 return gsl_vector_get(w->knots, j);
133 Free a bspline workspace
135 Inputs: w - workspace to free
141 gsl_bspline_free(gsl_bspline_workspace *w)
144 gsl_vector_free(w->knots);
147 gsl_vector_free(w->deltal);
150 gsl_vector_free(w->deltar);
153 gsl_vector_free(w->B);
156 } /* gsl_bspline_free() */
160 Compute the knots from the given breakpoints:
162 knots(1:k) = breakpts(1)
163 knots(k+1:k+l-1) = breakpts(i), i = 2 .. l
164 knots(n+1:n+k) = breakpts(l + 1)
166 where l is the number of polynomial pieces (l = nbreak - 1) and
168 (using matlab syntax for the arrays)
170 The repeated knots at the beginning and end of the interval
171 correspond to the continuity condition there. See pg. 119
174 Inputs: breakpts - breakpoints
175 w - bspline workspace
177 Return: success or error
181 gsl_bspline_knots(const gsl_vector *breakpts, gsl_bspline_workspace *w)
183 if (breakpts->size != w->nbreak)
185 GSL_ERROR("breakpts vector has wrong size", GSL_EBADLEN);
189 size_t i; /* looping */
191 for (i = 0; i < w->k; ++i)
192 gsl_vector_set(w->knots, i, gsl_vector_get(breakpts, 0));
194 for (i = 1; i < w->l; ++i)
196 gsl_vector_set(w->knots, w->k - 1 + i,
197 gsl_vector_get(breakpts, i));
200 for (i = w->n; i < w->n + w->k; ++i)
201 gsl_vector_set(w->knots, i, gsl_vector_get(breakpts, w->l));
205 } /* gsl_bspline_knots() */
208 gsl_bspline_knots_uniform()
209 Construct uniformly spaced knots on the interval [a,b] using
210 the previously specified number of breakpoints. 'a' is the position
211 of the first breakpoint and 'b' is the position of the last
214 Inputs: a - left side of interval
215 b - right side of interval
216 w - bspline workspace
218 Return: success or error
220 Notes: 1) w->knots is modified to contain the uniformly spaced
223 2) The knots vector is set up as follows (using octave syntax):
226 knots(k+1:k+l-1) = a + i*delta, i = 1 .. l - 1
231 gsl_bspline_knots_uniform(const double a, const double b,
232 gsl_bspline_workspace *w)
234 size_t i; /* looping */
235 double delta; /* interval spacing */
238 delta = (b - a) / (double) w->l;
240 for (i = 0; i < w->k; ++i)
241 gsl_vector_set(w->knots, i, a);
244 for (i = 0; i < w->l - 1; ++i)
246 gsl_vector_set(w->knots, w->k + i, x);
250 for (i = w->n; i < w->n + w->k; ++i)
251 gsl_vector_set(w->knots, i, b);
254 } /* gsl_bspline_knots_uniform() */
258 Evaluate the basis functions B_i(x) for all i. This is
259 basically a wrapper function for bspline_eval_all()
260 which formats the output in a nice way.
262 Inputs: x - point for evaluation
263 B - (output) where to store B_i(x) values
264 the length of this vector is
265 n = nbreak + k - 2 = l + k - 1 = w->n
266 w - bspline workspace
268 Return: success or error
270 Notes: The w->knots vector must be initialized prior to calling
271 this function (see gsl_bspline_knots())
275 gsl_bspline_eval(const double x, gsl_vector *B,
276 gsl_bspline_workspace *w)
280 GSL_ERROR("B vector length does not match n", GSL_EBADLEN);
284 size_t i; /* looping */
286 size_t start; /* first non-zero spline */
288 /* find all non-zero B_i(x) values */
289 bspline_eval_all(x, w->B, &idx, w);
291 /* store values in appropriate part of given vector */
293 start = idx - w->k + 1;
294 for (i = 0; i < start; ++i)
295 gsl_vector_set(B, i, 0.0);
297 for (i = start; i <= idx; ++i)
298 gsl_vector_set(B, i, gsl_vector_get(w->B, i - start));
300 for (i = idx + 1; i < w->n; ++i)
301 gsl_vector_set(B, i, 0.0);
305 } /* gsl_bspline_eval() */
307 /****************************************
308 * INTERNAL ROUTINES *
309 ****************************************/
313 Evaluate all non-zero B-splines B_i(x) using algorithm (8)
316 The idea is something like this. Given x \in [t_i, t_{i+1}]
317 with t_i < t_{i+1} and the t_i are knots, the values of the
318 B-splines not automatically zero fit into a triangular
324 B_{i,1} B_{i-1,3} ...
330 where B_{i,k} is the ith B-spline of order k. The boundary 0s
331 indicate that those B-splines not in the table vanish at x.
333 To compute the non-zero B-splines of a given order k, we use
334 Eqs. (4) and (5) of chapter X of [1]:
336 (4) B_{i,1}(x) = { 1, t_i <= x < t_{i+1}
339 (5) B_{i,k}(x) = (x - t_i)
340 ----------------- B_{i,k-1}(x)
344 + ----------------- B_{i+1,k-1}(x)
347 So (4) gives us the first column of the table and we can use
348 the recurrence relation (5) to get the rest of the columns.
350 Inputs: x - point at which to evaluate splines
351 B - (output) where to store B-spline values (length k)
352 idx - (output) B-spline function index of last output
353 value (B_{idx}(x) is stored in the last slot of 'B')
354 w - bspline workspace
356 Return: success or error
358 Notes: 1) the w->knots vector must be initialized before calling
361 2) On output, B contains:
363 B = [B_{i-k+1,k}, B_{i-k+2,k}, ..., B_{i-1,k}, B_{i,k}]
365 where 'i' is stored in 'idx' on output
367 3) based on PPPACK bsplvb
371 bspline_eval_all(const double x, gsl_vector *B, size_t *idx,
372 gsl_bspline_workspace *w)
376 GSL_ERROR("B vector not of length k", GSL_EBADLEN);
380 size_t i; /* spline index */
381 size_t j; /* looping */
382 size_t ii; /* looping */
383 int flag = 0; /* error flag */
387 i = bspline_find_interval(x, &flag, w);
391 GSL_ERROR("x outside of knot interval", GSL_EINVAL);
395 if (x <= gsl_vector_get(w->knots, i) + GSL_DBL_EPSILON)
401 GSL_ERROR("x outside of knot interval", GSL_EINVAL);
405 if (gsl_vector_get(w->knots, i) == gsl_vector_get(w->knots, i + 1))
407 GSL_ERROR("knot(i) = knot(i+1) will result in division by zero",
413 gsl_vector_set(B, 0, 1.0);
415 for (j = 0; j < w->k - 1; ++j)
417 gsl_vector_set(w->deltar, j,
418 gsl_vector_get(w->knots, i + j + 1) - x);
419 gsl_vector_set(w->deltal, j,
420 x - gsl_vector_get(w->knots, i - j));
424 for (ii = 0; ii <= j; ++ii)
426 term = gsl_vector_get(B, ii) /
427 (gsl_vector_get(w->deltar, ii) +
428 gsl_vector_get(w->deltal, j - ii));
430 gsl_vector_set(B, ii,
432 gsl_vector_get(w->deltar, ii) * term);
434 saved = gsl_vector_get(w->deltal, j - ii) * term;
437 gsl_vector_set(B, j + 1, saved);
442 } /* bspline_eval_all() */
445 bspline_find_interval()
446 Find knot interval such that
450 where the t_i are knot values.
453 flag - (output) error flag
454 w - bspline workspace
456 Return: i (index in w->knots corresponding to left limit of interval)
458 Notes: The error conditions are reported as follows:
460 Condition Return value Flag
461 --------- ------------ ----
463 t_i <= x < t_{i+1} i 0
464 t_i < x = t_{i+1} = t_{n+k-1} i 0
465 t_{n+k-1} < x l+k-1 +1
469 bspline_find_interval(const double x, int *flag,
470 gsl_bspline_workspace *w)
474 if (x < gsl_vector_get(w->knots, 0))
480 /* find i such that t_i <= x < t_{i+1} */
481 for (i = w->k - 1; i < w->k + w->l - 1; ++i)
483 const double ti = gsl_vector_get(w->knots, i);
484 const double tip1 = gsl_vector_get(w->knots, i + 1);
488 GSL_ERROR("knots vector is not increasing", GSL_EINVAL);
491 if (ti <= x && x < tip1)
494 if (ti < x && x == tip1 &&
495 tip1 == gsl_vector_get(w->knots, w->k + w->l - 1))
499 if (i == w->k + w->l - 1)
505 } /* bspline_find_interval() */