Loading...
Searching...
No Matches
augmentation.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * Ultrasoft pseudopotential augmentation stress contribution.
5 *
6 * Computes the stress tensor contribution from the augmentation charges
7 * that arise in ultrasoft pseudopotentials. For norm-conserving
8 * pseudopotentials, this contribution is identically zero.
9 *
10 * Uses analytical derivatives of Q_ij(G) via the product rule applied to
11 * Q_ij(G) = sum_lm (-i)^l * c_lm * Ylm(G^) * Q_ij^l(|G|)
12 * (Vanderbilt, PRB 41, 7892 (1990)), which has exactly two terms:
13 * dQ_ij/dG_alpha = sum_lm (-i)^l * c_lm * [dYlm/dG_alpha * Q_ij^l(|G|)
14 * + Ylm * dQ_ij^l/d|G| * G_alpha/|G|]
15 * where Q_ij^l(|G|) is the radial Fourier transform of Q_ij in channel l.
16 *
17 * Two stages, in this order because Q_ij(G) carries no atom dependence while
18 * the occupancies and the structure factor carry nothing else:
19 * 1. Pre-compute dQ_ij(G) for ALL G-vectors per (pair, direction), once per
20 * species rather than once per atom
21 * 2. Weight by the projector occupancies and contract with
22 * V*(G)*e^{-iGτ}*G_alpha
23 *
24 * The expansion differentiated is the Fourier transform of Eq. (27) of
25 * K. Laasonen, A. Pasquarello, R. Car, C. Lee & D. Vanderbilt, Phys. Rev. B
26 * 47, 10142 (1993), with the radial transform of their Eq. (32). The strain
27 * derivative is taken in the stress formalism of O. H. Nielsen &
28 * R. M. Martin, Phys. Rev. B 32, 3780 (1985).
29 */
30
31#include <cmath>
32#include <stdexcept>
33#include <vector>
34
40
45
46namespace q3 {
47
49 public:
51 const Crystal& crystal_structure,
52 const PlanewaveBasis& planewave_basis,
54 const std::vector<std::vector<Complex>>& potential_g_by_spin,
55 const std::vector<Matrix<Real>>& coefficients_by_spin,
57 : crystal_structure_(crystal_structure)
58 , planewave_basis_(planewave_basis)
59 , quantum_system_(quantum_system)
60 , potential_g_by_spin_(potential_g_by_spin)
61 , coefficients_by_spin_(coefficients_by_spin)
62 , result_(result) {}
63
64 void compute() {
65 for (int index_a = 0; index_a < 3; index_a++)
66 for (int index_b = 0; index_b < 3; index_b++)
67 result_.augmentation[index_a][index_b] = 0.0;
68
69 if (!quantum_system_.has_ultrasoft())
70 return;
71 if (potential_g_by_spin_.size() != coefficients_by_spin_.size())
72 throw std::runtime_error(
73 "augmentation stress: " + std::to_string(potential_g_by_spin_.size()) +
74 " potential channels against " + std::to_string(coefficients_by_spin_.size()) +
75 " occupancy channels; they must pair up spin by spin");
76
77 for (size_t spin_index = 0; spin_index < potential_g_by_spin_.size(); spin_index++)
78 accumulate_spin_channel(potential_g_by_spin_[spin_index],
79 coefficients_by_spin_[spin_index]);
80
81 /**
82 * Symmetrized once over the spin-summed tensor rather than per channel.
83 * Symmetrization is linear, so the two are identical; doing it here
84 * keeps the accumulation loop free of it.
85 */
86 for (int index_a = 0; index_a < 3; index_a++) {
87 for (int index_b = index_a + 1; index_b < 3; index_b++) {
88 Real average_stress_component = (result_.augmentation[index_a][index_b] +
89 result_.augmentation[index_b][index_a]) /
90 2.0;
91 result_.augmentation[index_a][index_b] = average_stress_component;
92 result_.augmentation[index_b][index_a] = average_stress_component;
93 }
94 }
95 }
96
97 private:
98 /**
99 * One spin channel's contribution, accumulated into `result_.augmentation`.
100 *
101 * Spin has to be resolved here for the same reason it does in the
102 * augmentation *force*: contracting the spin-averaged potential against the
103 * spin-summed occupancies drops the (V↑−V↓)(ρ↑−ρ↓) cross term, which is
104 * zero only for a non-magnetic cell.
105 */
106 void accumulate_spin_channel(const std::vector<Complex>& total_potential_g,
107 const Matrix<Real>& projector_overlap_coefficients) {
108 int number_of_atoms = crystal_structure_.number_of_atoms();
109 int number_of_g_vectors = planewave_basis_.number_of_g_vectors();
110 Real two_pi_over_lattice_parameter = crystal_structure_.two_pi_over_lattice_parameter();
111 int first_nonzero_g_index = planewave_basis_.first_nonzero_g_index();
112
113 auto& ultrasoft_system =
114 static_cast<const UltrasoftPlanewaveKohnShamQuantumSystem&>(quantum_system_);
115 const auto& augmentation_q_table =
116 ultrasoft_system.ultrasoft_basis().augmentation_q_table();
117 const auto& spherical_harmonics_table =
118 ultrasoft_system.ultrasoft_basis().spherical_harmonics_table();
119 const auto& gaunt_table = ultrasoft_system.ultrasoft_basis().gaunt_table();
120 int number_of_lm_components = spherical_harmonics_table.number_of_lm_components();
121 const auto& projector_mapping_table =
123
124 // Pre-compute G-vector data
125 std::vector<std::array<Real, 3>> g_cartesian_vectors(number_of_g_vectors);
126 std::vector<Real> g_squared_values(number_of_g_vectors);
127 const auto& g_vector_magnitudes = planewave_basis_.g_vector_magnitudes();
128
129 for (int g_index = 0; g_index < number_of_g_vectors; g_index++) {
130 const auto& g_vector = planewave_basis_.g_vectors()[g_index];
131 g_cartesian_vectors[g_index] = {g_vector.g_cart.x * two_pi_over_lattice_parameter,
132 g_vector.g_cart.y * two_pi_over_lattice_parameter,
133 g_vector.g_cart.z * two_pi_over_lattice_parameter};
134 g_squared_values[g_index] =
135 g_cartesian_vectors[g_index][0] * g_cartesian_vectors[g_index][0] +
136 g_cartesian_vectors[g_index][1] * g_cartesian_vectors[g_index][1] +
137 g_cartesian_vectors[g_index][2] * g_cartesian_vectors[g_index][2];
138 }
139
140 // Pre-compute |G| values and inverse |G| for chain rule
141 std::vector<Real> inverse_g_magnitude(number_of_g_vectors, 0.0);
142 for (int g_index = first_nonzero_g_index; g_index < number_of_g_vectors; g_index++) {
143 if (g_vector_magnitudes[g_index] > math::tolerances::zero_vector_magnitude_threshold) {
144 inverse_g_magnitude[g_index] = 1.0 / g_vector_magnitudes[g_index];
145 }
146 }
147
148 /**
149 * One species type at a time, to bound the per-species dQ buffer below.
150 * Note that Q_ij^l(|G|) and its |G|-derivative are NOT pre-tabulated: both
151 * are interpolated inside the G loop, once per (pair, LM, G).
152 */
153
154 // For each polarization direction beta:
155 for (int polarization_index_b = 0; polarization_index_b < 3; polarization_index_b++) {
156
157 // Pre-compute dYlm/dG_beta for all G-vectors (one batch call)
158 std::vector<std::vector<Real>> spherical_harmonic_derivatives_beta;
159 compute_spherical_harmonic_derivatives_batch(number_of_lm_components,
160 number_of_g_vectors,
161 g_cartesian_vectors,
162 g_squared_values,
163 polarization_index_b,
164 spherical_harmonic_derivatives_beta);
165
166 // Process each unique species type once
167 int number_of_species = static_cast<int>(
168 quantum_system_.pseudopotential_basis().pseudopotential_set().size());
169 for (int species_type_index = 0; species_type_index < number_of_species;
170 species_type_index++) {
171 if (!quantum_system_.pseudopotential_basis()
172 .pseudopotential_set()[species_type_index]
174 continue;
175
176 int number_of_projectors =
177 projector_mapping_table.number_of_channels(species_type_index);
178 int number_of_beta_functions = quantum_system_.pseudopotential_basis()
179 .pseudopotential_set()[species_type_index]
180 .number_of_projectors;
181 int maximum_angular_momentum_q_plus_one =
182 augmentation_q_table.maximum_angular_momentum_q() + 1;
183 int number_of_lm_projectors =
184 maximum_angular_momentum_q_plus_one * maximum_angular_momentum_q_plus_one;
185
186 /**
187 * Compute dQ_ij(G)/dG_beta for all (i,j) pairs and all G-vectors:
188 * one complex vector of length Ng per pair. The contraction below
189 * needs every G of a pair at once, and the pair set is fixed per
190 * species while the atoms that consume it are not.
191 */
192 int number_of_triangle_pairs =
193 number_of_projectors * (number_of_projectors + 1) / 2;
194 // Flat storage: dqg[pair_triangle_index * Ng + g_index]
195 std::vector<Complex> dqg(static_cast<size_t>(number_of_triangle_pairs) *
196 number_of_g_vectors,
197 Complex(0.0, 0.0));
198
199 int triangle_pair_index = 0;
200 for (int projector_index_i = 0; projector_index_i < number_of_projectors;
201 projector_index_i++) {
202 for (int projector_index_j = projector_index_i;
203 projector_index_j < number_of_projectors;
204 projector_index_j++, triangle_pair_index++) {
205
206 int combined_lm_index_i = projector_mapping_table.combined_lm_index(
207 species_type_index, projector_index_i);
208 int combined_lm_index_j = projector_mapping_table.combined_lm_index(
209 species_type_index, projector_index_j);
210 int angular_momentum_i = projector_mapping_table.angular_momentum(
211 species_type_index, projector_index_i);
212 int angular_momentum_j = projector_mapping_table.angular_momentum(
213 species_type_index, projector_index_j);
214 int beta_index_i = projector_mapping_table.to_beta_index(species_type_index,
215 projector_index_i);
216 int beta_index_j = projector_mapping_table.to_beta_index(species_type_index,
217 projector_index_j);
218 int projector_pair_index =
219 beta_index_i * number_of_beta_functions + beta_index_j;
220
221 if (combined_lm_index_i >= number_of_lm_projectors ||
222 combined_lm_index_j >= number_of_lm_projectors)
223 continue;
224 if (gaunt_table.number_of_nonzero_product_indices[combined_lm_index_i]
225 [combined_lm_index_j] == 0)
226 continue;
227
228 Complex* dqg_pair =
229 &dqg[static_cast<size_t>(triangle_pair_index) * number_of_g_vectors];
230
231 // For each Gaunt term, sweep over ALL G-vectors
232 for (int gaunt_index = 0;
233 gaunt_index <
234 gaunt_table.number_of_nonzero_product_indices[combined_lm_index_i]
235 [combined_lm_index_j];
236 gaunt_index++) {
237 int product_lm_index =
238 gaunt_table
239 .nonzero_product_indices[combined_lm_index_i]
240 [combined_lm_index_j][gaunt_index];
241 Real gaunt_coefficient_value =
242 gaunt_table.coefficients[product_lm_index][combined_lm_index_i]
243 [combined_lm_index_j];
244 int angular_momentum =
245 static_cast<int>(std::sqrt(static_cast<double>(product_lm_index)));
246 if (angular_momentum <
247 std::abs(angular_momentum_i - angular_momentum_j) ||
248 angular_momentum > angular_momentum_i + angular_momentum_j ||
249 (angular_momentum + angular_momentum_i + angular_momentum_j) % 2 !=
250 0)
251 continue;
252
253 int phase_type = angular_momentum % 4;
254
255 // Vectorized inner loop over G-vectors
256 for (int g_index = first_nonzero_g_index; g_index < number_of_g_vectors;
257 g_index++) {
258 Real g_abs = g_vector_magnitudes[g_index];
260 continue;
261
262 Real augmentation_function_value =
263 augmentation_q_table.interpolate_augmentation_function(
264 species_type_index,
265 projector_pair_index,
266 angular_momentum,
267 g_abs);
268
269 Real augmentation_function_derivative_value =
270 augmentation_q_table
271 .interpolate_augmentation_function_derivative(
272 species_type_index,
273 projector_pair_index,
274 angular_momentum,
275 g_abs);
276
277 Real ylm_value =
278 spherical_harmonics_table.value(g_index, product_lm_index);
279 Real dylm_value =
280 spherical_harmonic_derivatives_beta[g_index][product_lm_index];
281
282 Real g_beta = g_cartesian_vectors[g_index][polarization_index_b];
283 Real derivative_contribution =
284 dylm_value * augmentation_function_value +
285 ylm_value * augmentation_function_derivative_value * g_beta *
286 inverse_g_magnitude[g_index];
287
288 Real weighted = gaunt_coefficient_value * derivative_contribution;
289
290 switch (phase_type) {
291 case 0:
292 dqg_pair[g_index] += Complex(weighted, 0.0);
293 break;
294 case 1:
295 dqg_pair[g_index] += Complex(0.0, -weighted);
296 break;
297 case 2:
298 dqg_pair[g_index] += Complex(-weighted, 0.0);
299 break;
300 case 3:
301 dqg_pair[g_index] += Complex(0.0, weighted);
302 break;
303 }
304 }
305 }
306 }
307 }
308
309 // Now for each atom of this species, contract dqg with ρ_ij and V*(G)*SF
310 for (int atom_index = 0; atom_index < number_of_atoms; atom_index++) {
311 if (crystal_structure_.species_type_index(atom_index) != species_type_index)
312 continue;
313
314 // Compute ρ_ij-weighted dQ(G): aux(G) = sum_{ij} D_ij * dQ_ij(G)/dG_beta
315 std::vector<Complex> aux(number_of_g_vectors, Complex(0.0, 0.0));
316
317 triangle_pair_index = 0;
318 for (int projector_index_i = 0; projector_index_i < number_of_projectors;
319 projector_index_i++) {
320 for (int projector_index_j = projector_index_i;
321 projector_index_j < number_of_projectors;
322 projector_index_j++, triangle_pair_index++) {
323 int projector_pair_linear =
324 projector_index_i * number_of_projectors + projector_index_j;
325 Real projector_overlap_coefficient =
326 (projector_pair_linear <
327 static_cast<int>(projector_overlap_coefficients.cols()))
328 ? projector_overlap_coefficients(atom_index,
329 projector_pair_linear)
330 : 0.0;
331 if (std::abs(projector_overlap_coefficient) <
333 continue;
334 Real symmetry_factor =
335 (projector_index_i == projector_index_j) ? 1.0 : 2.0;
336 Real coefficient = projector_overlap_coefficient * symmetry_factor;
337
338 const Complex* dqg_pair =
339 &dqg[static_cast<size_t>(triangle_pair_index) *
340 number_of_g_vectors];
341 for (int g_index = 0; g_index < number_of_g_vectors; g_index++) {
342 aux[g_index] += coefficient * dqg_pair[g_index];
343 }
344 }
345 }
346
347 // Multiply by V*(G) * structure_factor
348 Vector3D atomic_position =
349 crystal_structure_.convert_from_crystal_to_cartesian(atom_index);
350 for (int g_index = first_nonzero_g_index; g_index < number_of_g_vectors;
351 g_index++) {
352 const auto& g_vector = planewave_basis_.g_vectors()[g_index];
353 Real phase_angle = constants::two_pi * g_vector.g_cart.dot(atomic_position);
354 Complex structure_factor = compute_phase_factor(phase_angle);
355 aux[g_index] *= std::conj(total_potential_g[g_index]) * structure_factor;
356 }
357
358 // Contract: sigma_{alpha, beta} += sum_G G_alpha * aux(G)
359 for (int polarization_index_a = 0; polarization_index_a < 3;
360 polarization_index_a++) {
361 Real sigma_component = 0.0;
362 Real reciprocal_space_multiplicity =
363 planewave_basis_.is_gamma_only() ? 2.0 : 1.0;
364 for (int g_index = first_nonzero_g_index; g_index < number_of_g_vectors;
365 g_index++) {
366 Real g_alpha = g_cartesian_vectors[g_index][polarization_index_a];
367 sigma_component += reciprocal_space_multiplicity *
368 (Complex(g_alpha, 0.0) * aux[g_index]).real();
369 }
370 result_.augmentation[polarization_index_a][polarization_index_b] +=
371 sigma_component;
372 }
373 }
374 }
375 }
376 }
377
378 const Crystal& crystal_structure_;
379 const PlanewaveBasis& planewave_basis_;
380 const NormConservingPlanewaveKohnShamQuantumSystem& quantum_system_;
381 const std::vector<std::vector<Complex>>& potential_g_by_spin_;
382 const std::vector<Matrix<Real>>& coefficients_by_spin_;
383 StressTensorContributions& result_;
384};
385
386} // namespace q3
Vector3D convert_from_crystal_to_cartesian(int atom_index) const
Definition crystal.hpp:225
Real two_pi_over_lattice_parameter() const
Definition crystal.hpp:196
int species_type_index(int atom_index) const
Definition crystal.hpp:273
int number_of_atoms() const
Definition crystal.hpp:209
size_t cols() const
Definition matrix.hpp:65
const PseudopotentialSet & pseudopotential_set() const
Definition init.hpp:156
const ProjectorMappingTable & projector_mapping_table() const
Definition init.hpp:363
const NormConservingPlanewaveBasis & pseudopotential_basis() const
Definition init.hpp:65
int number_of_g_vectors() const
Definition init.hpp:145
const std::vector< GVector > & g_vectors() const
Definition init.hpp:148
int first_nonzero_g_index() const
Definition init.hpp:151
bool is_gamma_only() const
Definition init.hpp:157
const std::vector< Real > & g_vector_magnitudes() const
Definition init.hpp:154
int size() const
Definition set.hpp:98
bool has_augmentation_charges() const
Definition set.hpp:129
UltrasoftAugmentationStressTensorCalculator(const Crystal &crystal_structure, const PlanewaveBasis &planewave_basis, const NormConservingPlanewaveKohnShamQuantumSystem &quantum_system, const std::vector< std::vector< Complex > > &potential_g_by_spin, const std::vector< Matrix< Real > > &coefficients_by_spin, StressTensorContributions &result)
const AugmentationQTable & augmentation_q_table() const
Definition init.hpp:62
const UltrasoftPlanewaveBasis & ultrasoft_basis() const
Definition init.hpp:59
constexpr Real two_pi
Definition constants.hpp:25
constexpr double numerical_zero_threshold
constexpr double zero_vector_magnitude_threshold
void compute_spherical_harmonic_derivatives_batch(int number_of_lm_components, int number_of_g_vectors, const std::vector< std::array< Real, 3 > > &g_vectors, const std::vector< Real > &g_squared, int polarization_index, std::vector< std::vector< Real > > &spherical_harmonic_derivatives)
double Real
Complex compute_phase_factor(Real phase)
Compute e^{-iφ} = cos(φ) - i·sin(φ) (standard structure factor phase).
std::complex< double > Complex
Per-contribution stress tensor breakdown (kbar after conversion).