ampsci
c++ program for high-precision atomic structure calculations of single-valence systems
BSpline.hpp
1 #pragma once
2 #include "LinAlg/LinAlg.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 
35 class 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 
51 public:
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 
190 private:
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
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
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::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 > 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
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