Loading...
Searching...
No Matches
initializer.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * NormConservingPlanewaveWavefunctionInitializer — initialize wavefunctions from atomic orbitals.
5 *
6 * Provides the "atomic+random" starting guess for the Kohn-Sham eigensolver:
7 * 1. Tabulates atomic pseudo-wavefunctions from the pseudopotential files
8 * 2. Computes atomic orbital projections onto the plane-wave basis at each k-point
9 * 3. Applies a small random perturbation to break symmetry
10 * 4. Orthogonalizes the wavefunctions via subspace rotation (Löwdin / Cholesky)
11 * 5. For ultrasoft PPs, applies the S-operator overlap correction
12 *
13 * This generates a starting wavefunction set for the Davidson or direct diagonalizer,
14 * avoiding the need for expensive random initialization.
15 */
16#include <algorithm>
17#include <cmath>
18#include <complex>
19#include <cstdint>
20#include <cstring>
21#include <memory>
22#include <numeric>
23#include <random>
24#include <string>
25#include <vector>
26
46
48
49namespace q3 {
50using namespace q3::blas;
51using namespace q3::lapack;
52
53/**
54 * Computes starting wavefunctions from atomic orbitals (chi) in the pseudopotential.
55 *
56 * The procedure is the standard LCAO starting guess for a plane-wave SCF
57 * (Payne et al., Rev. Mod. Phys. 64, 1045 (1992), sec. V): superpose the
58 * pseudo-atomic orbitals of every atom, pad the set to the requested band
59 * count, then take the Rayleigh-Ritz solution of H c = e S c on that
60 * subspace. Rayleigh-Ritz is used because it is the variationally optimal
61 * selection of `number_of_bands` vectors out of a larger non-orthogonal
62 * subspace, and because it delivers them S-orthonormal, which is what the
63 * Davidson solver requires on entry.
64 *
65 * Quantum ESPRESSO's pw.x reaches the same three stages. Its routine names
66 * are not reproduced here and its stage boundaries are not the ones below:
67 * its driver also performs wavefunction file I/O and Hubbard-projector
68 * orthogonalisation, neither of which this class does, and the per-k-point
69 * driver loop lives in the SCF iterator rather than here.
70 */
72 public:
73 /**
74 * Precompute the radial Fourier transform interpolation table for all species:
75 *
76 * table[species][shell][q] = 4π/√Ω ∫ chi(r) j_l(q·r) r dr
77 *
78 * The 4π and the j_l come from the Rayleigh expansion of a plane wave, the
79 * 1/√Ω from the plane-wave normalisation, and the single power of r from
80 * UPF storing r·R_nl(r) rather than R_nl(r).
81 *
82 * The q axis is innermost because the only consumer is a 4-point Lagrange
83 * stencil that reads four consecutive q entries, and lagrange_interpolation_4point
84 * takes a one-dimensional q table. Species is outermost because the shell
85 * count varies per species, so the jagged axis has to be the inner of the
86 * two; that leaves the shell axis with no remaining freedom.
87 */
88 void initialize_interpolation_table(const PseudopotentialSet& pseudopotential_set,
89 Real cell_volume) {
91
92 atomic_wavefunction_table_ = tabulate_atomic_wavefunctions_in_q_space(
93 pseudopotential_set, cell_volume, q_spacing_, number_of_q_points_);
94
95 log::info(" Atomic wavefunctions interpolation table initialized ({} q-points, dq={})",
96 number_of_q_points_,
97 q_spacing_);
98 }
99
100 /**
101 * Compute the starting vector set in G-space for one k-point.
102 * Returns atomic_wavefunctions[starting_vector_count][number_of_plane_waves],
103 * where starting_vector_count = max(atomic orbital count, band count).
104 *
105 * Two steps: the pure atomic orbitals come from the shared builder (see
106 * atomic_orbital_builder.hpp for their derivation), and this method then
107 * brings the set up to the band count -- padding the tail with random
108 * vectors when the atomic orbitals do not reach it.
109 */
110 void compute_atomic_wavefunctions(int kpoint_index,
111 int number_of_plane_waves,
112 int number_of_bands,
113 const std::vector<int>& g_vector_indices,
114 const Crystal& crystal_structure,
115 const KPointsGrid& kpoint_grid,
116 const PlanewaveBasis& planewave_basis,
117 const PseudopotentialSet& pseudopotential_set,
118 std::vector<std::vector<Complex>>& atomic_wavefunctions) {
119 /**
120 * Build the pure atomic orbital set (no padding) via the shared builder --
121 * identical construction used by PDOS/Löwdin projection
122 * (layer_3_representation/pseudopotential/planewave/internal/atomic_orbital_builder.hpp).
123 */
124 std::vector<std::vector<Complex>> pure_atomic_orbitals;
126 number_of_plane_waves,
127 g_vector_indices,
128 crystal_structure,
129 kpoint_grid,
130 planewave_basis,
131 pseudopotential_set,
132 atomic_wavefunction_table_,
133 q_spacing_,
134 number_of_q_points_,
135 pure_atomic_orbitals);
136
137 int number_of_atomic_wavefunctions = static_cast<int>(pure_atomic_orbitals.size());
138 int total_wavefunction_count = std::max(number_of_atomic_wavefunctions, number_of_bands);
139 atomic_wavefunctions.assign(total_wavefunction_count,
140 std::vector<Complex>(number_of_plane_waves, Complex(0.0, 0.0)));
141 for (int wavefunction_index = 0; wavefunction_index < number_of_atomic_wavefunctions;
142 wavefunction_index++) {
143 atomic_wavefunctions[wavefunction_index] =
144 std::move(pure_atomic_orbitals[wavefunction_index]);
145 }
146
147 /**
148 * Symmetry-breaking hook, currently INERT: the coefficient below is 0.0,
149 * so the loop that follows multiplies every coefficient by exactly 1 and
150 * no perturbation is applied. It is kept as the place a perturbation
151 * would go, not as one that happens. See the audit note (R-D5-1) --
152 * either the branch goes, or a magnitude criterion gets written down.
153 *
154 * The branch exists at all because a perturbation is only useful when
155 * the atomic orbitals already cover the band count: when they do not,
156 * the random padding below breaks the same degeneracy for free.
157 */
158 static constexpr int default_random_seed = 42;
159 std::mt19937 random_number_generator(default_random_seed +
160 kpoint_index); // deterministic seed per k-point
161 std::uniform_real_distribution<Real> distribution(0.0, 1.0);
162
163 if (number_of_atomic_wavefunctions >= number_of_bands) {
164 for (int wavefunction_loop_index = 0;
165 wavefunction_loop_index < total_wavefunction_count;
166 wavefunction_loop_index++) {
167 for (int g_index = 0; g_index < number_of_plane_waves; g_index++) {
168 Real random_amplitude = distribution(random_number_generator);
169 Real random_phase = distribution(random_number_generator);
170 Real perturbation_arg = constants::two_pi * random_phase;
171 atomic_wavefunctions[wavefunction_loop_index][g_index] *=
172 (1.0 + 0.0 * Complex(random_amplitude * std::cos(perturbation_arg),
173 random_amplitude * std::sin(perturbation_arg)));
174 }
175 }
176 } else {
177 /**
178 * Fill the missing vectors with random coefficients. Each is drawn from
179 * a hash of the k-point, the vector and the plane wave's Miller indices,
180 * not from a generator stepped along the plane-wave list: ranks sharing a
181 * pool each hold a different part of that list, and k-point pools number
182 * their k-points locally, so a stepped generator gave every topology its
183 * own starting guess. The ultrasoft silicon dataset carries no atomic
184 * orbitals and starts from these vectors alone, and took a different SCF
185 * path on every rank count to the same fixed point.
186 */
187 const Vector3D& kpoint_coordinates = kpoint_grid[kpoint_index].coordinates;
188 for (int wavefunction_loop_index = number_of_atomic_wavefunctions;
189 wavefunction_loop_index < total_wavefunction_count;
190 wavefunction_loop_index++) {
191 for (int g_index = 0; g_index < number_of_plane_waves; g_index++) {
192 const IntegerVector3D& miller =
193 planewave_basis.g_vectors()[g_vector_indices[g_index]].miller;
194 Real random_amplitude = hashed_unit_interval(
195 kpoint_coordinates, wavefunction_loop_index, miller, 0);
196 Real random_phase = hashed_unit_interval(
197 kpoint_coordinates, wavefunction_loop_index, miller, 1);
198 Real phase_argument = constants::two_pi * random_phase;
199 atomic_wavefunctions[wavefunction_loop_index][g_index] =
200 Complex(random_amplitude * std::cos(phase_argument),
201 random_amplitude * std::sin(phase_argument));
202 }
203 }
204 }
205 }
206
207 /**
208 * Rayleigh-Ritz on the starting subspace: form H_ij and S_ij in the basis of
209 * the starting vectors, solve the generalised eigenproblem H c = e S c, and
210 * rotate. Taking the lowest `number_of_bands` Ritz pairs is the variationally
211 * optimal selection out of that subspace, and the Ritz vectors come out
212 * S-orthonormal, which is the eigensolver's entry condition.
213 *
214 * Gamma-only takes the real path throughout: under psi(-G) = conj(psi(G)) the
215 * subspace matrices are real symmetric, so a real solver is both cheaper and
216 * the one that keeps the rotated basis gamma-symmetric.
217 *
218 * This overload accepts pre-computed KB projectors to avoid redundant recomputation.
219 *
220 * `smooth_potential` is the local potential on this rank's part of the
221 * wavefunction grid, already multiplied by 1/N of that grid -- what the
222 * Hamiltonian application holds, not the density-grid potential the SCF
223 * stores.
224 */
226 int kpoint_index,
227 int kpoint_index_state,
228 int number_of_plane_waves,
229 int number_of_bands,
230 const std::vector<int>& g_vector_indices,
231 const std::vector<Real>& kinetic_energy_squared,
232 const std::vector<Real>& smooth_potential,
233 const Crystal& crystal_structure,
234 const KPointsGrid& kpoint_grid,
235 const PlanewaveBasis& planewave_basis,
236 const execution::PlanewaveExecutionPolicy& execution_policy,
237 const NormConservingPlanewaveKohnShamQuantumSystem& pseudopotential_quantum_system,
238 const std::vector<std::vector<Complex>>& atomic_wavefunctions,
239 PlanewaveKohnShamQuantumSystem& quantum_system,
240 const Matrix<Complex>& precomputed_kleinman_bylander_potential) {
241 int number_of_starting_wavefunctions = static_cast<int>(atomic_wavefunctions.size());
242 int leading_dimension = std::max(number_of_plane_waves, 1);
243
244 /**
245 * Pack atomic wavefunctions into contiguous column-major matrix:
246 * contiguous_wavefunctions[g + wfc * npw] = atomic_wavefunctions[wfc][g]
247 */
248 std::vector<Complex> contiguous_wavefunctions(static_cast<size_t>(number_of_plane_waves) *
249 number_of_starting_wavefunctions,
250 Complex(0.0, 0.0));
251 for (int wavefunction_index = 0; wavefunction_index < number_of_starting_wavefunctions;
252 wavefunction_index++) {
253 std::memcpy(&contiguous_wavefunctions[wavefunction_index * number_of_plane_waves],
254 atomic_wavefunctions[wavefunction_index].data(),
255 number_of_plane_waves * sizeof(Complex));
256 }
257
258 // Apply H to all atomic wfcs using smooth grid + ZGEMM for KB projectors
259 std::vector<Complex> contiguous_hamiltonian_wavefunctions(
260 static_cast<size_t>(number_of_plane_waves) * number_of_starting_wavefunctions,
261 Complex(0.0, 0.0));
262 apply_hamiltonian_batch_contiguous(number_of_plane_waves,
263 number_of_starting_wavefunctions,
264 g_vector_indices,
265 kinetic_energy_squared,
266 smooth_potential,
267 crystal_structure,
268 kpoint_grid,
269 kpoint_index,
270 planewave_basis,
271 execution_policy,
272 pseudopotential_quantum_system,
273 precomputed_kleinman_bylander_potential,
274 contiguous_wavefunctions.data(),
275 contiguous_hamiltonian_wavefunctions.data());
276
277 /**
278 * Apply S to every starting vector. For norm-conserving pseudopotentials
279 * S = I, so the overlap-applied set IS the input set and nothing is
280 * allocated: everything downstream reads it, so pointing at the input
281 * is enough. Ultrasoft needs the q_ij augmentation and so needs its own
282 * buffer. This block is the tallest point of a run's setup, and memory
283 * released here is not returned to the system, so the array not taken
284 * is worth more than its lifetime suggests.
285 */
286 std::vector<Complex> contiguous_overlap_storage;
287 Complex* contiguous_overlap_wavefunctions = contiguous_wavefunctions.data();
288 if (pseudopotential_quantum_system.has_ultrasoft()) {
289 // US overlap: S|ψ⟩ = |ψ⟩ + Σ_ab q_ab |β_a⟩⟨β_b|ψ⟩
290 contiguous_overlap_storage.assign(contiguous_wavefunctions.begin(),
291 contiguous_wavefunctions.end());
292 contiguous_overlap_wavefunctions = contiguous_overlap_storage.data();
293 apply_overlap_contiguous(number_of_plane_waves,
294 number_of_starting_wavefunctions,
295 crystal_structure,
296 execution_policy,
297 pseudopotential_quantum_system,
298 precomputed_kleinman_bylander_potential,
299 contiguous_wavefunctions.data(),
300 contiguous_overlap_wavefunctions);
301 }
302
303 // Build H_ij = χ†·Hχ and S_ij = χ†·Sχ via ZGEMM
304 std::vector<Complex> hamiltonian_matrix(
305 static_cast<size_t>(number_of_starting_wavefunctions) *
306 number_of_starting_wavefunctions,
307 Complex(0.0, 0.0));
308 std::vector<Complex> overlap_matrix(static_cast<size_t>(number_of_starting_wavefunctions) *
309 number_of_starting_wavefunctions,
310 Complex(0.0, 0.0));
311
312 std::complex<double> alpha_one(1.0, 0.0);
313 std::complex<double> beta_zero(0.0, 0.0);
314
315 auto* cast_wavefunctions =
316 reinterpret_cast<std::complex<double>*>(contiguous_wavefunctions.data());
317 auto* cast_hamiltonian_wavefunctions =
318 reinterpret_cast<std::complex<double>*>(contiguous_hamiltonian_wavefunctions.data());
319 auto* cast_overlap_wavefunctions =
320 reinterpret_cast<std::complex<double>*>(contiguous_overlap_wavefunctions);
321
322 if (planewave_basis.is_gamma_only()) {
323 /**
324 * Gamma-only: use DGEMM on interleaved real/imaginary parts.
325 * Reinterpret complex arrays as double arrays of length 2*npw.
326 * DGEMM("T","N", n, n, 2*npw, alpha=2) computes 2*Re(Σ conj(a)*b)
327 * which is the gamma inner product before G=0 correction.
328 */
329 int leading_dimension = std::max(number_of_plane_waves, 1);
330 int double_number_of_plane_waves = 2 * number_of_plane_waves;
331 int double_leading_dimension = 2 * leading_dimension;
332 double alpha_two = 2.0;
333 double d_beta_zero = 0.0;
334
335 auto* real_wavefunctions = reinterpret_cast<double*>(contiguous_wavefunctions.data());
336 auto* real_hamiltonian_wavefunctions =
337 reinterpret_cast<double*>(contiguous_hamiltonian_wavefunctions.data());
338 auto* real_overlap_wavefunctions =
339 reinterpret_cast<double*>(contiguous_overlap_wavefunctions);
340
341 // H_ij = 2*Re(χ†·Hχ) via DGEMM
342 std::vector<double> hamiltonian_matrix_real(
343 static_cast<size_t>(number_of_starting_wavefunctions) *
344 number_of_starting_wavefunctions,
345 0.0);
346 dgemm_("T",
347 "N",
348 &number_of_starting_wavefunctions,
349 &number_of_starting_wavefunctions,
350 &double_number_of_plane_waves,
351 &alpha_two,
352 real_wavefunctions,
353 &double_leading_dimension,
354 real_hamiltonian_wavefunctions,
355 &double_leading_dimension,
356 &d_beta_zero,
357 hamiltonian_matrix_real.data(),
358 &number_of_starting_wavefunctions);
359
360 // S_ij = 2*Re(χ†·Sχ) via DGEMM
361 std::vector<double> overlap_matrix_real(
362 static_cast<size_t>(number_of_starting_wavefunctions) *
363 number_of_starting_wavefunctions,
364 0.0);
365 dgemm_("T",
366 "N",
367 &number_of_starting_wavefunctions,
368 &number_of_starting_wavefunctions,
369 &double_number_of_plane_waves,
370 &alpha_two,
371 real_wavefunctions,
372 &double_leading_dimension,
373 real_overlap_wavefunctions,
374 &double_leading_dimension,
375 &d_beta_zero,
376 overlap_matrix_real.data(),
377 &number_of_starting_wavefunctions);
378
379 // G=0 correction: subtract Re(conj(left_g0)*right_g0)
380 if (planewave_basis.gamma_origin_is_local()) {
381 for (int column = 0; column < number_of_starting_wavefunctions; ++column) {
382 Complex h_right_g0 =
383 contiguous_hamiltonian_wavefunctions[column * number_of_plane_waves];
384 Complex s_right_g0 =
385 contiguous_overlap_wavefunctions[column * number_of_plane_waves];
386 for (int row = 0; row < number_of_starting_wavefunctions; ++row) {
387 Complex left_g0 = contiguous_wavefunctions[row * number_of_plane_waves];
388 int index = column * number_of_starting_wavefunctions + row;
389 hamiltonian_matrix_real[index] -= (std::conj(left_g0) * h_right_g0).real();
390 overlap_matrix_real[index] -= (std::conj(left_g0) * s_right_g0).real();
391 }
392 }
393 }
394
395 // Copy to Complex matrices for zhegv
396 for (int index = 0;
397 index < number_of_starting_wavefunctions * number_of_starting_wavefunctions;
398 ++index) {
399 hamiltonian_matrix[index] = Complex(hamiltonian_matrix_real[index], 0.0);
400 overlap_matrix[index] = Complex(overlap_matrix_real[index], 0.0);
401 }
402 } else {
403 int leading_dimension = std::max(number_of_plane_waves, 1);
404 /**
405 * Non-gamma: use ZGEMM
406 * H_ij = χ†·Hχ: ZGEMM('C', 'N', n_wfc, n_wfc, npw)
407 */
408 zgemm_("C",
409 "N",
410 &number_of_starting_wavefunctions,
411 &number_of_starting_wavefunctions,
412 &number_of_plane_waves,
413 &alpha_one,
414 cast_wavefunctions,
415 &leading_dimension,
416 cast_hamiltonian_wavefunctions,
417 &leading_dimension,
418 &beta_zero,
419 reinterpret_cast<std::complex<double>*>(hamiltonian_matrix.data()),
420 &number_of_starting_wavefunctions);
421
422 // S_ij = χ†·Sχ: ZGEMM('C', 'N', n_wfc, n_wfc, npw)
423 zgemm_("C",
424 "N",
425 &number_of_starting_wavefunctions,
426 &number_of_starting_wavefunctions,
427 &number_of_plane_waves,
428 &alpha_one,
429 cast_wavefunctions,
430 &leading_dimension,
431 cast_overlap_wavefunctions,
432 &leading_dimension,
433 &beta_zero,
434 reinterpret_cast<std::complex<double>*>(overlap_matrix.data()),
435 &number_of_starting_wavefunctions);
436 }
437
438 /**
439 * Reduce H_ij and S_ij across band ranks within this pool.
440 * Each band rank holds the same G-vectors (replicated), but in the future
441 * when G-space is distributed, each rank holds a slab and we need
442 * reduce_within_pool_vector to sum the partial dot products.
443 */
444 execution_policy.reduce_within_pool_vector(
445 reinterpret_cast<std::vector<Real>&>(hamiltonian_matrix));
446 execution_policy.reduce_within_pool_vector(
447 reinterpret_cast<std::vector<Real>&>(overlap_matrix));
448
449 /**
450 * Diagonalize: H*c = e*S*c
451 * For gamma-only, use dsygv (real symmetric) to guarantee real
452 * eigenvectors that preserve gamma symmetry after ZGEMM rotation.
453 */
454 std::vector<Real> subspace_eigenvalues(number_of_starting_wavefunctions, 0.0);
455 int info;
456 int eigenproblem_type = 1;
457
458 if (planewave_basis.is_gamma_only()) {
459 // Extract real parts from the Complex matrices
460 int matrix_size = number_of_starting_wavefunctions * number_of_starting_wavefunctions;
461 std::vector<Real> hamiltonian_real(matrix_size);
462 std::vector<Real> overlap_real(matrix_size);
463 for (int matrix_element_index = 0; matrix_element_index < matrix_size;
464 ++matrix_element_index) {
465 hamiltonian_real[matrix_element_index] =
466 hamiltonian_matrix[matrix_element_index].real();
467 overlap_real[matrix_element_index] = overlap_matrix[matrix_element_index].real();
468 }
469
470 int work_size = std::max(1, 3 * number_of_starting_wavefunctions - 1);
471 std::vector<Real> work(work_size);
472 Real optimal_work_size;
473 int query_size = -1;
474 dsygv_(&eigenproblem_type,
475 "V",
476 "U",
477 &number_of_starting_wavefunctions,
478 hamiltonian_real.data(),
479 &number_of_starting_wavefunctions,
480 overlap_real.data(),
481 &number_of_starting_wavefunctions,
482 subspace_eigenvalues.data(),
483 &optimal_work_size,
484 &query_size,
485 &info);
486 work_size = static_cast<int>(optimal_work_size);
487 work.resize(work_size);
488
489 dsygv_(&eigenproblem_type,
490 "V",
491 "U",
492 &number_of_starting_wavefunctions,
493 hamiltonian_real.data(),
494 &number_of_starting_wavefunctions,
495 overlap_real.data(),
496 &number_of_starting_wavefunctions,
497 subspace_eigenvalues.data(),
498 work.data(),
499 &work_size,
500 &info);
501
502 // Store real eigenvectors back into hamiltonian_matrix for ZGEMM rotation
503 for (int matrix_element_index = 0; matrix_element_index < matrix_size;
504 ++matrix_element_index) {
505 hamiltonian_matrix[matrix_element_index] =
506 Complex(hamiltonian_real[matrix_element_index], 0.0);
507 }
508 } else {
509 int work_size = 2 * number_of_starting_wavefunctions +
510 number_of_starting_wavefunctions * number_of_starting_wavefunctions;
511 std::vector<Complex> complex_work_space(work_size);
512 std::vector<Real> real_work_space(
513 std::max(1, 3 * number_of_starting_wavefunctions - 2));
514
515 zhegv_(&eigenproblem_type,
516 "V",
517 "U",
518 &number_of_starting_wavefunctions,
519 reinterpret_cast<std::complex<double>*>(hamiltonian_matrix.data()),
520 &number_of_starting_wavefunctions,
521 reinterpret_cast<std::complex<double>*>(overlap_matrix.data()),
522 &number_of_starting_wavefunctions,
523 subspace_eigenvalues.data(),
524 reinterpret_cast<std::complex<double>*>(complex_work_space.data()),
525 &work_size,
526 real_work_space.data(),
527 &info);
528 }
529
530 if (info != 0) {
531 log::error("LAPACK eigensolver failed in rotate_wavefunctions with info = {}", info);
532 }
533
534 // Store eigenvalues
535 for (int band_index = 0; band_index < number_of_bands; band_index++) {
536 quantum_system.set_eigenvalue(
537 kpoint_index_state, band_index, subspace_eigenvalues[band_index]);
538 }
539
540 /**
541 * Rotate wavefunctions: evc = χ · eigenvectors via ZGEMM
542 * evc(npw × n_bands) = χ(npw × n_wfc) · c(n_wfc × n_bands)
543 * The eigenvectors are stored column-major in hamiltonian_matrix after zhegv.
544 */
545 std::vector<Complex> rotated_wavefunctions(
546 static_cast<size_t>(number_of_plane_waves) * number_of_bands, Complex(0.0, 0.0));
547
548 zgemm_("N",
549 "N",
550 &number_of_plane_waves,
551 &number_of_bands,
552 &number_of_starting_wavefunctions,
553 &alpha_one,
554 cast_wavefunctions,
555 &leading_dimension,
556 reinterpret_cast<std::complex<double>*>(hamiltonian_matrix.data()),
557 &number_of_starting_wavefunctions,
558 &beta_zero,
559 reinterpret_cast<std::complex<double>*>(rotated_wavefunctions.data()),
560 &leading_dimension);
561
562 /**
563 * Gamma-only: enforce real G=0 for the rotated wavefunctions, which the
564 * two-band FFT trick requires.
565 *
566 * The atomic orbitals are already real at G=0 here -- at gamma the k-phase
567 * and the structure factor are both unity, and chi_l(0) = 0 for l >= 1
568 * because j_l(0) = 0, so only the l = 0 shells contribute and they carry
569 * i^0 = 1. The complex G=0 component this removes comes from the random
570 * padding vectors, which are generated with an arbitrary phase. Because
571 * the subspace matrices above were formed from the uncleaned vectors, the
572 * G=0 correction there uses the general Re[conj(a0)*b0] form rather than
573 * the real-parts-only form that would need this cleanup to run first.
574 */
575 if (planewave_basis.is_gamma_only() && planewave_basis.gamma_origin_is_local()) {
576 for (int band_index = 0; band_index < number_of_bands; ++band_index) {
577 Complex& g_zero_coefficient =
578 rotated_wavefunctions[band_index * number_of_plane_waves];
579 g_zero_coefficient = Complex(g_zero_coefficient.real(), 0.0);
580 }
581 }
582
583 // Copy rotated wavefunctions into quantum system
584 for (int band_index = 0; band_index < number_of_bands; band_index++) {
585 Complex* band_data =
586 quantum_system.wavefunction_band_data(kpoint_index_state, band_index);
587 std::memcpy(band_data,
588 &rotated_wavefunctions[band_index * number_of_plane_waves],
589 number_of_plane_waves * sizeof(Complex));
590 }
591 }
592
593 private:
594 /**
595 * A reproducible number in [0, 1) for one coefficient of one starting vector,
596 * keyed on what identifies the coefficient physically -- the k-point's
597 * coordinates, the vector, the plane wave's Miller indices -- and on a stream
598 * index, so the amplitude and the phase are independent. SplitMix64 finalizers
599 * chain the keys; the top 53 bits become the fraction.
600 */
601 [[nodiscard]] static Real hashed_unit_interval(const Vector3D& kpoint_coordinates,
602 int vector_index,
603 const IntegerVector3D& miller,
604 int stream) {
605 auto mix = [](std::uint64_t value) {
606 value += 0x9E3779B97F4A7C15ULL;
607 value = (value ^ (value >> 30)) * 0xBF58476D1CE4E5B9ULL;
608 value = (value ^ (value >> 27)) * 0x94D049BB133111EBULL;
609 return value ^ (value >> 31);
610 };
611 /**
612 * k-point coordinates are exact rationals of the grid in crystal units, so
613 * rounding them to 1e-9 names the same k-point identically everywhere.
614 */
615 constexpr Real kpoint_resolution = 1.0e9;
616 std::uint64_t state = mix(0x51A3C0FFEEULL);
617 for (Real component : {kpoint_coordinates.x, kpoint_coordinates.y, kpoint_coordinates.z}) {
618 state = mix(state ^
619 static_cast<std::uint64_t>(std::llround(component * kpoint_resolution)));
620 }
621 state = mix(state ^ static_cast<std::uint64_t>(vector_index));
622 for (int component : {miller.x, miller.y, miller.z}) {
623 state = mix(state ^ static_cast<std::uint64_t>(static_cast<std::int64_t>(component)));
624 }
625 state = mix(state ^ static_cast<std::uint64_t>(stream));
626 return static_cast<Real>(state >> 11) * 0x1.0p-53;
627 }
628
629 /// Interpolation table: atomic_wavefunction_table_[species_type][wfc_index][q_index]
630 std::vector<std::vector<std::vector<Real>>> atomic_wavefunction_table_;
631 Real q_spacing_ =
632 math::tolerances::default_q_grid_spacing; // grid spacing in atomic units (matching QE)
633 int number_of_q_points_ = 0; // table size
634
635 /**
636 * Apply H|ψ⟩ for a batch of vectors using contiguous layout.
637 *
638 * Uses the smooth (wavefunction) FFT grid instead of the dense (charge density) grid
639 * for ~3.4× faster FFTs, and batched ZGEMM for KB projectors.
640 *
641 * Input/output layout: column-major, psi[g + vector * npw].
642 */
643 static void apply_hamiltonian_batch_contiguous(
644 int number_of_plane_waves,
645 int number_of_vectors,
646 const std::vector<int>& g_vector_indices,
647 const std::vector<Real>& kinetic_energy_squared,
648 const std::vector<Real>& smooth_potential, // Accepts precomputed smooth potential
649 const Crystal& crystal_structure,
650 const KPointsGrid& kpoint_grid,
651 int kpoint_index,
652 const PlanewaveBasis& planewave_basis,
653 const execution::PlanewaveExecutionPolicy& execution_policy,
654 const NormConservingPlanewaveKohnShamQuantumSystem& pseudopotential_quantum_system,
655 const Matrix<Complex>& kleinman_bylander_potential,
656 const Complex* psi_wavefunctions,
657 Complex* hamiltonian_psi_wavefunctions) {
658 // Use smooth (wavefunction) FFT grid for V·ψ product
659 int local_smooth_grid_points = planewave_basis.fft_wave().local_grid_points();
660 /**
661 * Sized for whichever of the two layouts is larger: the scatter fills
662 * reciprocal space, the transform leaves real space, and under the
663 * stick layout those counts differ.
664 */
665 int transform_buffer_points =
666 std::max(local_smooth_grid_points, planewave_basis.fft_wave().local_g_space_points());
667
668 // Precompute G→smooth FFT mapping (constant across all vectors)
669 std::vector<int> g_to_fft_mapping(number_of_plane_waves);
670 for (int g_vector_index = 0; g_vector_index < number_of_plane_waves; g_vector_index++) {
671 const auto& g_vector = planewave_basis.g_vectors()[g_vector_indices[g_vector_index]];
672 g_to_fft_mapping[g_vector_index] =
673 planewave_basis.fft_wave().g_vector_to_fft_grid_index(
674 g_vector.miller.x, g_vector.miller.y, g_vector.miller.z);
675 }
676
677 // Pre-compute -G FFT mapping for gamma-only
678 std::vector<int> minus_g_to_fft_mapping;
679 bool is_gamma_only = planewave_basis.is_gamma_only();
680 /**
681 * G = 0 is its own mirror. On the rank that holds it, it is index 0 and
682 * is written once; on every other rank index 0 is an ordinary G with a
683 * mirror of its own, so the mirrored loops start there.
684 */
685 int first_mirrored_index = planewave_basis.gamma_origin_is_local() ? 1 : 0;
686 if (is_gamma_only) {
687 minus_g_to_fft_mapping.resize(number_of_plane_waves);
688 for (int g_vector_index = 0; g_vector_index < number_of_plane_waves; g_vector_index++) {
689 const auto& g_vector =
690 planewave_basis.g_vectors()[g_vector_indices[g_vector_index]];
691 minus_g_to_fft_mapping[g_vector_index] =
692 planewave_basis.fft_wave().g_vector_to_fft_grid_index(
693 -g_vector.miller.x, -g_vector.miller.y, -g_vector.miller.z);
694 }
695 }
696
697 // Process vectors: kinetic + local potential via smooth FFT
698 std::vector<Complex> psi_real_space(transform_buffer_points, Complex(0.0, 0.0));
699
700 if (is_gamma_only) {
701 // Gamma-only: two-bands-at-a-time
702 for (int pair_index = 0; pair_index < number_of_vectors; pair_index += 2) {
703 int band_1 = pair_index;
704 int band_2 = pair_index + 1;
705 bool has_second_band = (band_2 < number_of_vectors);
706
707 const Complex* psi_1 = &psi_wavefunctions[band_1 * number_of_plane_waves];
708 Complex* hpsi_1 = &hamiltonian_psi_wavefunctions[band_1 * number_of_plane_waves];
709 const Complex* psi_2 =
710 has_second_band ? &psi_wavefunctions[band_2 * number_of_plane_waves] : nullptr;
711 Complex* hpsi_2 =
712 has_second_band ? &hamiltonian_psi_wavefunctions[band_2 * number_of_plane_waves]
713 : nullptr;
714
715 // 1. Kinetic energy (done per-band, not part of the FFT trick)
716 for (int g_index = 0; g_index < number_of_plane_waves; ++g_index) {
717 hpsi_1[g_index] = kinetic_energy_squared[g_index] * psi_1[g_index];
718 }
719 if (has_second_band) {
720 for (int g_index = 0; g_index < number_of_plane_waves; ++g_index) {
721 hpsi_2[g_index] = kinetic_energy_squared[g_index] * psi_2[g_index];
722 }
723 }
724
725 /**
726 * 2. Pack two bands: combined(G) = ψ₁(G) + i·ψ₂(G)
727 * Scatter to FFT grid with full-sphere reconstruction:
728 * grid[fft(+G)] = ψ₁(G) + i·ψ₂(G)
729 * grid[fft(-G)] = conj(ψ₁(G)) + i·conj(ψ₂(G))
730 */
731 std::memset(psi_real_space.data(), 0, transform_buffer_points * sizeof(Complex));
732
733 if (has_second_band) {
734 if (planewave_basis.gamma_origin_is_local()) {
735 // G=0 component: (Re psi_1, Re psi_2), once
736 int fft_index_0 = g_to_fft_mapping[0];
737 if (fft_index_0 >= 0) {
738 psi_real_space[fft_index_0] = Complex(psi_1[0].real(), psi_2[0].real());
739 }
740 }
741 // Every other G: scatter +G and -G
742 for (int g_index = first_mirrored_index; g_index < number_of_plane_waves;
743 ++g_index) {
744 int fft_plus = g_to_fft_mapping[g_index];
745 int fft_minus = minus_g_to_fft_mapping[g_index];
746 if (fft_plus >= 0) {
747 // +G: ψ₁(G) + i·ψ₂(G)
748 Complex p1 = psi_1[g_index];
749 Complex p2 = psi_2[g_index];
750 psi_real_space[fft_plus] =
751 Complex(p1.real() - p2.imag(), p1.imag() + p2.real());
752 // -G: conj(ψ₁(G)) + i·conj(ψ₂(G))
753 psi_real_space[fft_minus] =
754 Complex(p1.real() + p2.imag(), -p1.imag() + p2.real());
755 }
756 }
757 } else {
758 // Single band: standard gamma scatter
759 for (int g_index = 0; g_index < number_of_plane_waves; ++g_index) {
760 int fft_index = g_to_fft_mapping[g_index];
761 if (fft_index >= 0) {
762 psi_real_space[fft_index] = psi_1[g_index];
763 }
764 }
765 for (int g_index = first_mirrored_index; g_index < number_of_plane_waves;
766 ++g_index) {
767 int fft_minus = minus_g_to_fft_mapping[g_index];
768 psi_real_space[fft_minus] = std::conj(psi_1[g_index]);
769 }
770 }
771
772 // Inverse FFT → real space
773 planewave_basis.fft_wave().inverse_raw(psi_real_space.data());
774
775 /**
776 * Multiply by V(r): both bands simultaneously
777 * After inverse FFT: Re(grid) = ψ₁(r), Im(grid) = ψ₂(r)
778 * V·ψ₁ = V*Re, V·ψ₂ = V*Im → recombine as complex V*Re + i*V*Im
779 */
780 for (int index_grid = 0; index_grid < local_smooth_grid_points; ++index_grid) {
781 psi_real_space[index_grid] *= smooth_potential[index_grid];
782 }
783
784 // Forward FFT → G-space
785 planewave_basis.fft_wave().forward(psi_real_space.data());
786
787 /**
788 * Separate the two bands using ±G symmetry:
789 * (V·ψ₁)(G) = [result(+G) + conj(result(-G))] / 2
790 * (V·ψ₂)(G) = [result(+G) - conj(result(-G))] / (2i)
791 */
792 if (has_second_band) {
793 if (planewave_basis.gamma_origin_is_local()) {
794 // G=0 component
795 int fft_index_0 = g_to_fft_mapping[0];
796 if (fft_index_0 >= 0) {
797 Complex result_0 = psi_real_space[fft_index_0];
798 hpsi_1[0] += Complex(result_0.real(), 0.0);
799 hpsi_2[0] += Complex(result_0.imag(), 0.0);
800 }
801 }
802 // Every other G: use ±G symmetry
803 for (int g_index = first_mirrored_index; g_index < number_of_plane_waves;
804 ++g_index) {
805 int fft_plus = g_to_fft_mapping[g_index];
806 int fft_minus = minus_g_to_fft_mapping[g_index];
807 if (fft_plus >= 0) {
808 Complex result_plus = psi_real_space[fft_plus];
809 Complex result_minus_conj = std::conj(psi_real_space[fft_minus]);
810 // (V·ψ₁)(G) = (result+ + conj(result-)) / 2
811 hpsi_1[g_index] += (result_plus + result_minus_conj) * 0.5;
812 /**
813 * (V·ψ₂)(G) = (result+ - conj(result-)) / (2i)
814 * = -i * (result+ - conj(result-)) / 2
815 */
816 Complex diff = (result_plus - result_minus_conj) * 0.5;
817 hpsi_2[g_index] += Complex(diff.imag(), -diff.real());
818 }
819 }
820 } else {
821 // Single band: standard gather
822 for (int g_index = 0; g_index < number_of_plane_waves; ++g_index) {
823 int fft_index = g_to_fft_mapping[g_index];
824 if (fft_index >= 0) {
825 hpsi_1[g_index] += psi_real_space[fft_index];
826 }
827 }
828 }
829 }
830 } else {
831 // Non-gamma: process one band at a time (original path)
832 for (int vector_index = 0; vector_index < number_of_vectors; ++vector_index) {
833 const Complex* psi_vector =
834 &psi_wavefunctions[vector_index * number_of_plane_waves];
835 Complex* hamiltonian_psi_vector =
836 &hamiltonian_psi_wavefunctions[vector_index * number_of_plane_waves];
837
838 // 1. Kinetic energy
839 for (int g_index = 0; g_index < number_of_plane_waves; ++g_index) {
840 hamiltonian_psi_vector[g_index] =
841 kinetic_energy_squared[g_index] * psi_vector[g_index];
842 }
843
844 // 2. Local potential via smooth grid FFT
845 std::memset(psi_real_space.data(), 0, transform_buffer_points * sizeof(Complex));
846 for (int g_index = 0; g_index < number_of_plane_waves; ++g_index) {
847 int fft_index = g_to_fft_mapping[g_index];
848 if (fft_index >= 0) {
849 psi_real_space[fft_index] = psi_vector[g_index];
850 }
851 }
852 planewave_basis.fft_wave().inverse_raw(psi_real_space.data());
853 for (int index_grid = 0; index_grid < local_smooth_grid_points; ++index_grid) {
854 psi_real_space[index_grid] *= smooth_potential[index_grid];
855 }
856 planewave_basis.fft_wave().forward(psi_real_space.data());
857 for (int g_index = 0; g_index < number_of_plane_waves; ++g_index) {
858 int fft_index = g_to_fft_mapping[g_index];
859 if (fft_index >= 0) {
860 hamiltonian_psi_vector[g_index] += psi_real_space[fft_index];
861 }
862 }
863 }
864 }
865
866 // 3. Nonlocal PP (KB projectors) via batched ZGEMM for all atoms at once
867 if (kleinman_bylander_potential.empty()) {
868 return;
869 }
870
871 int total_number_of_projectors = static_cast<int>(kleinman_bylander_potential.rows());
872
873 // Step 1: ⟨β_all|ψ⟩
874 std::vector<Complex> all_overlap_coefficients(
875 static_cast<size_t>(total_number_of_projectors) * number_of_vectors, Complex(0.0, 0.0));
876
877 std::complex<double> alpha_one(1.0, 0.0);
878 std::complex<double> beta_zero(0.0, 0.0);
879 std::complex<double> beta_one(1.0, 0.0);
880
881 if (is_gamma_only) {
882 // Gamma-only: DGEMM on interleaved real/imag parts
883 int double_number_of_plane_waves = 2 * number_of_plane_waves;
884 int double_leading_dimension = std::max(double_number_of_plane_waves, 1);
885 double alpha_two = 2.0;
886 double d_beta_zero = 0.0;
887
888 auto* real_projectors = reinterpret_cast<const double*>(kleinman_bylander_potential[0]);
889 auto* real_wavefunctions = reinterpret_cast<const double*>(psi_wavefunctions);
890
891 std::vector<double> overlap_real(
892 static_cast<size_t>(total_number_of_projectors) * number_of_vectors, 0.0);
893 dgemm_("T",
894 "N",
895 &total_number_of_projectors,
896 &number_of_vectors,
897 &double_number_of_plane_waves,
898 &alpha_two,
899 const_cast<double*>(real_projectors),
900 &double_leading_dimension,
901 const_cast<double*>(real_wavefunctions),
902 &double_leading_dimension,
903 &d_beta_zero,
904 overlap_real.data(),
905 &total_number_of_projectors);
906
907 // Remove the origin from the double count where this rank holds it; copy to Complex
908 for (int vector_index = 0; vector_index < number_of_vectors; ++vector_index) {
909 const Complex* psi_g0 = &psi_wavefunctions[vector_index * number_of_plane_waves];
910 for (int projector_index = 0; projector_index < total_number_of_projectors;
911 ++projector_index) {
912 int index = projector_index + vector_index * total_number_of_projectors;
913 if (planewave_basis.gamma_origin_is_local()) {
914 Complex beta_g0 = kleinman_bylander_potential(projector_index, 0);
915 overlap_real[index] -= (std::conj(beta_g0) * psi_g0[0]).real();
916 }
917 all_overlap_coefficients[index] = Complex(overlap_real[index], 0.0);
918 }
919 }
920 } else {
921 int leading_dimension = std::max(number_of_plane_waves, 1);
922 // Non-gamma: ZGEMM
923 zgemm_("C",
924 "N",
925 &total_number_of_projectors,
926 &number_of_vectors,
927 &number_of_plane_waves,
928 &alpha_one,
929 reinterpret_cast<const std::complex<double>*>(kleinman_bylander_potential[0]),
930 &leading_dimension,
931 reinterpret_cast<const std::complex<double>*>(psi_wavefunctions),
932 &leading_dimension,
933 &beta_zero,
934 reinterpret_cast<std::complex<double>*>(all_overlap_coefficients.data()),
935 &total_number_of_projectors);
936 }
937
938 /**
939 * Each rank summed over its own plane waves; the overlap is the sum
940 * over the pool. A no-op while the coefficients are replicated, which
941 * is why its absence went unnoticed: it only changes the starting
942 * guess, and the SCF converges to the same answer from a wrong one.
943 */
944 execution_policy.reduce_within_pool_vector(
945 reinterpret_cast<std::vector<Real>&>(all_overlap_coefficients));
946
947 // Step 2: Apply block-diagonal D-matrix per atom
948 std::vector<Complex> scaled_overlap_coefficients(
949 static_cast<size_t>(total_number_of_projectors) * number_of_vectors, Complex(0.0, 0.0));
950
951 int starting_projector_index = 0;
952 for (int atom_index = 0; atom_index < crystal_structure.number_of_atoms(); atom_index++) {
953 int species_index = crystal_structure.species_type_index(atom_index);
954 int number_of_projectors =
955 pseudopotential_quantum_system.pseudopotential_basis().number_of_projectors(
956 species_index);
957 std::vector<int> angular_momentum_values;
958 for (int index_projector = 0; index_projector < number_of_projectors;
959 index_projector++) {
960 int angular_momentum =
961 pseudopotential_quantum_system.pseudopotential_basis().beta_angular_momentum(
962 species_index, index_projector);
963 for (int magnetic_projection : harmonic_order_sequence(angular_momentum)) {
964 (void)magnetic_projection;
965 angular_momentum_values.push_back(angular_momentum);
966 }
967 }
968 int number_of_projectors_atom = static_cast<int>(angular_momentum_values.size());
969
970 if (number_of_projectors_atom > 0) {
971 for (int vector_index = 0; vector_index < number_of_vectors; vector_index++) {
972 int offset_in =
973 vector_index * total_number_of_projectors + starting_projector_index;
974 int offset_out =
975 vector_index * total_number_of_projectors + starting_projector_index;
976 for (int projector_index_a = 0; projector_index_a < number_of_projectors_atom;
977 projector_index_a++) {
978 for (int projector_index_b = 0;
979 projector_index_b < number_of_projectors_atom;
980 projector_index_b++) {
981 Real d_matrix_element = pseudopotential_quantum_system.d_matrix_element(
982 atom_index, species_index, projector_index_a, projector_index_b);
983 if (std::abs(d_matrix_element) >
985 scaled_overlap_coefficients[offset_out + projector_index_a] +=
986 d_matrix_element *
987 all_overlap_coefficients[offset_in + projector_index_b];
988 }
989 }
990 }
991 }
992 starting_projector_index += number_of_projectors_atom;
993 }
994 }
995
996 int leading_dimension_apply = std::max(number_of_plane_waves, 1);
997 // Step 3: H|ψ⟩ += β_all · w via single ZGEMM('N','N', npw, n_vectors, total_proj)
998 zgemm_("N",
999 "N",
1000 &number_of_plane_waves,
1001 &number_of_vectors,
1002 &total_number_of_projectors,
1003 &alpha_one,
1004 reinterpret_cast<const std::complex<double>*>(kleinman_bylander_potential[0]),
1005 &leading_dimension_apply,
1006 reinterpret_cast<const std::complex<double>*>(scaled_overlap_coefficients.data()),
1007 &total_number_of_projectors,
1008 &beta_one,
1009 reinterpret_cast<std::complex<double>*>(hamiltonian_psi_wavefunctions),
1010 &leading_dimension_apply);
1011 }
1012
1013 /**
1014 * Apply S|ψ⟩ for a batch of vectors using contiguous layout.
1015 *
1016 * For NC pseudopotentials, S = I so this is a no-op (caller already memcpy'd).
1017 * For US pseudopotentials, adds the augmentation overlap contribution.
1018 *
1019 * Input/output layout: column-major, psi[g + vector * npw].
1020 * The output overlap_psi is modified in-place (should be pre-initialized to psi).
1021 */
1022 static void apply_overlap_contiguous(
1023 int number_of_plane_waves,
1024 int number_of_vectors,
1025 const Crystal& crystal_structure,
1026 const execution::PlanewaveExecutionPolicy& execution_policy,
1027 const NormConservingPlanewaveKohnShamQuantumSystem& pseudopotential_quantum_system,
1028 const Matrix<Complex>& kleinman_bylander_potential,
1029 const Complex* psi_wavefunctions,
1030 Complex* overlap_psi_wavefunctions) {
1031 if (!pseudopotential_quantum_system.has_ultrasoft()) {
1032 return;
1033 }
1034
1035 int total_number_of_projectors = static_cast<int>(kleinman_bylander_potential.rows());
1036
1037 // Step 1: ⟨β_all|ψ⟩
1038 std::vector<Complex> all_overlap_coefficients(
1039 static_cast<size_t>(total_number_of_projectors) * number_of_vectors, Complex(0.0, 0.0));
1040
1041 std::complex<double> alpha_one(1.0, 0.0);
1042 std::complex<double> beta_zero(0.0, 0.0);
1043 std::complex<double> beta_one(1.0, 0.0);
1044
1045 bool is_gamma_only = pseudopotential_quantum_system.basis().is_gamma_only();
1046
1047 if (is_gamma_only) {
1048 int double_number_of_plane_waves = 2 * number_of_plane_waves;
1049 int double_leading_dimension = std::max(double_number_of_plane_waves, 1);
1050 double alpha_two = 2.0;
1051 double double_beta_zero = 0.0;
1052
1053 auto* real_projectors = reinterpret_cast<const double*>(kleinman_bylander_potential[0]);
1054 auto* real_wavefunctions = reinterpret_cast<const double*>(psi_wavefunctions);
1055
1056 std::vector<double> overlap_real(
1057 static_cast<size_t>(total_number_of_projectors) * number_of_vectors, 0.0);
1058 dgemm_("T",
1059 "N",
1060 &total_number_of_projectors,
1061 &number_of_vectors,
1062 &double_number_of_plane_waves,
1063 &alpha_two,
1064 const_cast<double*>(real_projectors),
1065 &double_leading_dimension,
1066 const_cast<double*>(real_wavefunctions),
1067 &double_leading_dimension,
1068 &double_beta_zero,
1069 overlap_real.data(),
1070 &total_number_of_projectors);
1071
1072 // G=0 correction, on the rank whose list begins with the origin
1073 bool has_zero_g_vector = pseudopotential_quantum_system.basis().gamma_origin_is_local();
1074 if (has_zero_g_vector) {
1075 for (int vector_index = 0; vector_index < number_of_vectors; ++vector_index) {
1076 const Complex* psi_g0 =
1077 &psi_wavefunctions[vector_index * number_of_plane_waves];
1078 for (int projector_index = 0; projector_index < total_number_of_projectors;
1079 ++projector_index) {
1080 Complex beta_g0 = kleinman_bylander_potential(projector_index, 0);
1081 int index = projector_index + vector_index * total_number_of_projectors;
1082 overlap_real[index] -= (std::conj(beta_g0) * psi_g0[0]).real();
1083 all_overlap_coefficients[index] = Complex(overlap_real[index], 0.0);
1084 }
1085 }
1086 } else {
1087 for (size_t index = 0; index < overlap_real.size(); ++index) {
1088 all_overlap_coefficients[index] = Complex(overlap_real[index], 0.0);
1089 }
1090 }
1091 } else {
1092 int leading_dimension = std::max(number_of_plane_waves, 1);
1093 zgemm_("C",
1094 "N",
1095 &total_number_of_projectors,
1096 &number_of_vectors,
1097 &number_of_plane_waves,
1098 &alpha_one,
1099 reinterpret_cast<const std::complex<double>*>(kleinman_bylander_potential[0]),
1100 &leading_dimension,
1101 reinterpret_cast<const std::complex<double>*>(psi_wavefunctions),
1102 &leading_dimension,
1103 &beta_zero,
1104 reinterpret_cast<std::complex<double>*>(all_overlap_coefficients.data()),
1105 &total_number_of_projectors);
1106 }
1107
1108 // Reduce overlap coefficients across band ranks within this pool
1109 execution_policy.reduce_within_pool_vector(
1110 reinterpret_cast<std::vector<Real>&>(all_overlap_coefficients));
1111
1112 // Step 2: Apply q_ij augmentation overlap per atom
1113 std::vector<Complex> scaled_overlap_coefficients(
1114 static_cast<size_t>(total_number_of_projectors) * number_of_vectors, Complex(0.0, 0.0));
1115
1116 int starting_projector_index = 0;
1117 for (int atom_index = 0; atom_index < crystal_structure.number_of_atoms(); atom_index++) {
1118 int element_type = crystal_structure.species_type_index(atom_index);
1119 int number_of_beta_functions =
1120 pseudopotential_quantum_system.pseudopotential_basis().number_of_projectors(
1121 element_type);
1122 std::vector<int> angular_momentums, magnetic_projections, beta_indices;
1123 for (int beta_index = 0; beta_index < number_of_beta_functions; beta_index++) {
1124 int angular_momentum =
1125 pseudopotential_quantum_system.pseudopotential_basis().beta_angular_momentum(
1126 element_type, beta_index);
1127 for (int magnetic_projection : harmonic_order_sequence(angular_momentum)) {
1128 angular_momentums.push_back(angular_momentum);
1129 magnetic_projections.push_back(magnetic_projection);
1130 beta_indices.push_back(beta_index);
1131 }
1132 }
1133 int number_of_projectors_on_atom = static_cast<int>(angular_momentums.size());
1134 if (number_of_projectors_on_atom > 0) {
1135 for (int vector_index = 0; vector_index < number_of_vectors; vector_index++) {
1136 int offset =
1137 vector_index * total_number_of_projectors + starting_projector_index;
1138 for (int projector_a_index = 0;
1139 projector_a_index < number_of_projectors_on_atom;
1140 projector_a_index++) {
1141 for (int projector_b_index = 0;
1142 projector_b_index < number_of_projectors_on_atom;
1143 projector_b_index++) {
1144 int beta_a = beta_indices[projector_a_index];
1145 int beta_b = beta_indices[projector_b_index];
1146 Real q_value = 0.0;
1147 if (angular_momentums[projector_a_index] ==
1148 angular_momentums[projector_b_index] &&
1149 magnetic_projections[projector_a_index] ==
1150 magnetic_projections[projector_b_index]) {
1151 q_value =
1152 static_cast<const UltrasoftPlanewaveKohnShamQuantumSystem&>(
1153 pseudopotential_quantum_system)
1154 .ultrasoft_basis()
1155 .augmentation_overlap_integral(
1156 element_type, beta_a, beta_b);
1157 }
1158 if (std::abs(q_value) > math::tolerances::d_matrix_element_threshold) {
1159 scaled_overlap_coefficients[offset + projector_a_index] +=
1160 q_value * all_overlap_coefficients[offset + projector_b_index];
1161 }
1162 }
1163 }
1164 }
1165 starting_projector_index += number_of_projectors_on_atom;
1166 }
1167 }
1168
1169 int leading_dimension_step3 = std::max(number_of_plane_waves, 1);
1170 // Step 3: S|ψ⟩ += β_all · w via ZGEMM
1171 zgemm_("N",
1172 "N",
1173 &number_of_plane_waves,
1174 &number_of_vectors,
1175 &total_number_of_projectors,
1176 &alpha_one,
1177 reinterpret_cast<const std::complex<double>*>(kleinman_bylander_potential[0]),
1178 &leading_dimension_step3,
1179 reinterpret_cast<const std::complex<double>*>(scaled_overlap_coefficients.data()),
1180 &total_number_of_projectors,
1181 &beta_one,
1182 reinterpret_cast<std::complex<double>*>(overlap_psi_wavefunctions),
1183 &leading_dimension_step3);
1184 }
1185};
1186
1187} // namespace q3
int species_type_index(int atom_index) const
Definition crystal.hpp:273
int number_of_atoms() const
Definition crystal.hpp:209
virtual int g_vector_to_fft_grid_index(int miller_1, int miller_2, int miller_3) const =0
virtual int local_g_space_points() const
Definition fft_grid.hpp:88
virtual void forward(Complex *data) const =0
virtual void inverse_raw(Complex *data) const =0
virtual int local_grid_points() const =0
size_t rows() const
Definition matrix.hpp:62
bool empty() const
Definition matrix.hpp:90
T * data()
Returns the underlying flat data pointer.
Definition matrix.hpp:75
int number_of_projectors(int species_type) const
Definition init.hpp:188
int beta_angular_momentum(int species_type, int projector_index) const
Definition init.hpp:195
const NormConservingPlanewaveBasis & pseudopotential_basis() const
Definition init.hpp:65
virtual Real d_matrix_element(int atom_index, int species_type, int projector_index_i, int projector_index_j, int spin=0) const
Definition init.hpp:84
void compute_atomic_wavefunctions(int kpoint_index, int number_of_plane_waves, int number_of_bands, const std::vector< int > &g_vector_indices, const Crystal &crystal_structure, const KPointsGrid &kpoint_grid, const PlanewaveBasis &planewave_basis, const PseudopotentialSet &pseudopotential_set, std::vector< std::vector< Complex > > &atomic_wavefunctions)
void initialize_interpolation_table(const PseudopotentialSet &pseudopotential_set, Real cell_volume)
void rotate_wavefunctions(int kpoint_index, int kpoint_index_state, int number_of_plane_waves, int number_of_bands, const std::vector< int > &g_vector_indices, const std::vector< Real > &kinetic_energy_squared, const std::vector< Real > &smooth_potential, const Crystal &crystal_structure, const KPointsGrid &kpoint_grid, const PlanewaveBasis &planewave_basis, const execution::PlanewaveExecutionPolicy &execution_policy, const NormConservingPlanewaveKohnShamQuantumSystem &pseudopotential_quantum_system, const std::vector< std::vector< Complex > > &atomic_wavefunctions, PlanewaveKohnShamQuantumSystem &quantum_system, const Matrix< Complex > &precomputed_kleinman_bylander_potential)
const std::vector< GVector > & g_vectors() const
Definition init.hpp:148
FFTGrid & fft_wave()
Definition init.hpp:292
bool gamma_origin_is_local() const
Definition init.hpp:172
bool is_gamma_only() const
Definition init.hpp:157
const PlanewaveBasis & basis() const
Definition init.hpp:85
void set_eigenvalue(int kpoint_state_index, int band_index, Real value)
Definition init.hpp:121
Complex * wavefunction_band_data(int kpoint_state_index, int band_index)
Definition init.hpp:107
virtual void reduce_within_pool_vector(std::vector< Real > &values) const =0
void dgemm_(const char *transa, const char *transb, const int *m, const int *n, const int *k, const double *alpha, const double *a, const int *lda, const double *b, const int *ldb, const double *beta, double *c, const int *ldc)
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
void zhegv_(const int *itype, const char *jobz, const char *uplo, const int *n, std::complex< double > *a, const int *lda, std::complex< double > *b, const int *ldb, double *w, std::complex< double > *work, const int *lwork, double *rwork, int *info)
void dsygv_(const int *itype, const char *jobz, const char *uplo, const int *n, double *a, const int *lda, double *b, const int *ldb, double *w, double *work, const int *lwork, int *info)
void error(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:192
void info(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:183
constexpr double d_matrix_element_threshold
constexpr int default_number_of_q_points_for_atomic_wavefunctions
constexpr double default_q_grid_spacing
void build_atomic_orbitals_g_space(int kpoint_index, int number_of_plane_waves, const std::vector< int > &g_vector_indices, const Crystal &crystal_structure, const KPointsGrid &kpoint_grid, const PlanewaveBasis &planewave_basis, const PseudopotentialSet &pseudopotential_set, const std::vector< std::vector< std::vector< Real > > > &atomic_wavefunction_table, Real q_spacing, int number_of_q_points, std::vector< std::vector< Complex > > &orbitals)
double Real
std::vector< std::vector< std::vector< Real > > > tabulate_atomic_wavefunctions_in_q_space(const PseudopotentialSet &pseudopotential_set, Real cell_volume, Real q_spacing, int number_of_q_points)
std::complex< double > Complex
std::vector< int > harmonic_order_sequence(int degree)
Definition harmonics.hpp:40
Integer 3-vector for Miller indices / FFT grid indices.