Loading...
Searching...
No Matches
init.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * UltrasoftPseudopotentialOverlapOperator — S = I + Σ q_ij |β_i⟩⟨β_j|.
5 *
6 * Ultrasoft pseudopotential_set relax the norm-conservation constraint, adding the
7 * augmentation overlap built from the Kleinman-Bylander projectors and the q_ij
8 * augmentation charges. This makes the eigenproblem generalized (zhegv).
9 */
10
11#include <algorithm>
12#include <complex>
13#include <cstring>
14#include <vector>
15
23
24namespace q3 {
25
27 public:
29
30 [[nodiscard]] bool is_identity() const override {
31 return false;
32 }
33
34 void apply(int number_of_plane_waves,
35 const Complex* wavefunction,
36 Complex* overlap_wavefunction) const override {
37 // Copy input to output: S = I + augmentation
38 std::memcpy(overlap_wavefunction, wavefunction, number_of_plane_waves * sizeof(Complex));
39
40 int total_projectors = static_cast<int>(kleinman_bylander_potential_.rows());
41 if (total_projectors == 0) {
42 return;
43 }
44
45 // Ensure q-matrix is cached
46 if (cached_q_matrix_.empty()) {
47 build_q_matrix();
48 }
49
50 // 1. Compute <β|ψ> for all projectors
51 std::vector<Complex> projector_overlaps(total_projectors, Complex(0.0, 0.0));
52 bool is_gamma_only = quantum_system_.basis().is_gamma_only();
53
54 if (is_gamma_only) {
55 int double_number_of_plane_waves = 2 * number_of_plane_waves;
56 double alpha_two = 2.0;
57 double double_beta_zero = 0.0;
58 int one_increment = 1;
59
60 std::vector<double> real_projector_overlaps(total_projectors, 0.0);
61 const double* real_kleinman_bylander_potential =
62 reinterpret_cast<const double*>(kleinman_bylander_potential_.data());
63 const double* real_wavefunction = reinterpret_cast<const double*>(wavefunction);
64
66 &double_number_of_plane_waves,
67 &total_projectors,
68 &alpha_two,
69 real_kleinman_bylander_potential,
70 &double_number_of_plane_waves,
71 real_wavefunction,
72 &one_increment,
73 &double_beta_zero,
74 real_projector_overlaps.data(),
75 &one_increment);
76
77 /**
78 * G = 0 correction: the origin is removed from the double count on
79 * the rank whose plane-wave list begins with it, and nowhere else.
80 */
81 bool has_zero_g_vector = quantum_system_.basis().gamma_origin_is_local();
82 if (has_zero_g_vector) {
83 for (int projector_index = 0; projector_index < total_projectors;
84 ++projector_index) {
85 Complex kleinman_bylander_g0 = kleinman_bylander_potential_[projector_index][0];
86 Complex wavefunction_g0 = wavefunction[0];
87 real_projector_overlaps[projector_index] -=
88 (std::conj(kleinman_bylander_g0) * wavefunction_g0).real();
89 }
90 }
91
92 for (int projector_index = 0; projector_index < total_projectors; ++projector_index) {
93 projector_overlaps[projector_index] =
94 Complex(real_projector_overlaps[projector_index], 0.0);
95 }
96 } else {
97 Complex alpha_one(1.0, 0.0);
98 Complex beta_zero_val(0.0, 0.0);
99 int one_increment = 1;
100 auto* cast_alpha = reinterpret_cast<const std::complex<double>*>(&alpha_one);
101 auto* cast_beta_zero = reinterpret_cast<const std::complex<double>*>(&beta_zero_val);
102 auto* cast_kleinman_bylander =
103 reinterpret_cast<const std::complex<double>*>(kleinman_bylander_potential_.data());
104 auto* cast_wavefunction = reinterpret_cast<const std::complex<double>*>(wavefunction);
105 auto* cast_projector_overlaps =
106 reinterpret_cast<std::complex<double>*>(projector_overlaps.data());
107
109 &number_of_plane_waves,
110 &total_projectors,
111 cast_alpha,
112 cast_kleinman_bylander,
113 &number_of_plane_waves,
114 cast_wavefunction,
115 &one_increment,
116 cast_beta_zero,
117 cast_projector_overlaps,
118 &one_increment);
119 }
120
121 execution_policy_.reduce_within_group(projector_overlaps.data(), total_projectors);
122
123 // 2. Apply q-matrix: projector_overlaps_with_q_matrix = Q × projector_overlaps
124 std::vector<Complex> projector_overlaps_with_q_matrix(total_projectors, Complex(0.0, 0.0));
125 Complex alpha_one_val(1.0, 0.0);
126 Complex beta_zero_val(0.0, 0.0);
127 int one_increment = 1;
128 auto* cast_alpha = reinterpret_cast<const std::complex<double>*>(&alpha_one_val);
129 auto* cast_beta_zero = reinterpret_cast<const std::complex<double>*>(&beta_zero_val);
130 auto* cast_q_matrix =
131 reinterpret_cast<const std::complex<double>*>(cached_q_matrix_.data());
132 auto* cast_projector_overlaps =
133 reinterpret_cast<std::complex<double>*>(projector_overlaps.data());
134 auto* cast_projector_overlaps_with_q_matrix =
135 reinterpret_cast<std::complex<double>*>(projector_overlaps_with_q_matrix.data());
136
138 &total_projectors,
139 &total_projectors,
140 cast_alpha,
141 cast_q_matrix,
142 &total_projectors,
143 cast_projector_overlaps,
144 &one_increment,
145 cast_beta_zero,
146 cast_projector_overlaps_with_q_matrix,
147 &one_increment);
148
149 // 3. S|ψ⟩ += β × projector_overlaps_with_q_matrix
150 Complex beta_one(1.0, 0.0);
151 auto* cast_one = reinterpret_cast<const std::complex<double>*>(&beta_one);
152 auto* cast_kleinman_bylander =
153 reinterpret_cast<const std::complex<double>*>(kleinman_bylander_potential_.data());
154 auto* cast_overlap_wavefunction =
155 reinterpret_cast<std::complex<double>*>(overlap_wavefunction);
156
158 &number_of_plane_waves,
159 &total_projectors,
160 cast_alpha,
161 cast_kleinman_bylander,
162 &number_of_plane_waves,
163 cast_projector_overlaps_with_q_matrix,
164 &one_increment,
165 cast_one,
166 cast_overlap_wavefunction,
167 &one_increment);
168 }
169
170 /**
171 * Efficient batched S|ψ⟩ for ultrasoft pseudopotentials.
172 *
173 * Uses zgemm for computing all <β|ψ> projections across all vectors at once,
174 * then applies the q-matrix and adds back via zgemm.
175 */
176 void apply_batch(int number_of_plane_waves,
177 int number_of_vectors,
178 const Complex* wavefunctions,
179 Complex* overlap_wavefunctions) const override {
180 // Start with S = I: copy all vectors
181 std::memcpy(overlap_wavefunctions,
182 wavefunctions,
183 static_cast<size_t>(number_of_plane_waves) * number_of_vectors *
184 sizeof(Complex));
185
186 int total_projectors = static_cast<int>(kleinman_bylander_potential_.rows());
187 if (total_projectors == 0) {
188 return;
189 }
190
191 /**
192 * Step 1: Compute all projections projector_overlaps(total_projectors, number_of_vectors) =
193 * β^H × ψ
194 */
195 std::vector<Complex> projector_overlaps(
196 static_cast<size_t>(total_projectors) * number_of_vectors, Complex(0.0, 0.0));
197 bool is_gamma_only = quantum_system_.basis().is_gamma_only();
198
199 if (is_gamma_only) {
200 int double_number_of_plane_waves = 2 * number_of_plane_waves;
201 double alpha_two = 2.0;
202 double double_beta_zero = 0.0;
203
204 std::vector<double> real_projector_overlaps(
205 static_cast<size_t>(total_projectors) * number_of_vectors, 0.0);
206 const double* real_kleinman_bylander_potential =
207 reinterpret_cast<const double*>(kleinman_bylander_potential_.data());
208 const double* real_wavefunctions = reinterpret_cast<const double*>(wavefunctions);
209
210 // Compute 2 * Re(KB^H · wavefunctions) via DGEMM
212 "N",
213 &total_projectors,
214 &number_of_vectors,
215 &double_number_of_plane_waves,
216 &alpha_two,
217 real_kleinman_bylander_potential,
218 &double_number_of_plane_waves,
219 real_wavefunctions,
220 &double_number_of_plane_waves,
221 &double_beta_zero,
222 real_projector_overlaps.data(),
223 &total_projectors);
224
225 /**
226 * G = 0 correction: the origin is removed from the double count on
227 * the rank whose plane-wave list begins with it, and nowhere else.
228 */
229 bool has_zero_g_vector = quantum_system_.basis().gamma_origin_is_local();
230 if (has_zero_g_vector) {
231 for (int vector_index = 0; vector_index < number_of_vectors; ++vector_index) {
232 Complex wavefunction_g0 =
233 wavefunctions[static_cast<size_t>(vector_index) * number_of_plane_waves];
234 for (int projector_index = 0; projector_index < total_projectors;
235 ++projector_index) {
236 Complex kleinman_bylander_g0 =
237 kleinman_bylander_potential_[projector_index][0];
238 size_t index = static_cast<size_t>(projector_index) +
239 static_cast<size_t>(vector_index) * total_projectors;
240 real_projector_overlaps[index] -=
241 (std::conj(kleinman_bylander_g0) * wavefunction_g0).real();
242 }
243 }
244 }
245
246 for (size_t index = 0; index < real_projector_overlaps.size(); ++index) {
247 projector_overlaps[index] = Complex(real_projector_overlaps[index], 0.0);
248 }
249 } else {
250 Complex alpha_blas(1.0, 0.0);
251 Complex beta_blas_zero(0.0, 0.0);
252 auto* cast_alpha = reinterpret_cast<const std::complex<double>*>(&alpha_blas);
253 auto* cast_zero = reinterpret_cast<const std::complex<double>*>(&beta_blas_zero);
254 auto* cast_kleinman_bylander =
255 reinterpret_cast<const std::complex<double>*>(kleinman_bylander_potential_.data());
256 auto* cast_wavefunctions = reinterpret_cast<const std::complex<double>*>(wavefunctions);
257 auto* cast_projector_overlaps =
258 reinterpret_cast<std::complex<double>*>(projector_overlaps.data());
259
261 "N",
262 &total_projectors,
263 &number_of_vectors,
264 &number_of_plane_waves,
265 cast_alpha,
266 cast_kleinman_bylander,
267 &number_of_plane_waves,
268 cast_wavefunctions,
269 &number_of_plane_waves,
270 cast_zero,
271 cast_projector_overlaps,
272 &total_projectors);
273 }
274
275 execution_policy_.reduce_within_group(projector_overlaps.data(),
276 total_projectors * number_of_vectors);
277
278 /**
279 * Step 2: Apply q-matrix: projector_overlaps_with_q_matrix(i, vec) = Σ_j q_ij *
280 * projector_overlaps(j, vec)
281 */
282 if (cached_q_matrix_.empty()) {
283 build_q_matrix();
284 }
285 std::vector<Complex> projector_overlaps_with_q_matrix(
286 static_cast<size_t>(total_projectors) * number_of_vectors, Complex(0.0, 0.0));
287 Complex alpha_blas(1.0, 0.0);
288 Complex beta_blas_zero(0.0, 0.0);
289 auto* cast_alpha = reinterpret_cast<const std::complex<double>*>(&alpha_blas);
290 auto* cast_zero = reinterpret_cast<const std::complex<double>*>(&beta_blas_zero);
291 auto* cast_q_matrix =
292 reinterpret_cast<const std::complex<double>*>(cached_q_matrix_.data());
293 auto* cast_projector_overlaps =
294 reinterpret_cast<std::complex<double>*>(projector_overlaps.data());
295 auto* cast_projector_overlaps_with_q_matrix =
296 reinterpret_cast<std::complex<double>*>(projector_overlaps_with_q_matrix.data());
297
299 "N",
300 &total_projectors,
301 &number_of_vectors,
302 &total_projectors,
303 cast_alpha,
304 cast_q_matrix,
305 &total_projectors,
306 cast_projector_overlaps,
307 &total_projectors,
308 cast_zero,
309 cast_projector_overlaps_with_q_matrix,
310 &total_projectors);
311
312 // Step 3: S|ψ⟩ += β × projector_overlaps_with_q_matrix
313 Complex beta_blas_one(1.0, 0.0);
314 auto* cast_one = reinterpret_cast<const std::complex<double>*>(&beta_blas_one);
315 auto* cast_kleinman_bylander =
316 reinterpret_cast<const std::complex<double>*>(kleinman_bylander_potential_.data());
317 auto* cast_overlap_wavefunctions =
318 reinterpret_cast<std::complex<double>*>(overlap_wavefunctions);
319
321 "N",
322 &number_of_plane_waves,
323 &number_of_vectors,
324 &total_projectors,
325 cast_alpha,
326 cast_kleinman_bylander,
327 &number_of_plane_waves,
328 cast_projector_overlaps_with_q_matrix,
329 &total_projectors,
330 cast_one,
331 cast_overlap_wavefunctions,
332 &number_of_plane_waves);
333 }
334
335 void build_dense_matrix(int number_of_plane_waves,
336 std::vector<Complex>& overlap_matrix) const override {
337 overlap_matrix.assign(number_of_plane_waves * number_of_plane_waves, Complex(0.0, 0.0));
338 for (int g_index = 0; g_index < number_of_plane_waves; g_index++) {
339 overlap_matrix[g_index + g_index * number_of_plane_waves] = Complex(1.0, 0.0);
340 }
341
342 int projector_index_offset = 0;
343 for (int atom_index = 0; atom_index < crystal_structure_.number_of_atoms(); atom_index++) {
344 int species_type_index = crystal_structure_.species_type_index(atom_index);
345 int number_of_projectors =
347 std::vector<int> projector_angular_momenta, projector_magnetic_projections,
348 projector_beta_indices;
349 for (int beta_index = 0; beta_index < number_of_projectors; beta_index++) {
350 int angular_momentum =
352 species_type_index, beta_index);
353 for (int magnetic_projection : harmonic_order_sequence(angular_momentum)) {
354 projector_angular_momenta.push_back(angular_momentum);
355 projector_magnetic_projections.push_back(magnetic_projection);
356 projector_beta_indices.push_back(beta_index);
357 }
358 }
359 int number_of_projectors_for_atom = static_cast<int>(projector_angular_momenta.size());
360 for (int projector_index_a = 0; projector_index_a < number_of_projectors_for_atom;
361 projector_index_a++) {
362 for (int projector_index_b = 0; projector_index_b < number_of_projectors_for_atom;
363 projector_index_b++) {
364 Real overlap_coefficient =
366 .ultrasoft_basis()
367 .augmentation_overlap_integral(
368 species_type_index,
369 projector_beta_indices[projector_index_a],
370 projector_beta_indices[projector_index_b]);
371 if (projector_angular_momenta[projector_index_a] !=
372 projector_angular_momenta[projector_index_b] ||
373 projector_magnetic_projections[projector_index_a] !=
374 projector_magnetic_projections[projector_index_b])
375 overlap_coefficient = 0.0;
376 if (std::abs(overlap_coefficient) <
378 continue;
379 for (int g_index = 0; g_index < number_of_plane_waves; g_index++) {
380 for (int plane_wave_index_jg = 0;
381 plane_wave_index_jg < number_of_plane_waves;
382 plane_wave_index_jg++) {
383 overlap_matrix[g_index + plane_wave_index_jg * number_of_plane_waves] +=
384 Complex(overlap_coefficient, 0.0) *
385 kleinman_bylander_potential_[projector_index_offset +
386 projector_index_a][g_index] *
387 std::conj(kleinman_bylander_potential_[projector_index_offset +
388 projector_index_b]
389 [plane_wave_index_jg]);
390 }
391 }
392 }
393 }
394 projector_index_offset += number_of_projectors_for_atom;
395 }
396 }
397
398 private:
399 /**
400 * Build the dense q-matrix Q(total_projectors × total_projectors) in column-major layout.
401 * Q(i,j) = q_ab for the corresponding beta functions if l,m quantum numbers match.
402 */
403 void build_q_matrix() const {
404 int total_projectors = static_cast<int>(kleinman_bylander_potential_.rows());
405 cached_q_matrix_.assign(static_cast<size_t>(total_projectors) * total_projectors,
406 Complex(0.0, 0.0));
407
408 int starting_projector_index = 0;
409 for (int atom_index = 0; atom_index < crystal_structure_.number_of_atoms(); atom_index++) {
410 int species_index = crystal_structure_.species_type_index(atom_index);
411 int number_of_projectors =
413
414 std::vector<int> angular_momentum_l, magnetic_projection_m, beta_projector_index;
415 for (int index_projector = 0; index_projector < number_of_projectors;
416 index_projector++) {
417 int angular_momentum =
419 index_projector);
420 for (int magnetic_projection : harmonic_order_sequence(angular_momentum)) {
421 angular_momentum_l.push_back(angular_momentum);
422 magnetic_projection_m.push_back(magnetic_projection);
423 beta_projector_index.push_back(index_projector);
424 }
425 }
426
427 int number_of_projectors_atom = static_cast<int>(angular_momentum_l.size());
428 for (int index_a = 0; index_a < number_of_projectors_atom; index_a++) {
429 for (int index_b = 0; index_b < number_of_projectors_atom; index_b++) {
430 if (angular_momentum_l[index_a] != angular_momentum_l[index_b] ||
431 magnetic_projection_m[index_a] != magnetic_projection_m[index_b]) {
432 continue;
433 }
434 int beta_a = beta_projector_index[index_a];
435 int beta_b = beta_projector_index[index_b];
436 Real q_matrix_element =
437 static_cast<const UltrasoftPlanewaveKohnShamQuantumSystem&>(quantum_system_)
438 .ultrasoft_basis()
439 .augmentation_overlap_integral(species_index, beta_a, beta_b);
440 if (std::abs(q_matrix_element) > math::tolerances::d_matrix_element_threshold) {
441 int global_a = starting_projector_index + index_a;
442 int global_b = starting_projector_index + index_b;
443 // Column-major: Q(global_a, global_b)
444 cached_q_matrix_[static_cast<size_t>(global_a) +
445 static_cast<size_t>(global_b) * total_projectors] =
446 Complex(q_matrix_element, 0.0);
447 }
448 }
449 }
450 starting_projector_index += number_of_projectors_atom;
451 }
452 }
453
454 mutable std::vector<Complex> cached_q_matrix_;
455};
456
457} // namespace q3
int species_type_index(int atom_index) const
Definition crystal.hpp:273
int number_of_atoms() const
Definition crystal.hpp:209
size_t rows() const
Definition matrix.hpp:62
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
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
PseudopotentialOverlapOperator(const Crystal &crystal_structure, const NormConservingPlanewaveKohnShamQuantumSystem &quantum_system, const Matrix< Complex > &kleinman_bylander_potential, const execution::ExecutionPolicy &execution_policy)
Definition init.hpp:30
const NormConservingPlanewaveKohnShamQuantumSystem & quantum_system_
Definition init.hpp:68
const execution::ExecutionPolicy & execution_policy_
Definition init.hpp:70
const Matrix< Complex > & kleinman_bylander_potential_
Definition init.hpp:69
const Crystal & crystal_structure_
Definition init.hpp:67
void apply_batch(int number_of_plane_waves, int number_of_vectors, const Complex *wavefunctions, Complex *overlap_wavefunctions) const override
Definition init.hpp:176
void build_dense_matrix(int number_of_plane_waves, std::vector< Complex > &overlap_matrix) const override
Definition init.hpp:335
void apply(int number_of_plane_waves, const Complex *wavefunction, Complex *overlap_wavefunction) const override
Definition init.hpp:34
virtual void reduce_within_group(ComplexValue *data, int number_of_elements) 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)
void dgemv_(const char *trans, const int *m, const int *n, const double *alpha, const double *a, const int *lda, const double *x, const int *incx, const double *beta, double *y, const int *incy)
void zgemv_(const char *trans, const int *m, const int *n, const std::complex< double > *alpha, const std::complex< double > *a, const int *lda, const std::complex< double > *x, const int *incx, const std::complex< double > *beta, std::complex< double > *y, const int *incy)
constexpr double d_matrix_element_threshold
double Real
std::complex< double > Complex
std::vector< int > harmonic_order_sequence(int degree)
Definition harmonics.hpp:40