Loading...
Searching...
No Matches
init.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * UltrasoftPlanewaveEffectivePotential — non-local effective potential
5 * for ultrasoft pseudopotentials (the D_eff matrix).
6 *
7 * D_eff_ij = D_ion_ij + ∫ conj(V_eff(G)) * Q_ij(G) * e^{-iG·τ} dG
8 *
9 * This is the non-local counterpart to PlanewaveEffectivePotential (local V_eff).
10 * It is recomputed each SCF step from the current V_eff via the pre-computed Q(G) table.
11 */
12
13#include <cmath>
14#include <stdexcept>
15#include <string>
16#include <vector>
17
26
27namespace q3 {
28using namespace q3::blas;
29
31 public:
33 using NormConservingPlanewaveEffectivePotential::NormConservingPlanewaveEffectivePotential;
34
35 /**
36 * Compute the effective D-matrix for all atoms from the current V_eff(G).
37 *
38 * Uses the pre-computed Q_ij(G) table from the ultrasoft basis to avoid
39 * per-G interpolation. The computation reduces to:
40 * D_eff(i,j) = D_ion(i,j) + Re(Σ_G conj(V(G)) * Q_ij(G) * e^{-iG·τ}) * Ω
41 *
42 * @param crystal_structure Crystal geometry
43 * @param planewave_basis Plane-wave basis (G-vectors)
44 * @param ultrasoft_pseudopotential_basis Ultrasoft basis (pre-computed Q(G), projector
45 * mappings)
46 * @param total_potential_g Total effective potential in G-space
47 */
48 /**
49 * Compute D for every spin channel.
50 *
51 * D_ij = D_ion + ∫v_eff·Q_ij, and v_eff is spin-dependent, so D is too.
52 * Calling this with one potential reproduces the unpolarized result
53 * exactly; calling it with two gives each channel its own D, which is what
54 * a magnetic system needs and what a shared, spin-averaged D silently
55 * denies it.
56 */
57 void compute_by_spin(const Crystal& crystal_structure,
58 const PlanewaveBasis& planewave_basis,
59 const UltrasoftPlanewaveBasis& ultrasoft_pseudopotential_basis,
60 const std::vector<std::vector<Complex>>& total_potential_g_by_spin) {
61 int number_of_spins = static_cast<int>(total_potential_g_by_spin.size());
62 effective_d_matrix_by_spin_.resize(number_of_spins);
63 for (int spin = 0; spin < number_of_spins; spin++) {
64 compute(crystal_structure,
65 planewave_basis,
66 ultrasoft_pseudopotential_basis,
67 total_potential_g_by_spin[spin]);
69 }
70 /**
71 * Leave `effective_d_matrix_` holding channel 0 so anything still
72 * reading the spin-free accessor sees a real D rather than whatever the
73 * last loop iteration left behind.
74 */
75 if (number_of_spins > 0)
77 }
78
79 /**
80 * Add per-atom terms to one spin channel's D (PAW's one-center term).
81 *
82 * Refuses a channel that was never computed rather than returning quietly.
83 * A silent no-op would leave that channel's Hamiltonian carrying the other
84 * channel's one-center D — via `element()`'s fallback — while the
85 * double-counting term still subtracted its own, so the SCF would converge
86 * to a stationary point of something that is not the energy it prints.
87 */
88 void add_per_atom_d_terms_for_spin(int spin, const std::vector<std::vector<Real>>& terms) {
89 if (spin >= static_cast<int>(effective_d_matrix_by_spin_.size()))
90 throw std::runtime_error(
91 "one-center D terms were computed for more spin channels than the nonlocal "
92 "effective potential holds; the two must be built from the same spin count");
96 if (spin == 0)
98 }
99
100 void compute(const Crystal& crystal_structure,
101 const PlanewaveBasis& planewave_basis,
102 const UltrasoftPlanewaveBasis& ultrasoft_pseudopotential_basis,
103 const std::vector<Complex>& total_potential_g) {
104 int number_of_atoms = crystal_structure.number_of_atoms();
105 int number_of_g_vectors = planewave_basis.number_of_g_vectors();
106 Real cell_volume = crystal_structure.cell_volume();
107 const auto& pseudopotential_set = ultrasoft_pseudopotential_basis.pseudopotential_set();
108 const auto& projector_mapping = ultrasoft_pseudopotential_basis.projector_mapping_table();
109 int number_of_species = static_cast<int>(pseudopotential_set.size());
110
111 /**
112 * Phase 1: For each US species, precompute aux_ij(G) = conj(V(G)) * Q_ij(G)
113 * This is done once per species, not once per atom.
114 *
115 * Correct formula: D_ij = ∫ Q(r−R)·V(r) dr = Σ_G Q(G)·conj(V(G))·e^{−iGR}·Ω
116 * [For real V: V(−G)=V*(G), so the integral picks up V*(G).]
117 * Taking conj(Q) instead of conj(V) gives the same result only when
118 * Im(V(G)) = 0 — true for symmetric structures but NOT for displaced atoms.
119 */
120 struct SpeciesAuxData {
121 int number_of_projectors;
122 // aux_data[pair_index * n_g + g_index] for upper-triangle pairs
123 std::vector<Complex> v_conj_times_q;
124 // Linear pair indices for the upper triangle
125 std::vector<std::pair<int, int>> upper_triangle_pairs;
126 };
127 std::vector<SpeciesAuxData> species_aux(number_of_species);
128
129 for (int species_index = 0; species_index < number_of_species; ++species_index) {
130 int number_of_projectors = projector_mapping.number_of_channels(species_index);
131 species_aux[species_index].number_of_projectors = number_of_projectors;
132
133 /**
134 * A species contributes ∫v_eff·Q_ij here exactly when it has
135 * augmentation charges — which PAW does. Skipping on `!=
136 * ultrasoft` would leave a PAW atom's D at D_ion alone, a large
137 * and entirely silent error.
138 */
139 if (!pseudopotential_set[species_index].has_augmentation_charges()) {
140 continue;
141 }
142
143 auto& aux = species_aux[species_index];
144 // Collect upper-triangle pairs
145 for (int projector_index_i = 0; projector_index_i < number_of_projectors;
146 ++projector_index_i) {
147 for (int projector_index_j = projector_index_i;
148 projector_index_j < number_of_projectors;
149 ++projector_index_j) {
150 aux.upper_triangle_pairs.emplace_back(projector_index_i, projector_index_j);
151 }
152 }
153
154 int number_of_pairs = static_cast<int>(aux.upper_triangle_pairs.size());
155 aux.v_conj_times_q.resize(static_cast<size_t>(number_of_pairs) * number_of_g_vectors);
156
157 for (int pair_index = 0; pair_index < number_of_pairs; ++pair_index) {
158 auto [proj_i, proj_j] = aux.upper_triangle_pairs[pair_index];
159 int channel_pair_index = proj_i * number_of_projectors + proj_j;
160 const Complex* q_g_data = ultrasoft_pseudopotential_basis.q_function_g_data(
161 species_index, channel_pair_index, number_of_g_vectors);
162
163 Complex* dest = aux.v_conj_times_q.data() +
164 static_cast<size_t>(pair_index) * number_of_g_vectors;
165 for (int g_index = 0; g_index < number_of_g_vectors; ++g_index) {
166 /**
167 * D_ij = ∫ Q(r−R)·V(r) dr = Σ_G Q(G)·V*(G)·e^{−iGR}·Ω
168 * Must conjugate V, NOT Q (Q may also be complex due to (−i)^l factor).
169 */
170 dest[g_index] = std::conj(total_potential_g[g_index]) * q_g_data[g_index];
171 }
172 }
173 }
174
175 /**
176 * Phase 2: Compute D_eff for all atoms using batched BLAS zgemm per species.
177 * Instead of 54 individual zgemv calls, we group atoms by species and
178 * compute all integrals in a single zgemm: result = aux^T × sf_matrix.
179 * aux is (n_g × n_pairs), sf_matrix is (n_g × n_atoms_of_species),
180 * result is (n_pairs × n_atoms_of_species).
181 */
182 effective_d_matrix_.resize(number_of_atoms);
183
184 // First pass: initialize D-ion for all atoms
185 for (int atom_index = 0; atom_index < number_of_atoms; ++atom_index) {
186 int species_type_index = crystal_structure.species_type_index(atom_index);
187 const auto& aux = species_aux[species_type_index];
188 int number_of_projectors = aux.number_of_projectors;
189 effective_d_matrix_[atom_index].assign(number_of_projectors * number_of_projectors,
190 0.0);
191
192 for (int projector_index_i = 0; projector_index_i < number_of_projectors;
193 ++projector_index_i) {
194 for (int projector_index_j = 0; projector_index_j < number_of_projectors;
195 ++projector_index_j) {
196 int combined_lm_i =
197 projector_mapping.combined_lm_index(species_type_index, projector_index_i);
198 int combined_lm_j =
199 projector_mapping.combined_lm_index(species_type_index, projector_index_j);
200 if (combined_lm_i == combined_lm_j) {
201 int projector_beta_index_i =
202 projector_mapping.to_beta_index(species_type_index, projector_index_i);
203 int projector_beta_index_j =
204 projector_mapping.to_beta_index(species_type_index, projector_index_j);
205 effective_d_matrix_[atom_index][projector_index_i * number_of_projectors +
206 projector_index_j] =
207 pseudopotential_set[species_type_index]
208 .d_ion_matrix[projector_beta_index_i][projector_beta_index_j];
209 }
210 }
211 }
212 }
213
214 // Cache structure factors if not already done
215 if (cached_structure_factors_.empty()) {
216 precompute_structure_factors(crystal_structure, planewave_basis);
217 }
218
219 // Second pass: batch zgemm per species
220 for (int species_index = 0; species_index < number_of_species; ++species_index) {
221 if (!pseudopotential_set[species_index].has_augmentation_charges()) {
222 continue;
223 }
224
225 const auto& aux = species_aux[species_index];
226 int number_of_pairs = static_cast<int>(aux.upper_triangle_pairs.size());
227 int number_of_projectors = aux.number_of_projectors;
228
229 // Collect atom indices for this species
230 std::vector<int> species_atom_indices;
231 for (int atom_index = 0; atom_index < number_of_atoms; ++atom_index) {
232 if (crystal_structure.species_type_index(atom_index) == species_index) {
233 species_atom_indices.push_back(atom_index);
234 }
235 }
236 int number_of_atoms_of_species = static_cast<int>(species_atom_indices.size());
237
238 // Build contiguous structure factor matrix: (n_g × n_atoms_of_species) column-major
239 std::vector<Complex> structure_factor_matrix(static_cast<size_t>(number_of_g_vectors) *
240 number_of_atoms_of_species);
241 for (int local_atom = 0; local_atom < number_of_atoms_of_species; ++local_atom) {
242 int atom_index = species_atom_indices[local_atom];
243 const auto& structure_factor = cached_structure_factors_[atom_index];
244 std::copy(structure_factor.begin(),
245 structure_factor.end(),
246 structure_factor_matrix.begin() +
247 static_cast<size_t>(local_atom) * number_of_g_vectors);
248 }
249
250 /**
251 * zgemm: result = aux^T × sf_matrix
252 * aux is (n_g × n_pairs) column-major, sf_matrix is (n_g × n_atoms) column-major
253 * result = aux^T × sf = (n_pairs × n_g) × (n_g × n_atoms) = (n_pairs × n_atoms)
254 */
255 std::vector<Complex> all_integrals(static_cast<size_t>(number_of_pairs) *
256 number_of_atoms_of_species);
257 Complex blas_alpha(1.0, 0.0);
258 Complex blas_beta(0.0, 0.0);
259 zgemm_("T",
260 "N",
261 &number_of_pairs,
262 &number_of_atoms_of_species,
263 &number_of_g_vectors,
264 &blas_alpha,
265 aux.v_conj_times_q.data(),
266 &number_of_g_vectors,
267 structure_factor_matrix.data(),
268 &number_of_g_vectors,
269 &blas_beta,
270 all_integrals.data(),
271 &number_of_pairs);
272
273 /**
274 * Unpack results into per-atom D-matrices
275 * For gamma-only: multiply by 2 (half-sphere → full sphere), then
276 * subtract the G=0 contribution once (it was double-counted).
277 */
278 bool is_gamma_only = planewave_basis.is_gamma_only();
279 Real gamma_scale = is_gamma_only ? 2.0 : 1.0;
280
281 /**
282 * If gamma-only, precompute the G=0 correction:
283 * G=0 term = Re(conj(V(0))*Q_ij(0)*sf(0)) = Re(conj(V(0))*Q_ij(0)*1)
284 * sf(G=0) = exp(i·0·τ) = 1 for all atoms.
285 * At G=0, V(0) is real and Q(0) is real (l=0 only), so conj(V(0))=V(0).
286 * This correction is the same for all atoms of this species.
287 */
288 std::vector<Real> g0_correction;
289 if (is_gamma_only && planewave_basis.first_nonzero_g_index() == 1) {
290 g0_correction.resize(number_of_pairs, 0.0);
291 for (int pair_index = 0; pair_index < number_of_pairs; ++pair_index) {
292 // v_conj_times_q[pair * ngm + 0] = conj(V(0)) * Q(0) at G=0
293 Complex g0_val =
294 aux.v_conj_times_q[static_cast<size_t>(pair_index) * number_of_g_vectors];
295 g0_correction[pair_index] = std::real(g0_val) * cell_volume;
296 }
297 }
298
299 for (int local_atom = 0; local_atom < number_of_atoms_of_species; ++local_atom) {
300 int atom_index = species_atom_indices[local_atom];
301 const Complex* integrals =
302 all_integrals.data() + static_cast<size_t>(local_atom) * number_of_pairs;
303 for (int pair_index = 0; pair_index < number_of_pairs; ++pair_index) {
304 auto [proj_i, proj_j] = aux.upper_triangle_pairs[pair_index];
305 Real contribution =
306 std::real(integrals[pair_index]) * cell_volume * gamma_scale;
307 // Subtract the G=0 double-count for gamma-only
308 if (is_gamma_only && !g0_correction.empty()) {
309 contribution -= g0_correction[pair_index];
310 }
311 effective_d_matrix_[atom_index][proj_i * number_of_projectors + proj_j] +=
312 contribution;
313 if (proj_i != proj_j) {
314 effective_d_matrix_[atom_index][proj_j * number_of_projectors + proj_i] +=
315 contribution;
316 }
317 }
318 }
319 }
320 }
321
322 /**
323 * Add per-atom terms that a layered flavor contributes to D.
324 *
325 * PAW's D is this D plus a one-center term:
326 *
327 * D_ij = D⁰_ij + ∫ v_eff Q_ij + (D¹ − D̃¹)_Hxc
328 * └─ dataset ─┘ └─ here ─┘ └─ passed in ─┘
329 *
330 * The first two are what compute() built and are never recomputed for
331 * PAW — the kinetic and core-reference pieces baked into `PP_DIJ`/`DION`
332 * at generation time would be double-counted if they were. So the
333 * one-center term is a decoration of this result, and this is the seam it
334 * arrives through: the correction depends on the on-site occupancies,
335 * which only the SCF loop knows, so it is passed in rather than computed
336 * here.
337 *
338 * Expressed as a method rather than a subclass override deliberately. The
339 * iterators hold this type by value, so a `PawPlanewaveEffectivePotential`
340 * could not actually be substituted for it without pointer-ifying every
341 * holder — a subclass that cannot be substituted is worse than a named
342 * operation on the object that owns D.
343 *
344 * Call after compute(), once per SCF step. An empty entry means "no terms
345 * for this atom", so a mixed PAW/ultrasoft cell needs no special case.
346 */
347 void add_per_atom_d_terms(const std::vector<std::vector<Real>>& terms_by_atom) {
348 int number_of_atoms = static_cast<int>(effective_d_matrix_.size());
349 for (int atom_index = 0; atom_index < number_of_atoms; ++atom_index) {
350 const auto& terms = terms_by_atom[atom_index];
351 if (terms.empty()) {
352 continue;
353 }
354 auto& destination = effective_d_matrix_[atom_index];
355 /**
356 * A size mismatch means the caller's channel expansion disagrees
357 * with the projector mapping table's, which would otherwise write
358 * plausible numbers into the wrong elements. There is no
359 * partial-overlap reading of these matrices that is meaningful, so
360 * refusing is the only safe response.
361 */
362 if (terms.size() != destination.size()) {
363 throw std::runtime_error("per-atom D terms do not match the projector channel "
364 "count for atom " +
365 std::to_string(atom_index));
366 }
367 for (size_t index = 0; index < destination.size(); ++index) {
368 destination[index] += terms[index];
369 }
370 }
371 }
372
373 /**
374 * Read an element of the effective D-matrix.
375 */
376 [[nodiscard]] Real
377 element(int atom_index, int projector_index_i, int projector_index_j, int spin = 0) const {
378 /**
379 * Falls back to the spin-free matrix when no per-spin one was computed,
380 * which is every unpolarized run and every path that still calls the
381 * single-potential `compute`.
382 */
383 const auto& matrix = (spin < static_cast<int>(effective_d_matrix_by_spin_.size()) &&
384 !effective_d_matrix_by_spin_[spin].empty())
387 if (matrix.empty()) {
388 return 0.0;
389 }
390 if (atom_index < static_cast<int>(matrix.size())) {
391 int number_of_projectors_squared = static_cast<int>(matrix[atom_index].size());
392 int number_of_projectors = static_cast<int>(
393 std::round(std::sqrt(static_cast<double>(number_of_projectors_squared))));
394 if (projector_index_i < number_of_projectors &&
395 projector_index_j < number_of_projectors)
396 return matrix[atom_index]
397 [projector_index_i * number_of_projectors + projector_index_j];
398 }
399 return 0.0;
400 }
401
402 [[nodiscard]] bool is_initialized() const {
403 return !effective_d_matrix_.empty();
404 }
405
406 /**
407 * Precompute and cache structure factors for all atoms.
408 * These are constant during SCF since atomic positions don't change.
409 */
410 void precompute_structure_factors(const Crystal& crystal_structure,
411 const PlanewaveBasis& planewave_basis) {
412 int number_of_atoms = crystal_structure.number_of_atoms();
413 int number_of_g_vectors = planewave_basis.number_of_g_vectors();
414 cached_structure_factors_.resize(number_of_atoms);
415 for (int atom_index = 0; atom_index < number_of_atoms; ++atom_index) {
416 cached_structure_factors_[atom_index].resize(number_of_g_vectors);
417 Vector3D atomic_position =
418 crystal_structure.convert_from_crystal_to_cartesian(atom_index);
419 for (int g_index = 0; g_index < number_of_g_vectors; ++g_index) {
420 const auto& g_vector = planewave_basis.g_vectors()[g_index];
421 Real phase_angle = constants::two_pi * g_vector.g_cart.dot(atomic_position);
422 cached_structure_factors_[atom_index][g_index] = compute_phase_factor(phase_angle);
423 }
424 }
425 }
426
427 /**
428 * Return the cached structure factors (computed on first call to compute()).
429 * Useful for sharing with augmentation charge code.
430 */
431 [[nodiscard]] const std::vector<std::vector<Complex>>& cached_structure_factors() const {
432 return cached_structure_factors_;
433 }
434
435 protected:
436 /**
437 * Effective D-matrix: per-atom × (projector_channels²).
438 *
439 * Protected rather than private because PAW's D is this D plus a
440 * one-center term — the seam named in the A3 addendum. A subclass adds to
441 * it in place; nothing else may.
442 */
443 std::vector<std::vector<Real>> effective_d_matrix_;
444
445 /// One D per spin channel; empty when the spin-free `compute` was used.
446 std::vector<std::vector<std::vector<Real>>> effective_d_matrix_by_spin_;
447
448 private:
449 /// Cached structure factors: per-atom × G-vectors (constant during SCF).
450 std::vector<std::vector<Complex>> cached_structure_factors_;
451};
452
453} // namespace q3
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
const PseudopotentialSet & pseudopotential_set() const
Definition init.hpp:156
const ProjectorMappingTable & projector_mapping_table() const
Definition init.hpp:363
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 Complex * q_function_g_data(int species_type_index, int channel_pair_index, int number_of_g_vectors) const
Definition init.hpp:154
const std::vector< std::vector< Complex > > & cached_structure_factors() const
Definition init.hpp:431
Real element(int atom_index, int projector_index_i, int projector_index_j, int spin=0) const
Definition init.hpp:377
std::vector< std::vector< Real > > effective_d_matrix_
Definition init.hpp:443
void precompute_structure_factors(const Crystal &crystal_structure, const PlanewaveBasis &planewave_basis)
Definition init.hpp:410
void add_per_atom_d_terms(const std::vector< std::vector< Real > > &terms_by_atom)
Definition init.hpp:347
void compute(const Crystal &crystal_structure, const PlanewaveBasis &planewave_basis, const UltrasoftPlanewaveBasis &ultrasoft_pseudopotential_basis, const std::vector< Complex > &total_potential_g)
Definition init.hpp:100
void compute_by_spin(const Crystal &crystal_structure, const PlanewaveBasis &planewave_basis, const UltrasoftPlanewaveBasis &ultrasoft_pseudopotential_basis, const std::vector< std::vector< Complex > > &total_potential_g_by_spin)
Definition init.hpp:57
void add_per_atom_d_terms_for_spin(int spin, const std::vector< std::vector< Real > > &terms)
Definition init.hpp:88
std::vector< std::vector< std::vector< Real > > > effective_d_matrix_by_spin_
One D per spin channel; empty when the spin-free compute was used.
Definition init.hpp:446
void zgemm_(const char *transa, const char *transb, const int *m, const int *n, const int *k, const std::complex< double > *alpha, const std::complex< double > *a, const int *lda, const std::complex< double > *b, const int *ldb, const std::complex< double > *beta, std::complex< double > *c, const int *ldc)
constexpr Real two_pi
Definition constants.hpp:25
double Real
Complex compute_phase_factor(Real phase)
Compute e^{-iφ} = cos(φ) - i·sin(φ) (standard structure factor phase).
std::complex< double > Complex