Loading...
Searching...
No Matches
init.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * UltrasoftPlanewaveBasis — ultrasoft augmentation data in the plane-wave basis.
5 *
6 * Extends NormConservingPlanewaveBasis with static augmentation data:
7 * - AugmentationQTable (tabulated Q_ij(q) radial functions)
8 * - SphericalHarmonicsTable (precomputed Y_lm for G-vectors)
9 * - GauntCoefficientTable (Gaunt coefficients for L-resolved augmentation)
10 *
11 * Also provides augmentation operations that only depend on static data:
12 * - add_augmentation_charges()
13 * - compute_q_g_single()
14 * - interpolate_qfunc()
15 * - augmentation_overlap_integral()
16 *
17 * The effective D-matrix (dynamic, recomputed each SCF step) lives in the
18 * SCF iterator, not here.
19 */
20
21#include <algorithm>
22#include <cmath>
23#include <complex>
24#include <stdexcept>
25#include <string>
26#include <vector>
27
38
39namespace q3 {
40
42 public:
44 ~UltrasoftPlanewaveBasis() override = default;
45
46 void init(const Crystal& crystal_structure,
50 init_ultrasoft(crystal_structure, planewave_basis);
51 }
52
53 [[nodiscard]] bool is_ultrasoft() const override {
54 return true;
55 }
56
57 // ─── Augmentation table access ──────────────────────────────────
58
59 /**
60 * Access the augmentation Q-function table.
61 */
62 [[nodiscard]] const AugmentationQTable& augmentation_q_table() const {
63 return augmentation_q_table_;
64 }
65
66 /**
67 * Access the precomputed spherical harmonics table.
68 */
70 return spherical_harmonics_table_;
71 }
72
73 /**
74 * Access the Gaunt coefficient table.
75 */
76 [[nodiscard]] const GauntCoefficientTable& gaunt_table() const {
77 return gaunt_table_;
78 }
79
80 // ─── Augmentation operations ────────────────────────────────────
81
82 /**
83 * Interpolate augmentation function Q_{ij}(|q|) from the radial table.
84 * @param species_type_index Species index
85 * @param projector_pair_index Linearized (i,j) pair index for the Q function
86 * @param q_value Wavevector magnitude in atomic units
87 */
88 [[nodiscard]] Real
89 interpolate_qfunc(int species_type_index, int projector_pair_index, Real q_value) const {
90 return augmentation_q_table_.interpolate_qfunc(
91 species_type_index, projector_pair_index, q_value);
92 }
93
94 /**
95 * Augmentation overlap integral q_{ij} = ∫ Q_ij(r) dr, or zero when the
96 * species carries no augmentation charges.
97 *
98 * The gate belongs here, not in each caller. This class is instantiated
99 * whenever *any* species needs augmentation, so a mixed cell — one
100 * ultrasoft or PAW species alongside one norm-conserving species — brings
101 * its norm-conserving species through every method on it. `as_ultrasoft`
102 * is a `dynamic_cast` whose null check is an `assert`, so asking a
103 * norm-conserving species for its q_ij aborts a Debug build and
104 * dereferences null in the Release builds users actually run.
105 *
106 * q_ij = 0 is the physically correct answer, not merely a safe one: a
107 * norm-conserving dataset has no augmentation charges, so it contributes
108 * nothing to S = 1 + Σ q_ij |β_i⟩⟨β_j|. Its projectors still participate
109 * in the nonlocal potential; only the augmentation term vanishes.
110 *
111 * Callers used to carry this test themselves and mostly did not — the
112 * whole-system `has_ultrasoft()` flag reads as if it settled the question
113 * per species, and it does not. `hubbard_augmentation_overlap` and the
114 * force kernel in `forces/.../nonlocal.hpp` grew their own copies for this
115 * exact reason; a mixed US+NC cell still aborted in the wavefunction
116 * initializer's overlap, which had none.
117 *
118 * @param species_type Species index
119 * @param projector_a Beta projector index i (radial)
120 * @param projector_b Beta projector index j (radial)
121 */
122 [[nodiscard]] Real
123 augmentation_overlap_integral(int species_type, int projector_a, int projector_b) const {
124 if (!pseudopotential_set_[species_type].has_augmentation_charges()) {
125 return 0.0;
126 }
127 return pseudopotential_set_.as_ultrasoft(species_type)
128 .augmentation_overlap_integrals[projector_a][projector_b];
129 }
130
131 /**
132 * Access a pre-computed Q_ij(G) value.
133 *
134 * @param species_type_index Species index
135 * @param channel_pair_index Linearized channel pair: channel_i * n_channels + channel_j
136 * @param g_index G-vector index
137 * @param number_of_g_vectors Total number of G-vectors (stride for flat indexing)
138 * @return The pre-computed Q_ij(G) complex value
139 */
140 [[nodiscard]] Complex q_function_g_value(int species_type_index,
141 int channel_pair_index,
142 int g_index,
143 int number_of_g_vectors) const {
144 return q_functions_g_[species_type_index]
145 [channel_pair_index * number_of_g_vectors + g_index];
146 }
147
148 /**
149 * Access the raw Q(G) data pointer for a given species and channel pair.
150 *
151 * Returns a pointer to number_of_g_vectors contiguous Complex values.
152 * Used for BLAS-accelerated dot products in newd and addusdens.
153 */
154 [[nodiscard]] const Complex* q_function_g_data(int species_type_index,
155 int channel_pair_index,
156 int number_of_g_vectors) const {
157 return &q_functions_g_[species_type_index][channel_pair_index * number_of_g_vectors];
158 }
159
160 /**
161 * Check whether Q(G) table is available for a species.
162 */
163 [[nodiscard]] bool has_q_functions_g(int species_type_index) const {
164 return species_type_index < static_cast<int>(q_functions_g_.size()) &&
165 !q_functions_g_[species_type_index].empty();
166 }
167
168 /**
169 * Add ultrasoft augmentation charges to the charge density in G-space.
170 *
171 * Uses the pre-computed Q_ij(G) table for O(atoms × pairs × G) cost,
172 * eliminating the per-G interpolation that dominated the previous implementation.
173 *
174 * @param crystal_structure Crystal geometry
175 * @param projector_overlap_coefficients ⟨ψ|β⟩⟨β|ψ⟩ summed over bands
176 * @param charge_density_g Charge density in G-space (modified in place)
177 */
179 const Crystal& crystal_structure,
180 const Matrix<Real>& projector_overlap_coefficients,
181 std::vector<Complex>& charge_density_g,
182 const std::vector<std::vector<Complex>>* cached_structure_factors = nullptr) const {
183
184 int number_of_g_vectors = planewave_basis_ptr_->number_of_g_vectors();
185
186 for (int atom_index = 0; atom_index < crystal_structure.number_of_atoms(); atom_index++) {
187 int species_type_index = crystal_structure.species_type_index(atom_index);
188
189 /**
190 * Skip species that have no Q(G) table, exactly as the augmentation
191 * force kernel in `forces/.../ultrasoft/internal/augmentation.hpp`
192 * already does — the two assemble the same A_I(G) and must agree on
193 * which atoms contribute to it.
194 *
195 * In a mixed cell `precompute_q_functions_g` leaves the entry for a
196 * norm-conserving species empty, but ρ_ij is accumulated over
197 * *every* atom, so its ⟨ψ|β_i⟩⟨β_j|ψ⟩ are nonzero and this loop
198 * reached `q_function_g_data` for it anyway. That takes the address
199 * of an element of an empty vector and then reads
200 * `number_of_g_vectors` complex values through it — out of bounds on
201 * every SCF iteration, silently corrupting the density rather than
202 * failing outright. A norm-conserving species has no augmentation
203 * charge to add, so contributing nothing is also the right answer.
204 */
205 if (!has_q_functions_g(species_type_index)) {
206 continue;
207 }
208
211
212 /**
213 * Accumulate ρ_ij-weighted Q_ij(G) over all channel pairs.
214 * Exploit Q_ij(G) = Q_ji(G) symmetry: loop only over upper triangle.
215 * For i < j: use (ρ_ij + ρ_ji) * Q_ij(G) in a single pass.
216 * For i == j: use ρ_ii * Q_ii(G) directly.
217 */
218 std::vector<Complex> augmentation_g(number_of_g_vectors, Complex(0.0, 0.0));
219
220 for (int channel_index_i = 0; channel_index_i < number_of_projector_channels;
221 ++channel_index_i) {
222 // Diagonal: i == j
223 {
224 int channel_pair_index =
225 channel_index_i * number_of_projector_channels + channel_index_i;
226 Real diagonal_projector_overlap_coefficient =
227 projector_overlap_coefficients(atom_index, channel_pair_index);
228 if (std::abs(diagonal_projector_overlap_coefficient) >= 1e-15) {
229 const Complex* q_g_data = q_function_g_data(
230 species_type_index, channel_pair_index, number_of_g_vectors);
231 for (int g_index = 0; g_index < number_of_g_vectors; ++g_index) {
232 augmentation_g[g_index] +=
233 diagonal_projector_overlap_coefficient * q_g_data[g_index];
234 }
235 }
236 }
237
238 // Off-diagonal: j > i, use Q_ij = Q_ji symmetry
239 for (int channel_index_j = channel_index_i + 1;
240 channel_index_j < number_of_projector_channels;
241 ++channel_index_j) {
242 int pair_ij = channel_index_i * number_of_projector_channels + channel_index_j;
243 int pair_ji = channel_index_j * number_of_projector_channels + channel_index_i;
244 Real combined_projector_overlap_coefficient =
245 projector_overlap_coefficients(atom_index, pair_ij) +
246 projector_overlap_coefficients(atom_index, pair_ji);
247 if (std::abs(combined_projector_overlap_coefficient) < 1e-15)
248 continue;
249
250 const Complex* q_g_data =
251 q_function_g_data(species_type_index, pair_ij, number_of_g_vectors);
252 for (int g_index = 0; g_index < number_of_g_vectors; ++g_index) {
253 augmentation_g[g_index] +=
254 combined_projector_overlap_coefficient * q_g_data[g_index];
255 }
256 }
257 }
258
259 // Multiply by structure factor and add to charge density
260 if (cached_structure_factors != nullptr) {
261 const auto& structure_factor = (*cached_structure_factors)[atom_index];
262 for (int g_index = 0; g_index < number_of_g_vectors; ++g_index) {
263 charge_density_g[g_index] +=
264 augmentation_g[g_index] * structure_factor[g_index];
265 }
266 } else {
267 Vector3D atomic_position =
268 crystal_structure.convert_from_crystal_to_cartesian(atom_index);
269 for (int g_index = 0; g_index < number_of_g_vectors; ++g_index) {
270 const auto& g_vector = planewave_basis_ptr_->g_vectors()[g_index];
271 Real phase_angle = constants::two_pi * g_vector.g_cart.dot(atomic_position);
272 charge_density_g[g_index] +=
273 augmentation_g[g_index] * compute_phase_factor(phase_angle);
274 }
275 }
276 }
277 }
278
279 /**
280 * Compute the augmentation Q-function Q_ij(G) for a single G-vector.
281 * @param species_type_index Species index
282 * @param projector_index_i Projector channel index i
283 * @param projector_index_j Projector channel index j
284 * @param g_cartesian G-vector in Cartesian reciprocal units (2π/a)
285 * @param two_pi_over_lattice_parameter Conversion factor 2π/alat
286 * @return Complex Q_ij(G) value
287 */
288 [[nodiscard]] Complex compute_q_g_single(int species_type_index,
289 int projector_index_i,
290 int projector_index_j,
291 const Vector3D& g_cartesian,
292 Real two_pi_over_lattice_parameter) const {
293 /**
294 * Same per-species gate as augmentation_overlap_integral: Q_ij(G) is
295 * identically zero for a species with no augmentation charges, and
296 * reaching for its (nonexistent) table is the Release null dereference.
297 */
298 if (!pseudopotential_set_[species_type_index].has_augmentation_charges()) {
299 return 0.0;
300 }
301
302 Real g_norm = g_cartesian.norm() * two_pi_over_lattice_parameter;
304 if (projector_mapping_table_.combined_lm_index(species_type_index, projector_index_i) ==
305 projector_mapping_table_.combined_lm_index(species_type_index, projector_index_j)) {
306 int beta_index_i =
307 projector_mapping_table_.to_beta_index(species_type_index, projector_index_i);
308 int beta_index_j =
309 projector_mapping_table_.to_beta_index(species_type_index, projector_index_j);
310 return pseudopotential_set_.as_ultrasoft(species_type_index)
311 .augmentation_overlap_integrals[beta_index_i][beta_index_j];
312 }
313 return 0.0;
314 }
315
316 int beta_index_i =
317 projector_mapping_table_.to_beta_index(species_type_index, projector_index_i);
318 int beta_index_j =
319 projector_mapping_table_.to_beta_index(species_type_index, projector_index_j);
320 int combined_lm_index_i =
321 projector_mapping_table_.combined_lm_index(species_type_index, projector_index_i);
322 int combined_lm_index_j =
323 projector_mapping_table_.combined_lm_index(species_type_index, projector_index_j);
324 int angular_momentum_i =
325 projector_mapping_table_.angular_momentum(species_type_index, projector_index_i);
326 int angular_momentum_j =
327 projector_mapping_table_.angular_momentum(species_type_index, projector_index_j);
328 int number_of_projectors = pseudopotential_set_[species_type_index].number_of_projectors;
329 int projector_pair_index = beta_index_i * number_of_projectors + beta_index_j;
330
331 int maximum_angular_momentum_q_plus_one =
332 augmentation_q_table_.maximum_angular_momentum_q() + 1;
333 int number_of_lm_projectors =
334 maximum_angular_momentum_q_plus_one * maximum_angular_momentum_q_plus_one;
335 if (combined_lm_index_i >= number_of_lm_projectors ||
336 combined_lm_index_j >= number_of_lm_projectors)
337 return 0.0;
338 if (gaunt_table_
339 .number_of_nonzero_product_indices[combined_lm_index_i][combined_lm_index_j] == 0)
340 return 0.0;
341
342 Complex sum_integral(0.0, 0.0);
343 for (int gaunt_index = 0;
344 gaunt_index < gaunt_table_.number_of_nonzero_product_indices[combined_lm_index_i]
345 [combined_lm_index_j];
346 gaunt_index++) {
347 int product_lm_index =
348 gaunt_table_
349 .nonzero_product_indices[combined_lm_index_i][combined_lm_index_j][gaunt_index];
350 Real gaunt_coefficient_value =
351 gaunt_table_
352 .coefficients[product_lm_index][combined_lm_index_i][combined_lm_index_j];
353 int angular_momentum =
354 static_cast<int>(std::sqrt(static_cast<double>(product_lm_index)));
355 if (angular_momentum < std::abs(angular_momentum_i - angular_momentum_j) ||
356 angular_momentum > angular_momentum_i + angular_momentum_j ||
357 (angular_momentum + angular_momentum_i + angular_momentum_j) % 2 != 0)
358 continue;
359
360 Real augmentation_function_value =
361 augmentation_q_table_.interpolate_augmentation_function(
362 species_type_index, projector_pair_index, angular_momentum, g_norm);
363 int offset_in_shell = product_lm_index - angular_momentum * angular_momentum;
364 int magnetic_projection = (offset_in_shell == 0)
365 ? 0
366 : ((offset_in_shell % 2 == 1) ? (offset_in_shell + 1) / 2
367 : -(offset_in_shell / 2));
368 Real spherical_harmonic_value =
369 compute_real_spherical_harmonic(angular_momentum, magnetic_projection, g_cartesian);
370 Real integrand_contribution = augmentation_function_value * spherical_harmonic_value;
371
372 switch (angular_momentum % 4) {
373 case 0:
374 sum_integral += gaunt_coefficient_value * integrand_contribution;
375 break;
376 case 1:
377 sum_integral += Complex(0.0, -gaunt_coefficient_value * integrand_contribution);
378 break;
379 case 2:
380 sum_integral -= gaunt_coefficient_value * integrand_contribution;
381 break;
382 case 3:
383 sum_integral += Complex(0.0, gaunt_coefficient_value * integrand_contribution);
384 break;
385 }
386 }
387 return sum_integral;
388 }
389
390 protected:
391 void init_ultrasoft(const Crystal& crystal_structure, const PlanewaveBasis& planewave_basis) {
392 cell_volume_ = crystal_structure.cell_volume();
393
394 augmentation_q_table_.init(pseudopotential_set_,
396 beta_function_table_->number_of_q_points(),
397 beta_function_table_->q_spacing(),
399
400 spherical_harmonics_table_.init(
401 crystal_structure, planewave_basis, augmentation_q_table_.maximum_angular_momentum_q());
402
403 log::info(" Ultrasoft Q functions tabulated (L-resolved, lmaxq={})",
404 augmentation_q_table_.maximum_angular_momentum_q());
405
407 pseudopotential_set_, augmentation_q_table_.maximum_angular_momentum_q()));
408
410 }
411
412 /**
413 * The projector degree the Gaunt table is to be built for, checked
414 * against the product degree its consumers will index.
415 *
416 * `generate_gaunt_coefficients` expands products of *projector*
417 * harmonics, so its argument is the projector degree and it evaluates
418 * harmonics up to twice that. What used to be passed instead was
419 * `maximum_angular_momentum_q()`, which is the maximum L of the Q
420 * expansion and is already twice the projector degree -- so the table was
421 * sized for a product degree of 8 on any d-channel dataset, its
422 * collocation asked for harmonics at degrees 5 through 8, and
423 * `compute_real_spherical_harmonic` answered every one of them with 0.0.
424 *
425 * Nothing observed it: the damaged entries are the ones no consumer
426 * indexes. Consumers reach product degree `lmaxq` and no further, and
427 * every entry at or below that sum was correct. The table was simply
428 * about nine times larger than anything reads, with the excess wrong.
429 *
430 * Passing the projector degree makes the collocation exact over exactly
431 * the range consumers use, and -- because it no longer doubles an already
432 * doubled degree -- lets the harmonic refusal above degree 4 be added
433 * without rejecting every ultrasoft and PAW run.
434 *
435 * The relation `lmaxq <= 2 * projector_degree` is what makes the smaller
436 * table still cover every index a consumer forms. It holds for every
437 * dataset Q3 reads, because `lmaxq` is derived from the projector
438 * channels; a dataset that declared otherwise would index past the end of
439 * the table, so it is checked rather than assumed.
440 */
441 [[nodiscard]] static int
443 int maximum_angular_momentum_q) {
444 int projector_degree = 0;
445 for (int species_type_index = 0; species_type_index < pseudopotential_set.size();
446 species_type_index++) {
447 projector_degree = std::max(
448 projector_degree, pseudopotential_set[species_type_index].maximum_angular_momentum);
449 }
450 if (maximum_angular_momentum_q > 2 * projector_degree) {
451 throw std::invalid_argument(
452 "Gaunt table: the augmentation charges declare angular momenta up to L = " +
453 std::to_string(maximum_angular_momentum_q) +
454 ", above the 2 * l_max = " + std::to_string(2 * projector_degree) +
455 " that the projector channels of this dataset can produce. The table is "
456 "built from the projector degree, so such an expansion would be indexed "
457 "past its end.");
458 }
459 return projector_degree;
460 }
461
462 /**
463 * Pre-compute Q_ij(G) for all G-vectors and all channel pairs per species.
464 *
465 * For each species s with N_ch channels and each pair (i,j):
466 * Q_ij(G) = Σ_{lm} gaunt(lm, lm_i, lm_j) * Q_ij^l(s, |G|)
467 * * Ylm(Ĝ) * (-i)^l
468 *
469 * where Q_ij^l(s, |G|) is the radial Fourier transform of species s's
470 * augmentation function Q_ij in channel l (interpolate_augmentation_function).
471 *
472 * Stored as q_functions_g_[species][pair_linear_index][g_index]
473 * where pair_linear_index = channel_i * n_channels + channel_j.
474 *
475 * Memory: n_species × n_channels² × n_g_vectors × sizeof(Complex)
476 */
478 int number_of_species = static_cast<int>(pseudopotential_set_.size());
479 int number_of_g_vectors = planewave_basis.number_of_g_vectors();
480 const auto& g_vector_magnitudes = planewave_basis.g_vector_magnitudes();
481
482 int maximum_angular_momentum_q_plus_one =
483 augmentation_q_table_.maximum_angular_momentum_q() + 1;
484 int number_of_lm_projectors =
485 maximum_angular_momentum_q_plus_one * maximum_angular_momentum_q_plus_one;
486
487 q_functions_g_.resize(number_of_species);
488
489 for (int species_type_index = 0; species_type_index < number_of_species;
490 ++species_type_index) {
491 /**
492 * Capability, not type — a PAW species has Q_ij^L too (synthesized
493 * for datasets that ship only the augmentation charges) and needs the
494 * same Q(G) table.
495 */
496 if (!pseudopotential_set_[species_type_index].has_augmentation_charges()) {
497 continue;
498 }
499
502 int number_of_beta_functions =
503 pseudopotential_set_[species_type_index].number_of_projectors;
505
506 q_functions_g_[species_type_index].assign(total_pairs * number_of_g_vectors,
507 Complex(0.0, 0.0));
508
509 // Pre-compute valid Gaunt terms for each channel pair
510 struct GauntTermForPair {
511 int product_lm_index;
512 int angular_momentum;
513 int projector_pair_index;
514 Real gaunt_coefficient_value;
515 };
516
517 struct ChannelPairInfo {
518 int linear_pair_index;
519 std::vector<GauntTermForPair> gaunt_terms;
520 };
521
522 std::vector<ChannelPairInfo> active_pairs;
523
524 for (int channel_index_i = 0; channel_index_i < number_of_projector_channels;
525 ++channel_index_i) {
526 for (int channel_index_j = 0; channel_index_j < number_of_projector_channels;
527 ++channel_index_j) {
528 int combined_lm_index_i = projector_mapping_table_.combined_lm_index(
529 species_type_index, channel_index_i);
530 int combined_lm_index_j = projector_mapping_table_.combined_lm_index(
531 species_type_index, channel_index_j);
532 int angular_momentum_i = projector_mapping_table_.angular_momentum(
533 species_type_index, channel_index_i);
534 int angular_momentum_j = projector_mapping_table_.angular_momentum(
535 species_type_index, channel_index_j);
536 int beta_index_i =
537 projector_mapping_table_.to_beta_index(species_type_index, channel_index_i);
538 int beta_index_j =
539 projector_mapping_table_.to_beta_index(species_type_index, channel_index_j);
540 int projector_pair_index =
541 beta_index_i * number_of_beta_functions + beta_index_j;
542
543 if (combined_lm_index_i >= number_of_lm_projectors ||
544 combined_lm_index_j >= number_of_lm_projectors) {
545 continue;
546 }
547 if (gaunt_table_.number_of_nonzero_product_indices[combined_lm_index_i]
548 [combined_lm_index_j] == 0) {
549 continue;
550 }
551
552 std::vector<GauntTermForPair> terms;
553 for (int gaunt_index = 0;
554 gaunt_index <
555 gaunt_table_.number_of_nonzero_product_indices[combined_lm_index_i]
556 [combined_lm_index_j];
557 ++gaunt_index) {
558 int product_lm_index =
559 gaunt_table_.nonzero_product_indices[combined_lm_index_i]
560 [combined_lm_index_j][gaunt_index];
561 Real gaunt_coefficient_value =
562 gaunt_table_.coefficients[product_lm_index][combined_lm_index_i]
563 [combined_lm_index_j];
564 int angular_momentum =
565 static_cast<int>(std::sqrt(static_cast<double>(product_lm_index)));
566 if (angular_momentum < std::abs(angular_momentum_i - angular_momentum_j) ||
567 angular_momentum > angular_momentum_i + angular_momentum_j ||
568 (angular_momentum + angular_momentum_i + angular_momentum_j) % 2 != 0) {
569 continue;
570 }
571 terms.push_back({product_lm_index,
572 angular_momentum,
573 projector_pair_index,
574 gaunt_coefficient_value});
575 }
576
577 if (!terms.empty()) {
578 int linear_pair_index =
579 channel_index_i * number_of_projector_channels + channel_index_j;
580 active_pairs.push_back({linear_pair_index, std::move(terms)});
581 }
582 }
583 }
584
585 // Fill the Q(G) table for all G-vectors and active channel pairs
586 for (int g_index = 0; g_index < number_of_g_vectors; ++g_index) {
587 Real g_magnitude = g_vector_magnitudes[g_index];
588
589 for (const auto& pair : active_pairs) {
590 Real q_g_real = 0.0;
591 Real q_g_imag = 0.0;
592
593 for (const auto& term : pair.gaunt_terms) {
594 Real augmentation_function_value =
595 augmentation_q_table_.interpolate_augmentation_function(
596 species_type_index,
597 term.projector_pair_index,
598 term.angular_momentum,
599 g_magnitude);
600 Real spherical_harmonic_value =
601 spherical_harmonics_table_.value(g_index, term.product_lm_index);
602 Real integrand_contribution =
603 augmentation_function_value * spherical_harmonic_value;
604
605 switch (term.angular_momentum % 4) {
606 case 0:
607 q_g_real += term.gaunt_coefficient_value * integrand_contribution;
608 break;
609 case 1:
610 q_g_imag -= term.gaunt_coefficient_value * integrand_contribution;
611 break;
612 case 2:
613 q_g_real -= term.gaunt_coefficient_value * integrand_contribution;
614 break;
615 case 3:
616 q_g_imag += term.gaunt_coefficient_value * integrand_contribution;
617 break;
618 }
619 }
620
621 q_functions_g_[species_type_index]
622 [pair.linear_pair_index * number_of_g_vectors + g_index] =
623 Complex(q_g_real, q_g_imag);
624 }
625 }
626
627 log::info(
628 " Pre-computed Q(G) table for species {} ({} channel pairs, {} G-vectors)",
629 species_type_index,
630 static_cast<int>(active_pairs.size()),
631 number_of_g_vectors);
632 }
633 }
634
635 private:
636 AugmentationQTable augmentation_q_table_;
637 SphericalHarmonicsTable spherical_harmonics_table_;
638 GauntCoefficientTable gaunt_table_;
639
640 /**
641 * Pre-computed Q_ij(G) table.
642 *
643 * Indexed as: q_functions_g_[species_type][pair_index * n_g + g_index]
644 * where pair_index = channel_i * n_channels + channel_j.
645 * Flat layout for cache efficiency during G-vector inner loops.
646 */
647 std::vector<std::vector<Complex>> q_functions_g_;
648};
649
650} // 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
void init(const PseudopotentialSet &pseudopotential_set, const std::vector< int > &mesh_sizes, int number_of_q_points, Real q_spacing, Real cell_volume)
Real interpolate_augmentation_function(int species_type_index, int projector_pair_index, int angular_momentum, Real q_value) const override
Vector3D convert_from_crystal_to_cartesian(int atom_index) const
Definition crystal.hpp:225
Real cell_volume() const
Definition crystal.hpp:193
int species_type_index(int atom_index) const
Definition crystal.hpp:273
int number_of_atoms() const
Definition crystal.hpp:209
std::unique_ptr< BetaFunctionTable > beta_function_table_
Definition init.hpp:385
std::vector< int > mesh_sizes_
Definition init.hpp:392
ProjectorMappingTable projector_mapping_table_
Definition init.hpp:388
int number_of_projectors(int species_type) const
Definition init.hpp:188
PseudopotentialSet pseudopotential_set_
Definition init.hpp:380
const PlanewaveBasis & planewave_basis() const
Definition init.hpp:147
const PseudopotentialSet & pseudopotential_set() const
Definition init.hpp:156
int number_of_projector_channels(int species_type) const
Definition init.hpp:203
virtual void init(const Crystal &crystal_structure, const PlanewaveBasis &planewave_basis, const PseudopotentialSet &pseudopotential_set)
Definition init.hpp:57
const PlanewaveBasis * planewave_basis_ptr_
Definition init.hpp:378
int number_of_g_vectors() const
Definition init.hpp:145
const std::vector< GVector > & g_vectors() const
Definition init.hpp:148
const std::vector< Real > & g_vector_magnitudes() const
Definition init.hpp:154
int number_of_channels(int species_type) const
int to_beta_index(int species_type, int channel_index) const
int angular_momentum(int species_type, int channel_index) const
int combined_lm_index(int species_type, int channel_index) const
const UltrasoftPseudopotential & as_ultrasoft(int index) const
Definition set.hpp:63
int size() const
Definition set.hpp:98
void init(const Crystal &crystal_structure, const PlanewaveBasis &planewave_basis, int maximum_angular_momentum)
Real value(int g_index, int combined_lm_index) const
void add_augmentation_charges(const Crystal &crystal_structure, const Matrix< Real > &projector_overlap_coefficients, std::vector< Complex > &charge_density_g, const std::vector< std::vector< Complex > > *cached_structure_factors=nullptr) const
Definition init.hpp:178
const SphericalHarmonicsTable & spherical_harmonics_table() const
Definition init.hpp:69
void init(const Crystal &crystal_structure, const PlanewaveBasis &planewave_basis, const PseudopotentialSet &pseudopotential_set) override
Definition init.hpp:46
const GauntCoefficientTable & gaunt_table() const
Definition init.hpp:76
const Complex * q_function_g_data(int species_type_index, int channel_pair_index, int number_of_g_vectors) const
Definition init.hpp:154
Complex compute_q_g_single(int species_type_index, int projector_index_i, int projector_index_j, const Vector3D &g_cartesian, Real two_pi_over_lattice_parameter) const
Definition init.hpp:288
static int projector_degree_for_gaunt_table(const PseudopotentialSet &pseudopotential_set, int maximum_angular_momentum_q)
Definition init.hpp:442
~UltrasoftPlanewaveBasis() override=default
void init_ultrasoft(const Crystal &crystal_structure, const PlanewaveBasis &planewave_basis)
Definition init.hpp:391
Real augmentation_overlap_integral(int species_type, int projector_a, int projector_b) const
Definition init.hpp:123
bool has_q_functions_g(int species_type_index) const
Definition init.hpp:163
Real interpolate_qfunc(int species_type_index, int projector_pair_index, Real q_value) const
Definition init.hpp:89
const AugmentationQTable & augmentation_q_table() const
Definition init.hpp:62
bool is_ultrasoft() const override
Definition init.hpp:53
void precompute_q_functions_g(const PlanewaveBasis &planewave_basis)
Definition init.hpp:477
Complex q_function_g_value(int species_type_index, int channel_pair_index, int g_index, int number_of_g_vectors) const
Definition init.hpp:140
std::vector< std::vector< Real > > augmentation_overlap_integrals
Definition init.hpp:49
constexpr Real two_pi
Definition constants.hpp:25
void info(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:183
constexpr double zero_vector_magnitude_threshold
double Real
Real compute_real_spherical_harmonic(int degree, int order, const Vector3D &direction)
Definition harmonics.hpp:85
Complex compute_phase_factor(Real phase)
Compute e^{-iφ} = cos(φ) - i·sin(φ) (standard structure factor phase).
std::complex< double > Complex
GauntCoefficientTable generate_gaunt_coefficients(int max_degree)
std::vector< std::vector< std::vector< Real > > > coefficients
std::vector< std::vector< std::vector< int > > > nonzero_product_indices
std::vector< std::vector< int > > number_of_nonzero_product_indices
Real norm() const
Definition vector_3d.hpp:68