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 Spherical Bessel functions j_L(x) and y_L(x), and related utilities.
10 @details
11 Provides an approximate version (good to ~1e-9) and exact GSL-backed versions,
12 plus cell-averaged vectors for integration on coarse grids, and a lookup table.
13*/
14namespace SphericalBessel {
15
16//==============================================================================
17//! Spherical Bessel function j_L(x); uses low-x approximation where appropriate
18double jL(int L, double x);
19
20//! Spherical Bessel function of first kind via GSL
21double exactGSL_JL(int L, double x);
22
23//! Spherical Bessel functions of second kind
24double exactGSL_YL(int L, double x);
25
26//==============================================================================
27
28//! Spherical Bessel function of second kind, y_L(x)
29double yL(int L, double x);
30
31//! Phi_L(x) = [(2L+1)!! / x^L] * j_L(x). If tilde=true, returns 1 - Phi_L(x)
32double PhiL(int L, double x, bool tilde = false);
33
34//! Psi_L(x) = [x^{L+1} / (2L-1)!!] * y_L(x). If tilde=true, returns 1 - Psi_L(x)
35double PsiL(int L, double x, bool tilde = false);
36
37//==============================================================================
38/*!
39 @brief Fills a vector with the spherical Bessel function j_L sampled on the
40 supplied grid; oscillation-resolving cell-average is used where needed.
41 @details
42 Returns a vector v such that v[i] is the value (or cell-average) of j_L on
43 the grid point xvec[i]. The "cell" at index i is the interval bounded by the
44 midpoints to its neighbours (asymmetric for the first/last point).
45
46 This vector is intended for integration of the form
47 \f$ \int j_L(x)\,\psi(x)\,dx \approx \sum_i w_i\,v_i\,\psi(x_i) \f$
48 against a smooth wavefunction \f$\psi\f$ sampled on the same grid. When the
49 grid cell at index i resolves the oscillation of j_L (\f$\Delta x_i \le
50 \lambda_j / N\f$ with \f$\lambda_j = 2\pi\f$ asymptotically and N = 10), v[i]
51 is the point value j_L(xvec[i]). When the cell is too coarse, v[i] is the
52 cell average
53 \f[
54 v_i = \frac{1}{\Delta x_i}\int_{\text{cell}_i} j_L(x)\,dx ,
55 \f]
56 computed by trapezoidal sub-sampling with enough sub-points to resolve j_L
57 on the cell. Using these cell averages in the Riemann-sum quadrature is
58 equivalent to a Filon-style integration: the (smooth) factor \f$\psi\f$ is
59 treated as piecewise-constant per cell while the oscillation of j_L is
60 integrated exactly within each cell. This recovers the correct
61 Riemann-Lebesgue (~1/x) suppression at large x and removes the aliasing seen
62 with pure point-sampling on a coarse grid.
63
64 @param l Order of the spherical Bessel function.
65 @param xvec Grid (monotonic) on which j_L is sampled.
66 @param cell_average If true (default), enables the cell averaging described
67 above. If false, every entry is a pure point sample
68 j_L(xvec[i]); use this for inspection / plotting or to
69 reproduce legacy behaviour.
70 @return Vector v with v[i] = j_L(xvec[i]) or its cell average; same size as xvec.
71
72 @warning The returned values are NOT pure point samples on cells where
73 averaging triggers. They are only meaningful in conjunction with a
74 Riemann-sum-style quadrature using the same grid weights.
75*/
76std::vector<double> fillBesselVec(int l, const std::vector<double> &xvec,
77 bool cell_average = true);
78
79/*!
80 @brief As fillBesselVec, with the argument of j_L being k*r instead of x.
81 @details
82 Returns a vector v with v[i] = j_L(k*rvec[i]) or, where the cell at index i
83 is too coarse for the oscillation of j_L(kr) (cell width
84 \f$\Delta r_i > 2\pi / (k\,N)\f$ with N = 10), the cell average
85 \f[
86 v_i = \frac{1}{\Delta r_i}\int_{\text{cell}_i} j_L(k r)\,dr .
87 \f]
88 See fillBesselVec for the usage and warning.
89
90 @param l Order of the spherical Bessel function.
91 @param k Momentum (or other) factor multiplying r in the argument.
92 @param rvec Radial grid on which j_L(kr) is sampled.
93 @param cell_average If true (default), enables cell averaging; if false,
94 point-samples j_L(k*rvec[i]) at every grid point.
95 @return Vector v of same length as rvec.
96*/
97std::vector<double> fillBesselVec_kr(int l, double k,
98 const std::vector<double> &rvec,
99 bool cell_average = true);
100
101/*!
102 @brief In-place variant of fillBesselVec_kr; writes into a caller-owned vector.
103 @details Identical semantics to the returning overload (including the
104 cell-averaging behaviour); jl is resized to r.size().
105 @param l Order of the spherical Bessel function.
106 @param k Momentum factor multiplying r in the argument.
107 @param r Radial grid.
108 @param jl Output vector (resized internally); must be non-null.
109 @param cell_average If true (default), enables cell averaging; if false,
110 point-samples j_L(k*r[i]) at every grid point.
111*/
112void fillBesselVec_kr(int l, double k, const std::vector<double> &r,
113 std::vector<double> *jl, bool cell_average = true);
114
115//==============================================================================
116/*!
117 @brief Lookup table of spherical Bessel functions: j_L(q*r) = J[L][q][r].
118 @details
119 Pre-computes and stores j_L(q*r) for a grid of q and r values.
120 Provides first-match, nearest, and linear-interpolation lookup.
121 All lookups are only accurate if the q grid is dense enough.
122 Ideally, use the same r grid to extract values as was used to fill the table.
123*/
124class JL_table {
125public:
126 //! Default construct empty table; must call fill() before use
128 //! Construct and fill the table; equivalent to default-construct + fill()
129 JL_table(int max_L, const std::vector<double> &q,
130 const std::vector<double> &r, bool cell_average = true) {
131 fill(max_L, q, r, cell_average);
132 }
133
134 //! If derivatives required for degree L, max_L = L+1.
135 //! cell_average is forwarded to fillBesselVec_kr (see its docs); default true.
136 void fill(int max_L, const std::vector<double> &q,
137 const std::vector<double> &r, bool cell_average = true) {
138 m_q = q;
139 m_J_L_q.resize(std::size_t(max_L + 1), q.size());
140 m_J_L_q_on_qr.resize(std::size_t(max_L + 1), q.size());
141 using namespace qip::overloads;
142#pragma omp parallel for collapse(2)
143 for (auto L = 0ul; L <= std::size_t(max_L); ++L) {
144 for (auto iq = 0ul; iq < m_J_L_q.cols(); ++iq) {
145 const auto tq = q[iq];
146 // Fill jL(qr) (cell-averaged if cell_average and grid is too coarse)
147 m_J_L_q[L][iq] = fillBesselVec_kr(int(L), tq, r, cell_average);
148 // Store jL(qr)/qr
149 m_J_L_q_on_qr[L][iq] = m_J_L_q[L][iq] / (tq * r);
150 }
151 }
152 }
153
154 //----------------------------------------------------------------------------
155 //! Direct access to jL(q*r) for specific q grid index
156 const std::vector<double> &at(std::size_t L, std::size_t iq) const {
157 return m_J_L_q.atc(L, iq);
158 }
159
160 //----------------------------------------------------------------------------
161 //! Returns jL(q_i*r) for the first grid point q_i such that q_i >= q
162 const std::vector<double> &jL(int L, double q) const {
163 // The '-1' here ensures we always get a valid index, even if q > m_q.back()
164 const auto it = std::lower_bound(m_q.begin(), m_q.end() - 1, q);
165 const auto iq = std::size_t(std::distance(m_q.begin(), it));
166 return m_J_L_q.atc(std::size_t(L), iq);
167 }
168
169 //----------------------------------------------------------------------------
170 //! Returns jL(q_i*r) for the grid point q_i nearest to the requested q.
171 const std::vector<double> &jL_nearest(int L, double q) const {
172
173 // Clamp
174 if (q <= m_q.front())
175 return m_J_L_q.atc(std::size_t(L), 0);
176 if (q >= m_q.back())
177 return m_J_L_q.atc(std::size_t(L), m_q.size() - 1);
178
179 // Find i such that m_q[i] <= q < m_q[i+1]
180 const auto it = std::lower_bound(m_q.begin() + 1, m_q.end() - 1, q);
181 const auto i = std::size_t(std::distance(m_q.begin(), it)) - 1;
182
183 // Pick nearest
184 const auto iq = (q - m_q[i] < m_q[i + 1] - q) ? i : i + 1;
185
186 return m_J_L_q.atc(std::size_t(L), iq);
187 }
188
189 //----------------------------------------------------------------------------
190 //! Returns jL(q_i*r)/(q_i*r) for the grid point q_i nearest to the requested q.
191 const std::vector<double> &jL_on_qr_nearest(int L, double q) const {
192
193 // Clamp
194 if (q <= m_q.front())
195 return m_J_L_q_on_qr.atc(std::size_t(L), 0);
196 if (q >= m_q.back())
197 return m_J_L_q_on_qr.atc(std::size_t(L), m_q.size() - 1);
198
199 // Find i such that m_q[i] <= q < m_q[i+1]
200 const auto it = std::lower_bound(m_q.begin() + 1, m_q.end() - 1, q);
201 const auto i = std::size_t(std::distance(m_q.begin(), it)) - 1;
202
203 // Pick nearest
204 const auto iq = (q - m_q[i] < m_q[i + 1] - q) ? i : i + 1;
205
206 return m_J_L_q_on_qr.atc(std::size_t(L), iq);
207 }
208
209 //----------------------------------------------------------------------------
210 //! Returns jL(q*r) interpolated linearly between grid points.
211 //! nb: assumes q grid dense enough that linear interp is reasonable!
212 std::vector<double> jL_interp(int L, double q) const {
213
214 // Clamp q to the tabulated range
215 if (q <= m_q.front())
216 return m_J_L_q.atc(std::size_t(L), 0);
217 if (q >= m_q.back())
218 return m_J_L_q.atc(std::size_t(L), m_q.size() - 1);
219
220 // Find i such that m_q[i] <= q < m_q[i+1]
221 const auto it = std::lower_bound(m_q.begin() + 1, m_q.end() - 1, q);
222 const auto i = std::size_t(std::distance(m_q.begin(), it)) - 1;
223
224 // Linear interpolation weight
225 // const double t = (q - m_q[i]) / (m_q[i + 1] - m_q[i]);
226
227 const double logq = std::log10(q);
228 const double logq0 = std::log10(m_q[i]);
229 const double logq1 = std::log10(m_q[i + 1]);
230 const double t = (logq - logq0) / (logq1 - logq0);
231
232 const auto &v0 = m_J_L_q.atc(std::size_t(L), i);
233 const auto &v1 = m_J_L_q.atc(std::size_t(L), i + 1);
234
235 using namespace qip::overloads;
236 return (1.0 - t) * v0 + t * v1;
237 }
238
239 //----------------------------------------------------------------------------
240private:
242 LinAlg::Matrix<std::vector<double>> m_J_L_q_on_qr{};
243 std::vector<double> m_q{};
244};
245
246} // namespace SphericalBessel
Row-major dense matrix with arithmetic and linear algebra support.
Definition Matrix.hpp:209
const T & atc(std::size_t row_i, std::size_t col_j) const
at(i,j): element access with range checking, const ref
Definition Matrix.hpp:315
std::size_t cols() const
Return columns [minor index size].
Definition Matrix.hpp:284
void resize(std::size_t rows, std::size_t cols)
Resizes matrix to new dimension; all values reset to default.
Definition Matrix.hpp:266
Lookup table of spherical Bessel functions: j_L(q*r) = J[L][q][r].
Definition SphericalBessel.hpp:124
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:171
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:156
void fill(int max_L, const std::vector< double > &q, const std::vector< double > &r, bool cell_average=true)
If derivatives required for degree L, max_L = L+1. cell_average is forwarded to fillBesselVec_kr (see...
Definition SphericalBessel.hpp:136
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:191
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:162
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:212
JL_table()
Default construct empty table; must call fill() before use.
Definition SphericalBessel.hpp:127
JL_table(int max_L, const std::vector< double > &q, const std::vector< double > &r, bool cell_average=true)
Construct and fill the table; equivalent to default-construct + fill()
Definition SphericalBessel.hpp:129
Spherical Bessel functions j_L(x) and y_L(x), and related utilities.
Definition SphericalBessel.cpp:9
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:264
double exactGSL_JL(int L, double x)
Spherical Bessel function of first kind via GSL.
Definition SphericalBessel.cpp:98
std::vector< double > fillBesselVec_kr(int l, double k, const std::vector< double > &rvec, bool cell_average)
As fillBesselVec, with the argument of j_L being k*r instead of x.
Definition SphericalBessel.cpp:386
std::vector< double > fillBesselVec(int l, const std::vector< double > &xvec, bool cell_average)
Fills a vector with the spherical Bessel function j_L sampled on the supplied grid; oscillation-resol...
Definition SphericalBessel.cpp:378
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:207
double yL(int L, double x)
Spherical Bessel function of second kind, y_L(x)
Definition SphericalBessel.cpp:104
double exactGSL_YL(int L, double x)
Spherical Bessel functions of second kind.
Definition SphericalBessel.cpp:101
double jL(int L, double x)
Spherical Bessel function j_L(x); uses low-x approximation where appropriate.
Definition SphericalBessel.cpp:12
Operator overloads for std::vector.
Definition Vector.hpp:503