ampsci
c++ program for high-precision atomic structure calculations of single-valence systems
Loading...
Searching...
No Matches
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
35class BSpline {
36
37 // K is _order_ (degree is K-1)
38 std::size_t m_K;
39 // Total number of splines (i=0,1,...,N-1)
40 std::size_t m_N;
41 // Splines defined over range [0,x_max]
42 double m_xmax;
43 // Worskspace used by GSL library
44 gsl_bspline_workspace *gsl_bspl_work{nullptr};
45
46#ifdef GSL_VERSION_1
47 // Worskspace used by old version of GSL library
48 gsl_bspline_deriv_workspace *gsl_bspl_deriv_work{nullptr};
49#endif
50
51public:
52 enum class KnotDistro { logarithmic, linear, loglinear };
53 //============================================================================
56 BSpline(std::size_t n, std::size_t k, double x0, double xmax,
57 KnotDistro kd = KnotDistro::logarithmic)
58 : m_K(k), m_N(n), m_xmax(xmax) {
59 assert(m_N > m_K && "Require N>K for B-splines");
60 assert(xmax > x0 && x0 > 0.0 && xmax > 0.0 && "xmax>x0 and both positive");
61
62 // Allocate memory for the GSL workspace:
63 const std::size_t n_break = m_N - m_K + 2;
64 gsl_bspl_work = gsl_bspline_alloc(m_K, n_break);
65
66#ifdef GSL_VERSION_1
67 // Worskspace used by old version of GSL library
68 gsl_bspl_deriv_work = gsl_bspline_deriv_alloc(m_K);
69#endif
70
71 set_knots(x0, xmax, n_break, kd);
72
73 // Consistancy check:
74#ifdef GSL_VERSION_PRIOR_2_8
75 // gsl_bspline_ncoeffs will be deprecated in future GSL
76 assert(gsl_bspline_ncoeffs(gsl_bspl_work) == m_N);
77#else
78 assert(gsl_bspline_ncontrol(gsl_bspl_work) == m_N);
79#endif
80 assert(gsl_bspline_order(gsl_bspl_work) == m_K);
81 assert(gsl_bspline_nbreak(gsl_bspl_work) == n_break);
82 }
83
84 // delete copy assignment (no simple way to copy gsl_bspl_work)
85 BSpline &operator=(const BSpline &) = delete;
86 // delete copy constructor (no simple way to copy gsl_bspl_work)
87 BSpline(const BSpline &) = delete;
88
89 // Desctructor
90 ~BSpline() {
91 gsl_bspline_free(gsl_bspl_work);
92#ifdef GSL_VERSION_1
93 gsl_bspline_deriv_free(gsl_bspl_deriv_work);
94#endif
95 }
96
97 //============================================================================
99 std::size_t K() { return m_K; }
101 std::size_t d() { return m_K - 1; }
103 std::size_t N() { return m_N; }
104
105 //============================================================================
108 std::size_t find_i0(double x) {
109 for (std::size_t i = 0; i < m_N; ++i) {
110 if (gsl_vector_get(gsl_bspl_work->knots, i + m_K) > x) {
111 return i;
112 }
113 }
114 return m_N;
115 }
116
117 //============================================================================
119 std::vector<double> get(double x) {
120 std::vector<double> b(m_N);
121 gsl_vector_view b_gsl = gsl_vector_view_array(b.data(), b.size());
122 if (x >= 0.0 && x <= m_xmax) {
123
124#ifdef GSL_VERSION_PRIOR_2_8
125 // gsl_bspline_eval will be deprecated in future GSL
126 gsl_bspline_eval(x, &b_gsl.vector, gsl_bspl_work);
127#else
128 gsl_bspline_eval_basis(x, &b_gsl.vector, gsl_bspl_work);
129#endif
130 }
131 return b;
132 }
133
134 //============================================================================
138
145 std::pair<std::size_t, LinAlg::Matrix<double>>
146 get_nonzero(double x, std::size_t n_deriv) {
147 std::pair<std::size_t, LinAlg::Matrix<double>> out{0, {m_K, n_deriv + 1}};
148
149 if (x >= 0.0 && x <= m_xmax) {
150 // outside this range, spline set to zero by default. Invalid to call
151 // gsl_bspline_deriv_eval_nonzero() outside spline range
152
153 auto &i0 = out.first;
154 auto &bij = out.second;
155 gsl_matrix_view b_gsl = bij.as_gsl_view();
156
157#ifdef GSL_VERSION_1
158 size_t i_end{};
159 gsl_bspline_deriv_eval_nonzero(x, n_deriv, &b_gsl.matrix, &i0, &i_end,
160 gsl_bspl_work, gsl_bspl_deriv_work);
161#elif defined GSL_VERSION_PRIOR_2_8
162 size_t i_end{};
163 gsl_bspline_deriv_eval_nonzero(x, n_deriv, &b_gsl.matrix, &i0, &i_end,
164 gsl_bspl_work);
165#else
166 gsl_bspline_basis_deriv(x, n_deriv, &b_gsl.matrix, &i0, gsl_bspl_work);
167#endif
168 }
169
170 return out;
171 }
172
173 //============================================================================
174 void print_knots() const {
175
176 std::size_t n_cols_to_print = 4;
177 std::cout << "Knots:\n ";
178 std::size_t count = 0;
179 while (count < m_N + m_K) {
180 auto t = gsl_bspl_work->knots->data[count];
181 printf("%.5e, ", t);
182 ++count;
183 if (count % n_cols_to_print == 0)
184 std::cout << "\n ";
185 }
186 if (count % n_cols_to_print != 0)
187 std::cout << "\n";
188 }
189
190private:
191 //============================================================================
192 void set_knots(double x0, double xmax, std::size_t n_break, KnotDistro kd) {
193 // Make version that takes custom (internal) knot sequence?
194 // Option for linear/log-spaced knots?
195
196 // n_break break points range from [0,xmax] in n_break steps
197 // We define the _internal_ (non-zero) breakpoints
198 // these run from [x0, xmax] in n_break - 1 steps
199 // Knots are the same as breakpoints, but the first (0) and last (xmax)
200 // points are repeated K times
201 auto breaks = (kd == KnotDistro::logarithmic) ?
202 qip::logarithmic_range(x0, xmax, n_break - 1) :
203 (kd == KnotDistro::linear) ?
204 qip::uniform_range(x0, xmax, n_break - 1) :
205 (kd == KnotDistro::loglinear) ?
206 qip::loglinear_range(x0, xmax, 0.5 * xmax, n_break - 1) :
207 std::vector<double>{};
208
209 breaks.insert(breaks.begin(), 0.0);
210
211 auto gsl_break_vec = gsl_vector_view_array(breaks.data(), breaks.size());
212
213#ifdef GSL_VERSION_PRIOR_2_8
214 // gsl_bspline_knots will be deprecated in future GSL
215 gsl_bspline_knots(&gsl_break_vec.vector, gsl_bspl_work);
216#else
217 gsl_bspline_init_augment(&gsl_break_vec.vector, gsl_bspl_work);
218#endif
219 }
220};
221
222// Documentation from OLD GSL v:1.x
223// -- Function: gsl_bspline_deriv_workspace * gsl_bspline_deriv_alloc
224// (const size_t K)
225// This function allocates a workspace for computing the derivatives
226// of a B-spline basis function of order K. The size of the workspace
227// is O(2k^2).
228//
229// -- Function: int gsl_bspline_deriv_eval (const double X, const size_t
230// NDERIV, gsl_matrix * DB, gsl_bspline_workspace * W,
231// gsl_bspline_deriv_workspace * DW)
232// This function evaluates all B-spline basis function derivatives of
233// orders 0 through nderiv (inclusive) at the position X and stores
234// them in the matrix DB. The (i,j)-th element of DB is
235// d^jB_i(x)/dx^j. The matrix DB must be of size n = nbreak + k - 2
236// by nderiv + 1. The value n may also be obtained by calling
237// `gsl_bspline_ncoeffs'. Note that function evaluations are
238// included as the zeroth order derivatives in DB. Computing all the
239// basis function derivatives at once is more efficient than
240// computing them individually, due to the nature of the defining
241// recurrence relation.
242//
243// -- Function: int gsl_bspline_deriv_eval_nonzero (const double X, const
244// size_t NDERIV, gsl_matrix * DB, size_t * ISTART, size_t *
245// IEND, gsl_bspline_workspace * W, gsl_bspline_deriv_workspace
246// * DW)
247// This function evaluates all potentially nonzero B-spline basis
248// function derivatives of orders 0 through nderiv (inclusive) at the
249// position X and stores them in the matrix DB. The (i,j)-th element
250// of DB is d^j/dx^j B_(istart+i)(x). The last row of DB contains
251// d^j/dx^j B_(iend)(x). The matrix DB must be of size k by at least
252// nderiv + 1. Note that function evaluations are included as the
253// zeroth order derivatives in DB. By returning only the nonzero
254// basis functions, this function allows quantities involving linear
255// combinations of the B_i(x) and their derivatives to be computed
256// without unnecessary terms.
Definition BSpline.hpp:35
std::size_t d()
Degree of the splines, d:=K-1.
Definition BSpline.hpp:101
BSpline(std::size_t n, std::size_t k, double x0, double xmax, KnotDistro kd=KnotDistro::logarithmic)
Constructs basis of n splines of order k, defined over [0,xmax]. x0 is first non-zero knot....
Definition BSpline.hpp:56
std::size_t find_i0(double x)
Returns the first nonzero spline index i0; Note that i runs [0,N). The last non-zero index is min(i0+...
Definition BSpline.hpp:108
std::pair< std::size_t, LinAlg::Matrix< double > > get_nonzero(double x, std::size_t n_deriv)
Returns a pair {i0, M}, where i0 is spline index of first non-zero spline, and M is a matrix of non-z...
Definition BSpline.hpp:146
std::vector< double > get(double x)
Returns a std::vector of all splines {b0, b1, ..., b_N-1} evaluated at x.
Definition BSpline.hpp:119
std::size_t K()
Order of the splines, K.
Definition BSpline.hpp:99
std::size_t N()
Number of splines, N.
Definition BSpline.hpp:103
std::vector< T > logarithmic_range(T first, T last, N number)
Produces a logarithmicly*(see below) distributed range of values between [first,last] with number ste...
Definition Vector.hpp:261
std::vector< T > uniform_range(T first, T last, N number)
Produces a uniformly*(see below) distributed range of values between [first,last] with number steps....
Definition Vector.hpp:232
std::vector< T > loglinear_range(T first, T last, T b, N number)
Produces a Log-Linear distributed range of values between [first,last] with number steps....
Definition Vector.hpp:291