ampsci
High-precision calculations for one- and two-valence atomic systems
BSpline.hpp
1#pragma once
2#include "LinAlg/include.hpp"
3#include "qip/Vector.hpp"
4#include <gsl/gsl_bspline.h>
5#include <gsl/gsl_version.h>
6#include <iostream>
7#include <numeric>
8#include <vector>
9
10// This should make code work with old versions of GSL
11// See below for documentation of old GSL function
12#ifdef GSL_MAJOR_VERSION
13#if GSL_MAJOR_VERSION == 1
14#define GSL_VERSION_1
15#endif
16// GSL 2.8 introduces substantial changes to bspline library.
17#if GSL_MAJOR_VERSION <= 2 && GSL_MINOR_VERSION < 8
18#define GSL_VERSION_PRIOR_2_8
19#endif
20#endif
21
22/*!
23 @brief Basis of N B-splines of order K (degree K-1), defined over [0, xmax].
24
25 @details
26 Constructs and evaluates a B-spline basis using the GSL B-spline library
27 (gsl/gsl_bspline.h). See also: Bachau et al., Rep. Prog. Phys. 64, 1815
28 (2001).
29
30 **Knot structure:**
31 - N - K + 2 breakpoints (internal knots) are placed in [x0, xmax], where x0
32 is the first non-zero knot. The point 0 is prepended, giving N - K + 2
33 breakpoints total spanning [0, xmax].
34 - The full knot vector repeats the endpoints K times.
35 - Breakpoints may be placed on a logarithmic (default), linear, or
36 log-linear scale; see `KnotDistro`.
37
38 **Compatibility:**
39 Supports GSL v2.x (<2.8), and v2.8+. API differences are handled
40 internally via compile-time version macros.
41 Should also support v1.x, but this is difficult to test.
42
43 Copy construction and copy assignment are deleted (GSL workspace is not
44 copyable).
45*/
46class BSpline {
47
48 // K is _order_ (degree is K-1)
49 std::size_t m_K;
50 // Total number of splines (i=0,1,...,N-1)
51 std::size_t m_N;
52 // Splines defined over range [0,x_max]
53 double m_xmax;
54 // Worskspace used by GSL library
55 gsl_bspline_workspace *gsl_bspl_work{nullptr};
56
57#ifdef GSL_VERSION_1
58 // Worskspace used by old version of GSL library
59 gsl_bspline_deriv_workspace *gsl_bspl_deriv_work{nullptr};
60#endif
61
62public:
63 //! Distribution of internal knot points
64 enum class KnotDistro {
65 //! Knots on a logarithmic scale (default; suits bound-state grids)
67 //! Knots evenly spaced
68 linear,
69 //! Log-linear blend; midpoint at xmax/2
71 };
72
73 //============================================================================
74 /*!
75 @brief Construct a basis of N B-splines of order K over [0, xmax].
76 @param n Number of splines N. Must satisfy N > K.
77 @param k Order K (degree K-1). E.g. K=4 gives cubic splines.
78 @param x0 First non-zero (internal) knot; must satisfy 0 < x0 < xmax.
79 @param xmax Upper bound of the spline domain.
80 @param kd Knot distribution: logarithmic (default), linear, or loglinear.
81 */
82 BSpline(std::size_t n, std::size_t k, double x0, double xmax,
84 : m_K(k), m_N(n), m_xmax(xmax) {
85 assert(m_N > m_K && "Require N>K for B-splines");
86 assert(xmax > x0 && x0 > 0.0 && xmax > 0.0 && "xmax>x0 and both positive");
87
88 // Allocate memory for the GSL workspace:
89 const std::size_t n_break = m_N - m_K + 2;
90 gsl_bspl_work = gsl_bspline_alloc(m_K, n_break);
91
92#ifdef GSL_VERSION_1
93 // Worskspace used by old version of GSL library
94 gsl_bspl_deriv_work = gsl_bspline_deriv_alloc(m_K);
95#endif
96
97 set_knots(x0, xmax, n_break, kd);
98
99 // Consistancy check:
100#ifdef GSL_VERSION_PRIOR_2_8
101 // gsl_bspline_ncoeffs will be deprecated in future GSL
102 assert(gsl_bspline_ncoeffs(gsl_bspl_work) == m_N);
103#else
104 assert(gsl_bspline_ncontrol(gsl_bspl_work) == m_N);
105#endif
106 assert(gsl_bspline_order(gsl_bspl_work) == m_K);
107 assert(gsl_bspline_nbreak(gsl_bspl_work) == n_break);
108 }
109
110 //! Copy assignment deleted: GSL workspace is not copyable
111 BSpline &operator=(const BSpline &) = delete;
112 //! Copy construction deleted: GSL workspace is not copyable
113 BSpline(const BSpline &) = delete;
114
115 ~BSpline() {
116 gsl_bspline_free(gsl_bspl_work);
117#ifdef GSL_VERSION_1
118 gsl_bspline_deriv_free(gsl_bspl_deriv_work);
119#endif
120 }
121
122 //============================================================================
123 //! Order of the splines, K
124 std::size_t K() { return m_K; }
125 //! Degree of the splines, d := K-1
126 std::size_t d() { return m_K - 1; }
127 //! Number of splines, N
128 std::size_t N() { return m_N; }
129
130 //============================================================================
131 //! Returns the index i0 of the first non-zero spline at x.
132 //! Spline indices run [0, N); at most K splines are non-zero at any x,
133 //! occupying indices [i0, min(i0+K-1, N-1)].
134 std::size_t find_i0(double x) {
135 for (std::size_t i = 0; i < m_N; ++i) {
136 if (gsl_vector_get(gsl_bspl_work->knots, i + m_K) > x) {
137 return i;
138 }
139 }
140 return m_N;
141 }
142
143 //============================================================================
144 //! Returns all N spline values {b_0(x), b_1(x), ..., b_{N-1}(x)}.
145 //! Returns a vector of zeros if x is outside [0, xmax].
146 std::vector<double> get(double x) {
147 std::vector<double> b(m_N);
148 gsl_vector_view b_gsl = gsl_vector_view_array(b.data(), b.size());
149 if (x >= 0.0 && x <= m_xmax) {
150
151#ifdef GSL_VERSION_PRIOR_2_8
152 // gsl_bspline_eval will be deprecated in future GSL
153 gsl_bspline_eval(x, &b_gsl.vector, gsl_bspl_work);
154#else
155 gsl_bspline_eval_basis(x, &b_gsl.vector, gsl_bspl_work);
156#endif
157 }
158 return b;
159 }
160
161 //============================================================================
162 /*!
163 @brief Returns the non-zero splines and their derivatives at x.
164 @details
165 Returns `{i0, M}`, where:
166 - `i0` is the index of the first non-zero spline,
167 - `M` is a K (n_deriv+1) matrix; `M(i, j)` is the j-th derivative of
168 spline `b[i0 + i]` evaluated at x.
169
170 At most K splines are non-zero at any x. This is more efficient than
171 `get()` when only the non-zero splines are needed.
172
173 If x > xmax, returns a zero matrix with i0+K > N.
174 If x = xmax, only the last spline is non-zero (lower derivatives may not
175 be zero).
176
177 @param x Evaluation point.
178 @param n_deriv Maximum derivative order to compute (inclusive).
179 @returns Pair `{i0, M}`.
180 */
181 std::pair<std::size_t, LinAlg::Matrix<double>>
182 get_nonzero(double x, std::size_t n_deriv) {
183 std::pair<std::size_t, LinAlg::Matrix<double>> out{0, {m_K, n_deriv + 1}};
184
185 if (x >= 0.0 && x <= m_xmax) {
186 // outside this range, spline set to zero by default. Invalid to call
187 // gsl_bspline_deriv_eval_nonzero() outside spline range
188
189 auto &i0 = out.first;
190 auto &bij = out.second;
191 gsl_matrix_view b_gsl = bij.as_gsl_view();
192
193#ifdef GSL_VERSION_1
194 size_t i_end{};
195 gsl_bspline_deriv_eval_nonzero(x, n_deriv, &b_gsl.matrix, &i0, &i_end,
196 gsl_bspl_work, gsl_bspl_deriv_work);
197#elif defined GSL_VERSION_PRIOR_2_8
198 size_t i_end{};
199 gsl_bspline_deriv_eval_nonzero(x, n_deriv, &b_gsl.matrix, &i0, &i_end,
200 gsl_bspl_work);
201#else
202 gsl_bspline_basis_deriv(x, n_deriv, &b_gsl.matrix, &i0, gsl_bspl_work);
203#endif
204 }
205
206 return out;
207 }
208
209 //============================================================================
210 //! Prints the full knot vector to stdout (N+K values).
211 void print_knots() const {
212
213 std::size_t n_cols_to_print = 4;
214 std::cout << "Knots:\n ";
215 std::size_t count = 0;
216 while (count < m_N + m_K) {
217 auto t = gsl_bspl_work->knots->data[count];
218 printf("%.5e, ", t);
219 ++count;
220 if (count % n_cols_to_print == 0)
221 std::cout << "\n ";
222 }
223 if (count % n_cols_to_print != 0)
224 std::cout << "\n";
225 }
226
227private:
228 //============================================================================
229 void set_knots(double x0, double xmax, std::size_t n_break, KnotDistro kd) {
230 // Make version that takes custom (internal) knot sequence?
231 // Option for linear/log-spaced knots?
232
233 // n_break break points range from [0,xmax] in n_break steps
234 // We define the _internal_ (non-zero) breakpoints
235 // these run from [x0, xmax] in n_break - 1 steps
236 // Knots are the same as breakpoints, but the first (0) and last (xmax)
237 // points are repeated K times
238 auto breaks = (kd == KnotDistro::logarithmic) ?
239 qip::logarithmic_range(x0, xmax, n_break - 1) :
240 (kd == KnotDistro::linear) ?
241 qip::uniform_range(x0, xmax, n_break - 1) :
242 (kd == KnotDistro::loglinear) ?
243 qip::loglinear_range(x0, xmax, 0.5 * xmax, n_break - 1) :
244 std::vector<double>{};
245
246 breaks.insert(breaks.begin(), 0.0);
247
248 auto gsl_break_vec = gsl_vector_view_array(breaks.data(), breaks.size());
249
250#ifdef GSL_VERSION_PRIOR_2_8
251 // gsl_bspline_knots will be deprecated in future GSL
252 gsl_bspline_knots(&gsl_break_vec.vector, gsl_bspl_work);
253#else
254 gsl_bspline_init_augment(&gsl_break_vec.vector, gsl_bspl_work);
255#endif
256 }
257};
258
259// Documentation from OLD GSL v:1.x
260// -- Function: gsl_bspline_deriv_workspace * gsl_bspline_deriv_alloc
261// (const size_t K)
262// This function allocates a workspace for computing the derivatives
263// of a B-spline basis function of order K. The size of the workspace
264// is O(2k^2).
265//
266// -- Function: int gsl_bspline_deriv_eval (const double X, const size_t
267// NDERIV, gsl_matrix * DB, gsl_bspline_workspace * W,
268// gsl_bspline_deriv_workspace * DW)
269// This function evaluates all B-spline basis function derivatives of
270// orders 0 through nderiv (inclusive) at the position X and stores
271// them in the matrix DB. The (i,j)-th element of DB is
272// d^jB_i(x)/dx^j. The matrix DB must be of size n = nbreak + k - 2
273// by nderiv + 1. The value n may also be obtained by calling
274// `gsl_bspline_ncoeffs'. Note that function evaluations are
275// included as the zeroth order derivatives in DB. Computing all the
276// basis function derivatives at once is more efficient than
277// computing them individually, due to the nature of the defining
278// recurrence relation.
279//
280// -- Function: int gsl_bspline_deriv_eval_nonzero (const double X, const
281// size_t NDERIV, gsl_matrix * DB, size_t * ISTART, size_t *
282// IEND, gsl_bspline_workspace * W, gsl_bspline_deriv_workspace
283// * DW)
284// This function evaluates all potentially nonzero B-spline basis
285// function derivatives of orders 0 through nderiv (inclusive) at the
286// position X and stores them in the matrix DB. The (i,j)-th element
287// of DB is d^j/dx^j B_(istart+i)(x). The last row of DB contains
288// d^j/dx^j B_(iend)(x). The matrix DB must be of size k by at least
289// nderiv + 1. Note that function evaluations are included as the
290// zeroth order derivatives in DB. By returning only the nonzero
291// basis functions, this function allows quantities involving linear
292// combinations of the B_i(x) and their derivatives to be computed
293// without unnecessary terms.
Basis of N B-splines of order K (degree K-1), defined over [0, xmax].
Definition BSpline.hpp:46
std::size_t d()
Degree of the splines, d := K-1.
Definition BSpline.hpp:126
BSpline(std::size_t n, std::size_t k, double x0, double xmax, KnotDistro kd=KnotDistro::logarithmic)
Construct a basis of N B-splines of order K over [0, xmax].
Definition BSpline.hpp:82
KnotDistro
Distribution of internal knot points.
Definition BSpline.hpp:64
@ logarithmic
Knots on a logarithmic scale (default; suits bound-state grids)
@ linear
Knots evenly spaced.
@ loglinear
Log-linear blend; midpoint at xmax/2.
BSpline & operator=(const BSpline &)=delete
Copy assignment deleted: GSL workspace is not copyable.
void print_knots() const
Prints the full knot vector to stdout (N+K values).
Definition BSpline.hpp:211
std::size_t find_i0(double x)
Returns the index i0 of the first non-zero spline at x. Spline indices run [0, N); at most K splines ...
Definition BSpline.hpp:134
std::pair< std::size_t, LinAlg::Matrix< double > > get_nonzero(double x, std::size_t n_deriv)
Returns the non-zero splines and their derivatives at x.
Definition BSpline.hpp:182
std::vector< double > get(double x)
Returns all N spline values {b_0(x), b_1(x), ..., b_{N-1}(x)}. Returns a vector of zeros if x is outs...
Definition BSpline.hpp:146
std::size_t K()
Order of the splines, K.
Definition BSpline.hpp:124
std::size_t N()
Number of splines, N.
Definition BSpline.hpp:128
BSpline(const BSpline &)=delete
Copy construction deleted: GSL workspace is not copyable.
std::vector< T > logarithmic_range(T first, T last, N number)
Returns a logarithmically distributed range [first, last] with number points.
Definition Vector.hpp:283
std::vector< T > uniform_range(T first, T last, N number)
Returns a uniformly distributed range [first, last] with number points.
Definition Vector.hpp:252
std::vector< T > loglinear_range(T first, T last, T b, N number)
Returns a log-linear distributed range [first, last] with number points.
Definition Vector.hpp:316