Loading...
Searching...
No Matches
charge_density_augmentation.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * AugmentationQTable — precomputed augmentation charge Q_ij(q, L) radial
5 * interpolation tables for ultrasoft pseudopotential_set.
6 *
7 * Owns the Bessel-transformed Q radial tables and provides interpolation
8 * accessors used by the D-matrix, augmentation charge, and stress routines.
9 */
10
11#include <algorithm>
12#include <cmath>
13#include <vector>
14
22
23namespace q3 {
24
26 public:
27 AugmentationQTable() = default;
28 ~AugmentationQTable() override = default;
29
30 /**
31 * Initialize augmentation Q tables for all ultrasoft species.
32 *
33 * @param pseudopotential_set The pseudopotential set (one per species type).
34 * @param mesh_sizes Truncated radial mesh size per species.
35 * @param number_of_q_points Number of points in the q-grid.
36 * @param q_spacing Spacing between q-grid points.
37 * @param cell_volume Unit cell volume in Bohr³.
38 */
39 void init(const PseudopotentialSet& pseudopotential_set,
40 const std::vector<int>& mesh_sizes,
41 int number_of_q_points,
42 Real q_spacing,
43 Real cell_volume) {
44 number_of_q_points_ = number_of_q_points;
45 q_spacing_ = q_spacing;
46 cell_volume_ = cell_volume;
47
48 int number_of_species_types = static_cast<int>(pseudopotential_set.size());
49
50 determine_maximum_angular_momentum_q(pseudopotential_set, number_of_species_types);
51 build_all_tables(pseudopotential_set, mesh_sizes, number_of_species_types);
52 }
53
54 /**
55 * Interpolate Q_ij^L(q), the radial Fourier transform of Q_ij in channel L,
56 * at a given q value.
57 */
58 [[nodiscard]] Real interpolate_augmentation_function(int species_type_index,
59 int projector_pair_index,
60 int angular_momentum,
61 Real q_value) const override {
62 if (species_type_index >= static_cast<int>(augmentation_q_radial_table_.size()) ||
63 projector_pair_index >=
64 static_cast<int>(augmentation_q_radial_table_[species_type_index].size()) ||
65 angular_momentum >=
66 static_cast<int>(
67 augmentation_q_radial_table_[species_type_index][projector_pair_index]
68 .size()) ||
69 augmentation_q_radial_table_[species_type_index][projector_pair_index][angular_momentum]
70 .empty()) {
71 return 0.0;
72 }
74 augmentation_q_radial_table_[species_type_index][projector_pair_index]
75 [angular_momentum],
76 q_value,
77 q_spacing_,
78 number_of_q_points_);
79 }
80
81 /**
82 * Interpolate the derivative dQ_ij^L/dq of the radial Fourier transform of
83 * Q_ij in channel L.
84 */
85 [[nodiscard]] Real interpolate_augmentation_function_derivative(int species_type_index,
86 int projector_pair_index,
87 int angular_momentum,
88 Real q_value) const override {
89 if (species_type_index >= static_cast<int>(augmentation_q_radial_table_.size()) ||
90 projector_pair_index >=
91 static_cast<int>(augmentation_q_radial_table_[species_type_index].size()) ||
92 angular_momentum >=
93 static_cast<int>(
94 augmentation_q_radial_table_[species_type_index][projector_pair_index]
95 .size()) ||
96 augmentation_q_radial_table_[species_type_index][projector_pair_index][angular_momentum]
97 .empty()) {
98 return 0.0;
99 }
101 augmentation_q_radial_table_[species_type_index][projector_pair_index]
102 [angular_momentum],
103 q_value,
104 q_spacing_,
105 number_of_q_points_);
106 }
107
108 /**
109 * Interpolate the total (L=0) Q function at a given q value.
110 */
111 [[nodiscard]] Real interpolate_qfunc(int species_type_index,
112 int projector_pair_index,
113 Real q_value) const override {
114 if (q_function_table_.empty() || q_function_table_[species_type_index].empty())
115 return 0.0;
117 q_function_table_[species_type_index][projector_pair_index],
118 q_value,
119 q_spacing_,
120 number_of_q_points_);
121 }
122
123 [[nodiscard]] int maximum_angular_momentum_q() const override {
124 return maximum_angular_momentum_q_;
125 }
126
127 private:
128 /// Determine the maximum angular momentum for augmentation charges.
129 void determine_maximum_angular_momentum_q(const PseudopotentialSet& pseudopotential_set,
130 int number_of_species_types) {
131 maximum_angular_momentum_q_ = 0;
132 for (int species_type_index = 0; species_type_index < number_of_species_types;
133 species_type_index++) {
134 /**
135 * Capability, not type. This table is built whenever *any* species
136 * needs augmentation, so a mixed cell brings its norm-conserving
137 * species through here too, and `as_ultrasoft` is a dynamic_cast
138 * whose null check is an assert compiled out in Release. A species
139 * without augmentation charges contributes no Q angular momenta.
140 */
141 if (!pseudopotential_set[species_type_index].has_augmentation_charges()) {
142 continue;
143 }
144 const auto& ultrasoft_pseudopotential =
145 pseudopotential_set.as_ultrasoft(species_type_index);
146 if (ultrasoft_pseudopotential.number_of_q_angular_momenta > 0) {
147 maximum_angular_momentum_q_ =
148 std::max(maximum_angular_momentum_q_,
149 ultrasoft_pseudopotential.number_of_q_angular_momenta - 1);
150 }
151 }
152 if (maximum_angular_momentum_q_ == 0) {
153 for (int species_type_index = 0; species_type_index < number_of_species_types;
154 species_type_index++) {
155 maximum_angular_momentum_q_ =
156 std::max(maximum_angular_momentum_q_,
157 2 * pseudopotential_set[species_type_index].maximum_angular_momentum);
158 }
159 }
160 }
161
162 /// Build augmentation Q radial interpolation tables for all species.
163 void build_all_tables(const PseudopotentialSet& pseudopotential_set,
164 const std::vector<int>& mesh_sizes,
165 int number_of_species_types) {
166 augmentation_q_radial_table_.resize(number_of_species_types);
167 q_function_table_.resize(number_of_species_types);
168 for (int species_type_index = 0; species_type_index < number_of_species_types;
169 species_type_index++) {
170 /**
171 * Same per-species gate as determine_maximum_angular_momentum_q: a
172 * norm-conserving species in a mixed cell has no augmentation
173 * tables to build, and asking for it via `as_ultrasoft` is the
174 * Release null dereference. Its table entries stay empty, which is
175 * what precompute_q_functions_g's own gate already expects.
176 */
177 if (!pseudopotential_set[species_type_index].has_augmentation_charges()) {
178 continue;
179 }
180 build_tables_for_species(pseudopotential_set.as_ultrasoft(species_type_index),
181 mesh_sizes[species_type_index],
182 species_type_index);
183 }
184 }
185
186 /**
187 * Build augmentation Q tables for a single ultrasoft species.
188 * j_L(q*r) depends on (L, q) and on nothing about the projector pair, so it
189 * is evaluated once per (L, q) and reused across every pair the triangle
190 * rule admits. That reuse is what puts L and q outside the pair loop; the
191 * alternative -- pair outermost -- would make the table writes contiguous
192 * but would repeat a whole-mesh Bessel recurrence per pair.
193 */
194 void build_tables_for_species(const UltrasoftPseudopotential& pseudopotential,
195 int mesh_size,
196 int species_type_index) {
197 int number_of_projectors = pseudopotential.number_of_projectors;
198 int number_of_q_angular_momenta = pseudopotential.number_of_q_angular_momenta > 0
199 ? pseudopotential.number_of_q_angular_momenta
200 : (2 * pseudopotential.maximum_angular_momentum + 1);
201
202 augmentation_q_radial_table_[species_type_index].resize(number_of_projectors *
203 number_of_projectors);
204 q_function_table_[species_type_index].resize(number_of_projectors * number_of_projectors);
205
206 // Pre-allocate all tables
207 for (int beta_index_i = 0; beta_index_i < number_of_projectors; beta_index_i++) {
208 for (int beta_index_j = beta_index_i; beta_index_j < number_of_projectors;
209 beta_index_j++) {
210 int projector_pair_index = beta_index_i * number_of_projectors + beta_index_j;
211 int reverse_projector_pair_index =
212 beta_index_j * number_of_projectors + beta_index_i;
213 augmentation_q_radial_table_[species_type_index][projector_pair_index].resize(
214 number_of_q_angular_momenta);
215 augmentation_q_radial_table_[species_type_index][reverse_projector_pair_index]
216 .resize(number_of_q_angular_momenta);
217 for (int angular_momentum = 0; angular_momentum < number_of_q_angular_momenta;
218 angular_momentum++) {
219 augmentation_q_radial_table_[species_type_index][projector_pair_index]
220 [angular_momentum]
221 .assign(number_of_q_points_, 0.0);
222 augmentation_q_radial_table_[species_type_index][reverse_projector_pair_index]
223 [angular_momentum]
224 .assign(number_of_q_points_, 0.0);
225 }
226 }
227 }
228
229 // Pre-collect active projector pairs per angular momentum channel
230 struct ProjectorPairInfo {
231 int beta_index_i;
232 int beta_index_j;
233 int projector_pair_index;
234 int reverse_projector_pair_index;
235 const std::vector<Real>* augmentation_function_data;
236 };
237
238 Real prefactor = 4.0 * constants::pi / cell_volume_;
239
240 // Precompute bessel buffer
241 std::vector<Real> bessel_values(mesh_size);
242 std::vector<Real> integrand(mesh_size);
243
244 /**
245 * L outermost: the Bessel factor is shared by every pair at this (L, q),
246 * and the surviving-pair list built below is itself L-dependent.
247 */
248 for (int angular_momentum = 0; angular_momentum < number_of_q_angular_momenta;
249 angular_momentum++) {
250
251 // Collect all projector pairs active for this angular momentum
252 std::vector<ProjectorPairInfo> active_pairs;
253 for (int beta_index_i = 0; beta_index_i < number_of_projectors; beta_index_i++) {
254 for (int beta_index_j = beta_index_i; beta_index_j < number_of_projectors;
255 beta_index_j++) {
256 int angular_momentum_i =
257 pseudopotential.projectors[beta_index_i].angular_momentum;
258 int angular_momentum_j =
259 pseudopotential.projectors[beta_index_j].angular_momentum;
260
261 // Triangle rule check
262 if (angular_momentum < std::abs(angular_momentum_i - angular_momentum_j) ||
263 angular_momentum > angular_momentum_i + angular_momentum_j ||
264 (angular_momentum + angular_momentum_i + angular_momentum_j) % 2 != 0) {
265 continue;
266 }
267
268 int projector_pair_index = beta_index_i * number_of_projectors + beta_index_j;
269 int reverse_projector_pair_index =
270 beta_index_j * number_of_projectors + beta_index_i;
271
272 // Find the appropriate Q function data
273 const std::vector<Real>* augmentation_function_data = nullptr;
274 if (!pseudopotential.augmentation_function_by_angular_momentum.empty() &&
275 projector_pair_index <
276 static_cast<int>(
277 pseudopotential.augmentation_function_by_angular_momentum.size()) &&
278 angular_momentum <
279 static_cast<int>(
280 pseudopotential
281 .augmentation_function_by_angular_momentum[projector_pair_index]
282 .size()) &&
283 !pseudopotential
284 .augmentation_function_by_angular_momentum[projector_pair_index]
285 [angular_momentum]
286 .empty()) {
287 augmentation_function_data =
288 &pseudopotential
289 .augmentation_function_by_angular_momentum[projector_pair_index]
290 [angular_momentum];
291 } else if (projector_pair_index <
292 static_cast<int>(pseudopotential.augmentation_function.size()) &&
293 !pseudopotential.augmentation_function[projector_pair_index]
294 .empty()) {
295 /**
296 * `q_with_l = false` pseudopotentials carry a single
297 * un-decomposed Q_ij(r) per projector pair rather than
298 * one per L. The established convention for these is to
299 * reuse that same radial function at *every* L the
300 * triangle rule admits for the pair, not just L=0:
301 * the flat function is the pair's whole augmentation
302 * content, so each allowed multipole draws on it.
303 * Restricting this fallback to L=0 silently dropped the
304 * L>0 augmentation for such files (Q_ij(G=0) — total
305 * charge — is unaffected, since a pure L>0 term
306 * integrates to zero, but the anisotropic shape it
307 * contributes to the Hartree/XC energy is not).
308 */
309 augmentation_function_data =
310 &pseudopotential.augmentation_function[projector_pair_index];
311 }
312
313 if (!augmentation_function_data ||
314 static_cast<int>(augmentation_function_data->size()) < mesh_size) {
315 continue;
316 }
317
318 active_pairs.push_back({beta_index_i,
319 beta_index_j,
320 projector_pair_index,
321 reverse_projector_pair_index,
322 augmentation_function_data});
323 }
324 }
325
326 if (active_pairs.empty())
327 continue;
328
329 // For each q-point, compute bessel values ONCE and integrate all active pairs
330 for (int q_index = 0; q_index < number_of_q_points_; q_index++) {
331 Real q_value = q_index * q_spacing_;
332
333 // Compute spherical Bessel function for all radial points at this (l, q)
334 spherical_bessel_batch(angular_momentum,
335 q_value,
336 pseudopotential.radial_grid.data(),
337 mesh_size,
338 bessel_values.data());
339
340 // Integrate against each active projector pair's Q function
341 for (const auto& pair : active_pairs) {
342 Real sum = 0.0;
343 const auto& q_func = *pair.augmentation_function_data;
344 for (int radial_index = 0; radial_index < mesh_size; radial_index++) {
345 Real integrand_value = q_func[radial_index] * bessel_values[radial_index] *
346 pseudopotential.radial_grid_derivative[radial_index];
347 Real weight;
348 if (radial_index == 0 || radial_index == mesh_size - 1)
349 weight = 1.0;
350 else if (radial_index % 2 == 1)
351 weight = 4.0;
352 else
353 weight = 2.0;
354 sum += weight * integrand_value;
355 }
356 Real table_value = sum / 3.0 * prefactor;
357 augmentation_q_radial_table_[species_type_index][pair.projector_pair_index]
358 [angular_momentum][q_index] = table_value;
359 augmentation_q_radial_table_[species_type_index]
360 [pair.reverse_projector_pair_index]
361 [angular_momentum][q_index] = table_value;
362 }
363 }
364 }
365
366 // Store l=0 table as the total Q function table
367 for (int beta_index_i = 0; beta_index_i < number_of_projectors; beta_index_i++) {
368 for (int beta_index_j = beta_index_i; beta_index_j < number_of_projectors;
369 beta_index_j++) {
370 int projector_pair_index = beta_index_i * number_of_projectors + beta_index_j;
371 int reverse_projector_pair_index =
372 beta_index_j * number_of_projectors + beta_index_i;
373
374 if (!augmentation_q_radial_table_[species_type_index][projector_pair_index]
375 .empty() &&
376 !augmentation_q_radial_table_[species_type_index][projector_pair_index][0]
377 .empty()) {
378 q_function_table_[species_type_index][projector_pair_index] =
379 augmentation_q_radial_table_[species_type_index][projector_pair_index][0];
380 q_function_table_[species_type_index][reverse_projector_pair_index] =
381 augmentation_q_radial_table_[species_type_index][projector_pair_index][0];
382 } else {
383 q_function_table_[species_type_index][projector_pair_index].assign(
384 number_of_q_points_, 0.0);
385 q_function_table_[species_type_index][reverse_projector_pair_index].assign(
386 number_of_q_points_, 0.0);
387 }
388 }
389 }
390 }
391
392 /// Q radial tables: [species][projector_pair][angular_momentum][q_index]
393 std::vector<std::vector<std::vector<std::vector<Real>>>> augmentation_q_radial_table_;
394
395 /// Total Q function tables (L=0): [species][projector_pair][q_index]
396 std::vector<std::vector<std::vector<Real>>> q_function_table_;
397
398 int maximum_angular_momentum_q_{0};
399 int number_of_q_points_{};
400 Real q_spacing_{};
401 Real cell_volume_{};
402};
403
404} // namespace q3
Real interpolate_qfunc(int species_type_index, int projector_pair_index, Real q_value) const override
int maximum_angular_momentum_q() const override
Real interpolate_augmentation_function_derivative(int species_type_index, int projector_pair_index, int angular_momentum, Real q_value) const override
void init(const PseudopotentialSet &pseudopotential_set, const std::vector< int > &mesh_sizes, int number_of_q_points, Real q_spacing, Real cell_volume)
~AugmentationQTable() override=default
Real interpolate_augmentation_function(int species_type_index, int projector_pair_index, int angular_momentum, Real q_value) const override
AugmentationQTable()=default
const UltrasoftPseudopotential & as_ultrasoft(int index) const
Definition set.hpp:63
int size() const
Definition set.hpp:98
constexpr Real pi
Definition constants.hpp:24
double Real
void spherical_bessel_batch(int degree, Real q_value, const Real *radial_grid, int mesh_size, Real *result)
Definition bessel.hpp:105
Real lagrange_interpolation_4point_derivative(const std::vector< Real > &table, Real q_value, Real q_spacing, int table_size)
Definition lagrange.hpp:60
Real lagrange_interpolation_4point(const std::vector< Real > &table, Real q_value, Real q_spacing, int table_size)
Definition lagrange.hpp:31