Loading...
Searching...
No Matches
serial.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * SerialPlanewaveSubspaceManager — CPU serial implementation of subspace operations.
5 *
6 * Extracted from PlanewaveDavidsonDiagonalizer. Contains all subspace linear algebra:
7 * orthogonalization, subspace matrix construction, diagonalization, Ritz vector
8 * extraction, and subspace collapse.
9 *
10 * Constructor takes the execution policy for band-group collective operations
11 * and creates a SubspaceDiagonalizer for the eigenvalue problem.
12 */
13
14#include <algorithm>
15#include <cmath>
16#include <complex>
17#include <cstring>
18#include <memory>
19#include <vector>
20
28
29namespace q3 {
30
32 public:
34 : execution_policy_{execution_policy}
35 , subspace_diagonalizer_{SubspaceDiagonalizerFactory::create(execution_policy)} {}
36
37 void set_gamma_origin_is_local(bool origin_is_local) override {
38 gamma_origin_is_local_ = origin_is_local;
39 }
40
41 int orthogonalize_batch(int starting_vector_index,
42 int number_of_candidates,
43 int number_of_plane_waves,
44 bool is_gamma_only,
45 const OverlapOperator* overlap_operator,
46 Complex* basis_vectors,
47 Complex* overlap_basis_vectors) override {
48 if (number_of_candidates == 0) {
49 return 0;
50 }
51 int leading_dimension = std::max(number_of_plane_waves, 1);
52 int double_leading_dimension = 2 * leading_dimension;
53
54 /**
55 * When the overlap operator is the identity -- every norm-conserving
56 * run -- S|v> is |v>, and the caller hands us the same buffer twice
57 * rather than storing a second copy of the basis. Reads then resolve
58 * correctly on their own; what must not happen is applying an update
59 * to the same memory twice, so every write below is guarded.
60 */
61 const bool overlap_is_aliased = (overlap_basis_vectors == basis_vectors);
62
63 if (!overlap_is_aliased) {
64 overlap_operator->apply_batch(
65 number_of_plane_waves,
66 number_of_candidates,
67 &basis_vectors[starting_vector_index * number_of_plane_waves],
68 &overlap_basis_vectors[starting_vector_index * number_of_plane_waves]);
69 }
70
71 int number_of_existing = starting_vector_index;
72 if (number_of_existing > 0) {
73 if (is_gamma_only) {
74 int double_number_of_plane_waves = 2 * number_of_plane_waves;
75 double alpha_two = 2.0;
76 double d_beta_zero = 0.0;
77
78 auto* real_basis = reinterpret_cast<double*>(const_cast<Complex*>(basis_vectors));
79 auto* real_candidates_sv = reinterpret_cast<double*>(
80 &overlap_basis_vectors[starting_vector_index * number_of_plane_waves]);
81
82 std::vector<double> overlap_coefficients(number_of_existing * number_of_candidates,
83 0.0);
85 "N",
86 &number_of_existing,
87 &number_of_candidates,
88 &double_number_of_plane_waves,
89 &alpha_two,
90 real_basis,
91 &double_leading_dimension,
92 real_candidates_sv,
93 &double_leading_dimension,
94 &d_beta_zero,
95 overlap_coefficients.data(),
96 &number_of_existing);
97
98 if (gamma_origin_is_local_) {
99 for (int candidate_index = 0; candidate_index < number_of_candidates;
100 ++candidate_index) {
101 for (int basis_index = 0; basis_index < number_of_existing; ++basis_index) {
102 Complex basis_g0 = basis_vectors[basis_index * number_of_plane_waves];
103 Complex sv_g0 =
104 overlap_basis_vectors[(starting_vector_index + candidate_index) *
105 number_of_plane_waves];
106 overlap_coefficients[candidate_index * number_of_existing +
107 basis_index] -=
108 (std::conj(basis_g0) * sv_g0).real();
109 }
110 }
111 }
112
113 execution_policy_.reduce_within_group(overlap_coefficients.data(),
114 number_of_existing * number_of_candidates);
115
116 auto* real_candidates = reinterpret_cast<double*>(
117 &basis_vectors[starting_vector_index * number_of_plane_waves]);
118 auto* real_overlap_basis = reinterpret_cast<double*>(overlap_basis_vectors);
119
120 double alpha_neg_one = -1.0;
121 double beta_one = 1.0;
123 "N",
124 &double_number_of_plane_waves,
125 &number_of_candidates,
126 &number_of_existing,
127 &alpha_neg_one,
128 real_basis,
129 &double_leading_dimension,
130 overlap_coefficients.data(),
131 &number_of_existing,
132 &beta_one,
133 real_candidates,
134 &double_leading_dimension);
135
136 if (!overlap_is_aliased) {
137 /**
138 * Already subtracted from the basis; repeating it here would double it.
139 */
141 "N",
142 &double_number_of_plane_waves,
143 &number_of_candidates,
144 &number_of_existing,
145 &alpha_neg_one,
146 real_overlap_basis,
147 &double_leading_dimension,
148 overlap_coefficients.data(),
149 &number_of_existing,
150 &beta_one,
151 real_candidates_sv,
152 &double_leading_dimension);
153 }
154 } else {
155 Complex alpha_one(1.0, 0.0), beta_zero(0.0, 0.0);
156 auto* cast_basis =
157 reinterpret_cast<std::complex<double>*>(const_cast<Complex*>(basis_vectors));
158 auto* cast_candidates_sv = reinterpret_cast<std::complex<double>*>(
159 &overlap_basis_vectors[starting_vector_index * number_of_plane_waves]);
160 auto* cast_alpha = reinterpret_cast<std::complex<double>*>(&alpha_one);
161 auto* cast_beta = reinterpret_cast<std::complex<double>*>(&beta_zero);
162
163 std::vector<Complex> overlap_coefficients(number_of_existing * number_of_candidates,
164 Complex(0.0, 0.0));
165 auto* cast_coeffs =
166 reinterpret_cast<std::complex<double>*>(overlap_coefficients.data());
168 "N",
169 &number_of_existing,
170 &number_of_candidates,
171 &number_of_plane_waves,
172 cast_alpha,
173 cast_basis,
174 &leading_dimension,
175 cast_candidates_sv,
176 &leading_dimension,
177 cast_beta,
178 cast_coeffs,
179 &number_of_existing);
180
181 execution_policy_.reduce_within_group(overlap_coefficients.data(),
182 number_of_existing * number_of_candidates);
183
184 auto* cast_candidates = reinterpret_cast<std::complex<double>*>(
185 &basis_vectors[starting_vector_index * number_of_plane_waves]);
186 auto* cast_overlap_basis =
187 reinterpret_cast<std::complex<double>*>(overlap_basis_vectors);
188
189 Complex alpha_neg_one(-1.0, 0.0), beta_one(1.0, 0.0);
190 auto* cast_alpha_neg = reinterpret_cast<std::complex<double>*>(&alpha_neg_one);
191 auto* cast_beta_one = reinterpret_cast<std::complex<double>*>(&beta_one);
193 "N",
194 &number_of_plane_waves,
195 &number_of_candidates,
196 &number_of_existing,
197 cast_alpha_neg,
198 cast_basis,
199 &leading_dimension,
200 cast_coeffs,
201 &number_of_existing,
202 cast_beta_one,
203 cast_candidates,
204 &leading_dimension);
205
206 if (!overlap_is_aliased) {
207 /**
208 * Already subtracted from the basis; repeating it here would double it.
209 */
211 "N",
212 &number_of_plane_waves,
213 &number_of_candidates,
214 &number_of_existing,
215 cast_alpha_neg,
216 cast_overlap_basis,
217 &leading_dimension,
218 cast_coeffs,
219 &number_of_existing,
220 cast_beta_one,
221 cast_candidates_sv,
222 &leading_dimension);
223 }
224 }
225 }
226
227 // Attempt Cholesky QR to orthogonalize candidates against each other in batch
228 if (is_gamma_only) {
229 int double_number_of_plane_waves = 2 * number_of_plane_waves;
230 auto* real_candidates = reinterpret_cast<double*>(
231 &basis_vectors[starting_vector_index * number_of_plane_waves]);
232 auto* real_candidates_sv = reinterpret_cast<double*>(
233 &overlap_basis_vectors[starting_vector_index * number_of_plane_waves]);
234
235 std::vector<double> cholesky_factor(number_of_candidates * number_of_candidates, 0.0);
236 double alpha_two = 2.0;
237 double beta_zero = 0.0;
239 "N",
240 &number_of_candidates,
241 &number_of_candidates,
242 &double_number_of_plane_waves,
243 &alpha_two,
244 real_candidates,
245 &double_leading_dimension,
246 real_candidates_sv,
247 &double_leading_dimension,
248 &beta_zero,
249 cholesky_factor.data(),
250 &number_of_candidates);
251
252 if (gamma_origin_is_local_) {
253 for (int column_index = 0; column_index < number_of_candidates; ++column_index) {
254 for (int row_index = 0; row_index < number_of_candidates; ++row_index) {
255 Complex vi_g0 = basis_vectors[(starting_vector_index + row_index) *
256 number_of_plane_waves];
257 Complex svj_g0 =
258 overlap_basis_vectors[(starting_vector_index + column_index) *
259 number_of_plane_waves];
260 cholesky_factor[column_index * number_of_candidates + row_index] -=
261 (std::conj(vi_g0) * svj_g0).real();
262 }
263 }
264 }
265
266 execution_policy_.reduce_within_group(cholesky_factor.data(),
267 number_of_candidates * number_of_candidates);
268
269 int info = 0;
271 "U", &number_of_candidates, cholesky_factor.data(), &number_of_candidates, &info);
272 if (info == 0) {
273 double d_alpha_one = 1.0;
275 "U",
276 "N",
277 "N",
278 &double_number_of_plane_waves,
279 &number_of_candidates,
280 &d_alpha_one,
281 cholesky_factor.data(),
282 &number_of_candidates,
283 real_candidates,
284 &double_leading_dimension);
285 if (!overlap_is_aliased) {
286 /**
287 * The basis was already solved against this factor.
288 */
290 "U",
291 "N",
292 "N",
293 &double_number_of_plane_waves,
294 &number_of_candidates,
295 &d_alpha_one,
296 cholesky_factor.data(),
297 &number_of_candidates,
298 real_candidates_sv,
299 &double_leading_dimension);
300 }
301 return number_of_candidates;
302 }
303 } else {
304 auto* cast_candidates = reinterpret_cast<std::complex<double>*>(
305 &basis_vectors[starting_vector_index * number_of_plane_waves]);
306 auto* cast_candidates_sv = reinterpret_cast<std::complex<double>*>(
307 &overlap_basis_vectors[starting_vector_index * number_of_plane_waves]);
308
309 std::vector<Complex> cholesky_factor(number_of_candidates * number_of_candidates,
310 Complex(0.0, 0.0));
311 Complex alpha_one(1.0, 0.0), beta_zero(0.0, 0.0);
312 auto* cast_alpha = reinterpret_cast<std::complex<double>*>(&alpha_one);
313 auto* cast_beta = reinterpret_cast<std::complex<double>*>(&beta_zero);
314 auto* cast_cholesky_factor =
315 reinterpret_cast<std::complex<double>*>(cholesky_factor.data());
317 "N",
318 &number_of_candidates,
319 &number_of_candidates,
320 &number_of_plane_waves,
321 cast_alpha,
322 cast_candidates,
323 &leading_dimension,
324 cast_candidates_sv,
325 &leading_dimension,
326 cast_beta,
327 cast_cholesky_factor,
328 &number_of_candidates);
329
330 execution_policy_.reduce_within_group(cholesky_factor.data(),
331 number_of_candidates * number_of_candidates);
332
333 int info = 0;
335 "U", &number_of_candidates, cast_cholesky_factor, &number_of_candidates, &info);
336 if (info == 0) {
337 Complex z_alpha_one(1.0, 0.0);
338 auto* cast_alpha_one = reinterpret_cast<std::complex<double>*>(&z_alpha_one);
340 "U",
341 "N",
342 "N",
343 &number_of_plane_waves,
344 &number_of_candidates,
345 cast_alpha_one,
346 cast_cholesky_factor,
347 &number_of_candidates,
348 cast_candidates,
349 &leading_dimension);
350 if (!overlap_is_aliased) {
351 /**
352 * The basis was already solved against this factor.
353 */
355 "U",
356 "N",
357 "N",
358 &number_of_plane_waves,
359 &number_of_candidates,
360 cast_alpha_one,
361 cast_cholesky_factor,
362 &number_of_candidates,
363 cast_candidates_sv,
364 &leading_dimension);
365 }
366 return number_of_candidates;
367 }
368 }
369
370 // Fallback: Modified Gram-Schmidt loop
371 int accepted_count = 0;
372 int one_increment = 1;
373 std::vector<double> batch_coefficients_buffer(number_of_candidates, 0.0);
374 std::vector<Complex> z_batch_coefficients_buffer(number_of_candidates, Complex(0.0, 0.0));
375
376 for (int candidate_index = 0; candidate_index < number_of_candidates; ++candidate_index) {
377 int source_index = starting_vector_index + candidate_index;
378 int destination_index = starting_vector_index + accepted_count;
379
380 if (destination_index != source_index) {
381 std::memcpy(&basis_vectors[destination_index * number_of_plane_waves],
382 &basis_vectors[source_index * number_of_plane_waves],
383 number_of_plane_waves * sizeof(Complex));
384 if (!overlap_is_aliased) {
385 /**
386 * The basis copy above already moved these coefficients.
387 */
388 std::memcpy(&overlap_basis_vectors[destination_index * number_of_plane_waves],
389 &overlap_basis_vectors[source_index * number_of_plane_waves],
390 number_of_plane_waves * sizeof(Complex));
391 }
392 }
393
394 auto* current_vector = reinterpret_cast<std::complex<double>*>(
395 &basis_vectors[destination_index * number_of_plane_waves]);
396 auto* current_overlap = reinterpret_cast<std::complex<double>*>(
397 &overlap_basis_vectors[destination_index * number_of_plane_waves]);
398
399 int number_of_batch_existing = destination_index - starting_vector_index;
400 if (number_of_batch_existing > 0) {
401 if (is_gamma_only) {
402 int double_number_of_plane_waves = 2 * number_of_plane_waves;
403 double alpha_two = 2.0;
404 double d_beta_zero = 0.0;
405 int one_col = 1;
406
407 auto* real_batch_basis = reinterpret_cast<double*>(
408 &basis_vectors[starting_vector_index * number_of_plane_waves]);
409 auto* real_sv = reinterpret_cast<double*>(current_overlap);
410
412 "N",
413 &number_of_batch_existing,
414 &one_col,
415 &double_number_of_plane_waves,
416 &alpha_two,
417 real_batch_basis,
418 &double_leading_dimension,
419 real_sv,
420 &double_leading_dimension,
421 &d_beta_zero,
422 batch_coefficients_buffer.data(),
423 &number_of_batch_existing);
424
425 if (gamma_origin_is_local_) {
426 for (int basis_index = 0; basis_index < number_of_batch_existing;
427 ++basis_index) {
428 Complex basis_g0 = basis_vectors[(starting_vector_index + basis_index) *
429 number_of_plane_waves];
430 Complex sv_g0 =
431 overlap_basis_vectors[destination_index * number_of_plane_waves];
432 batch_coefficients_buffer[basis_index] -=
433 (std::conj(basis_g0) * sv_g0).real();
434 }
435 }
436
437 execution_policy_.reduce_within_group(batch_coefficients_buffer.data(),
438 number_of_batch_existing);
439
440 double alpha_neg_one = -1.0;
441 double beta_one = 1.0;
443 &double_number_of_plane_waves,
444 &number_of_batch_existing,
445 &alpha_neg_one,
446 real_batch_basis,
447 &double_leading_dimension,
448 batch_coefficients_buffer.data(),
449 &one_increment,
450 &beta_one,
451 reinterpret_cast<double*>(current_vector),
452 &one_increment);
453
454 auto* real_batch_overlap = reinterpret_cast<double*>(
455 &overlap_basis_vectors[starting_vector_index * number_of_plane_waves]);
456 if (!overlap_is_aliased) {
457 /**
458 * Already subtracted from the basis vector.
459 */
461 &double_number_of_plane_waves,
462 &number_of_batch_existing,
463 &alpha_neg_one,
464 real_batch_overlap,
465 &double_leading_dimension,
466 batch_coefficients_buffer.data(),
467 &one_increment,
468 &beta_one,
469 reinterpret_cast<double*>(current_overlap),
470 &one_increment);
471 }
472 } else {
473 int one_col = 1;
474 Complex alpha_one(1.0, 0.0), beta_zero(0.0, 0.0);
475 auto* cast_batch_basis = reinterpret_cast<std::complex<double>*>(
476 &basis_vectors[starting_vector_index * number_of_plane_waves]);
477 auto* cast_alpha = reinterpret_cast<std::complex<double>*>(&alpha_one);
478 auto* cast_beta = reinterpret_cast<std::complex<double>*>(&beta_zero);
479 auto* cast_coeffs =
480 reinterpret_cast<std::complex<double>*>(z_batch_coefficients_buffer.data());
481
483 "N",
484 &number_of_batch_existing,
485 &one_col,
486 &number_of_plane_waves,
487 cast_alpha,
488 cast_batch_basis,
489 &leading_dimension,
490 current_overlap,
491 &leading_dimension,
492 cast_beta,
493 cast_coeffs,
494 &number_of_batch_existing);
495
496 execution_policy_.reduce_within_group(z_batch_coefficients_buffer.data(),
497 number_of_batch_existing);
498
499 Complex alpha_neg_one(-1.0, 0.0), beta_one(1.0, 0.0);
500 auto* cast_alpha_neg = reinterpret_cast<std::complex<double>*>(&alpha_neg_one);
501 auto* cast_beta_one = reinterpret_cast<std::complex<double>*>(&beta_one);
503 &number_of_plane_waves,
504 &number_of_batch_existing,
505 cast_alpha_neg,
506 cast_batch_basis,
507 &leading_dimension,
508 cast_coeffs,
509 &one_increment,
510 cast_beta_one,
511 current_vector,
512 &one_increment);
513
514 auto* cast_batch_overlap = reinterpret_cast<std::complex<double>*>(
515 &overlap_basis_vectors[starting_vector_index * number_of_plane_waves]);
516 if (!overlap_is_aliased) {
517 /**
518 * Already subtracted from the basis vector.
519 */
521 &number_of_plane_waves,
522 &number_of_batch_existing,
523 cast_alpha_neg,
524 cast_batch_overlap,
525 &leading_dimension,
526 cast_coeffs,
527 &one_increment,
528 cast_beta_one,
529 current_overlap,
530 &one_increment);
531 }
532 }
533 }
534
535 Real norm_squared = gamma_aware_real_dot(
536 is_gamma_only, number_of_plane_waves, current_vector, current_overlap);
537
539 double inverse_norm = 1.0 / std::sqrt(norm_squared);
541 &number_of_plane_waves, &inverse_norm, current_vector, &one_increment);
542 if (!overlap_is_aliased) {
543 /**
544 * The basis vector carries the scaling already.
545 */
547 &number_of_plane_waves, &inverse_norm, current_overlap, &one_increment);
548 }
549 accepted_count++;
550 }
551 }
552 return accepted_count;
553 }
554
555 void update_subspace_matrices(int start_column,
556 int new_columns,
557 int total_basis_size,
558 int maximum_basis_size,
559 int number_of_plane_waves,
560 bool is_gamma_only,
561 const Complex* basis_vectors,
562 const Complex* hamiltonian_basis_vectors,
563 const Complex* overlap_basis_vectors,
564 Complex* subspace_hamiltonian,
565 Complex* subspace_overlap) override {
566 int leading_dimension = std::max(number_of_plane_waves, 1);
567 Complex alpha_one(1.0, 0.0), beta_zero(0.0, 0.0);
568 auto* cast_basis =
569 reinterpret_cast<std::complex<double>*>(const_cast<Complex*>(basis_vectors));
570 auto* cast_h_new = reinterpret_cast<std::complex<double>*>(
571 const_cast<Complex*>(&hamiltonian_basis_vectors[start_column * number_of_plane_waves]));
572 auto* cast_s_new = reinterpret_cast<std::complex<double>*>(
573 const_cast<Complex*>(&overlap_basis_vectors[start_column * number_of_plane_waves]));
574
575 if (is_gamma_only) {
576 int double_number_of_plane_waves = 2 * number_of_plane_waves;
577 int double_leading_dimension = 2 * leading_dimension;
578 double alpha_two = 2.0;
579 double d_beta_zero = 0.0;
580
581 auto* real_basis = reinterpret_cast<double*>(const_cast<Complex*>(basis_vectors));
582 auto* real_h_new = reinterpret_cast<double*>(const_cast<Complex*>(
583 &hamiltonian_basis_vectors[start_column * number_of_plane_waves]));
584 auto* real_s_new = reinterpret_cast<double*>(
585 const_cast<Complex*>(&overlap_basis_vectors[start_column * number_of_plane_waves]));
586
587 std::vector<double> h_block_real(static_cast<size_t>(total_basis_size) * new_columns,
588 0.0);
590 "N",
591 &total_basis_size,
592 &new_columns,
593 &double_number_of_plane_waves,
594 &alpha_two,
595 real_basis,
596 &double_leading_dimension,
597 real_h_new,
598 &double_leading_dimension,
599 &d_beta_zero,
600 h_block_real.data(),
601 &total_basis_size);
602
603 std::vector<double> s_block_real(static_cast<size_t>(total_basis_size) * new_columns,
604 0.0);
606 "N",
607 &total_basis_size,
608 &new_columns,
609 &double_number_of_plane_waves,
610 &alpha_two,
611 real_basis,
612 &double_leading_dimension,
613 real_s_new,
614 &double_leading_dimension,
615 &d_beta_zero,
616 s_block_real.data(),
617 &total_basis_size);
618
619 for (int col = 0; col < new_columns; ++col) {
620 int source_col = start_column + col;
621 Complex h_right_g0 = hamiltonian_basis_vectors[source_col * number_of_plane_waves];
622 Complex s_right_g0 = overlap_basis_vectors[source_col * number_of_plane_waves];
623 for (int row = 0; row < total_basis_size; ++row) {
624 Complex left_g0 = basis_vectors[row * number_of_plane_waves];
625 int real_index = col * total_basis_size + row;
626 if (gamma_origin_is_local_) {
627 h_block_real[real_index] -= (std::conj(left_g0) * h_right_g0).real();
628 s_block_real[real_index] -= (std::conj(left_g0) * s_right_g0).real();
629 }
630
631 int sub_index = (start_column + col) * maximum_basis_size + row;
632 subspace_hamiltonian[sub_index] = Complex(h_block_real[real_index], 0.0);
633 subspace_overlap[sub_index] = Complex(s_block_real[real_index], 0.0);
634 }
635 }
636 } else {
637 auto* cast_alpha = reinterpret_cast<std::complex<double>*>(&alpha_one);
638 auto* cast_beta = reinterpret_cast<std::complex<double>*>(&beta_zero);
639
640 auto* cast_h_block = reinterpret_cast<std::complex<double>*>(
641 &subspace_hamiltonian[start_column * maximum_basis_size]);
643 "N",
644 &total_basis_size,
645 &new_columns,
646 &number_of_plane_waves,
647 cast_alpha,
648 cast_basis,
649 &leading_dimension,
650 cast_h_new,
651 &leading_dimension,
652 cast_beta,
653 cast_h_block,
654 &maximum_basis_size);
655
656 auto* cast_s_block = reinterpret_cast<std::complex<double>*>(
657 &subspace_overlap[start_column * maximum_basis_size]);
659 "N",
660 &total_basis_size,
661 &new_columns,
662 &number_of_plane_waves,
663 cast_alpha,
664 cast_basis,
665 &leading_dimension,
666 cast_s_new,
667 &leading_dimension,
668 cast_beta,
669 cast_s_block,
670 &maximum_basis_size);
671 }
672
673 int new_block_size = maximum_basis_size * new_columns;
674 int new_block_offset = start_column * maximum_basis_size;
675 execution_policy_.reduce_within_group(&subspace_hamiltonian[new_block_offset],
676 new_block_size);
677 execution_policy_.reduce_within_group(&subspace_overlap[new_block_offset], new_block_size);
678 }
679
680 int diagonalize_subspace(int basis_size,
681 int maximum_basis_size,
682 bool is_gamma_only,
683 const Complex* subspace_hamiltonian,
684 const Complex* subspace_overlap,
685 Real* eigenvalues,
686 Complex* eigenvector_matrix) override {
687 /**
688 * The solver overwrites both inputs, and the caller's matrices carry
689 * the next iteration's incremental update, so it works on copies. The
690 * Hamiltonian's copy is the eigenvector buffer itself -- the solver
691 * returns the eigenvectors where the matrix was -- and only the overlap
692 * needs a buffer of its own. Both are compacted from the caller's
693 * leading dimension to basis_size. That is two matrices of basis_size
694 * squared here rather than three, on every rank.
695 */
696 diagonalize_overlap_copy_workspace_.resize(static_cast<std::size_t>(basis_size) *
697 basis_size);
698 for (int column = 0; column < basis_size; ++column) {
699 for (int row = 0; row < basis_size; ++row) {
700 eigenvector_matrix[column * basis_size + row] =
701 subspace_hamiltonian[column * maximum_basis_size + row];
702 diagonalize_overlap_copy_workspace_[column * basis_size + row] =
703 subspace_overlap[column * maximum_basis_size + row];
704 }
705 }
706
707 /**
708 * Which ranks run the eigensolver, in three cases:
709 *
710 * - a collective solver (ScaLAPACK) must be entered by EVERY rank of
711 * the group — gating on the root would deadlock — and returns
712 * redundant results, so no broadcast follows;
713 * - replicated coefficient vectors (group_size() <= 1) mean every rank
714 * already holds the same matrices, so each solves its own copy and
715 * there is no group to broadcast over. This includes the fallback
716 * that disables band distribution AFTER this manager was built, where
717 * group_rank_index() keeps its pool-local value and is not
718 * necessarily 0;
719 * - distributed vectors with a non-collective solver: the group root
720 * solves and broadcasts.
721 *
722 * The two conditions must agree. Testing group_rank_index() == 0 to
723 * solve but group_size() > 1 to broadcast leaves the non-root ranks of a
724 * replicated group with neither a result nor a broadcast, and they then
725 * diverge from the root at the next collective.
726 */
727 const bool all_ranks_solve = subspace_diagonalizer_->is_collective();
728 const bool vectors_are_replicated = execution_policy_.group_size() <= 1;
729 const bool this_rank_solves =
730 all_ranks_solve || vectors_are_replicated || execution_policy_.group_rank_index() == 0;
731 int info = 0;
732 if (this_rank_solves) {
733 info = subspace_diagonalizer_->solve(basis_size,
734 eigenvector_matrix,
735 diagonalize_overlap_copy_workspace_.data(),
736 eigenvalues,
737 eigenvector_matrix,
738 is_gamma_only);
739 }
740
741 if (!all_ranks_solve && !vectors_are_replicated) {
742 execution_policy_.broadcast_from_group_root(eigenvector_matrix,
743 basis_size * basis_size);
744 execution_policy_.broadcast_from_group_root(eigenvalues, basis_size);
745 Real info_as_real = static_cast<Real>(info);
746 execution_policy_.broadcast_from_group_root(&info_as_real, 1);
747 info = static_cast<int>(info_as_real);
748 }
749
750 if (info != 0) {
751 log::warn("LAPACK eigensolver returned info={} in Davidson subspace (basis_size={})",
752 info,
753 basis_size);
754 }
755 return info;
756 }
757
758 /**
759 * output = basis * coefficients, divided by band when bands are distributed.
760 *
761 * The product is (plane_waves x basis_size) times (basis_size x bands), so
762 * its columns are bands and each rank can form its own contiguous range
763 * against the matching column slice of the coefficient matrix. One
764 * allgather then completes the array on every rank. Because every block has
765 * exactly one source, the assembled result is identical on all ranks -- the
766 * property the replicated basis depends on and redundant ZGEMMs cannot give.
767 * The inputs must already agree across ranks, which the Davidson loop
768 * guarantees by broadcasting the basis and the eigenvector matrix.
769 */
770 void multiply_basis_by_coefficients_across_bands(int number_of_plane_waves,
771 int number_of_bands,
772 int basis_size,
773 const std::complex<double>* basis,
774 const std::complex<double>* coefficients,
775 std::complex<double>* output) const {
776 int leading_dimension = std::max(number_of_plane_waves, 1);
777 Complex alpha_one(1.0, 0.0), beta_zero(0.0, 0.0);
778 auto* cast_alpha = reinterpret_cast<std::complex<double>*>(&alpha_one);
779 auto* cast_beta = reinterpret_cast<std::complex<double>*>(&beta_zero);
780 int band_group_size = execution_policy_.band_distribution_size();
781 if (band_group_size <= 1 || number_of_bands < band_group_size) {
783 "N",
784 &number_of_plane_waves,
785 &number_of_bands,
786 &basis_size,
787 cast_alpha,
788 const_cast<std::complex<double>*>(basis),
789 &leading_dimension,
790 const_cast<std::complex<double>*>(coefficients),
791 &basis_size,
792 cast_beta,
793 output,
794 &leading_dimension);
795 return;
796 }
797 auto partition =
798 execution::partition_bands(number_of_bands,
799 band_group_size,
800 execution_policy_.band_distribution_rank_index(),
801 number_of_plane_waves);
802 int local_bands = partition.local_number_of_bands;
803 if (local_bands > 0) {
804 std::size_t column_offset = static_cast<std::size_t>(partition.local_first_band);
806 "N",
807 "N",
808 &number_of_plane_waves,
809 &local_bands,
810 &basis_size,
811 cast_alpha,
812 const_cast<std::complex<double>*>(basis),
813 &leading_dimension,
814 const_cast<std::complex<double>*>(coefficients + column_offset * basis_size),
815 &basis_size,
816 cast_beta,
817 output + column_offset * number_of_plane_waves,
818 &leading_dimension);
819 }
820 execution_policy_.allgather_across_bands(reinterpret_cast<Complex*>(output),
821 partition.counts.data(),
822 partition.displacements.data());
823 }
824
825 int collapse_subspace(int basis_size,
826 int number_of_plane_waves,
827 int number_of_bands,
828 int maximum_basis_size,
829 bool is_gamma_only,
830 bool is_identity_overlap,
831 const OverlapOperator* overlap_operator,
832 const Complex* subspace_hamiltonian,
833 const Complex* subspace_overlap,
834 Complex* basis_vectors,
835 Complex* hamiltonian_basis_vectors,
836 Complex* overlap_basis_vectors,
837 Complex* rotation_scratch) override {
838 std::vector<Real> eigenvalues_expanded(maximum_basis_size, 0.0);
839 std::vector<Complex> eigenvector_matrix(static_cast<std::size_t>(basis_size) * basis_size);
840 /**
841 * The subspace the caller holds is complete through basis_size, so its
842 * eigenvectors are the rotation. Rebuilding it from the vectors is kept
843 * only as the fallback, for the recovery collapse that follows a failed
844 * diagonalisation of these very matrices.
845 */
846 int info = -1;
847 if (subspace_hamiltonian != nullptr && subspace_overlap != nullptr) {
848 info = diagonalize_subspace(basis_size,
849 maximum_basis_size,
850 is_gamma_only,
851 subspace_hamiltonian,
852 subspace_overlap,
853 eigenvalues_expanded.data(),
854 eigenvector_matrix.data());
855 }
856 if (info != 0) {
857 build_subspace_matrices_and_diagonalize(basis_size,
858 number_of_plane_waves,
859 is_gamma_only,
860 overlap_operator,
861 basis_vectors,
862 hamiltonian_basis_vectors,
863 overlap_basis_vectors,
864 eigenvalues_expanded,
865 eigenvector_matrix);
866 }
867 /**
868 * This diagonalisation is local to the collapse, so it is not covered
869 * by the Davidson loop's eigenvector broadcast. Without republishing it,
870 * each rank would rotate its band range by a slightly different matrix
871 * and the allgathered basis would be a stitch of inconsistent rotations.
872 */
873 execution_policy_.broadcast_across_bands(eigenvector_matrix.data(),
874 static_cast<int>(eigenvector_matrix.size()));
875
876 /**
877 * Only the first number_of_bands columns survive a collapse, so only
878 * that much is rotated and only that much is copied back. Columns past
879 * it are left as they were rather than zeroed: every reader downstream
880 * is bounded by basis_size, which the caller resets to number_of_bands,
881 * and the expansion writes each new column before anything reads it.
882 */
883 std::size_t collapsed_size = static_cast<std::size_t>(number_of_bands) *
884 static_cast<std::size_t>(number_of_plane_waves);
885
886 auto* cast_basis = reinterpret_cast<std::complex<double>*>(basis_vectors);
887 auto* cast_h_basis = reinterpret_cast<std::complex<double>*>(hamiltonian_basis_vectors);
888 auto* cast_s_basis = reinterpret_cast<std::complex<double>*>(overlap_basis_vectors);
889 auto* cast_eigvec = reinterpret_cast<std::complex<double>*>(eigenvector_matrix.data());
890 auto* cast_scratch = reinterpret_cast<std::complex<double>*>(rotation_scratch);
891
892 /**
893 * One scratch buffer does for all three rotations, because each one
894 * reads a different array than it writes: rotate into the scratch,
895 * copy back, move on. The overlap array is the exception and needs no
896 * rotation at all when it aliases the basis -- it already holds the
897 * rotated vectors by the time the first copy-back lands.
898 */
900 number_of_bands,
901 basis_size,
902 cast_basis,
903 cast_eigvec,
904 cast_scratch);
905 std::memcpy(basis_vectors, rotation_scratch, collapsed_size * sizeof(Complex));
906
908 number_of_bands,
909 basis_size,
910 cast_h_basis,
911 cast_eigvec,
912 cast_scratch);
913 std::memcpy(hamiltonian_basis_vectors, rotation_scratch, collapsed_size * sizeof(Complex));
914
915 /**
916 * Skip the overlap rotation only when the overlap array is the basis
917 * itself. An identity overlap is not enough: the CPU workspace aliases
918 * S|V> to |V> for it, so rotating the basis has already rotated S|V>,
919 * but the device workspace keeps S|V> in a buffer of its own. Keying
920 * the skip on the flag left that buffer unrotated after every collapse,
921 * so the next orthogonalisation and subspace update read stale S|V>,
922 * and a run that collapsed often converged to the wrong answer -- 1.77
923 * Ry low on the two-atom silicon deck at expansion factor 2, which
924 * collapses every iteration. Factor 4 hid it: small decks converge
925 * before the basis fills.
926 */
927 const bool overlap_needs_rotation =
928 !is_identity_overlap || overlap_basis_vectors != basis_vectors;
929 if (overlap_needs_rotation) {
931 number_of_bands,
932 basis_size,
933 cast_s_basis,
934 cast_eigvec,
935 cast_scratch);
936 std::memcpy(overlap_basis_vectors, rotation_scratch, collapsed_size * sizeof(Complex));
937 }
938
939 enforce_gamma_g0_real(is_gamma_only, number_of_plane_waves, number_of_bands, basis_vectors);
940 enforce_gamma_g0_real(
941 is_gamma_only, number_of_plane_waves, number_of_bands, hamiltonian_basis_vectors);
942 enforce_gamma_g0_real(
943 is_gamma_only, number_of_plane_waves, number_of_bands, overlap_basis_vectors);
944 return number_of_bands;
945 }
946
947 void extract_eigenvectors(int basis_size,
948 int number_of_plane_waves,
949 int number_of_bands,
950 int kpoint_index_state,
951 bool is_gamma_only,
952 const Complex* eigenvector_matrix,
953 const Complex* basis_vectors,
954 const Real* eigenvalues,
955 Complex* rotated_scratch,
956 PlanewaveKohnShamQuantumSystem& quantum_system) override {
957 int leading_dimension = std::max(number_of_plane_waves, 1);
958 int leading_basis_dimension = std::max(basis_size, 1);
959 Complex alpha_one(1.0, 0.0), beta_zero(0.0, 0.0);
960 auto* cast_basis =
961 reinterpret_cast<std::complex<double>*>(const_cast<Complex*>(basis_vectors));
962 auto* cast_eigvec =
963 reinterpret_cast<std::complex<double>*>(const_cast<Complex*>(eigenvector_matrix));
964 auto* cast_result = reinterpret_cast<std::complex<double>*>(rotated_scratch);
965 auto* cast_alpha = reinterpret_cast<std::complex<double>*>(&alpha_one);
966 auto* cast_beta = reinterpret_cast<std::complex<double>*>(&beta_zero);
967
969 "N",
970 &number_of_plane_waves,
971 &number_of_bands,
972 &basis_size,
973 cast_alpha,
974 cast_basis,
975 &leading_dimension,
976 cast_eigvec,
977 &leading_basis_dimension,
978 cast_beta,
979 cast_result,
980 &leading_dimension);
981
982 enforce_gamma_g0_real(
983 is_gamma_only, number_of_plane_waves, number_of_bands, rotated_scratch);
984
985 for (int band_index = 0; band_index < number_of_bands; ++band_index) {
986 quantum_system.set_eigenvalue(kpoint_index_state, band_index, eigenvalues[band_index]);
987 Complex* band_data =
988 quantum_system.wavefunction_band_data(kpoint_index_state, band_index);
989 std::copy(rotated_scratch + band_index * number_of_plane_waves,
990 rotated_scratch + (band_index + 1) * number_of_plane_waves,
991 band_data);
992 }
993 }
994
995 int build_correction_vectors(int basis_size,
996 int number_of_plane_waves,
997 int number_of_bands,
998 int maximum_candidates,
999 bool is_gamma_only,
1000 const Complex* eigenvector_matrix,
1001 int eigenvector_leading_dimension,
1002 const Real* eigenvalues,
1003 const std::vector<bool>& band_converged,
1004 const Real* kinetic_energy_squared,
1005 const Complex* hamiltonian_basis_vectors,
1006 const Complex* overlap_basis_vectors,
1007 Complex* correction_columns) override {
1008 /**
1009 * Gather the unconverged bands' eigenvector columns, compacted, so the
1010 * two GEMMs below write consecutive columns and the caller can hand
1011 * the block straight to orthogonalisation.
1012 */
1013 std::vector<int> source_bands;
1014 source_bands.reserve(static_cast<std::size_t>(number_of_bands));
1015 for (int band_index = 0; band_index < number_of_bands; ++band_index) {
1016 if (!band_converged[band_index] &&
1017 static_cast<int>(source_bands.size()) < maximum_candidates) {
1018 source_bands.push_back(band_index);
1019 }
1020 }
1021 int number_of_candidates = static_cast<int>(source_bands.size());
1022 if (number_of_candidates == 0) {
1023 return 0;
1024 }
1025
1026 std::vector<Complex> packed_coefficients(static_cast<std::size_t>(basis_size) *
1027 static_cast<std::size_t>(number_of_candidates),
1028 Complex(0.0, 0.0));
1029 std::vector<Real> candidate_eigenvalues(static_cast<std::size_t>(number_of_candidates),
1030 0.0);
1031 for (int candidate_index = 0; candidate_index < number_of_candidates; ++candidate_index) {
1032 int band_index = source_bands[static_cast<std::size_t>(candidate_index)];
1033 candidate_eigenvalues[static_cast<std::size_t>(candidate_index)] =
1034 eigenvalues[band_index];
1035 std::memcpy(
1036 &packed_coefficients[static_cast<std::size_t>(candidate_index) * basis_size],
1037 eigenvector_matrix + static_cast<std::size_t>(band_index) *
1038 static_cast<std::size_t>(eigenvector_leading_dimension),
1039 static_cast<std::size_t>(basis_size) * sizeof(Complex));
1040 }
1041
1042 /**
1043 * R = (SV)c scaled by -eps, then (HV)c accumulated on top. Under band
1044 * distribution each rank owns a slice of the candidate columns and
1045 * both GEMMs plus the scaling touch only that slice, so one allgather
1046 * at the end republishes the block.
1047 */
1048 int band_group_size = execution_policy_.band_distribution_size();
1049 bool distribute = band_group_size > 1 && number_of_candidates >= band_group_size;
1050 int first_column = 0;
1051 int local_columns = number_of_candidates;
1052 execution::BandPartition partition;
1053 if (distribute) {
1054 partition = execution::partition_bands(number_of_candidates,
1055 band_group_size,
1056 execution_policy_.band_distribution_rank_index(),
1057 number_of_plane_waves);
1058 first_column = partition.local_first_band;
1059 local_columns = partition.local_number_of_bands;
1060 }
1061
1062 if (local_columns > 0) {
1063 int leading_dimension = std::max(number_of_plane_waves, 1);
1064 Complex alpha_one(1.0, 0.0), beta_zero(0.0, 0.0), beta_one(1.0, 0.0);
1065 auto* cast_alpha = reinterpret_cast<std::complex<double>*>(&alpha_one);
1066 auto* cast_beta_zero = reinterpret_cast<std::complex<double>*>(&beta_zero);
1067 auto* cast_beta_one = reinterpret_cast<std::complex<double>*>(&beta_one);
1068 auto* cast_coefficients = reinterpret_cast<std::complex<double>*>(
1069 packed_coefficients.data() + static_cast<std::size_t>(first_column) * basis_size);
1070 auto* cast_destination = reinterpret_cast<std::complex<double>*>(
1071 correction_columns +
1072 static_cast<std::size_t>(first_column) * number_of_plane_waves);
1073
1074 q3::blas::zgemm_("N",
1075 "N",
1076 &number_of_plane_waves,
1077 &local_columns,
1078 &basis_size,
1079 cast_alpha,
1080 reinterpret_cast<std::complex<double>*>(
1081 const_cast<Complex*>(overlap_basis_vectors)),
1082 &leading_dimension,
1083 cast_coefficients,
1084 &basis_size,
1085 cast_beta_zero,
1086 cast_destination,
1087 &leading_dimension);
1088
1089 for (int local_index = 0; local_index < local_columns; ++local_index) {
1090 Real negative_eigenvalue =
1091 -candidate_eigenvalues[static_cast<std::size_t>(first_column + local_index)];
1092 Complex* column =
1093 correction_columns +
1094 static_cast<std::size_t>(first_column + local_index) * number_of_plane_waves;
1095 for (int g_index = 0; g_index < number_of_plane_waves; ++g_index) {
1096 column[g_index] *= negative_eigenvalue;
1097 }
1098 }
1099
1100 q3::blas::zgemm_("N",
1101 "N",
1102 &number_of_plane_waves,
1103 &local_columns,
1104 &basis_size,
1105 cast_alpha,
1106 reinterpret_cast<std::complex<double>*>(
1107 const_cast<Complex*>(hamiltonian_basis_vectors)),
1108 &leading_dimension,
1109 cast_coefficients,
1110 &basis_size,
1111 cast_beta_one,
1112 cast_destination,
1113 &leading_dimension);
1114
1115 apply_preconditioner_to_columns(local_columns,
1116 number_of_plane_waves,
1117 kinetic_energy_squared,
1118 candidate_eigenvalues.data() + first_column,
1119 correction_columns +
1120 static_cast<std::size_t>(first_column) *
1121 number_of_plane_waves);
1122 }
1123
1124 if (distribute) {
1125 execution_policy_.allgather_across_bands(
1126 correction_columns, partition.counts.data(), partition.displacements.data());
1127 }
1128
1129 enforce_gamma_g0_real(
1130 is_gamma_only, number_of_plane_waves, number_of_candidates, correction_columns);
1131 return number_of_candidates;
1132 }
1133
1134 /**
1135 * Teter-Payne-Allan preconditioning of a compact block of columns, applied
1136 * in place. The legacy per-band entry point below delegates to this.
1137 */
1138 void apply_preconditioner_to_columns(int number_of_columns,
1139 int number_of_plane_waves,
1140 const Real* kinetic_energy_squared,
1141 const Real* eigenvalues,
1142 Complex* columns) const {
1143 for (int column_index = 0; column_index < number_of_columns; ++column_index) {
1144 Complex* column = &columns[static_cast<std::size_t>(column_index) *
1145 static_cast<std::size_t>(number_of_plane_waves)];
1146 for (int g_index = 0; g_index < number_of_plane_waves; ++g_index) {
1147 Real value_x = kinetic_energy_squared[g_index] - eigenvalues[column_index];
1148 Real denominator =
1149 0.5 * (1.0 + value_x + std::sqrt(1.0 + (value_x - 1.0) * (value_x - 1.0)));
1152 }
1153 column[g_index] /= denominator;
1154 }
1155 }
1156 }
1157
1158 void apply_preconditioner(int number_of_bands,
1159 int number_of_plane_waves,
1160 const Real* kinetic_energy_squared,
1161 const Real* eigenvalues,
1162 const std::vector<bool>& band_converged,
1163 Complex* residual_wavefunctions) override {
1164 for (int band_index = 0; band_index < number_of_bands; band_index++) {
1165 if (band_converged[band_index]) {
1166 continue;
1167 }
1168 Complex* residual = &residual_wavefunctions[band_index * number_of_plane_waves];
1169 for (int g_vector_index = 0; g_vector_index < number_of_plane_waves; g_vector_index++) {
1170 Real value_x = kinetic_energy_squared[g_vector_index] - eigenvalues[band_index];
1171 Real denominator =
1172 0.5 * (1.0 + value_x + std::sqrt(1.0 + (value_x - 1.0) * (value_x - 1.0)));
1175 }
1176 residual[g_vector_index] /= denominator;
1177 }
1178 }
1179 }
1180
1181 private:
1182 // ─── Gamma-only helpers ───────────────────────────────────────────
1183
1184 /**
1185 * Zero the imaginary part at G = 0 -- the two-band transform needs the
1186 * origin real -- on the rank whose index 0 is the origin. Anywhere else
1187 * index 0 is an ordinary G whose imaginary part is data.
1188 */
1189 void enforce_gamma_g0_real(bool is_gamma_only,
1190 int number_of_plane_waves,
1191 int number_of_vectors,
1192 Complex* vectors) const {
1193 if (!is_gamma_only || !gamma_origin_is_local_) {
1194 return;
1195 }
1196 for (int vector_index = 0; vector_index < number_of_vectors; ++vector_index) {
1197 Complex& g0 = vectors[vector_index * number_of_plane_waves];
1198 g0 = Complex(g0.real(), 0.0);
1199 }
1200 }
1201
1202 Real gamma_aware_real_dot(bool is_gamma_only,
1203 int number_of_plane_waves,
1204 const std::complex<double>* vector_a,
1205 const std::complex<double>* vector_b) const {
1206 if (!is_gamma_only) {
1207 return std::complex<double>(
1208 execution_policy_.dot_product(number_of_plane_waves, vector_a, vector_b))
1209 .real();
1210 }
1211 /**
1212 * 2 Re over this rank's half sphere, less the origin where it lives,
1213 * then one reduction. The correction has to go in before the sum is
1214 * shared, or the ranks would not agree on the norm.
1215 */
1216 std::complex<double> local_sum(
1217 q3::blas::q3_complex_conjugate_dot_product(number_of_plane_waves, vector_a, vector_b));
1218 Real full_sphere = 2.0 * local_sum.real();
1219 if (gamma_origin_is_local_) {
1220 full_sphere -= (std::conj(vector_a[0]) * vector_b[0]).real();
1221 }
1222 execution_policy_.reduce_within_group(&full_sphere, 1);
1223 return full_sphere;
1224 }
1225
1226 // ─── Build and diag (used by collapse) ───────────────────────────
1227
1228 void build_subspace_matrices_and_diagonalize(int basis_size,
1229 int number_of_plane_waves,
1230 bool is_gamma_only,
1231 const OverlapOperator* overlap_operator,
1232 const Complex* basis_vectors,
1233 const Complex* hamiltonian_basis_vectors,
1234 const Complex* overlap_basis_vectors,
1235 std::vector<Real>& eigenvalues,
1236 std::vector<Complex>& eigenvector_matrix) const {
1237 int leading_dimension = std::max(number_of_plane_waves, 1);
1238 eigenvector_matrix.assign(basis_size * basis_size, Complex(0.0, 0.0));
1239 std::vector<Complex> overlap_matrix(basis_size * basis_size, Complex(0.0, 0.0));
1240
1241 Complex alpha_one(1.0, 0.0), beta_zero(0.0, 0.0);
1242 auto* cast_basis =
1243 reinterpret_cast<std::complex<double>*>(const_cast<Complex*>(basis_vectors));
1244 auto* cast_h_basis = reinterpret_cast<std::complex<double>*>(
1245 const_cast<Complex*>(hamiltonian_basis_vectors));
1246 auto* cast_s_basis =
1247 reinterpret_cast<std::complex<double>*>(const_cast<Complex*>(overlap_basis_vectors));
1248 auto* cast_h_sub = reinterpret_cast<std::complex<double>*>(eigenvector_matrix.data());
1249 auto* cast_s_sub = reinterpret_cast<std::complex<double>*>(overlap_matrix.data());
1250 auto* cast_alpha = reinterpret_cast<std::complex<double>*>(&alpha_one);
1251 auto* cast_beta = reinterpret_cast<std::complex<double>*>(&beta_zero);
1252
1253 q3::blas::zgemm_("C",
1254 "N",
1255 &basis_size,
1256 &basis_size,
1257 &number_of_plane_waves,
1258 cast_alpha,
1259 cast_basis,
1260 &leading_dimension,
1261 cast_h_basis,
1262 &leading_dimension,
1263 cast_beta,
1264 cast_h_sub,
1265 &basis_size);
1266 q3::blas::zgemm_("C",
1267 "N",
1268 &basis_size,
1269 &basis_size,
1270 &number_of_plane_waves,
1271 cast_alpha,
1272 cast_basis,
1273 &leading_dimension,
1274 cast_s_basis,
1275 &leading_dimension,
1276 cast_beta,
1277 cast_s_sub,
1278 &basis_size);
1279
1280 if (is_gamma_only) {
1281 /**
1282 * The ZGEMMs summed the stored half sphere; the full-sphere value
1283 * is 2 Re of that with the origin, held once, removed from the
1284 * double count -- on the rank that holds it.
1285 */
1286 for (int column = 0; column < basis_size; ++column) {
1287 Complex h_right_g0 = hamiltonian_basis_vectors[column * number_of_plane_waves];
1288 Complex s_right_g0 = overlap_basis_vectors[column * number_of_plane_waves];
1289 for (int row = 0; row < basis_size; ++row) {
1290 Complex left_g0 = basis_vectors[row * number_of_plane_waves];
1291 Real h_origin =
1292 gamma_origin_is_local_ ? (std::conj(left_g0) * h_right_g0).real() : 0.0;
1293 Real s_origin =
1294 gamma_origin_is_local_ ? (std::conj(left_g0) * s_right_g0).real() : 0.0;
1295 int index = column * basis_size + row;
1296 eigenvector_matrix[index] =
1297 Complex(2.0 * eigenvector_matrix[index].real() - h_origin, 0.0);
1298 overlap_matrix[index] =
1299 Complex(2.0 * overlap_matrix[index].real() - s_origin, 0.0);
1300 }
1301 }
1302 }
1303
1304 execution_policy_.reduce_within_group(eigenvector_matrix.data(), basis_size * basis_size);
1305 execution_policy_.reduce_within_group(overlap_matrix.data(), basis_size * basis_size);
1306
1307 {
1308 int info;
1309 int diagonalization_type = 1;
1310
1311 if (is_gamma_only) {
1312 std::vector<Real> hamiltonian_real(basis_size * basis_size);
1313 std::vector<Real> overlap_real(basis_size * basis_size);
1314 for (int element_index = 0; element_index < basis_size * basis_size;
1315 ++element_index) {
1316 hamiltonian_real[element_index] = eigenvector_matrix[element_index].real();
1317 overlap_real[element_index] = overlap_matrix[element_index].real();
1318 }
1319
1320 int work_size = std::max(1, 3 * basis_size - 1);
1321 std::vector<Real> work(work_size);
1322 Real optimal_work_size;
1323 int query_size = -1;
1324 q3::lapack::dsygv_(&diagonalization_type,
1325 "V",
1326 "U",
1327 &basis_size,
1328 hamiltonian_real.data(),
1329 &basis_size,
1330 overlap_real.data(),
1331 &basis_size,
1332 eigenvalues.data(),
1333 &optimal_work_size,
1334 &query_size,
1335 &info);
1336 work_size = static_cast<int>(optimal_work_size);
1337 work.resize(work_size);
1338 q3::lapack::dsygv_(&diagonalization_type,
1339 "V",
1340 "U",
1341 &basis_size,
1342 hamiltonian_real.data(),
1343 &basis_size,
1344 overlap_real.data(),
1345 &basis_size,
1346 eigenvalues.data(),
1347 work.data(),
1348 &work_size,
1349 &info);
1350
1351 for (int element_index = 0; element_index < basis_size * basis_size;
1352 ++element_index) {
1353 eigenvector_matrix[element_index] =
1354 Complex(hamiltonian_real[element_index], 0.0);
1355 }
1356 } else {
1357 int work_size = 2 * basis_size + basis_size * basis_size;
1358 std::vector<Complex> work(work_size);
1359 std::vector<Real> real_work(std::max(1, 3 * basis_size - 2));
1360 std::complex<double>* cast_work =
1361 reinterpret_cast<std::complex<double>*>(work.data());
1362 q3::lapack::zhegv_(&diagonalization_type,
1363 "V",
1364 "U",
1365 &basis_size,
1366 cast_h_sub,
1367 &basis_size,
1368 cast_s_sub,
1369 &basis_size,
1370 eigenvalues.data(),
1371 cast_work,
1372 &work_size,
1373 real_work.data(),
1374 &info);
1375 }
1376
1377 if (info != 0) {
1378 log::warn(
1379 "LAPACK eigensolver returned info={} in Davidson collapse (basis_size={})",
1380 info,
1381 basis_size);
1382 }
1383 }
1384 }
1385
1386 // ─── Execution context ──────────────────────────────────────────
1387
1388 const execution::ExecutionPolicy& execution_policy_;
1389 bool gamma_origin_is_local_ = true;
1390 std::unique_ptr<SubspaceDiagonalizer> subspace_diagonalizer_;
1391
1392 // ─── Pre-allocated workspaces ───────────────────────────────────
1393
1394 mutable std::vector<Complex> diagonalize_overlap_copy_workspace_;
1395};
1396
1397} // namespace q3
virtual void apply_batch(int number_of_plane_waves, int number_of_vectors, const Complex *wavefunctions, Complex *overlap_wavefunctions) const
Definition init.hpp:39
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
int diagonalize_subspace(int basis_size, int maximum_basis_size, bool is_gamma_only, const Complex *subspace_hamiltonian, const Complex *subspace_overlap, Real *eigenvalues, Complex *eigenvector_matrix) override
Definition serial.hpp:680
int collapse_subspace(int basis_size, int number_of_plane_waves, int number_of_bands, int maximum_basis_size, bool is_gamma_only, bool is_identity_overlap, const OverlapOperator *overlap_operator, const Complex *subspace_hamiltonian, const Complex *subspace_overlap, Complex *basis_vectors, Complex *hamiltonian_basis_vectors, Complex *overlap_basis_vectors, Complex *rotation_scratch) override
Definition serial.hpp:825
void set_gamma_origin_is_local(bool origin_is_local) override
Definition serial.hpp:37
int build_correction_vectors(int basis_size, int number_of_plane_waves, int number_of_bands, int maximum_candidates, bool is_gamma_only, const Complex *eigenvector_matrix, int eigenvector_leading_dimension, const Real *eigenvalues, const std::vector< bool > &band_converged, const Real *kinetic_energy_squared, const Complex *hamiltonian_basis_vectors, const Complex *overlap_basis_vectors, Complex *correction_columns) override
Definition serial.hpp:995
void multiply_basis_by_coefficients_across_bands(int number_of_plane_waves, int number_of_bands, int basis_size, const std::complex< double > *basis, const std::complex< double > *coefficients, std::complex< double > *output) const
Definition serial.hpp:770
void extract_eigenvectors(int basis_size, int number_of_plane_waves, int number_of_bands, int kpoint_index_state, bool is_gamma_only, const Complex *eigenvector_matrix, const Complex *basis_vectors, const Real *eigenvalues, Complex *rotated_scratch, PlanewaveKohnShamQuantumSystem &quantum_system) override
Definition serial.hpp:947
int orthogonalize_batch(int starting_vector_index, int number_of_candidates, int number_of_plane_waves, bool is_gamma_only, const OverlapOperator *overlap_operator, Complex *basis_vectors, Complex *overlap_basis_vectors) override
Definition serial.hpp:41
void update_subspace_matrices(int start_column, int new_columns, int total_basis_size, int maximum_basis_size, int number_of_plane_waves, bool is_gamma_only, const Complex *basis_vectors, const Complex *hamiltonian_basis_vectors, const Complex *overlap_basis_vectors, Complex *subspace_hamiltonian, Complex *subspace_overlap) override
Definition serial.hpp:555
void apply_preconditioner_to_columns(int number_of_columns, int number_of_plane_waves, const Real *kinetic_energy_squared, const Real *eigenvalues, Complex *columns) const
Definition serial.hpp:1138
SerialPlanewaveSubspaceManager(const execution::ExecutionPolicy &execution_policy)
Definition serial.hpp:33
void apply_preconditioner(int number_of_bands, int number_of_plane_waves, const Real *kinetic_energy_squared, const Real *eigenvalues, const std::vector< bool > &band_converged, Complex *residual_wavefunctions) override
Definition serial.hpp:1158
virtual ComplexValue dot_product(int number_of_elements, const ComplexValue *vector_x, const ComplexValue *vector_y) const =0
virtual int band_distribution_rank_index() const
Definition init.hpp:247
virtual int group_size() const =0
virtual void allgather_across_bands(ComplexValue *, const int *, const int *) const
Definition init.hpp:265
virtual void broadcast_from_group_root(ComplexValue *, int) const
Definition init.hpp:310
virtual int group_rank_index() const =0
virtual void broadcast_across_bands(ComplexValue *, int) const
Definition init.hpp:293
virtual int band_distribution_size() const
Definition init.hpp:240
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 dpotrf_(const char *uplo, const int *n, double *a, const int *lda, int *info)
std::complex< double > q3_complex_conjugate_dot_product(int n, const std::complex< double > *x, const std::complex< double > *y)
Definition blas.hpp:100
void dtrsm_(const char *side, const char *uplo, const char *transa, const char *diag, const int *m, const int *n, const double *alpha, const double *a, const int *lda, double *b, const int *ldb)
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 zpotrf_(const char *uplo, const int *n, std::complex< double > *a, const int *lda, int *info)
void ztrsm_(const char *side, const char *uplo, const char *transa, const char *diag, const int *m, const int *n, const std::complex< double > *alpha, const std::complex< double > *a, const int *lda, std::complex< double > *b, const int *ldb)
void zdscal_(const int *n, const double *alpha, std::complex< double > *x, const int *incx)
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)
BandPartition partition_bands(int number_of_bands, int number_of_ranks, int rank_index, int elements_per_band, int band_granularity=1)
Definition init.hpp:52
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 warn(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:187
void info(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:183
constexpr double iterative_solver_norm_threshold
constexpr double preconditioner_denominator_floor
double Real
std::complex< double > Complex
std::vector< int > counts
Definition init.hpp:46
std::vector< int > displacements
Definition init.hpp:47