ampsci
High-precision calculations for one- and two-valence atomic systems
SphericalBessel.hpp
1#pragma once
2#include "LinAlg/Matrix.hpp"
3#include "qip/Maths.hpp"
4#include "qip/Vector.hpp"
5#include <cmath>
6#include <vector>
7
8/*!
9@brief Wrappers for returning Spherical Bessel functions.
10@details Has an "exact" version, and a faster approx version (good to ~ 1.e-9)
11*/
12namespace SphericalBessel {
13
14//==============================================================================
15//! Spherical Bessel function; uses low-x approximation where appropriate
16// Good to parts in _ for x and _ otherwise
17double jL(int L, double x);
18
19//! Spherical Bessel function of first kind via GSL
20double exactGSL_JL(int L, double x);
21
22//! Spherical Bessel functions of second kind
23double exactGSL_YL(int L, double x);
24
25//! Used for frequency-dependent Breit - more generic formula that can be used to evaluate spherical Bessel function of second kind for negative orders
26// double YL(int L, double x);
27
28//==============================================================================
29//! Spherical Bessel function of second kind, y_L(x)
30double yL(int L, double x);
31
32//! Phi_L(x) = [(2L+1)!! / x^L] * j_L(x). If tilde=true, returns 1 - Phi_L(x)
33double PhiL(int L, double x, bool tilde = false);
34
35//! Psi_L(x) = [x^{L+1} / (2L-1)!!] * y_L(x). If tilde=true, returns 1 - Psi_L(x)
36double PsiL(int L, double x, bool tilde = false);
37
38// //! Used for frequency-dependent Breit - more generic formula that can be used to evaluate spherical Bessel function of first kind for negative orders
39// template <typename T>
40// T exactGSL_JL_alt(int L, T x) {
41
42// // Very low x expansion [keeps terms up to order (wr)^8]
43// if (std::abs(x) < (T)1.0e-8) {
44// if (L == 0)
45// return 1.0;
46// else
47// return 0.0;
48// }
49
50// // series expansion for small wr [keeps every term up to order (wr)^8]
51// if (std::fabs(x) < 0.5) {
52// if (L == 0) {
53// return 1.0 - 0.166666667 * (x * x) + 0.00833333333 * qip::pow<4>(x) -
54// 0.000198412698 * qip::pow<6>(x) +
55// 0.00000275573192 * qip::pow<8>(x);
56// }
57// if (L == 1) {
58// return 0.333333333 * x - 0.0333333333 * (x * x * x) +
59// 0.00119047619 * qip::pow<5>(x) - 0.0000220458553 * qip::pow<7>(x);
60// }
61// if (L == 2) {
62// return 0.0666666667 * (x * x) - 0.00476190476 * qip::pow<4>(x) +
63// 0.000132275132 * qip::pow<6>(x) -
64// 0.00000200416867 * qip::pow<8>(x);
65// }
66// if (L == 3) {
67// return 0.00952380952 * (x * x * x) - 0.000529100529 * qip::pow<5>(x) +
68// 0.0000120250120 * qip::pow<7>(x);
69// }
70// if (L == 4) {
71// return 0.00105820105 * qip::pow<4>(x) - 0.0000481000481 * qip::pow<6>(x) +
72// 0.000000925000925 * qip::pow<8>(x);
73// }
74// if (L == 5) {
75// return 0.0000962000962 * qip::pow<5>(x) -
76// 0.0000037000037 * qip::pow<7>(x);
77// }
78// if (L == 6) {
79// return 0.0000074000074 * qip::pow<6>(x) -
80// 0.00000024666913 * qip::pow<8>(x);
81// }
82// if (L == 7) {
83// return 0.000000493333826 * qip::pow<7>(x);
84// }
85// }
86
87// // Explicit formulas for first few L's
88// if (L == 0) {
89// return std::sin(x) / x;
90// }
91// if (L == 1) {
92// return std::sin(x) / (x * x) - std::cos(x) / x;
93// }
94// if (L == 2) {
95// return (3.0 / (x * x) - 1.0) * std::sin(x) / x -
96// 3.0 * std::cos(x) / (x * x);
97// }
98// if (L == 3) {
99// return (15.0 / (x * x * x) - 6.0 / x) * std::sin(x) / x -
100// (15.0 / (x * x) - 1.0) * std::cos(x) / x;
101// }
102// if (L == 4) {
103// return 5.0 * (-21.0 + 2.0 * x * x) * std::cos(x) / qip::pow<4>(x) +
104// (105.0 - 45.0 * x * x + qip::pow<4>(x)) * std::sin(x) /
105// (qip::pow<5>(x));
106// }
107// if (L == 5) {
108// return (105.0 * x * x - 945.0 - qip::pow<4>(x)) * std::cos(x) /
109// qip::pow<5>(x) +
110// 15.0 * (63.0 - 28.0 * x * x + qip::pow<4>(x)) * std::sin(x) /
111// qip::pow<6>(x);
112// }
113// if (L == 6) {
114// return (10395.0 - qip::pow<6>(x) - 4725.0 * x * x +
115// 210.0 * qip::pow<4>(x)) *
116// std::sin(x) / qip::pow<7>(x) +
117// 21.0 * (60.0 * x * x - 495.0 - qip::pow<4>(x)) * std::cos(x) /
118// qip::pow<6>(x);
119// }
120// if (L == 7) {
121// return 7.0 *
122// (19305.0 - 4.0 * qip::pow<6>(x) - 8910.0 * x * x +
123// 450.0 * qip::pow<4>(x)) *
124// std::sin(x) / qip::pow<8>(x) +
125// (qip::pow<6>(x) + 17325.0 * x * x - 378.0 * qip::pow<4>(x) -
126// 135135.0) *
127// std::cos(x) / qip::pow<7>(x);
128// }
129
130// return (T)(sqrt(M_PI / (2.0 * x)) *
131// gsl_sf_bessel_Jnu(L + 1.0 / 2, (double)x));
132// }
133
134//! Spherical Bessel functions of second kind
135// template <typename T>
136// T exactGSL_YL(int L, T x) {
137// return (T)gsl_sf_bessel_yl(L, (double)x);
138// }
139
140// //! Used for frequency-dependent Breit - more generic formula that can be used to evaluate spherical Bessel function of second kind for negative orders
141// template <typename T>
142// T exactGSL_YL_alt(int L, T x) {
143
144// // series expansion for small wr [keeps every term up to order (wr)^8]
145// if (std::fabs(x) < 0.1) {
146// if (L == 0) {
147// return -1.0 / x + 0.5 * x - 0.0416666667 * (x * x * x) +
148// 0.00138888888 * qip::pow<5>(x) - 0.0000248015873 * qip::pow<7>(x);
149// }
150// if (L == 1) {
151// return -1.0 / (x * x) - 0.5 + 0.125 * (x * x) -
152// 0.00694444444 * qip::pow<4>(x) + 0.000173611111 * qip::pow<6>(x) -
153// 0.00000248015873 * qip::pow<8>(x);
154// }
155// if (L == 2) {
156// return -3.0 / (x * x * x) - 0.5 / x - 0.125 * x +
157// 0.0208333333 * (x * x * x) - 0.000868055555 * qip::pow<5>(x) +
158// 0.0000173611111 * qip::pow<7>(x);
159// }
160// if (L == 3) {
161// return -15.0 / (qip::pow<4>(x)) - 1.5 / (x * x) - 0.125 -
162// 0.0208333333 * x * x + 0.00260416666 * qip::pow<4>(x) -
163// 0.0000868055555 * qip::pow<6>(x) +
164// 0.00000144675925 * qip::pow<8>(x);
165// }
166// if (L == 4) {
167// return -105.0 / (qip::pow<5>(x)) - 7.5 / (x * x * x) - 0.375 / x -
168// 0.0208333333 * x - 0.00260416666 * (x * x * x) +
169// 0.000260416666 * qip::pow<5>(x) -
170// 0.00000723379629 * qip::pow<7>(x);
171// }
172// if (L == 5) {
173// return -945.0 / (qip::pow<6>(x)) - 52.5 / (qip::pow<4>(x)) -
174// 1.875 / (x * x) - 0.0625 - 0.00260416666 * x * x -
175// 0.000260416666 * qip::pow<4>(x) +
176// 0.0000217013888 * qip::pow<6>(x) -
177// 0.000000516699735 * qip::pow<8>(x);
178// }
179// if (L == 6) {
180// return -10395.0 / (qip::pow<7>(x)) - 472.5 / (qip::pow<5>(x)) -
181// 13.125 / (x * x * x) - 0.3125 / x - 0.0078125 * x -
182// 0.000260416666 * (x * x * x) - 0.0000217013888 * qip::pow<5>(x) +
183// 0.0000015500992 * qip::pow<7>(x);
184// }
185// if (L == 7) {
186// return -135135.0 / qip::pow<8>(x) - 5197.5 / qip::pow<6>(x) -
187// 118.125 / qip::pow<4>(x) - 2.1875 / (x * x) - 0.0390625 -
188// 0.00078125 * x * x - 0.0000217013888 * qip::pow<4>(x) -
189// 0.00000155009920 * qip::pow<6>(x) +
190// 0.0000000968812004 * qip::pow<8>(x);
191// }
192// }
193
194// // Explicit formulas for first few L's
195// if (L == 0) {
196// return -std::cos(x) / x;
197// }
198// if (L == 1) {
199// return -std::cos(x) / (x * x) - std::sin(x) / x;
200// }
201// if (L == 2) {
202// return (1.0 - 3.0 / (x * x)) * std::cos(x) / x -
203// 3.0 * std::sin(x) / (x * x);
204// }
205// if (L == 3) {
206// return (6.0 / x - 15.0 / (x * x * x)) * std::cos(x) / x -
207// (15.0 / (x * x) - 1.0) * std::sin(x) / x;
208// }
209// if (L == 4) {
210// return 5.0 * (-21.0 + 2.0 * x * x) * std::sin(x) / qip::pow<4>(x) +
211// (45.0 * x * x - 105.0 - qip::pow<4>(x)) * std::cos(x) /
212// (qip::pow<5>(x));
213// }
214// if (L == 5) {
215// return 15.0 * (28.0 * x * x - qip::pow<4>(x) - 63.0) * std::cos(x) /
216// qip::pow<6>(x) +
217// (105.0 * x * x - qip::pow<4>(x) - 945.0) * std::sin(x) /
218// qip::pow<5>(x);
219// }
220// if (L == 6) {
221// return (qip::pow<6>(x) - 10395.0 + 4725.0 * x * x -
222// 210.0 * qip::pow<4>(x)) *
223// std::cos(x) / qip::pow<7>(x) +
224// 21.0 * (60.0 * x * x - 495.0 - qip::pow<4>(x)) * std::sin(x) /
225// qip::pow<6>(x);
226// }
227// if (L == 7) {
228// return 7.0 *
229// (4.0 * qip::pow<6>(x) + 8910.0 * x * x - 450.0 * qip::pow<4>(x) -
230// 19305.0) *
231// std::cos(x) / qip::pow<8>(x) +
232// (qip::pow<6>(x) + 17325.0 * x * x - 378.0 * qip::pow<4>(x) -
233// 135135.0) *
234// std::sin(x) / qip::pow<7>(x);
235// }
236
237// return (T)(sqrt(M_PI / (2.0 * x)) *
238// gsl_sf_bessel_Ynu(L + 1.0 / 2, (double)x));
239// }
240
241//==============================================================================
242//! Creates a vector of Jl(r) for given r
243std::vector<double> fillBesselVec(int l, const std::vector<double> &xvec);
244
245std::vector<double> fillBesselVec_kr(int l, double k,
246 const std::vector<double> &rvec);
247
248void fillBesselVec_kr(int l, double k, const std::vector<double> &r,
249 std::vector<double> *jl);
250
251//==============================================================================
252//! Spherical Bessel Lookup table; in the form j_L(qr) = J[L][q][r].
253/*!
254@details
255 - Allows 'first match', 'nearest', and 'interpolation' lookup
256 - All of these are only accurate if grid dense enough
257 - Ideally, use exact same grid to extract values as used to create them
258*/
259class JL_table {
260public:
261 JL_table() {}
262 JL_table(int max_L, const std::vector<double> &q,
263 const std::vector<double> &r) {
264 fill(max_L, q, r);
265 }
266
267 //! If derivatives required for degree L, max_L = L+1
268 void fill(int max_L, const std::vector<double> &q,
269 const std::vector<double> &r) {
270 m_q = q;
271 m_J_L_q.resize(std::size_t(max_L + 1), q.size());
272 m_J_L_q_on_qr.resize(std::size_t(max_L + 1), q.size());
273 using namespace qip::overloads;
274#pragma omp parallel for collapse(2)
275 for (auto L = 0ul; L <= std::size_t(max_L); ++L) {
276 for (auto iq = 0ul; iq < m_J_L_q.cols(); ++iq) {
277 const auto tq = q[iq];
278 // Fill jL(qr)
279 m_J_L_q[L][iq] = fillBesselVec_kr(int(L), tq, r);
280 // Store jL(qr)/qr
281 m_J_L_q_on_qr[L][iq] = m_J_L_q[L][iq] / (tq * r);
282 }
283 }
284 }
285
286 //----------------------------------------------------------------------------
287 //! Direct access to jL(q*r) for specific q grid index
288 const std::vector<double> &at(std::size_t L, std::size_t iq) const {
289 return m_J_L_q.atc(L, iq);
290 }
291
292 //----------------------------------------------------------------------------
293 //! Returns jL(q_i*r) for the first grid point q_i such that q_i >= q
294 const std::vector<double> &jL(int L, double q) const {
295 // The '-1' here ensures we always get a valid index, even if q > m_q.back()
296 const auto it = std::lower_bound(m_q.begin(), m_q.end() - 1, q);
297 const auto iq = std::size_t(std::distance(m_q.begin(), it));
298 return m_J_L_q.atc(std::size_t(L), iq);
299 }
300
301 //----------------------------------------------------------------------------
302 //! Returns jL(q_i*r) for the grid point q_i nearest to the requested q.
303 const std::vector<double> &jL_nearest(int L, double q) const {
304
305 // Clamp
306 if (q <= m_q.front())
307 return m_J_L_q.atc(std::size_t(L), 0);
308 if (q >= m_q.back())
309 return m_J_L_q.atc(std::size_t(L), m_q.size() - 1);
310
311 // Find i such that m_q[i] <= q < m_q[i+1]
312 const auto it = std::lower_bound(m_q.begin() + 1, m_q.end() - 1, q);
313 const auto i = std::size_t(std::distance(m_q.begin(), it)) - 1;
314
315 // Pick nearest
316 const auto iq = (q - m_q[i] < m_q[i + 1] - q) ? i : i + 1;
317
318 return m_J_L_q.atc(std::size_t(L), iq);
319 }
320
321 //----------------------------------------------------------------------------
322 //! Returns jL(q_i*r)/(q_i*r) for the grid point q_i nearest to the requested q.
323 const std::vector<double> &jL_on_qr_nearest(int L, double q) const {
324
325 // Clamp
326 if (q <= m_q.front())
327 return m_J_L_q_on_qr.atc(std::size_t(L), 0);
328 if (q >= m_q.back())
329 return m_J_L_q_on_qr.atc(std::size_t(L), m_q.size() - 1);
330
331 // Find i such that m_q[i] <= q < m_q[i+1]
332 const auto it = std::lower_bound(m_q.begin() + 1, m_q.end() - 1, q);
333 const auto i = std::size_t(std::distance(m_q.begin(), it)) - 1;
334
335 // Pick nearest
336 const auto iq = (q - m_q[i] < m_q[i + 1] - q) ? i : i + 1;
337
338 return m_J_L_q_on_qr.atc(std::size_t(L), iq);
339 }
340
341 //----------------------------------------------------------------------------
342 //! Returns jL(q*r) interpolated linearly between grid points.
343 //! nb: assumes q grid dense enough that linear interp is reasonable!
344 std::vector<double> jL_interp(int L, double q) const {
345
346 // Clamp q to the tabulated range
347 if (q <= m_q.front())
348 return m_J_L_q.atc(std::size_t(L), 0);
349 if (q >= m_q.back())
350 return m_J_L_q.atc(std::size_t(L), m_q.size() - 1);
351
352 // Find i such that m_q[i] <= q < m_q[i+1]
353 const auto it = std::lower_bound(m_q.begin() + 1, m_q.end() - 1, q);
354 const auto i = std::size_t(std::distance(m_q.begin(), it)) - 1;
355
356 // Linear interpolation weight
357 // const double t = (q - m_q[i]) / (m_q[i + 1] - m_q[i]);
358
359 const double logq = std::log10(q);
360 const double logq0 = std::log10(m_q[i]);
361 const double logq1 = std::log10(m_q[i + 1]);
362 const double t = (logq - logq0) / (logq1 - logq0);
363
364 const auto &v0 = m_J_L_q.atc(std::size_t(L), i);
365 const auto &v1 = m_J_L_q.atc(std::size_t(L), i + 1);
366
367 using namespace qip::overloads;
368 return (1.0 - t) * v0 + t * v1;
369 }
370
371 //----------------------------------------------------------------------------
372private:
374 LinAlg::Matrix<std::vector<double>> m_J_L_q_on_qr{};
375 std::vector<double> m_q{};
376};
377
378} // namespace SphericalBessel
Matrix class; row-major.
Definition Matrix.hpp:39
const T & atc(std::size_t row_i, std::size_t col_j) const
const ref () index access (with range checking). (i,j) ith row, jth col
Definition Matrix.hpp:146
std::size_t cols() const
Return columns [minor index size].
Definition Matrix.hpp:114
void resize(std::size_t rows, std::size_t cols)
Resizes matrix to new dimension; all values reset to default.
Definition Matrix.hpp:96
Spherical Bessel Lookup table; in the form j_L(qr) = J[L][q][r].
Definition SphericalBessel.hpp:259
const std::vector< double > & jL_nearest(int L, double q) const
Returns jL(q_i*r) for the grid point q_i nearest to the requested q.
Definition SphericalBessel.hpp:303
const std::vector< double > & at(std::size_t L, std::size_t iq) const
Direct access to jL(q*r) for specific q grid index.
Definition SphericalBessel.hpp:288
const std::vector< double > & jL_on_qr_nearest(int L, double q) const
Returns jL(q_i*r)/(q_i*r) for the grid point q_i nearest to the requested q.
Definition SphericalBessel.hpp:323
const std::vector< double > & jL(int L, double q) const
Returns jL(q_i*r) for the first grid point q_i such that q_i >= q.
Definition SphericalBessel.hpp:294
std::vector< double > jL_interp(int L, double q) const
Returns jL(q*r) interpolated linearly between grid points. nb: assumes q grid dense enough that linea...
Definition SphericalBessel.hpp:344
void fill(int max_L, const std::vector< double > &q, const std::vector< double > &r)
If derivatives required for degree L, max_L = L+1.
Definition SphericalBessel.hpp:268
Wrappers for returning Spherical Bessel functions.
Definition SphericalBessel.cpp:7
double PsiL(int L, double x, bool tilde)
Psi_L(x) = [x^{L+1} / (2L-1)!!] * y_L(x). If tilde=true, returns 1 - Psi_L(x)
Definition SphericalBessel.cpp:253
double exactGSL_JL(int L, double x)
Spherical Bessel function of first kind via GSL.
Definition SphericalBessel.cpp:93
double PhiL(int L, double x, bool tilde)
Phi_L(x) = [(2L+1)!! / x^L] * j_L(x). If tilde=true, returns 1 - Phi_L(x)
Definition SphericalBessel.cpp:199
double yL(int L, double x)
Used for frequency-dependent Breit - more generic formula that can be used to evaluate spherical Bess...
Definition SphericalBessel.cpp:99
double exactGSL_YL(int L, double x)
Spherical Bessel functions of second kind.
Definition SphericalBessel.cpp:96
double jL(int L, double x)
Spherical Bessel function; uses low-x approximation where appropriate.
Definition SphericalBessel.cpp:10
std::vector< double > fillBesselVec(int l, const std::vector< double > &xvec)
Spherical Bessel functions of second kind.
Definition SphericalBessel.cpp:302
Operator overloads for std::vector.
Definition Vector.hpp:503