Quantum 3xplorer (Q3)
An AI-native quantum simulation engine
Loading...
Searching...
No Matches
davidson.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * PlanewaveDavidsonDiagonalizer — planewave Davidson iterative diagonalizer.
5 *
6 * Implements the block Davidson algorithm for finding the lowest eigenvalues
7 * and eigenvectors of the Kohn-Sham Hamiltonian. The subspace is iteratively
8 * expanded with preconditioned residuals, and collapsed back to the target
9 * number of bands when it reaches the maximum allowed size (4×number_of_bands).
10 *
11 * Implements the iterative method of E. R. Davidson, J. Comput. Phys. 17, 87
12 * (1975), in block form: the subspace is expanded for all bands together.
13 *
14 * The Hamiltonian application H|ψ⟩ is delegated to a
15 * PlanewaveHamiltonianApplicator (serial or GPU).
16 *
17 * Subspace linear algebra (orthogonalization, subspace matrix construction,
18 * diagonalization, Ritz vectors, collapse) is delegated to a
19 * PlanewaveSubspaceManager (serial or GPU).
20 *
21 * This class retains only the iteration loop control, basis seeding,
22 * and preconditioner application.
23 */
24#include <algorithm>
25#include <cassert>
26#include <chrono>
27#include <cmath>
28#include <complex>
29#include <cstring>
30#include <map>
31#include <memory>
32#include <string>
33#include <vector>
34
35#include "io/logger.hpp"
52
53namespace q3 {
54
55/**
56 * Abstract workspace for Davidson diagonalizer.
57 *
58 * Holds state resident in the appropriate memory space (CPU or GPU)
59 * to avoid memory copies during iterative steps.
60 */
62 public:
63 virtual ~PlanewaveDavidsonWorkspace() = default;
64
65 /**
66 * Size the workspace for this k-point.
67 *
68 * overlap_is_identity lets a norm-conserving run skip the S arrays
69 * entirely; the accessors then return the basis and Ritz buffers, which
70 * hold exactly the same values.
71 */
72 virtual void resize(int maximum_basis_size,
73 int number_of_plane_waves,
74 int number_of_bands,
75 bool overlap_is_identity) = 0;
76
77 virtual Complex* basis_vectors() = 0;
78 virtual const Complex* basis_vectors() const = 0;
79
81 virtual const Complex* hamiltonian_basis_vectors() const = 0;
82
84 virtual const Complex* overlap_basis_vectors() const = 0;
85
87 return local_eigenvalues_host_.data();
88 }
89 const Real* local_eigenvalues() const {
90 return local_eigenvalues_host_.data();
91 }
92
94 virtual const Complex* subspace_hamiltonian() const = 0;
95
96 virtual Complex* subspace_overlap() = 0;
97 virtual const Complex* subspace_overlap() const = 0;
98
99 /**
100 * One band-sized buffer the solver borrows for a rotation that cannot be
101 * done in place -- the subspace collapse and the final extraction. It
102 * replaced the Ritz vectors, H|Ritz>, S|Ritz> and residual arrays, which
103 * the correction-vector build made unnecessary.
104 */
105 virtual Complex* band_scratch() = 0;
106 virtual const Complex* band_scratch() const = 0;
107
108 virtual void sync_to_host() = 0;
109
110 protected:
111 std::vector<Real> local_eigenvalues_host_;
112};
113
115 public:
116 void resize(int maximum_basis_size,
117 int number_of_plane_waves,
118 int number_of_bands,
119 bool overlap_is_identity) override {
120 std::size_t basis_elements = static_cast<std::size_t>(maximum_basis_size) *
121 static_cast<std::size_t>(number_of_plane_waves);
122 std::size_t band_elements = static_cast<std::size_t>(number_of_bands) *
123 static_cast<std::size_t>(number_of_plane_waves);
124 std::size_t current_size =
125 basis_vectors_.empty()
126 ? 0
127 : basis_vectors_.size() / static_cast<std::size_t>(number_of_plane_waves);
128 if (current_size >= static_cast<std::size_t>(maximum_basis_size) &&
129 overlap_is_aliased_ == overlap_is_identity) {
130 return;
131 }
132
133 /**
134 * For norm-conserving runs the overlap operator is the identity, so
135 * S|V> and S|psi> are bit-for-bit the basis and the Ritz vectors. The
136 * two arrays are not allocated at all; the accessors hand back the
137 * originals, and the subspace manager skips the writes that would
138 * otherwise be applied to the same memory twice. That is five arrays
139 * of number_of_bands x number_of_plane_waves not held.
140 */
141 overlap_is_aliased_ = overlap_is_identity;
142
143 basis_vectors_.assign(basis_elements, Complex(0.0, 0.0));
144 hamiltonian_basis_vectors_.assign(basis_elements, Complex(0.0, 0.0));
145 subspace_hamiltonian_.assign(static_cast<std::size_t>(maximum_basis_size) *
146 static_cast<std::size_t>(maximum_basis_size),
147 Complex(0.0, 0.0));
148 subspace_overlap_.assign(static_cast<std::size_t>(maximum_basis_size) *
149 static_cast<std::size_t>(maximum_basis_size),
150 Complex(0.0, 0.0));
151 band_scratch_.assign(band_elements, Complex(0.0, 0.0));
152 if (overlap_is_aliased_) {
153 overlap_basis_vectors_.clear();
154 overlap_basis_vectors_.shrink_to_fit();
155 } else {
156 overlap_basis_vectors_.assign(basis_elements, Complex(0.0, 0.0));
157 }
158
159 local_eigenvalues_host_.assign(maximum_basis_size, 0.0);
160 }
161
162 Complex* basis_vectors() override {
163 return basis_vectors_.data();
164 }
165 const Complex* basis_vectors() const override {
166 return basis_vectors_.data();
167 }
168
170 return hamiltonian_basis_vectors_.data();
171 }
172 const Complex* hamiltonian_basis_vectors() const override {
173 return hamiltonian_basis_vectors_.data();
174 }
175
177 return overlap_is_aliased_ ? basis_vectors_.data() : overlap_basis_vectors_.data();
178 }
179 const Complex* overlap_basis_vectors() const override {
180 return overlap_is_aliased_ ? basis_vectors_.data() : overlap_basis_vectors_.data();
181 }
182
184 return subspace_hamiltonian_.data();
185 }
186 const Complex* subspace_hamiltonian() const override {
187 return subspace_hamiltonian_.data();
188 }
189
191 return subspace_overlap_.data();
192 }
193 const Complex* subspace_overlap() const override {
194 return subspace_overlap_.data();
195 }
196
197 Complex* band_scratch() override {
198 return band_scratch_.data();
199 }
200 const Complex* band_scratch() const override {
201 return band_scratch_.data();
202 }
203
204 void sync_to_host() override {}
205
206 private:
207 std::vector<Complex> basis_vectors_;
208 std::vector<Complex> hamiltonian_basis_vectors_;
209 std::vector<Complex> overlap_basis_vectors_;
210 std::vector<Complex> subspace_hamiltonian_;
211 std::vector<Complex> subspace_overlap_;
212 std::vector<Complex> band_scratch_;
213 /// True when S is the identity and the overlap accessors alias the originals.
214 bool overlap_is_aliased_ = false;
215};
216
218 public:
220 std::shared_ptr<execution::gpu::device::GPUDeviceBackend> device_backend)
221 : device_backend_{std::move(device_backend)}
222 , basis_vectors_(0, device_backend_)
223 , hamiltonian_basis_vectors_(0, device_backend_)
224 , overlap_basis_vectors_(0, device_backend_)
225 , subspace_hamiltonian_(0, device_backend_)
226 , subspace_overlap_(0, device_backend_)
227 , band_scratch_(0, device_backend_)
228 , eigenvalues_device_(0, device_backend_) {}
229
230 void resize(int maximum_basis_size,
231 int number_of_plane_waves,
232 int number_of_bands,
233 bool overlap_is_identity) override {
234 /**
235 * The device path still allocates its S buffers. Aliasing them would
236 * need the same write audit on every GPU kernel, and that wants its own
237 * change with a device to test it on.
238 */
239 (void)overlap_is_identity;
240 int current_size = basis_vectors_.size() / number_of_plane_waves;
241 if (current_size >= maximum_basis_size) {
242 return;
243 }
244
246 maximum_basis_size * number_of_plane_waves, device_backend_);
247 hamiltonian_basis_vectors_ = execution::gpu::device::GPUDeviceBuffer<Complex>(
248 maximum_basis_size * number_of_plane_waves, device_backend_);
250 maximum_basis_size * number_of_plane_waves, device_backend_);
252 maximum_basis_size * maximum_basis_size, device_backend_);
254 maximum_basis_size * maximum_basis_size, device_backend_);
256 number_of_bands * number_of_plane_waves, device_backend_);
257 eigenvalues_device_ =
258 execution::gpu::device::GPUDeviceBuffer<Real>(maximum_basis_size, device_backend_);
259
260 local_eigenvalues_host_.assign(maximum_basis_size, 0.0);
261
262 // Zero out initial memory
263 std::memset(basis_vectors_.data(), 0, basis_vectors_.size() * sizeof(Complex));
264 std::memset(hamiltonian_basis_vectors_.data(),
265 0,
266 hamiltonian_basis_vectors_.size() * sizeof(Complex));
267 std::memset(
268 overlap_basis_vectors_.data(), 0, overlap_basis_vectors_.size() * sizeof(Complex));
269 std::memset(
270 subspace_hamiltonian_.data(), 0, subspace_hamiltonian_.size() * sizeof(Complex));
271 std::memset(subspace_overlap_.data(), 0, subspace_overlap_.size() * sizeof(Complex));
272 std::memset(band_scratch_.data(), 0, band_scratch_.size() * sizeof(Complex));
273 std::memset(eigenvalues_device_.data(), 0, eigenvalues_device_.size() * sizeof(Real));
274 }
275
276 Complex* basis_vectors() override {
277 return basis_vectors_.data();
278 }
279 const Complex* basis_vectors() const override {
280 return basis_vectors_.data();
281 }
282
284 return hamiltonian_basis_vectors_.data();
285 }
286 const Complex* hamiltonian_basis_vectors() const override {
287 return hamiltonian_basis_vectors_.data();
288 }
289
291 return overlap_basis_vectors_.data();
292 }
293 const Complex* overlap_basis_vectors() const override {
294 return overlap_basis_vectors_.data();
295 }
296
298 return subspace_hamiltonian_.data();
299 }
300 const Complex* subspace_hamiltonian() const override {
301 return subspace_hamiltonian_.data();
302 }
303
305 return subspace_overlap_.data();
306 }
307 const Complex* subspace_overlap() const override {
308 return subspace_overlap_.data();
309 }
310
311 Complex* band_scratch() override {
312 return band_scratch_.data();
313 }
314 const Complex* band_scratch() const override {
315 return band_scratch_.data();
316 }
317
319 return eigenvalues_device_.data();
320 }
321 const Real* eigenvalues_device() const {
322 return eigenvalues_device_.data();
323 }
324
326 std::memcpy(eigenvalues_device_.data(),
328 local_eigenvalues_host_.size() * sizeof(Real));
329 }
330
331 void sync_to_host() override {
332 device_backend_->synchronize();
333 }
334
335 private:
336 std::shared_ptr<execution::gpu::device::GPUDeviceBackend> device_backend_;
338 execution::gpu::device::GPUDeviceBuffer<Complex> hamiltonian_basis_vectors_;
344};
345
346/**
347 * Davidson iterative diagonalizer.
348 * Finds only the lowest number_of_bands eigenvalues in O(number_of_plane_waves * number_of_bands)
349 * per iteration, vs O(number_of_plane_waves³) for direct.
350 */
352 public:
353 // Fallback serial execution policy — used as default when no policy is injected.
355
356 /**
357 * Construct the Davidson diagonalizer with an execution policy for band-group
358 * vector operations (dot_product, norm). Defaults to a serial (BLAS-only) policy
359 * if none is provided, preserving compatibility with existing call sites.
360 *
361 * Optionally accepts a PlanewaveHamiltonianApplicator and/or
362 * PlanewaveSubspaceManager for delegation. If not provided, internal serial
363 * implementations are created on first solve() call.
364 */
367 PlanewaveHamiltonianApplicator* hamiltonian_applicator = nullptr,
368 PlanewaveSubspaceManager* subspace_manager = nullptr)
369 : execution_policy_{execution_policy}
370 , injected_hamiltonian_applicator_{hamiltonian_applicator}
371 , injected_subspace_manager_{subspace_manager} {
372 // Create owned subspace manager if none injected.
373 if (injected_subspace_manager_ == nullptr) {
374 owned_subspace_manager_ =
375 std::make_unique<SerialPlanewaveSubspaceManager>(execution_policy);
376 }
377 }
378
379 /**
380 * Construct a GPU-accelerated Davidson diagonalizer.
381 *
382 * Stores GPU context (BLAS, FFT, device backend) for lazy construction of
383 * GPUPlanewaveHamiltonianApplicator and GPUPlanewaveSubspaceManager on
384 * first solve() call, when the Crystal/PlanewaveBasis are available.
385 *
386 * @param execution_policy Execution policy for band-group operations.
387 * @param gpu_blas GPU BLAS wrapper. Must outlive this object.
388 * @param gpu_fft_grid GPU FFT grid. Must outlive this object.
389 * @param gpu_eigensolver GPU eigensolver. Must outlive this object.
390 * @param device_backend GPU device backend (shared ownership).
391 */
393 const execution::ExecutionPolicy& execution_policy,
395 gpu::GPUFFTGrid* gpu_fft_grid,
397 std::shared_ptr<execution::gpu::device::GPUDeviceBackend> device_backend)
398 : execution_policy_{execution_policy}
399 , gpu_blas_{&gpu_blas}
400 , gpu_fft_grid_{gpu_fft_grid}
401 , gpu_eigensolver_{&gpu_eigensolver}
402 , gpu_device_backend_{std::move(device_backend)} {}
403
404 /**
405 * Enable mixed-precision mode for GPU Davidson.
406 *
407 * When enabled, the local potential FFT section of H|ψ⟩ runs in FP32
408 * while kinetic energy and nonlocal projectors remain FP64.
409 */
410 void set_use_mixed_precision(bool use_mixed_precision) {
411 use_mixed_precision_ = use_mixed_precision;
412 }
413
414 /**
415 * Set pre-computed KB projectors for a specific k-point.
416 * No-op if the applicator has not been created yet (before first solve()).
417 */
418 void set_basis_expansion_factor(int factor) override {
419 if (factor >= 2) {
420 davidson_basis_expansion_factor = factor;
421 }
422 }
423
424 /**
425 * Drop the working basis, its Hamiltonian image and the subspace: the
426 * largest thing the solver owns, and dead once the SCF has converged.
427 */
428 void set_maximum_iterations(int maximum_iterations) override {
429 max_iterations_ = std::max(1, maximum_iterations);
430 }
431
432 [[nodiscard]] int number_of_unconverged_bands() const override {
433 return number_of_unconverged_bands_;
434 }
435
436 void release_workspace() override {
437 workspace_.reset();
438 }
439
440 void set_cached_projectors(int kpoint_index, const q3::Matrix<Complex>* projectors) override {
441 if (has_hamiltonian_applicator()) {
442 get_hamiltonian_applicator().set_cached_projectors(kpoint_index, projectors);
443 }
444 pending_cached_projectors_[kpoint_index] = projectors;
445 }
446
447 /**
448 * Set pre-computed DFT+U (Hubbard) atomic-orbital projectors for a specific k-point.
449 * No-op if the applicator has not been created yet (before first solve()).
450 */
451 void set_cached_hubbard_projectors(int kpoint_index,
452 const q3::Matrix<Complex>* projectors) override {
453 if (has_hamiltonian_applicator()) {
454 get_hamiltonian_applicator().set_cached_hubbard_projectors(kpoint_index, projectors);
455 }
456 pending_cached_hubbard_projectors_[kpoint_index] = projectors;
457 }
458
459 /**
460 * Set the dense block-diagonal Hubbard D-matrix (refreshed once per SCF iteration).
461 * No-op if the applicator has not been created yet (before first solve()).
462 */
463 void set_hubbard_d_matrix(const std::vector<Complex>* dense_d_matrix_column_major) override {
464 if (has_hamiltonian_applicator()) {
465 get_hamiltonian_applicator().set_hubbard_d_matrix(dense_d_matrix_column_major);
466 }
467 pending_hubbard_d_matrix_ = dense_d_matrix_column_major;
468 }
469
470 /**
471 * Invalidate the cached smooth grid potential.
472 * No-op if the applicator has not been created yet (nothing cached).
473 */
475 if (has_hamiltonian_applicator()) {
476 get_hamiltonian_applicator().invalidate_smooth_potential_cache();
477 }
478 }
479
480 void solve(int kpoint_index_state,
481 int kpoint_index,
482 int number_of_plane_waves,
483 int number_of_bands,
484 const std::vector<int>& g_vector_indices,
485 const std::vector<Real>& kinetic_energy_squared,
486 const std::vector<Real>& real_space_potential,
487 const Crystal& crystal_structure,
488 const KPointsGrid& kpoint_grid,
489 const PlanewaveBasis& planewave_basis,
490 const NormConservingPlanewaveKohnShamQuantumSystem& pseudopotential_quantum_system,
491 PlanewaveKohnShamQuantumSystem& quantum_system) override {
492 // Ensure the applicator exists (lazy construction if not injected).
493 ensure_hamiltonian_applicator_initialized(
494 crystal_structure, planewave_basis, kpoint_grid, pseudopotential_quantum_system);
495
496 auto& applicator = get_hamiltonian_applicator();
497 auto& subspace = get_subspace_manager();
498
499 /**
500 * Which spin channel this call belongs to.
501 *
502 * The caller flattens (spin, k-point) into `kpoint_index_state`, so the
503 * channel is the quotient. The applicator needs it because the nonlocal
504 * D is per channel for ultrasoft and PAW; `real_space_potential` below
505 * is already this channel's, and the two have to agree.
506 */
507 int spin_channel = kpoint_grid.number_of_kpoints() > 0
508 ? kpoint_index_state / kpoint_grid.number_of_kpoints()
509 : 0;
510 applicator.set_spin_channel(spin_channel);
511
512 // Update the smooth potential if it has been invalidated.
513 if (!applicator.is_smooth_potential_cache_valid()) {
514 applicator.update_potential(real_space_potential);
515 }
516
517 // Configure the applicator for this k-point.
518 applicator.prepare_kpoint(
519 kpoint_index, number_of_plane_waves, g_vector_indices, kinetic_energy_squared);
520
521 // Cache gamma-only flag for the duration of this solve() call.
522 bool is_gamma_only = applicator.is_gamma_only_current();
523 /**
524 * Under a reciprocal-space distribution one rank holds G = 0, and the
525 * manager's gamma corrections must know whether this is that rank.
526 */
527 subspace.set_gamma_origin_is_local(applicator.gamma_origin_is_local_current());
528
529 // The overlap operator selects the standard (NC, S=I) or generalized (US) eigenproblem.
530 Matrix<Complex> computed_projectors;
531 const Matrix<Complex>* kleinman_bylander_potential_ptr = nullptr;
532 if (applicator.get_cached_projectors(kpoint_index) != nullptr) {
533 kleinman_bylander_potential_ptr = applicator.get_cached_projectors(kpoint_index);
534 } else {
535 Vector3D kpoint_coordinates = kpoint_grid[kpoint_index].coordinates;
537 kpoint_coordinates,
538 crystal_structure,
539 planewave_basis,
540 pseudopotential_quantum_system.pseudopotential_basis().pseudopotential_set(),
541 pseudopotential_quantum_system.pseudopotential_basis().beta_function_table(),
542 g_vector_indices,
543 number_of_plane_waves,
544 pseudopotential_quantum_system.pseudopotential_basis().total_number_of_projectors(),
545 computed_projectors);
546 kleinman_bylander_potential_ptr = &computed_projectors;
547 }
548 const auto& kleinman_bylander_potential = *kleinman_bylander_potential_ptr;
549
550 auto overlap_operator = create_overlap_operator(crystal_structure,
551 pseudopotential_quantum_system,
552 kleinman_bylander_potential,
553 execution_policy_);
554 bool is_identity_overlap = overlap_operator->is_identity();
555
556 int global_number_of_plane_waves =
557 execution_policy_.global_number_of_elements(number_of_plane_waves);
558 int maximum_basis_size =
559 std::max(std::min(davidson_basis_expansion_factor * number_of_bands,
560 global_number_of_plane_waves),
561 number_of_bands);
562
563 // Ensure workspace is initialized and sized.
564 if (workspace_ == nullptr) {
565 if (gpu_device_backend_ != nullptr) {
566 workspace_ = std::make_unique<GPUDavidsonWorkspace>(gpu_device_backend_);
567 } else {
568 workspace_ = std::make_unique<SerialDavidsonWorkspace>();
569 }
570 }
571 workspace_->resize(
572 maximum_basis_size, number_of_plane_waves, number_of_bands, is_identity_overlap);
573 log_davidson_memory("after workspace resize", maximum_basis_size, number_of_plane_waves);
574
575 // Phase 1: Seed basis from previous wavefunctions
576 auto timer_seed_start = std::chrono::high_resolution_clock::now();
577 int basis_size = seed_basis_from_wavefunctions(number_of_plane_waves,
578 number_of_bands,
579 quantum_system,
580 kpoint_index_state,
581 is_gamma_only,
582 overlap_operator.get(),
583 applicator,
584 subspace,
585 workspace_->basis_vectors(),
586 workspace_->hamiltonian_basis_vectors(),
587 workspace_->overlap_basis_vectors());
588 auto timer_seed_end = std::chrono::high_resolution_clock::now();
590 std::chrono::duration<double, std::milli>(timer_seed_end - timer_seed_start).count();
591
592 // Phase 2: Fill remaining basis slots with kinetic-energy-sorted plane waves
593 if (basis_size < number_of_bands) {
594 basis_size = seed_basis_from_kinetic_energy(basis_size,
595 number_of_plane_waves,
596 number_of_bands,
597 kinetic_energy_squared,
598 is_gamma_only,
599 overlap_operator.get(),
600 applicator,
601 subspace,
602 workspace_->basis_vectors(),
603 workspace_->hamiltonian_basis_vectors(),
604 workspace_->overlap_basis_vectors());
605 }
606
607 execution_policy_.broadcast_across_bands(&basis_size, 1);
608 synchronize_basis_across_bands(0,
609 basis_size,
610 number_of_plane_waves,
611 workspace_->basis_vectors(),
612 workspace_->hamiltonian_basis_vectors(),
613 workspace_->overlap_basis_vectors());
614
615 // Build initial subspace matrices for the seeded basis.
616 subspace.update_subspace_matrices(0,
617 basis_size,
618 basis_size,
619 maximum_basis_size,
620 number_of_plane_waves,
621 is_gamma_only,
622 workspace_->basis_vectors(),
623 workspace_->hamiltonian_basis_vectors(),
624 workspace_->overlap_basis_vectors(),
625 workspace_->subspace_hamiltonian(),
626 workspace_->subspace_overlap());
627
628 // Hybrid lrot approach: use stored eigenvalues when available
629 std::vector<Real> previous_eigenvalues(number_of_bands, 0.0);
630 bool has_stored_eigenvalues = false;
631 for (int band_index = 0; band_index < number_of_bands; band_index++) {
632 Real stored = quantum_system.eigenvalue(kpoint_index_state, band_index);
633 if (stored != 0.0) {
634 has_stored_eigenvalues = true;
635 break;
636 }
637 }
638 if (has_stored_eigenvalues) {
639 for (int band_index = 0; band_index < number_of_bands; band_index++) {
640 previous_eigenvalues[band_index] =
641 quantum_system.eigenvalue(kpoint_index_state, band_index);
642 }
643 } else {
644 for (int band_index = 0; band_index < number_of_bands; band_index++) {
645 previous_eigenvalues[band_index] =
646 workspace_->subspace_hamiltonian()[band_index * maximum_basis_size + band_index]
647 .real();
648 }
649 }
650
651 // Phase 3: Davidson iteration loop
652 number_of_unconverged_bands_ = number_of_bands;
653 for (int iteration = 0; iteration < max_iterations_; iteration++) {
655 auto timer_diag_start = std::chrono::high_resolution_clock::now();
656 std::vector<Complex> eigenvector_matrix(maximum_basis_size * maximum_basis_size);
657 int diagonalization_info =
658 subspace.diagonalize_subspace(basis_size,
659 maximum_basis_size,
660 is_gamma_only,
661 workspace_->subspace_hamiltonian(),
662 workspace_->subspace_overlap(),
663 workspace_->local_eigenvalues(),
664 eigenvector_matrix.data());
665 execution_policy_.broadcast_across_bands(&diagonalization_info, 1);
666 synchronize_eigenpairs_across_bands(basis_size,
667 maximum_basis_size,
668 workspace_->local_eigenvalues(),
669 eigenvector_matrix.data());
670 auto timer_diag_end = std::chrono::high_resolution_clock::now();
672 std::chrono::duration<double, std::milli>(timer_diag_end - timer_diag_start)
673 .count();
674
675 // Recovery: if zhegv failed, force collapse and retry
676 if (diagonalization_info != 0 && basis_size > number_of_bands) {
677 log::warn(" Davidson: zhegv failed (info={}), forcing subspace collapse",
678 diagonalization_info);
679 auto timer_collapse_start = std::chrono::high_resolution_clock::now();
680 basis_size = subspace.collapse_subspace(basis_size,
681 number_of_plane_waves,
682 number_of_bands,
683 maximum_basis_size,
684 is_gamma_only,
685 is_identity_overlap,
686 overlap_operator.get(),
687 /*subspace_hamiltonian=*/nullptr,
688 /*subspace_overlap=*/nullptr,
689 workspace_->basis_vectors(),
690 workspace_->hamiltonian_basis_vectors(),
691 workspace_->overlap_basis_vectors(),
692 workspace_->band_scratch());
693 /**
694 * Only the size needs agreeing here. The collapse itself now
695 * forms each band range on one rank and allgathers, so the
696 * collapsed basis is already identical everywhere; republishing
697 * it would move three full arrays for nothing.
698 */
699 execution_policy_.broadcast_across_bands(&basis_size, 1);
700 std::memset(workspace_->subspace_hamiltonian(),
701 0,
702 maximum_basis_size * maximum_basis_size * sizeof(Complex));
703 std::memset(workspace_->subspace_overlap(),
704 0,
705 maximum_basis_size * maximum_basis_size * sizeof(Complex));
706 subspace.update_subspace_matrices(0,
707 basis_size,
708 basis_size,
709 maximum_basis_size,
710 number_of_plane_waves,
711 is_gamma_only,
712 workspace_->basis_vectors(),
713 workspace_->hamiltonian_basis_vectors(),
714 workspace_->overlap_basis_vectors(),
715 workspace_->subspace_hamiltonian(),
716 workspace_->subspace_overlap());
717 auto timer_collapse_end = std::chrono::high_resolution_clock::now();
719 std::chrono::duration<double, std::milli>(timer_collapse_end -
720 timer_collapse_start)
721 .count();
722 log_davidson_memory("after collapse", maximum_basis_size, number_of_plane_waves);
723 diagonalization_info =
724 subspace.diagonalize_subspace(basis_size,
725 maximum_basis_size,
726 is_gamma_only,
727 workspace_->subspace_hamiltonian(),
728 workspace_->subspace_overlap(),
729 workspace_->local_eigenvalues(),
730 eigenvector_matrix.data());
731 execution_policy_.broadcast_across_bands(&diagonalization_info, 1);
732 synchronize_eigenpairs_across_bands(basis_size,
733 maximum_basis_size,
734 workspace_->local_eigenvalues(),
735 eigenvector_matrix.data());
736 if (diagonalization_info != 0) {
737 log::warn(" Davidson: zhegv still failed after collapse (info={}), "
738 "using previous eigenvalues",
739 diagonalization_info);
740 for (int band_index = 0; band_index < number_of_bands; band_index++) {
741 workspace_->local_eigenvalues()[band_index] =
742 previous_eigenvalues[band_index];
743 }
744 }
745 }
746
747 bool converged = true;
748 int new_vectors = 0;
749
750 /**
751 * No Ritz vectors are formed. Convergence is decided on the
752 * eigenvalues, and the correction vectors below are built straight
753 * from the subspace coefficients into the free basis columns.
754 */
755 auto timer_ritz_start = std::chrono::high_resolution_clock::now();
756 std::vector<bool> band_converged(number_of_bands, true);
757 auto timer_ritz_end = std::chrono::high_resolution_clock::now();
759 std::chrono::duration<double, std::milli>(timer_ritz_end - timer_ritz_start)
760 .count();
761
762 auto timer_residual_start = std::chrono::high_resolution_clock::now();
763 bool skip_convergence_check = (iteration == 0);
764
765 int number_of_occupied_bands = quantum_system.number_of_occupied_bands_per_spin();
766 Real empty_band_tolerance =
769
770 for (int band_index = 0; band_index < number_of_bands; band_index++) {
771 Real eigenvalue_change = std::abs(workspace_->local_eigenvalues()[band_index] -
772 previous_eigenvalues[band_index]);
773 Real band_tolerance =
774 (band_index < number_of_occupied_bands) ? tolerance_ : empty_band_tolerance;
775 bool this_band_converged =
776 (!skip_convergence_check && eigenvalue_change < band_tolerance);
777
778 if (this_band_converged) {
779 continue;
780 }
781
782 converged = false;
783 band_converged[band_index] = false;
784 }
785
786 int number_unconverged = 0;
787 Real max_eigenvalue_change = 0.0;
788 for (int band_index = 0; band_index < number_of_bands; band_index++) {
789 if (!band_converged[band_index])
790 number_unconverged++;
791 Real change = std::abs(workspace_->local_eigenvalues()[band_index] -
792 previous_eigenvalues[band_index]);
793 max_eigenvalue_change = std::max(max_eigenvalue_change, change);
794 }
795 number_of_unconverged_bands_ = number_unconverged;
796 log::debug(" Davidson iter {}: basis={} unconv={}/{} tol={:.2e} max_de={:.4e}",
797 iteration,
798 basis_size,
799 number_unconverged,
800 number_of_bands,
801 tolerance_,
802 max_eigenvalue_change);
803
804 if (converged) {
805 auto timer_residual_end = std::chrono::high_resolution_clock::now();
807 std::chrono::duration<double, std::milli>(timer_residual_end -
808 timer_residual_start)
809 .count();
810 subspace.extract_eigenvectors(basis_size,
811 number_of_plane_waves,
812 number_of_bands,
813 kpoint_index_state,
814 is_gamma_only,
815 eigenvector_matrix.data(),
816 workspace_->basis_vectors(),
817 workspace_->local_eigenvalues(),
818 workspace_->band_scratch(),
819 quantum_system);
820 return;
821 }
822
823 /**
824 * Expand subspace with preconditioned residuals if not converged
825 * 1. Compute residuals and apply preconditioner.
826 */
827 if (auto* gpu_workspace = dynamic_cast<GPUDavidsonWorkspace*>(workspace_.get())) {
828 gpu_workspace->sync_to_device();
829 }
830
831 auto timer_residual_end = std::chrono::high_resolution_clock::now();
833 std::chrono::duration<double, std::milli>(timer_residual_end - timer_residual_start)
834 .count();
835
836 auto timer_ortho_start = std::chrono::high_resolution_clock::now();
837 int start_new_vectors = new_vectors;
838 workspace_->sync_to_host();
839 /**
840 * Build the preconditioned corrections directly into the columns
841 * the basis is about to grow into, compacted over the unconverged
842 * bands. Nothing is staged through a residual array on the way.
843 */
844 int number_of_candidates = subspace.build_correction_vectors(
845 basis_size,
846 number_of_plane_waves,
847 number_of_bands,
848 maximum_basis_size - (basis_size + new_vectors),
849 is_gamma_only,
850 eigenvector_matrix.data(),
851 basis_size,
852 workspace_->local_eigenvalues(),
853 band_converged,
854 kinetic_energy_squared.data(),
855 workspace_->hamiltonian_basis_vectors(),
856 workspace_->overlap_basis_vectors(),
857 workspace_->basis_vectors() +
858 static_cast<std::size_t>(basis_size + new_vectors) * number_of_plane_waves);
859
860 int accepted_candidates =
861 subspace.orthogonalize_batch(basis_size + new_vectors,
862 number_of_candidates,
863 number_of_plane_waves,
864 is_gamma_only,
865 overlap_operator.get(),
866 workspace_->basis_vectors(),
867 workspace_->overlap_basis_vectors());
868
869 execution_policy_.broadcast_across_bands(&accepted_candidates, 1);
870 synchronize_basis_across_bands(basis_size + new_vectors,
871 accepted_candidates,
872 number_of_plane_waves,
873 workspace_->basis_vectors(),
874 nullptr,
875 workspace_->overlap_basis_vectors());
876 new_vectors += accepted_candidates;
877 int added_vectors = new_vectors - start_new_vectors;
878 auto timer_ortho_end = std::chrono::high_resolution_clock::now();
880 std::chrono::duration<double, std::milli>(timer_ortho_end - timer_ortho_start)
881 .count();
882
883 if (added_vectors > 0) {
884 auto timer_hpsi_start = std::chrono::high_resolution_clock::now();
885 applicator.apply_batch(
886 workspace_->basis_vectors() + basis_size * number_of_plane_waves,
887 workspace_->hamiltonian_basis_vectors() + basis_size * number_of_plane_waves,
888 added_vectors);
889 auto timer_hpsi_end = std::chrono::high_resolution_clock::now();
891 std::chrono::duration<double, std::milli>(timer_hpsi_end - timer_hpsi_start)
892 .count();
893 }
894
895 int old_basis_size = basis_size;
896 basis_size += new_vectors;
897
898 if (new_vectors > 0) {
899 auto timer_update_start = std::chrono::high_resolution_clock::now();
900 subspace.update_subspace_matrices(old_basis_size,
901 new_vectors,
902 basis_size,
903 maximum_basis_size,
904 number_of_plane_waves,
905 is_gamma_only,
906 workspace_->basis_vectors(),
907 workspace_->hamiltonian_basis_vectors(),
908 workspace_->overlap_basis_vectors(),
909 workspace_->subspace_hamiltonian(),
910 workspace_->subspace_overlap());
911 auto timer_update_end = std::chrono::high_resolution_clock::now();
913 std::chrono::duration<double, std::milli>(timer_update_end - timer_update_start)
914 .count();
915 }
916
917 // Subspace collapse when basis is full
918 if (basis_size >= maximum_basis_size) {
919 auto timer_collapse_start = std::chrono::high_resolution_clock::now();
920 basis_size = subspace.collapse_subspace(basis_size,
921 number_of_plane_waves,
922 number_of_bands,
923 maximum_basis_size,
924 is_gamma_only,
925 is_identity_overlap,
926 overlap_operator.get(),
927 workspace_->subspace_hamiltonian(),
928 workspace_->subspace_overlap(),
929 workspace_->basis_vectors(),
930 workspace_->hamiltonian_basis_vectors(),
931 workspace_->overlap_basis_vectors(),
932 workspace_->band_scratch());
933 /**
934 * Only the size needs agreeing here. The collapse itself now
935 * forms each band range on one rank and allgathers, so the
936 * collapsed basis is already identical everywhere; republishing
937 * it would move three full arrays for nothing.
938 */
939 execution_policy_.broadcast_across_bands(&basis_size, 1);
940 std::memset(workspace_->subspace_hamiltonian(),
941 0,
942 maximum_basis_size * maximum_basis_size * sizeof(Complex));
943 std::memset(workspace_->subspace_overlap(),
944 0,
945 maximum_basis_size * maximum_basis_size * sizeof(Complex));
946 subspace.update_subspace_matrices(0,
947 basis_size,
948 basis_size,
949 maximum_basis_size,
950 number_of_plane_waves,
951 is_gamma_only,
952 workspace_->basis_vectors(),
953 workspace_->hamiltonian_basis_vectors(),
954 workspace_->overlap_basis_vectors(),
955 workspace_->subspace_hamiltonian(),
956 workspace_->subspace_overlap());
957 auto timer_collapse_end = std::chrono::high_resolution_clock::now();
959 std::chrono::duration<double, std::milli>(timer_collapse_end -
960 timer_collapse_start)
961 .count();
962 log_davidson_memory("after collapse", maximum_basis_size, number_of_plane_waves);
963 }
964
965 for (int band_index = 0; band_index < number_of_bands; band_index++) {
966 previous_eigenvalues[band_index] = workspace_->local_eigenvalues()[band_index];
967 }
968 log_davidson_memory("at end of iteration", basis_size, number_of_plane_waves);
969 }
970
971 // Final extraction after max iterations exhausted
972 std::vector<Complex> eigenvector_matrix(maximum_basis_size * maximum_basis_size);
973 subspace.diagonalize_subspace(basis_size,
974 maximum_basis_size,
975 is_gamma_only,
976 workspace_->subspace_hamiltonian(),
977 workspace_->subspace_overlap(),
978 workspace_->local_eigenvalues(),
979 eigenvector_matrix.data());
980 synchronize_eigenpairs_across_bands(basis_size,
981 maximum_basis_size,
982 workspace_->local_eigenvalues(),
983 eigenvector_matrix.data());
984 subspace.extract_eigenvectors(basis_size,
985 number_of_plane_waves,
986 number_of_bands,
987 kpoint_index_state,
988 is_gamma_only,
989 eigenvector_matrix.data(),
990 workspace_->basis_vectors(),
991 workspace_->local_eigenvalues(),
992 workspace_->band_scratch(),
993 quantum_system);
994 }
995
996 [[nodiscard]] std::string name() const override {
997 return "davidson";
998 }
999
1000 void set_tolerance(Real tolerance) override {
1001 tolerance_ = tolerance;
1002 }
1003
1004 private:
1005 /**
1006 * Working basis size as a multiple of the band count, from
1007 * method.scf.davidson.basis_expansion_factor.
1008 *
1009 * Two arrays of this many vectors dominate the solver's memory. The
1010 * default is 2; 4 restarts less often at twice
1011 * the footprint, and was the default until the collapse stopped
1012 * rebuilding its subspace from the vectors, which was most of what a
1013 * restart cost. Measured on the 54-atom silicon cell at four ranks, 2
1014 * takes 14% more wall time than 4 for 12% less memory; on the 432-atom
1015 * deck the same switch removes 1.0 GB of replicated subspace per rank.
1016 */
1017 int davidson_basis_expansion_factor = 2;
1018
1019 /**
1020 * Record the resident set at a point inside the solver, at debug level.
1021 *
1022 * The solver allocates in two steps that are far apart in time: the
1023 * workspace on the first solve, and the collapse scratch at the first
1024 * restart, several Davidson iterations later. A run sampled before the
1025 * first restart understates its own footprint, so both are probed.
1026 */
1027 void log_davidson_memory(const char* label,
1028 int maximum_basis_size,
1029 int number_of_plane_waves) const {
1031 if (!sample.available) {
1032 return;
1033 }
1034 log::debug(" Davidson memory {}: basis={} npw={} rss={} peak={}",
1035 label,
1036 maximum_basis_size,
1037 number_of_plane_waves,
1040 }
1041
1042 // ─── Applicator management ────────────────────────────────────
1043
1044 bool has_hamiltonian_applicator() const {
1045 return injected_hamiltonian_applicator_ != nullptr || owned_hamiltonian_applicator_;
1046 }
1047
1048 void ensure_hamiltonian_applicator_initialized(
1049 const Crystal& crystal_structure,
1050 const PlanewaveBasis& planewave_basis,
1051 const KPointsGrid& kpoint_grid,
1052 const NormConservingPlanewaveKohnShamQuantumSystem& pseudopotential_quantum_system) {
1053 if (injected_hamiltonian_applicator_ == nullptr && !owned_hamiltonian_applicator_) {
1054 if (gpu_blas_ != nullptr) {
1055 // GPU path: create GPU applicator and subspace manager.
1056 owned_hamiltonian_applicator_ = std::make_unique<GPUPlanewaveHamiltonianApplicator>(
1057 crystal_structure,
1058 planewave_basis,
1059 kpoint_grid,
1060 pseudopotential_quantum_system,
1061 *gpu_blas_,
1062 gpu_fft_grid_,
1063 gpu_device_backend_);
1064 // Enable mixed-precision if requested.
1065 if (use_mixed_precision_) {
1066 auto* gpu_applicator = static_cast<GPUPlanewaveHamiltonianApplicator*>(
1067 owned_hamiltonian_applicator_.get());
1068 gpu_applicator->set_use_mixed_precision(true);
1069 }
1070 owned_subspace_manager_ = std::make_unique<GPUPlanewaveSubspaceManager>(
1071 *gpu_blas_, execution_policy_, gpu_device_backend_);
1072 log::info(" GPU Davidson: using GPU applicator and subspace manager");
1073 } else {
1074 // Serial path: create serial applicator.
1075 owned_hamiltonian_applicator_ =
1076 std::make_unique<SerialPlanewaveHamiltonianApplicator>(
1077 crystal_structure,
1078 planewave_basis,
1079 kpoint_grid,
1080 pseudopotential_quantum_system,
1081 execution_policy_);
1082 }
1083 for (auto& [kpoint_index, projectors] : pending_cached_projectors_) {
1084 owned_hamiltonian_applicator_->set_cached_projectors(kpoint_index, projectors);
1085 }
1086 pending_cached_projectors_.clear();
1087 for (auto& [kpoint_index, projectors] : pending_cached_hubbard_projectors_) {
1088 owned_hamiltonian_applicator_->set_cached_hubbard_projectors(kpoint_index,
1089 projectors);
1090 }
1091 pending_cached_hubbard_projectors_.clear();
1092 if (pending_hubbard_d_matrix_ != nullptr) {
1093 owned_hamiltonian_applicator_->set_hubbard_d_matrix(pending_hubbard_d_matrix_);
1094 pending_hubbard_d_matrix_ = nullptr;
1095 }
1096 }
1097 }
1098
1099 PlanewaveHamiltonianApplicator& get_hamiltonian_applicator() {
1100 if (injected_hamiltonian_applicator_ != nullptr) {
1101 return *injected_hamiltonian_applicator_;
1102 }
1103 return *owned_hamiltonian_applicator_;
1104 }
1105 PlanewaveSubspaceManager& get_subspace_manager() {
1106 if (injected_subspace_manager_ != nullptr) {
1107 return *injected_subspace_manager_;
1108 }
1109 return *owned_subspace_manager_;
1110 }
1111
1112 // ─── Basis initialization helpers ────────────────────────────────
1113
1114 int seed_basis_from_wavefunctions(int number_of_plane_waves,
1115 int number_of_bands,
1116 const PlanewaveKohnShamQuantumSystem& quantum_system,
1117 int kpoint_index_state,
1118 bool is_gamma_only,
1119 OverlapOperator* overlap_operator,
1120 PlanewaveHamiltonianApplicator& applicator,
1121 PlanewaveSubspaceManager& subspace,
1122 Complex* basis_vectors,
1123 Complex* hamiltonian_basis_vectors,
1124 Complex* overlap_basis_vectors) const {
1125 int basis_size = 0;
1126
1127 int number_of_pw_for_state =
1128 quantum_system.number_of_plane_waves_for_kpoint_state(kpoint_index_state);
1129 if (number_of_pw_for_state == number_of_plane_waves) {
1130 int number_of_candidates = 0;
1131 for (int band_index = 0; band_index < number_of_bands; band_index++) {
1132 const Complex* band_data =
1133 quantum_system.wavefunction_band_data(kpoint_index_state, band_index);
1134 for (int g_vector_index = 0; g_vector_index < number_of_plane_waves;
1135 g_vector_index++) {
1136 basis_vectors[(basis_size + number_of_candidates) * number_of_plane_waves +
1137 g_vector_index] = band_data[g_vector_index];
1138 }
1139 number_of_candidates++;
1140 }
1141
1142 int accepted = subspace.orthogonalize_batch(basis_size,
1143 number_of_candidates,
1144 number_of_plane_waves,
1145 is_gamma_only,
1146 overlap_operator,
1147 basis_vectors,
1148 overlap_basis_vectors);
1149 execution_policy_.broadcast_across_bands(&accepted, 1);
1150 synchronize_basis_across_bands(basis_size,
1151 accepted,
1152 number_of_plane_waves,
1153 basis_vectors,
1154 nullptr,
1155 overlap_basis_vectors);
1156 basis_size += accepted;
1157
1158 if (basis_size > 0) {
1159 applicator.apply_batch(basis_vectors, hamiltonian_basis_vectors, basis_size);
1160 }
1161 }
1162
1163 return basis_size;
1164 }
1165
1166 int seed_basis_from_kinetic_energy(int basis_size,
1167 int number_of_plane_waves,
1168 int number_of_bands,
1169 const std::vector<Real>& kinetic_energy_squared,
1170 bool is_gamma_only,
1171 OverlapOperator* overlap_operator,
1172 PlanewaveHamiltonianApplicator& applicator,
1173 PlanewaveSubspaceManager& subspace,
1174 Complex* basis_vectors,
1175 Complex* hamiltonian_basis_vectors,
1176 Complex* overlap_basis_vectors) const {
1177 std::vector<std::pair<Real, int>> kinetic_energy_with_index;
1178 for (int g_vector_index = 0; g_vector_index < number_of_plane_waves; g_vector_index++) {
1179 kinetic_energy_with_index.push_back(
1180 {kinetic_energy_squared[g_vector_index], g_vector_index});
1181 }
1182 std::sort(kinetic_energy_with_index.begin(), kinetic_energy_with_index.end());
1183
1184 int start_index = basis_size;
1185 int number_of_candidates = 0;
1186 for (int band_index = start_index; band_index < number_of_bands; band_index++) {
1187 std::fill(
1188 &basis_vectors[(basis_size + number_of_candidates) * number_of_plane_waves],
1189 &basis_vectors[(basis_size + number_of_candidates + 1) * number_of_plane_waves],
1190 Complex(0.0, 0.0));
1191 basis_vectors[(basis_size + number_of_candidates) * number_of_plane_waves +
1192 kinetic_energy_with_index[band_index].second] = Complex(1.0, 0.0);
1193 number_of_candidates++;
1194 }
1195
1196 int accepted = subspace.orthogonalize_batch(basis_size,
1197 number_of_candidates,
1198 number_of_plane_waves,
1199 is_gamma_only,
1200 overlap_operator,
1201 basis_vectors,
1202 overlap_basis_vectors);
1203 execution_policy_.broadcast_across_bands(&accepted, 1);
1204 synchronize_basis_across_bands(basis_size,
1205 accepted,
1206 number_of_plane_waves,
1207 basis_vectors,
1208 nullptr,
1209 overlap_basis_vectors);
1210 basis_size += accepted;
1211
1212 int new_vectors = basis_size - start_index;
1213 if (new_vectors > 0) {
1214 applicator.apply_batch(&basis_vectors[start_index * number_of_plane_waves],
1215 &hamiltonian_basis_vectors[start_index * number_of_plane_waves],
1216 new_vectors);
1217 }
1218
1219 return basis_size;
1220 }
1221
1222 /**
1223 * ── Band-distribution synchronisation ─────────────────────────────────
1224 *
1225 * With bands distributed, every rank holds the whole Davidson basis and
1226 * runs the subspace algebra redundantly. Redundant is not identical:
1227 * vendor BLAS chooses its summation order from the machine state, so ranks
1228 * drift apart in the last bits, and Davidson turns that into control-flow
1229 * divergence -- orthogonalisation rejects candidates on a threshold, so
1230 * ranks stop agreeing on how many vectors the next batch holds, and the
1231 * allgather that divides H|psi> fails outright.
1232 *
1233 * The fix is the one Quantum ESPRESSO uses across its band groups: one
1234 * rank's result is authoritative, and it is republished at every point the
1235 * basis or a decision changes. Republishing only the newly accepted block
1236 * is not enough -- it was tried -- because those vectors are orthogonal to
1237 * the sender's basis, not to a receiver's slightly different one. The
1238 * basis therefore has to be rank 0's everywhere, at all times, which is
1239 * what the two helpers below and their call sites guarantee.
1240 */
1241 void synchronize_basis_across_bands(int first_vector,
1242 int number_of_vectors,
1243 int number_of_plane_waves,
1244 Complex* basis_vectors,
1245 Complex* hamiltonian_basis_vectors,
1246 Complex* overlap_basis_vectors) const {
1247 if (execution_policy_.band_distribution_size() <= 1 || number_of_vectors <= 0)
1248 return;
1249 std::size_t offset = static_cast<std::size_t>(first_vector) * number_of_plane_waves;
1250 int count = number_of_vectors * number_of_plane_waves;
1251 execution_policy_.broadcast_across_bands(basis_vectors + offset, count);
1252 if (hamiltonian_basis_vectors != nullptr)
1253 execution_policy_.broadcast_across_bands(hamiltonian_basis_vectors + offset, count);
1254 /**
1255 * Skip the overlap broadcast when it aliases the basis: the bytes have
1256 * just been sent, and sending them again would double the traffic of
1257 * the largest collective in the solver for no change in the result.
1258 */
1259 if (overlap_basis_vectors != nullptr && overlap_basis_vectors != basis_vectors)
1260 execution_policy_.broadcast_across_bands(overlap_basis_vectors + offset, count);
1261 }
1262
1263 /// Eigenvalues drive every convergence decision; eigenvectors drive every Ritz vector.
1264 void synchronize_eigenpairs_across_bands(int basis_size,
1265 int maximum_basis_size,
1266 Real* eigenvalues,
1267 Complex* eigenvector_matrix) const {
1268 if (execution_policy_.band_distribution_size() <= 1)
1269 return;
1270 execution_policy_.broadcast_across_bands(eigenvalues, basis_size);
1271 execution_policy_.broadcast_across_bands(eigenvector_matrix,
1272 maximum_basis_size * maximum_basis_size);
1273 }
1274
1275 int max_iterations_ = 20;
1276 /// Bands the last solve() left short of the tolerance when it ran out of iterations.
1277 int number_of_unconverged_bands_ = 0;
1279
1280 // ─── Execution policy ──────────────────────────────────────────────
1281
1282 const execution::ExecutionPolicy& execution_policy_;
1283
1284 // ─── Hamiltonian applicator ────────────────────────────────────────
1285
1286 PlanewaveHamiltonianApplicator* injected_hamiltonian_applicator_ = nullptr;
1287 std::unique_ptr<PlanewaveHamiltonianApplicator> owned_hamiltonian_applicator_;
1288 std::map<int, const q3::Matrix<Complex>*> pending_cached_projectors_;
1289 std::map<int, const q3::Matrix<Complex>*> pending_cached_hubbard_projectors_;
1290 const std::vector<Complex>* pending_hubbard_d_matrix_ = nullptr;
1291
1292 // ─── Subspace manager ──────────────────────────────────────────────
1293
1294 PlanewaveSubspaceManager* injected_subspace_manager_ = nullptr;
1295 std::unique_ptr<PlanewaveSubspaceManager> owned_subspace_manager_;
1296 std::unique_ptr<PlanewaveDavidsonWorkspace> workspace_;
1297
1298 // ─── GPU context (null for serial path) ────────────────────────────
1299
1300 execution::gpu::blas::GPUBLAS* gpu_blas_ = nullptr;
1301 gpu::GPUFFTGrid* gpu_fft_grid_ = nullptr;
1302 execution::gpu::lapack::GPUEigensolver* gpu_eigensolver_ = nullptr;
1303 std::shared_ptr<execution::gpu::device::GPUDeviceBackend> gpu_device_backend_;
1304 bool use_mixed_precision_ = false;
1305};
1306
1307} // namespace q3
Complex * hamiltonian_basis_vectors() override
Definition davidson.hpp:283
const Complex * subspace_hamiltonian() const override
Definition davidson.hpp:300
const Complex * subspace_overlap() const override
Definition davidson.hpp:307
void sync_to_host() override
Definition davidson.hpp:331
Complex * basis_vectors() override
Definition davidson.hpp:276
const Complex * basis_vectors() const override
Definition davidson.hpp:279
const Complex * band_scratch() const override
Definition davidson.hpp:314
Complex * subspace_overlap() override
Definition davidson.hpp:304
GPUDavidsonWorkspace(std::shared_ptr< execution::gpu::device::GPUDeviceBackend > device_backend)
Definition davidson.hpp:219
const Complex * hamiltonian_basis_vectors() const override
Definition davidson.hpp:286
const Real * eigenvalues_device() const
Definition davidson.hpp:321
Complex * overlap_basis_vectors() override
Definition davidson.hpp:290
Complex * band_scratch() override
Definition davidson.hpp:311
const Complex * overlap_basis_vectors() const override
Definition davidson.hpp:293
void resize(int maximum_basis_size, int number_of_plane_waves, int number_of_bands, bool overlap_is_identity) override
Definition davidson.hpp:230
Complex * subspace_hamiltonian() override
Definition davidson.hpp:297
int number_of_kpoints() const
const BetaFunctionTable & beta_function_table() const
Definition init.hpp:284
int total_number_of_projectors() const
Definition init.hpp:181
const PseudopotentialSet & pseudopotential_set() const
Definition init.hpp:156
const NormConservingPlanewaveBasis & pseudopotential_basis() const
Definition init.hpp:65
std::string name() const override
Definition davidson.hpp:996
void set_cached_hubbard_projectors(int kpoint_index, const q3::Matrix< Complex > *projectors) override
Definition davidson.hpp:451
void invalidate_smooth_potential_cache() override
Definition davidson.hpp:474
void solve(int kpoint_index_state, int kpoint_index, 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 > &real_space_potential, const Crystal &crystal_structure, const KPointsGrid &kpoint_grid, const PlanewaveBasis &planewave_basis, const NormConservingPlanewaveKohnShamQuantumSystem &pseudopotential_quantum_system, PlanewaveKohnShamQuantumSystem &quantum_system) override
Definition davidson.hpp:480
void set_hubbard_d_matrix(const std::vector< Complex > *dense_d_matrix_column_major) override
Definition davidson.hpp:463
PlanewaveDavidsonDiagonalizer(const execution::ExecutionPolicy &execution_policy, execution::gpu::blas::GPUBLAS &gpu_blas, gpu::GPUFFTGrid *gpu_fft_grid, execution::gpu::lapack::GPUEigensolver &gpu_eigensolver, std::shared_ptr< execution::gpu::device::GPUDeviceBackend > device_backend)
Definition davidson.hpp:392
void set_use_mixed_precision(bool use_mixed_precision)
Definition davidson.hpp:410
void set_cached_projectors(int kpoint_index, const q3::Matrix< Complex > *projectors) override
Definition davidson.hpp:440
int number_of_unconverged_bands() const override
Definition davidson.hpp:432
PlanewaveDavidsonDiagonalizer(const execution::ExecutionPolicy &execution_policy=default_serial_execution_policy, PlanewaveHamiltonianApplicator *hamiltonian_applicator=nullptr, PlanewaveSubspaceManager *subspace_manager=nullptr)
Definition davidson.hpp:365
static const execution::SerialExecutionPolicy default_serial_execution_policy
Definition davidson.hpp:354
void set_basis_expansion_factor(int factor) override
Definition davidson.hpp:418
void set_tolerance(Real tolerance) override
void set_maximum_iterations(int maximum_iterations) override
Definition davidson.hpp:428
virtual void resize(int maximum_basis_size, int number_of_plane_waves, int number_of_bands, bool overlap_is_identity)=0
virtual Complex * band_scratch()=0
std::vector< Real > local_eigenvalues_host_
Definition davidson.hpp:111
virtual Complex * overlap_basis_vectors()=0
virtual const Complex * overlap_basis_vectors() const =0
virtual Complex * subspace_overlap()=0
const Real * local_eigenvalues() const
Definition davidson.hpp:89
virtual const Complex * hamiltonian_basis_vectors() const =0
virtual Complex * hamiltonian_basis_vectors()=0
virtual const Complex * basis_vectors() const =0
virtual Complex * basis_vectors()=0
virtual const Complex * subspace_hamiltonian() const =0
virtual ~PlanewaveDavidsonWorkspace()=default
virtual Complex * subspace_hamiltonian()=0
virtual const Complex * subspace_overlap() const =0
virtual const Complex * band_scratch() const =0
virtual void set_cached_projectors(int kpoint_index, const q3::Matrix< Complex > *projectors)
Definition init.hpp:131
virtual void invalidate_smooth_potential_cache()=0
virtual void set_cached_hubbard_projectors(int kpoint_index, const q3::Matrix< Complex > *projectors)
Definition init.hpp:141
virtual void set_hubbard_d_matrix(const std::vector< Complex > *dense_d_matrix_column_major)
Definition init.hpp:154
int number_of_occupied_bands_per_spin() const
Definition init.hpp:156
Real eigenvalue(int kpoint_state_index, int band_index) const
Definition init.hpp:118
const Complex * band_scratch() const override
Definition davidson.hpp:200
const Complex * basis_vectors() const override
Definition davidson.hpp:165
void resize(int maximum_basis_size, int number_of_plane_waves, int number_of_bands, bool overlap_is_identity) override
Definition davidson.hpp:116
Complex * subspace_overlap() override
Definition davidson.hpp:190
const Complex * subspace_overlap() const override
Definition davidson.hpp:193
Complex * basis_vectors() override
Definition davidson.hpp:162
const Complex * hamiltonian_basis_vectors() const override
Definition davidson.hpp:172
Complex * overlap_basis_vectors() override
Definition davidson.hpp:176
Complex * hamiltonian_basis_vectors() override
Definition davidson.hpp:169
Complex * band_scratch() override
Definition davidson.hpp:197
const Complex * subspace_hamiltonian() const override
Definition davidson.hpp:186
const Complex * overlap_basis_vectors() const override
Definition davidson.hpp:179
void sync_to_host() override
Definition davidson.hpp:204
Complex * subspace_hamiltonian() override
Definition davidson.hpp:183
virtual int global_number_of_elements(int local_number_of_elements) const =0
virtual void broadcast_across_bands(ComplexValue *, int) const
Definition init.hpp:293
virtual int band_distribution_size() const
Definition init.hpp:240
void debug(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:179
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 empty_band_tolerance_relaxation_factor
constexpr double empty_band_tolerance_floor
constexpr double initial_eigenvalue_tolerance
double Real
std::unique_ptr< OverlapOperator > create_overlap_operator(const Crystal &crystal_structure, const NormConservingPlanewaveKohnShamQuantumSystem &quantum_system, const Matrix< Complex > &kleinman_bylander_potential, const execution::ExecutionPolicy &execution_policy)
Definition factory.hpp:23
void compute_kleinman_bylander_projectors(const Vector3D &k_crystal, const Crystal &crystal_structure, const PlanewaveBasis &planewave_basis, const PseudopotentialSet &pseudopotential_set, const BetaFunctionTable &beta_function_table, const std::vector< int > &g_vector_indices, int number_of_plane_waves, int total_number_of_projectors, Matrix< Complex > &kleinman_bylander_potential)
Definition projector.hpp:36
std::complex< double > Complex
ResidentMemorySample record_resident_memory()
Definition counters.hpp:132
static ProfilingCounters & instance()
Definition counters.hpp:220
std::atomic< long long > davidson_iteration_count
Definition counters.hpp:23
DavidsonTimingAccumulator davidson_timing
Definition counters.hpp:124
static std::string format_gibibytes(std::uint64_t bytes)
Definition memory.hpp:152
std::uint64_t current_bytes
Resident set size at the moment of sampling.
Definition memory.hpp:49
std::uint64_t peak_bytes
High-water mark of the resident set over the life of the process.
Definition memory.hpp:51
bool available
Whether any source could be read.
Definition memory.hpp:59