blob: 847a4984038702a5d283cba7d124b60510e30b14 [file] [log] [blame]
/* bspline/bspline.c
*
* Copyright (C) 2006 Patrick Alken
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <config.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_bspline.h>
/*
* This module contains routines related to calculating B-splines.
* The algorithms used are described in
*
* [1] Carl de Boor, "A Practical Guide to Splines", Springer
* Verlag, 1978.
*/
static int bspline_eval_all(const double x, gsl_vector *B, size_t *idx,
gsl_bspline_workspace *w);
static inline size_t bspline_find_interval(const double x, int *flag,
gsl_bspline_workspace *w);
/*
gsl_bspline_alloc()
Allocate space for a bspline workspace. The size of the
workspace is O(5k + nbreak)
Inputs: k - spline order (cubic = 4)
nbreak - number of breakpoints
Return: pointer to workspace
*/
gsl_bspline_workspace *
gsl_bspline_alloc(const size_t k, const size_t nbreak)
{
if (k == 0)
{
GSL_ERROR_NULL("k must be at least 1", GSL_EINVAL);
}
else if (nbreak < 2)
{
GSL_ERROR_NULL("nbreak must be at least 2", GSL_EINVAL);
}
else
{
gsl_bspline_workspace *w;
w = (gsl_bspline_workspace *)
calloc(1, sizeof(gsl_bspline_workspace));
if (w == 0)
{
GSL_ERROR_NULL("failed to allocate space for workspace", GSL_ENOMEM);
}
w->k = k;
w->km1 = k - 1;
w->nbreak = nbreak;
w->l = nbreak - 1;
w->n = w->l + k - 1;
w->knots = gsl_vector_alloc(w->n + k);
if (w->knots == 0)
{
gsl_bspline_free(w);
GSL_ERROR_NULL("failed to allocate space for knots vector", GSL_ENOMEM);
}
w->deltal = gsl_vector_alloc(k);
w->deltar = gsl_vector_alloc(k);
if (!w->deltal || !w->deltar)
{
gsl_bspline_free(w);
GSL_ERROR_NULL("failed to allocate space for delta vectors", GSL_ENOMEM);
}
w->B = gsl_vector_alloc(k);
if (w->B == 0)
{
gsl_bspline_free(w);
GSL_ERROR_NULL("failed to allocate space for temporary spline vector", GSL_ENOMEM);
}
return (w);
}
} /* gsl_bspline_alloc() */
/* Return number of coefficients */
size_t
gsl_bspline_ncoeffs (gsl_bspline_workspace * w)
{
return w->n;
}
/* Return order */
size_t
gsl_bspline_order (gsl_bspline_workspace * w)
{
return w->k;
}
/* Return number of breakpoints */
size_t
gsl_bspline_nbreak (gsl_bspline_workspace * w)
{
return w->nbreak;
}
double
gsl_bspline_breakpoint (size_t i, gsl_bspline_workspace * w)
{
size_t j = i + w->k - 1;
return gsl_vector_get(w->knots, j);
}
/*
gsl_bspline_free()
Free a bspline workspace
Inputs: w - workspace to free
Return: none
*/
void
gsl_bspline_free(gsl_bspline_workspace *w)
{
if (!w)
return;
if (w->knots)
gsl_vector_free(w->knots);
if (w->deltal)
gsl_vector_free(w->deltal);
if (w->deltar)
gsl_vector_free(w->deltar);
if (w->B)
gsl_vector_free(w->B);
free(w);
} /* gsl_bspline_free() */
/*
gsl_bspline_knots()
Compute the knots from the given breakpoints:
knots(1:k) = breakpts(1)
knots(k+1:k+l-1) = breakpts(i), i = 2 .. l
knots(n+1:n+k) = breakpts(l + 1)
where l is the number of polynomial pieces (l = nbreak - 1) and
n = k + l - 1
(using matlab syntax for the arrays)
The repeated knots at the beginning and end of the interval
correspond to the continuity condition there. See pg. 119
of [1].
Inputs: breakpts - breakpoints
w - bspline workspace
Return: success or error
*/
int
gsl_bspline_knots(const gsl_vector *breakpts, gsl_bspline_workspace *w)
{
if (breakpts->size != w->nbreak)
{
GSL_ERROR("breakpts vector has wrong size", GSL_EBADLEN);
}
else
{
size_t i; /* looping */
for (i = 0; i < w->k; ++i)
gsl_vector_set(w->knots, i, gsl_vector_get(breakpts, 0));
for (i = 1; i < w->l; ++i)
{
gsl_vector_set(w->knots, w->k - 1 + i,
gsl_vector_get(breakpts, i));
}
for (i = w->n; i < w->n + w->k; ++i)
gsl_vector_set(w->knots, i, gsl_vector_get(breakpts, w->l));
return GSL_SUCCESS;
}
} /* gsl_bspline_knots() */
/*
gsl_bspline_knots_uniform()
Construct uniformly spaced knots on the interval [a,b] using
the previously specified number of breakpoints. 'a' is the position
of the first breakpoint and 'b' is the position of the last
breakpoint.
Inputs: a - left side of interval
b - right side of interval
w - bspline workspace
Return: success or error
Notes: 1) w->knots is modified to contain the uniformly spaced
knots
2) The knots vector is set up as follows (using octave syntax):
knots(1:k) = a
knots(k+1:k+l-1) = a + i*delta, i = 1 .. l - 1
knots(n+1:n+k) = b
*/
int
gsl_bspline_knots_uniform(const double a, const double b,
gsl_bspline_workspace *w)
{
size_t i; /* looping */
double delta; /* interval spacing */
double x;
delta = (b - a) / (double) w->l;
for (i = 0; i < w->k; ++i)
gsl_vector_set(w->knots, i, a);
x = a + delta;
for (i = 0; i < w->l - 1; ++i)
{
gsl_vector_set(w->knots, w->k + i, x);
x += delta;
}
for (i = w->n; i < w->n + w->k; ++i)
gsl_vector_set(w->knots, i, b);
return GSL_SUCCESS;
} /* gsl_bspline_knots_uniform() */
/*
gsl_bspline_eval()
Evaluate the basis functions B_i(x) for all i. This is
basically a wrapper function for bspline_eval_all()
which formats the output in a nice way.
Inputs: x - point for evaluation
B - (output) where to store B_i(x) values
the length of this vector is
n = nbreak + k - 2 = l + k - 1 = w->n
w - bspline workspace
Return: success or error
Notes: The w->knots vector must be initialized prior to calling
this function (see gsl_bspline_knots())
*/
int
gsl_bspline_eval(const double x, gsl_vector *B,
gsl_bspline_workspace *w)
{
if (B->size != w->n)
{
GSL_ERROR("B vector length does not match n", GSL_EBADLEN);
}
else
{
size_t i; /* looping */
size_t idx = 0;
size_t start; /* first non-zero spline */
/* find all non-zero B_i(x) values */
bspline_eval_all(x, w->B, &idx, w);
/* store values in appropriate part of given vector */
start = idx - w->k + 1;
for (i = 0; i < start; ++i)
gsl_vector_set(B, i, 0.0);
for (i = start; i <= idx; ++i)
gsl_vector_set(B, i, gsl_vector_get(w->B, i - start));
for (i = idx + 1; i < w->n; ++i)
gsl_vector_set(B, i, 0.0);
return GSL_SUCCESS;
}
} /* gsl_bspline_eval() */
/****************************************
* INTERNAL ROUTINES *
****************************************/
/*
bspline_eval_all()
Evaluate all non-zero B-splines B_i(x) using algorithm (8)
of chapter X of [1]
The idea is something like this. Given x \in [t_i, t_{i+1}]
with t_i < t_{i+1} and the t_i are knots, the values of the
B-splines not automatically zero fit into a triangular
array as follows:
0
0
0 B_{i-2,3}
B_{i-1,2}
B_{i,1} B_{i-1,3} ...
B_{i,2}
0 B_{i,3}
0
0
where B_{i,k} is the ith B-spline of order k. The boundary 0s
indicate that those B-splines not in the table vanish at x.
To compute the non-zero B-splines of a given order k, we use
Eqs. (4) and (5) of chapter X of [1]:
(4) B_{i,1}(x) = { 1, t_i <= x < t_{i+1}
0, else }
(5) B_{i,k}(x) = (x - t_i)
----------------- B_{i,k-1}(x)
(t_{i+k-1} - t_i)
t_{i+k} - x
+ ----------------- B_{i+1,k-1}(x)
t_{i+k} - t_{i+1}
So (4) gives us the first column of the table and we can use
the recurrence relation (5) to get the rest of the columns.
Inputs: x - point at which to evaluate splines
B - (output) where to store B-spline values (length k)
idx - (output) B-spline function index of last output
value (B_{idx}(x) is stored in the last slot of 'B')
w - bspline workspace
Return: success or error
Notes: 1) the w->knots vector must be initialized before calling
this function
2) On output, B contains:
B = [B_{i-k+1,k}, B_{i-k+2,k}, ..., B_{i-1,k}, B_{i,k}]
where 'i' is stored in 'idx' on output
3) based on PPPACK bsplvb
*/
static int
bspline_eval_all(const double x, gsl_vector *B, size_t *idx,
gsl_bspline_workspace *w)
{
if (B->size != w->k)
{
GSL_ERROR("B vector not of length k", GSL_EBADLEN);
}
else
{
size_t i; /* spline index */
size_t j; /* looping */
size_t ii; /* looping */
int flag = 0; /* error flag */
double saved;
double term;
i = bspline_find_interval(x, &flag, w);
if (flag == -1)
{
GSL_ERROR("x outside of knot interval", GSL_EINVAL);
}
else if (flag == 1)
{
if (x <= gsl_vector_get(w->knots, i) + GSL_DBL_EPSILON)
{
--i;
}
else
{
GSL_ERROR("x outside of knot interval", GSL_EINVAL);
}
}
*idx = i;
gsl_vector_set(B, 0, 1.0);
for (j = 0; j < w->k - 1; ++j)
{
gsl_vector_set(w->deltar, j,
gsl_vector_get(w->knots, i + j + 1) - x);
gsl_vector_set(w->deltal, j,
x - gsl_vector_get(w->knots, i - j));
saved = 0.0;
for (ii = 0; ii <= j; ++ii)
{
term = gsl_vector_get(B, ii) /
(gsl_vector_get(w->deltar, ii) +
gsl_vector_get(w->deltal, j - ii));
gsl_vector_set(B, ii,
saved +
gsl_vector_get(w->deltar, ii) * term);
saved = gsl_vector_get(w->deltal, j - ii) * term;
}
gsl_vector_set(B, j + 1, saved);
}
return GSL_SUCCESS;
}
} /* bspline_eval_all() */
/*
bspline_find_interval()
Find knot interval such that
t_i <= x < t_{i + 1}
where the t_i are knot values.
Inputs: x - x value
flag - (output) error flag
w - bspline workspace
Return: i (index in w->knots corresponding to left limit of interval)
Notes: The error conditions are reported as follows:
Condition Return value Flag
--------- ------------ ----
x < t_0 0 -1
t_i <= x < t_{i+1} i 0
t_{n+k-1} <= x l+k-1 +1
*/
static inline size_t
bspline_find_interval(const double x, int *flag,
gsl_bspline_workspace *w)
{
size_t i;
if (x < gsl_vector_get(w->knots, 0))
{
*flag = -1;
return 0;
}
/* find i such that t_i <= x < t_{i+1} */
for (i = w->k - 1; i < w->k + w->l - 1; ++i)
{
const double ti = gsl_vector_get(w->knots, i);
const double tip1 = gsl_vector_get(w->knots, i + 1);
if (tip1 < ti)
{
GSL_ERROR("knots vector is not increasing", GSL_EINVAL);
}
if (ti <= x && x < tip1)
break;
}
if (i == w->k + w->l - 1)
*flag = 1;
else
*flag = 0;
return i;
} /* bspline_find_interval() */