ampsci
High-precision calculations for one- and two-valence atomic systems
NumCalc_quadIntegrate.hpp
1#pragma once
2#include "Maths/Grid.hpp"
3#include "Maths/NumCalc_coeficients.hpp"
4#include "qip/Vector.hpp"
5#include <array>
6#include <cmath>
7#include <functional>
8#include <iostream>
9#include <vector>
10
11//! Numerical integration and differentiation routines.
12namespace NumCalc {
13
14//! Quadrature integration order; must be odd and in [1, 13]
15constexpr std::size_t Nquad = 13;
16static_assert(
17 Nquad >= 1 && Nquad <= 13 && Nquad % 2 != 0,
18 "\nFAIL10 in NumCalc: Nquad must be in [1,13], and must be odd\n");
19
20// Instantiate quadrature coefficients for the chosen order:
21constexpr QintCoefs<Nquad> quintcoef;
22constexpr auto cq = quintcoef.cq;
23constexpr auto dq_inv = quintcoef.dq_inv;
24
25//==============================================================================
26/*!
27 @brief Quadrature integration of one or more vectors over a regular grid in t.
28 @details
29 Integrates the element-wise product `f1[i] * rest[i] * ...` from index `beg`
30 to `end-1` (i.e., `end` is exclusive), multiplied by the step size `dt`.
31
32 The grid must be uniform in the parametric variable t, but not necessarily
33 in the physical variable x. For a non-uniform x grid, fold the Jacobian
34 (dx/dt) into the integrand: pass `f(x) * (dx/dt)` rather than `f(x)` alone.
35
36 End-point corrections (using `Nquad`-point quadrature weights) are applied
37 at the start and end of the range. The additivity property is preserved:
38 `int(a->b) + int(b->c) == int(a->c)`.
39
40 No safety checks are performed. Requirements:
41 - `end - beg > 2 * Nquad`
42 - `end >= Nquad`
43 - `beg + 2 * Nquad <= f1.size()`
44
45 @param dt Step size in the parametric variable t.
46 @param beg First grid index (inclusive).
47 @param end Last grid index (exclusive); if 0, defaults to `f1.size()`.
48 @param f1 First vector to integrate (include Jacobian if grid is non-uniform in x).
49 @param rest Additional vectors; all multiplied element-wise with f1.
50 @returns Integral of the element-wise product times dt.
51*/
52template <typename C, typename... Args>
53inline double integrate(const double dt, std::size_t beg, std::size_t end,
54 const C &f1, const Args &...rest) {
55
56 const auto max_grid = f1.size();
57 if (end == 0)
58 end = max_grid;
59 const auto end_mid = std::min(max_grid - Nquad, end);
60 const auto start_mid = std::max(Nquad, beg);
61
62 double Rint_ends = qip::inner_product_sub(beg, Nquad, cq, f1, rest...);
63
64 const double Rint_mid =
65 qip::inner_product_sub(start_mid, end_mid, f1, rest...);
66
67 for (auto i = end_mid; i < end; ++i) {
68 Rint_ends += cq[end_mid + Nquad - i - 1] * qip::multiply_at(i, f1, rest...);
69 }
70 return (Rint_mid + dq_inv * Rint_ends) * dt;
71}
72
73//==============================================================================
74/*!
75 @brief Computes the derivative df/dr on a non-uniform grid.
76 @details
77 Given f sampled on a non-uniform grid r(t) with uniform step dt,
78 computes df/dr using finite difference coefficients:
79
80 `df/dr = (df/di) / (dt * dr/dt)`
81
82 where i is the grid index. Coefficients from:
83 http://en.wikipedia.org/wiki/Finite_difference_coefficient
84
85 Uses a 7-point stencil in the interior; lower-order one-sided differences
86 near the endpoints.
87
88 For `order > 1`, applies the derivative recursively.
89
90 @param f Function values on the grid.
91 @param drdt Jacobian dr/dt at each grid point.
92 @param dt Uniform step size in the parametric variable t.
93 @param order Derivative order (default 1); higher orders applied recursively.
94 @returns df/dr at each grid point.
95*/
96template <typename T>
97inline std::vector<T> derivative(const std::vector<T> &f,
98 const std::vector<T> &drdt, const T dt,
99 const int order = 1) {
100 // df/dr = df/dt * dt/dr = (df/dt) / (dr/dt)
101 // = (df/di) * (di/dt) / (dr/dt)
102 // = (df/di) / (dt * dr/dt)
103
104 std::size_t num_points = f.size();
105 std::vector<T> df(num_points);
106 if (num_points < 4)
107 return df;
108
109 df[0] = (f[1] - f[0]) / (dt * drdt[0]);
110 df[num_points - 1] =
111 (f[num_points - 1] - f[num_points - 2]) / (dt * drdt[num_points - 1]);
112
113 df[1] = (f[2] - f[0]) / (2 * dt * drdt[1]);
114 df[num_points - 2] =
115 (f[num_points - 1] - f[num_points - 3]) / (2 * dt * drdt[num_points - 2]);
116
117 df[2] = (f[0] - 8 * f[1] + 8 * f[3] - f[4]) / (12 * dt * drdt[2]);
118 df[num_points - 3] = (f[num_points - 5] - 8 * f[num_points - 4] +
119 8 * f[num_points - 2] - f[num_points - 1]) /
120 (12 * dt * drdt[num_points - 3]);
121
122 df[3] = (-1 * f[0] + 9 * f[1] - 45 * f[2] + 45 * f[4] - 9 * f[5] + 1 * f[6]) /
123 (60 * dt * drdt[3]);
124 df[num_points - 4] =
125 (-1 * f[num_points - 7] + 9 * f[num_points - 6] - 45 * f[num_points - 5] +
126 45 * f[num_points - 3] - 9 * f[num_points - 2] + 1 * f[num_points - 1]) /
127 (60 * dt * drdt[num_points - 4]);
128
129 for (std::size_t i = 4; i < (num_points - 4); i++) {
130 df[i] = ((1.0 / 8) * f[i - 4] - (4.0 / 3) * f[i - 3] + 7 * f[i - 2] -
131 28 * f[i - 1] - (1.0 / 8) * f[i + 4] + (4.0 / 3) * f[i + 3] -
132 7 * f[i + 2] + 28 * f[i + 1]) /
133 (35 * dt * drdt[i]);
134 }
135
136 if (order > 1)
137 df = derivative(df, drdt, dt, order - 1);
138
139 return df;
140}
141
142//==============================================================================
143
144//! Integration direction for additivePIntegral
146 //! Integrate from 0 outward to r
148 //! Integrate from infinity inward to r
151
152/*!
153 @brief Additively accumulates a partial integral into `answer`.
154 @details
155 Computes and adds to `answer`:
156
157 `answer(r) += f(r) * Int[g(r') * h(r'), {r', 0, r}]`
158
159 (or from infinity, depending on `direction`). Uses the trapezoidal rule.
160
161 @note This is additive (`+=`). `answer` must already exist and be
162 initialised (typically to zero).
163
164 @param answer Accumulation vector (modified in place).
165 @param f Prefactor at each grid point.
166 @param g First integrand factor.
167 @param h Second integrand factor.
168 @param gr Radial grid (provides dr/du and du).
169 @param pinf Upper index limit; 0 means use full grid.
170*/
171template <Direction direction, typename Real>
172inline void
173additivePIntegral(std::vector<Real> &answer, const std::vector<Real> &f,
174 const std::vector<Real> &g, const std::vector<Real> &h,
175 const Grid &gr, std::size_t pinf = 0) {
176 const auto size = g.size();
177 if (pinf == 0 || pinf >= size)
178 pinf = size;
179 const auto max = static_cast<int>(pinf - 1); // must be signed
180
181 constexpr const bool forward = (direction == zero_to_r);
182 constexpr const int inc = forward ? +1 : -1;
183 const auto init = forward ? 0ul : std::size_t(max);
184 const auto fin = forward ? std::size_t(max) : 0ul;
185
186 Real x = int(init) < max ? 0.5 * g[init] * h[init] * gr.drdu()[init] : 0.0;
187 answer[init] += f[init] * x * gr.du();
188 for (auto i = int(init) + inc; i != int(fin) + inc; i += inc) {
189 const auto im = std::size_t(i - inc);
190 const auto i2 = std::size_t(i);
191 x += 0.5 * (g[im] * h[im] * gr.drdu()[im] + g[i2] * h[i2] * gr.drdu()[i2]);
192 answer[i2] += f[i2] * x * gr.du();
193 }
194 if (int(fin) < max)
195 answer[fin] += 0.5 * f[fin] * g[fin] * h[fin] * gr.drdu()[fin] * gr.du();
196}
197
198/*!
199 @brief Additively accumulates a partial integral into `answer` (two-function overload).
200 @details
201 As above, but integrand has only one factor g (no h):
202
203 `answer(r) += f(r) * Int[g(r'), {r', 0, r}]`
204
205 @note This is additive (`+=`). `answer` must already exist and be
206 initialised (typically to zero).
207*/
208template <Direction direction, typename Real>
209inline void additivePIntegral(std::vector<Real> &answer,
210 const std::vector<Real> &f,
211 const std::vector<Real> &g, const Grid &gr,
212 std::size_t pinf = 0) {
213 const auto size = g.size();
214 if (pinf == 0 || pinf >= size)
215 pinf = size;
216 const auto max = static_cast<int>(pinf - 1); // must be signed
217
218 constexpr const bool forward = (direction == zero_to_r);
219 constexpr const int inc = forward ? +1 : -1;
220 const auto init = forward ? 0ul : std::size_t(max);
221 const auto fin = forward ? std::size_t(max) : 0ul;
222
223 Real x = int(init) < max ? 0.5 * g[init] * gr.drdu()[init] : 0.0;
224 answer[init] += f[init] * x * gr.du();
225 for (auto i = int(init) + inc; i != int(fin) + inc; i += inc) {
226 const auto im = std::size_t(i - inc);
227 const auto i2 = std::size_t(i);
228 x += 0.5 * (g[im] * gr.drdu()[im] + g[i2] * gr.drdu()[i2]);
229 answer[i2] += f[i2] * x * gr.du();
230 }
231 if (int(fin) < max)
232 answer[fin] += 0.5 * f[fin] * g[fin] * gr.drdu()[fin] * gr.du();
233}
234
235//------------------------------------------------------------------------------
236/*!
237 @brief Returns the partial integral `f(r) * Int[g(r')*h(r'), {r', 0, r}]`.
238 @details
239 Allocates and returns the result vector; wrapper around additivePIntegral().
240*/
241template <Direction direction, typename Real>
242std::vector<Real> partialIntegral(const std::vector<Real> &f,
243 const std::vector<Real> &g,
244 const std::vector<Real> &h, const Grid &gr,
245 std::size_t pinf = 0) {
246 std::vector<Real> answer(f.size(), 0.0);
247 additivePIntegral<direction>(answer, f, g, h, gr, pinf);
248 return answer;
249}
250
251//==============================================================================
252//==============================================================================
253
254//! Grid type for num_integrate
255enum t_grid { linear, logarithmic };
256
257//! Returns a function giving linearly-spaced x values: x(i) = a + i*dt
258inline std::function<double(long unsigned)> linx(double a, double dt) {
259 return [=](long unsigned i) { return a + double(i) * dt; };
260}
261
262//! Returns a function that always returns 1.0 (uniform Jacobian)
263inline std::function<double(long unsigned)> one() {
264 return [=](long unsigned) { return 1.0; };
265}
266
267//! Returns a function giving logarithmically-spaced x values: x(i) = a*exp(i*dt)
268inline std::function<double(long unsigned)> logx(double a, double dt) {
269 return [=](long unsigned i) { return a * std::exp(double(i) * dt); };
270}
271
272//==============================================================================
273/*!
274 @brief Numerically integrates a function f(x) over [a, b].
275 @details
276 Uses the same quadrature scheme as `integrate()`, on either a linear or
277 logarithmic grid of `n_pts` points.
278
279 Returns 0 if `a >= b` or `n_pts <= 1`.
280
281 @param f Function to integrate.
282 @param a Lower bound.
283 @param b Upper bound.
284 @param n_pts Number of quadrature points.
285 @param type Grid spacing: linear (default) or logarithmic.
286 @returns Approximate integral of f over [a, b].
287*/
288inline double num_integrate(const std::function<double(double)> &f, double a,
289 double b, long unsigned n_pts,
290 t_grid type = linear) {
291 if (a >= b || n_pts <= 1)
292 return 0.0;
293 const auto dt = (type == linear) ? (b - a) / double(n_pts - 1) :
294 std::log(b / a) / double(n_pts - 1);
295
296 std::function<double(long unsigned)> x =
297 (type == linear) ? linx(a, dt) : logx(a, dt);
298 std::function<double(long unsigned)> dxdt =
299 (type == linear) ? one() : logx(a, dt);
300
301 double Rint_s = 0.0;
302 for (long unsigned i = 0; i < Nquad; i++) {
303 Rint_s += cq[i] * f(x(i)) * dxdt(i);
304 }
305
306 double Rint_m = 0.0;
307 for (auto i = Nquad; i < n_pts - Nquad; i++) {
308 Rint_m += f(x(i)) * dxdt(i);
309 }
310
311 double Rint_e = 0;
312 for (auto i = n_pts - Nquad; i < n_pts; i++) {
313 Rint_e += cq[n_pts - i - 1] * f(x(i)) * dxdt(i);
314 }
315
316 return (Rint_m + dq_inv * (Rint_s + Rint_e)) * dt;
317}
318
319} // namespace NumCalc
Non-uniform radial grid with Jacobian, suitable for atomic structure calculations.
Definition Grid.hpp:85
auto du() const
Uniform step size du.
Definition Grid.hpp:124
const std::vector< double > & drdu() const
Full Jacobian vector dr/du.
Definition Grid.hpp:140
Numerical integration and differentiation routines.
Definition NumCalc_coeficients.hpp:4
std::vector< Real > partialIntegral(const std::vector< Real > &f, const std::vector< Real > &g, const std::vector< Real > &h, const Grid &gr, std::size_t pinf=0)
Returns the partial integral ‘f(r) * Int[g(r’)*h(r'), {r', 0, r}]`.
Definition NumCalc_quadIntegrate.hpp:242
t_grid
Grid type for num_integrate.
Definition NumCalc_quadIntegrate.hpp:255
double integrate(const double dt, std::size_t beg, std::size_t end, const C &f1, const Args &...rest)
Quadrature integration of one or more vectors over a regular grid in t.
Definition NumCalc_quadIntegrate.hpp:53
double num_integrate(const std::function< double(double)> &f, double a, double b, long unsigned n_pts, t_grid type=linear)
Numerically integrates a function f(x) over [a, b].
Definition NumCalc_quadIntegrate.hpp:288
Direction
Integration direction for additivePIntegral.
Definition NumCalc_quadIntegrate.hpp:145
@ r_to_inf
Integrate from infinity inward to r.
Definition NumCalc_quadIntegrate.hpp:149
@ zero_to_r
Integrate from 0 outward to r.
Definition NumCalc_quadIntegrate.hpp:147
std::function< double(long unsigned)> one()
Returns a function that always returns 1.0 (uniform Jacobian)
Definition NumCalc_quadIntegrate.hpp:263
void additivePIntegral(std::vector< Real > &answer, const std::vector< Real > &f, const std::vector< Real > &g, const std::vector< Real > &h, const Grid &gr, std::size_t pinf=0)
Additively accumulates a partial integral into answer.
Definition NumCalc_quadIntegrate.hpp:173
std::function< double(long unsigned)> linx(double a, double dt)
Returns a function giving linearly-spaced x values: x(i) = a + i*dt.
Definition NumCalc_quadIntegrate.hpp:258
constexpr std::size_t Nquad
Quadrature integration order; must be odd and in [1, 13].
Definition NumCalc_quadIntegrate.hpp:15
std::vector< T > derivative(const std::vector< T > &f, const std::vector< T > &drdt, const T dt, const int order=1)
Computes the derivative df/dr on a non-uniform grid.
Definition NumCalc_quadIntegrate.hpp:97
std::function< double(long unsigned)> logx(double a, double dt)
Returns a function giving logarithmically-spaced x values: x(i) = a*exp(i*dt)
Definition NumCalc_quadIntegrate.hpp:268
Quadrature integration coefficients for a given number of points N. cq holds the weights; the step co...
Definition NumCalc_coeficients.hpp:10
auto inner_product_sub(std::size_t p0, std::size_t pinf, const T &first, const Args &...rest)
Inner product over the subrange [p0, pinf).
Definition Vector.hpp:378
constexpr auto multiply_at(std::size_t i, const T &first, const Args &...rest)
Helper: returns first[i] * rest[i] * ... at index i.
Definition Vector.hpp:357