Quantum 3xplorer (Q3)
An AI-native quantum simulation engine
Loading...
Searching...
No Matches
angular_grid.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * Angular quadrature for one-center exchange-correlation.
5 *
6 * XC is not linear in the density, so it cannot be evaluated channel by
7 * channel the way Hartree can: the lm-decomposed density has to be summed onto
8 * real directions, the functional evaluated pointwise there, and the result
9 * projected back onto Y_LM. That needs a quadrature exact for the products
10 * that appear — which is a Gauss-Legendre grid in cos θ crossed with a uniform
11 * grid in φ.
12 *
13 * Quantum ESPRESSO's PAW one-center machinery uses exactly this family, and the
14 * one-center parity gates — the µRy QE control and the absolute-reference
15 * gate — confirm the same choice reproduces both references, so quadrature is not a
16 * candidate explanation for a mismatch against either. A Lebedev grid would
17 * also work and is not used: it would be a third convention for no accuracy
18 * gain at one-center scale.
19 *
20 * Sizing: Gauss-Legendre with n points integrates polynomials in cos θ up to
21 * degree 2n−1 exactly, and products of two Y_LM reach degree 2·l_max, so
22 * n = l_max + 1 suffices for the density-squared products; the φ grid needs
23 * more than 2·l_max points to resolve the azimuthal modes. Both are taken
24 * with a factor of three of headroom, since the cost is negligible at
25 * one-center scale and GGA raises the effective degree.
26 */
27
28#include <cmath>
29#include <vector>
30
33
34namespace q3 {
35namespace paw_one_center {
36
37/// One direction on the unit sphere with its quadrature weight.
42 Real weight{}; // ∫dΩ f ≈ Σ weight · f, so Σ weight = 4π
43};
44
45/**
46 * Gauss-Legendre nodes and weights on [-1, 1], by Newton iteration on the
47 * Legendre polynomial. Converges quadratically; the iteration is run to
48 * machine tolerance because the exactness gate leaves no room for a loose
49 * root.
50 */
51inline void
52gauss_legendre_nodes(int number_of_points, std::vector<Real>& nodes, std::vector<Real>& weights) {
53 nodes.assign(number_of_points, 0.0);
54 weights.assign(number_of_points, 0.0);
55 for (int index = 0; index < number_of_points; index++) {
56 // Chebyshev starting guess, then Newton on P_n.
57 Real node = std::cos(constants::pi * (static_cast<Real>(index) + 0.75) /
58 (static_cast<Real>(number_of_points) + 0.5));
59 Real derivative = 0.0;
60 for (int iteration = 0; iteration < 100; iteration++) {
61 Real previous = 1.0;
62 Real current = 0.0;
63 for (int degree = 0; degree < number_of_points; degree++) {
64 Real older = current;
65 current = previous;
66 previous =
67 ((2.0 * degree + 1.0) * node * current - degree * older) / (degree + 1.0);
68 }
69 derivative = number_of_points * (node * previous - current) / (node * node - 1.0);
70 Real step = previous / derivative;
71 node -= step;
72 if (std::abs(step) < 1e-15)
73 break;
74 }
75 nodes[index] = node;
76 weights[index] = 2.0 / ((1.0 - node * node) * derivative * derivative);
77 }
78}
79
80/**
81 * Build the θ × φ product grid.
82 *
83 * @param maximum_angular_momentum l_max the grid must integrate products of
84 */
85inline std::vector<AngularPoint> build_angular_grid(int maximum_angular_momentum) {
86 int theta_points = 3 * (maximum_angular_momentum + 1);
87 int phi_points = 3 * (maximum_angular_momentum + 1);
88 if (theta_points < 2)
89 theta_points = 2;
90 if (phi_points < 2)
91 phi_points = 2;
92
93 std::vector<Real> cosines;
94 std::vector<Real> theta_weights;
95 gauss_legendre_nodes(theta_points, cosines, theta_weights);
96
97 std::vector<AngularPoint> grid;
98 grid.reserve(static_cast<size_t>(theta_points) * phi_points);
99 const Real phi_step = 2.0 * constants::pi / static_cast<Real>(phi_points);
100 for (int theta_index = 0; theta_index < theta_points; theta_index++) {
101 Real cosine = cosines[theta_index];
102 Real sine = std::sqrt(std::max(Real(0.0), 1.0 - cosine * cosine));
103 for (int phi_index = 0; phi_index < phi_points; phi_index++) {
104 Real phi = phi_step * static_cast<Real>(phi_index);
105 AngularPoint point;
106 point.x = sine * std::cos(phi);
107 point.y = sine * std::sin(phi);
108 point.z = cosine;
109 point.weight = theta_weights[theta_index] * phi_step;
110 grid.push_back(point);
111 }
112 }
113 return grid;
114}
115
116} // namespace paw_one_center
117} // namespace q3
constexpr Real pi
Definition constants.hpp:24
void gauss_legendre_nodes(int number_of_points, std::vector< Real > &nodes, std::vector< Real > &weights)
std::vector< AngularPoint > build_angular_grid(int maximum_angular_momentum)
double Real
One direction on the unit sphere with its quadrature weight.