Loading...
Searching...
No Matches
gradient.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * Density gradients on the one-center radial × angular grid.
5 *
6 * A GGA functional needs σ = |∇n|², and on this grid the gradient separates
7 * into a radial and a tangential part:
8 *
9 * ∇n = Σ_lm [ f'_lm(r) Y_lm(Ω) r̂ + (f_lm(r)/r) ∇_Ω Y_lm(Ω) ]
10 *
11 * with f_lm(r) = u_lm(r)/r² the actual density channel (the stored form is
12 * r-weighted). The two pieces are orthogonal, so
13 * σ = (∂n/∂r)² + |∇_Ω n|²/r².
14 *
15 * The angular gradients ∇_Ω Y_lm depend only on direction, so they are
16 * tabulated once for the whole grid and reused at every radius.
17 *
18 * Endpoint care: scalar-relativistic partial waves behave as r^γ with
19 * non-integer γ toward the origin, so a radial derivative that assumes
20 * polynomial behaviour there is wrong for these datasets specifically. The
21 * scheme below differentiates in index space on the logarithmic grid — where
22 * r^γ becomes a smooth exponential — and the gate differentiates the datasets'
23 * own partial waves rather than only analytic test functions.
24 */
25
26#include <array>
27#include <cmath>
28#include <vector>
29
33
34namespace q3 {
35namespace paw_one_center {
36
37/**
38 * df/dr on a non-uniform grid, by five-point central differences in index
39 * space divided by dr/di.
40 *
41 * Differentiating in index space is what makes this safe on a logarithmic
42 * mesh: a power law r^γ is an exponential in the index, so the stencil sees a
43 * smooth function even where the density itself has a cusp-like profile.
44 */
45inline std::vector<Real> radial_derivative(const std::vector<Real>& values,
46 const std::vector<Real>& measures,
47 int mesh_size) {
48 std::vector<Real> derivative(mesh_size, 0.0);
49 if (mesh_size < 5)
50 return derivative;
51
52 for (int index = 2; index < mesh_size - 2; index++) {
53 Real index_derivative = (values[index - 2] - 8.0 * values[index - 1] +
54 8.0 * values[index + 1] - values[index + 2]) /
55 12.0;
56 derivative[index] = index_derivative / measures[index];
57 }
58 // One-sided fourth-order stencils at the four boundary points.
59 auto forward = [&](int index) {
60 return (-25.0 * values[index] + 48.0 * values[index + 1] - 36.0 * values[index + 2] +
61 16.0 * values[index + 3] - 3.0 * values[index + 4]) /
62 12.0;
63 };
64 auto backward = [&](int index) {
65 return (25.0 * values[index] - 48.0 * values[index - 1] + 36.0 * values[index - 2] -
66 16.0 * values[index - 3] + 3.0 * values[index - 4]) /
67 12.0;
68 };
69 derivative[0] = forward(0) / measures[0];
70 derivative[1] = forward(1) / measures[1];
71 derivative[mesh_size - 1] = backward(mesh_size - 1) / measures[mesh_size - 1];
72 derivative[mesh_size - 2] = backward(mesh_size - 2) / measures[mesh_size - 2];
73 return derivative;
74}
75
76/**
77 * Tangential gradients ∇_Ω Y_lm at every angular grid point, [point][lm][xyz].
78 *
79 * Obtained by differentiating Y_lm along the unit sphere: perturbing the
80 * direction and renormalizing projects out the radial component
81 * automatically, which is exactly the tangential derivative wanted here.
82 */
83inline std::vector<std::vector<std::array<Real, 3>>>
84tabulate_harmonic_gradients(const std::vector<AngularPoint>& angular_grid, int number_of_lm) {
85 int maximum_angular_momentum = 0;
86 while ((maximum_angular_momentum + 1) * (maximum_angular_momentum + 1) < number_of_lm)
87 maximum_angular_momentum++;
88
89 constexpr Real step = 1e-5;
90 std::vector<std::vector<std::array<Real, 3>>> gradients(
91 angular_grid.size(), std::vector<std::array<Real, 3>>(number_of_lm, {0.0, 0.0, 0.0}));
92
93 for (size_t point = 0; point < angular_grid.size(); point++) {
94 std::array<Real, 3> direction{
95 angular_grid[point].x, angular_grid[point].y, angular_grid[point].z};
96 for (int axis = 0; axis < 3; axis++) {
97 std::array<Real, 3> plus = direction;
98 std::array<Real, 3> minus = direction;
99 plus[axis] += step;
100 minus[axis] -= step;
101 Real plus_norm = std::sqrt(plus[0] * plus[0] + plus[1] * plus[1] + plus[2] * plus[2]);
102 Real minus_norm =
103 std::sqrt(minus[0] * minus[0] + minus[1] * minus[1] + minus[2] * minus[2]);
104 Vector3D plus_direction{plus[0] / plus_norm, plus[1] / plus_norm, plus[2] / plus_norm};
105 Vector3D minus_direction{
106 minus[0] / minus_norm, minus[1] / minus_norm, minus[2] / minus_norm};
107
108 for (int degree = 0; degree <= maximum_angular_momentum; degree++) {
109 for (int order : harmonic_order_sequence(degree)) {
110 int lm = harmonic_combined_degree_order_index(degree, order);
111 if (lm >= number_of_lm)
112 continue;
113 Real value_plus =
114 compute_real_spherical_harmonic(degree, order, plus_direction);
115 Real value_minus =
116 compute_real_spherical_harmonic(degree, order, minus_direction);
117 gradients[point][lm][axis] = (value_plus - value_minus) / (2.0 * step);
118 }
119 }
120 }
121 }
122 return gradients;
123}
124
125/// ∇n at one radius, for every angular point: radial part, tangential part, σ.
127 std::vector<Real> radial_component; ///< ∂n/∂r per angle
128 std::vector<std::array<Real, 3>> tangential; ///< ∇_Ω n / r per angle
129 std::vector<Real> gradient_squared; ///< σ = |∇n|² per angle
130};
131
132/**
133 * Assemble ∇n and σ at one radius from the density channels and their radial
134 * derivatives.
135 *
136 * @param channel_values f_lm(r) — the true density channels, not r-weighted
137 * @param channel_derivatives f'_lm(r)
138 */
139inline GradientAtRadius
140build_gradient_at_radius(const std::vector<Real>& channel_values,
141 const std::vector<Real>& channel_derivatives,
142 Real radius,
143 const std::vector<std::vector<Real>>& harmonics,
144 const std::vector<std::vector<std::array<Real, 3>>>& harmonic_gradients) {
145 int number_of_angles = static_cast<int>(harmonics.size());
146 int number_of_lm = static_cast<int>(channel_values.size());
147
148 GradientAtRadius result;
149 result.radial_component.assign(number_of_angles, 0.0);
150 result.tangential.assign(number_of_angles, {0.0, 0.0, 0.0});
151 result.gradient_squared.assign(number_of_angles, 0.0);
152
153 Real inverse_radius = 1.0 / radius;
154 for (int angle = 0; angle < number_of_angles; angle++) {
155 Real radial = 0.0;
156 std::array<Real, 3> tangential{0.0, 0.0, 0.0};
157 for (int lm = 0; lm < number_of_lm; lm++) {
158 radial += channel_derivatives[lm] * harmonics[angle][lm];
159 for (int axis = 0; axis < 3; axis++)
160 tangential[axis] += channel_values[lm] * harmonic_gradients[angle][lm][axis];
161 }
162 for (int axis = 0; axis < 3; axis++)
163 tangential[axis] *= inverse_radius;
164
165 result.radial_component[angle] = radial;
166 result.tangential[angle] = tangential;
167 result.gradient_squared[angle] = radial * radial + tangential[0] * tangential[0] +
168 tangential[1] * tangential[1] +
169 tangential[2] * tangential[2];
170 }
171 return result;
172}
173
174} // namespace paw_one_center
175} // namespace q3
std::vector< std::vector< std::array< Real, 3 > > > tabulate_harmonic_gradients(const std::vector< AngularPoint > &angular_grid, int number_of_lm)
Definition gradient.hpp:84
GradientAtRadius build_gradient_at_radius(const std::vector< Real > &channel_values, const std::vector< Real > &channel_derivatives, Real radius, const std::vector< std::vector< Real > > &harmonics, const std::vector< std::vector< std::array< Real, 3 > > > &harmonic_gradients)
Definition gradient.hpp:140
std::vector< Real > radial_derivative(const std::vector< Real > &values, const std::vector< Real > &measures, int mesh_size)
Definition gradient.hpp:45
int harmonic_combined_degree_order_index(int degree, int order)
Definition harmonics.hpp:61
double Real
Real compute_real_spherical_harmonic(int degree, int order, const Vector3D &direction)
Definition harmonics.hpp:85
std::vector< int > harmonic_order_sequence(int degree)
Definition harmonics.hpp:40
∇n at one radius, for every angular point: radial part, tangential part, σ.
Definition gradient.hpp:126
std::vector< Real > gradient_squared
σ = |∇n|² per angle
Definition gradient.hpp:129
std::vector< std::array< Real, 3 > > tangential
∇_Ω n / r per angle
Definition gradient.hpp:128
std::vector< Real > radial_component
∂n/∂r per angle
Definition gradient.hpp:127