Loading...
Searching...
No Matches
init.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * PlanewaveKohnShamSCFIterator — abstract plane-wave Kohn-Sham SCF layer.
5 *
6 * Owns the plane-wave basis, per-k-point G-vector mappings, wavefunctions,
7 * eigenvalues, the eigensolver, and FFT-based density/potential operations.
8 * Implements all SCF hooks that are purely plane-wave:
9 * representation setup, mixing, occupations, density accumulation,
10 * symmetrization, double-counting correction, energy components,
11 * convergence reporting.
12 *
13 * Does NOT own pseudopotential-specific data (KB projectors, local ionic
14 * potential, atomic superposition, core charge). Those are deferred to
15 * concrete subclasses such as PlanewavePseudopotentialKohnShamSCFIterator.
16 */
17
18#include <algorithm>
19#include <cmath>
20#include <memory>
21#include <numeric>
22#include <optional>
23#include <string>
24#include <utility>
25#include <vector>
26
27#include "io/logger.hpp"
49
50namespace q3 {
51
53 public:
61 ~PlanewaveKohnShamSCFIterator() override = default;
62
63 /**
64 * Assemble and return a snapshot of the converged SCF state.
65 *
66 * Returns a self-contained state carrying all converged data needed by
67 * post-SCF consumers (forces, stress, output writer, binary checkpoints).
68 */
70
71 /// Transfer ownership of a KPointsGrid into this iterator.
73 owned_kpoint_grid_ = std::move(kpoint_grid);
74 kpoint_grid_ = &owned_kpoint_grid_.value();
75 }
76
77 /**
78 * Transfer ownership of an execution policy into this iterator.
79 *
80 * After this call the iterator holds the policy for its lifetime and
81 * uses it for pool-level collective operations (charge-density reduction,
82 * scalar reduction).
83 */
84 void set_owned_execution_policy(std::unique_ptr<execution::PlanewaveExecutionPolicy> policy) {
85 owned_execution_policy_ = std::move(policy);
87 }
88
89 /**
90 * Inject an externally-constructed diagonalizer.
91 *
92 * When set before run(), the iterator skips its internal
93 * create_planewave_diagonalizer() call and uses this one instead.
94 * Ownership is transferred to the iterator.
95 */
96 void set_diagonalizer(std::unique_ptr<PlanewaveDiagonalizer> diagonalizer) {
97 diagonalizer_ = std::move(diagonalizer);
98 }
99
100 /**
101 * Release what only the SCF needed, once it has converged: the solver's
102 * working basis and the mixing history. The forces and stress that follow
103 * work from the wavefunctions and density in the quantum system, and they
104 * allocate arrays of their own -- on the 54-atom silicon cell at two ranks
105 * the forces stage set the run's peak 0.3 GiB above the SCF's, because
106 * the SCF's working set was still alive under it. A subclass adds the
107 * caches it owns. A later run() rebuilds everything on demand.
108 */
109 virtual void release_scf_workspace() {
110 if (diagonalizer_) {
111 diagonalizer_->release_workspace();
112 }
113 if (mixer_) {
114 mixer_->reset();
115 }
116 ProfilingCounters::instance().release_unused_heap("SCF workspace release");
117 }
118
120 if (execution_policy_ == nullptr) {
121 throw std::runtime_error("PlanewaveKohnShamSCFIterator::execution_policy(): null.");
122 }
123 return *execution_policy_;
124 }
125
126 /**
127 * Set the preloaded quantum system snapshot.
128 */
132
133 /**
134 * Preload single-particle states from the cached quantum system snapshot.
135 */
138 log::debug("Planewave preload: Copying states from preloaded quantum system.");
142 } else {
143 log::debug("Planewave preload: preloaded_quantum_system_ is null.");
144 }
145 }
146
147 /**
148 * Whether the loaded density has this run's spin components and this
149 * rank's share of its charge-density grid. Every rank of a run reads the
150 * same saved file, so the answer is the same on every rank: a whole grid
151 * fits ranks that each hold one, and no rank of a pool split into slabs.
152 */
153 [[nodiscard]] bool loaded_charge_density_fits_grid() const {
154 const auto grid_points =
155 static_cast<std::size_t>(planewave_basis_.fft_rho().local_grid_points());
156 if (static_cast<int>(charge_density_real_.size()) != number_of_spin_components()) {
157 return false;
158 }
159 for (const auto& spin_density : charge_density_real_) {
160 if (spin_density.size() != grid_points) {
161 return false;
162 }
163 }
164 return true;
165 }
166
167 /// Rescale the loaded density so it integrates to this system's electron count.
169 Real integrated_electrons = 0.0;
170 for (const auto& spin_density : charge_density_real_) {
171 for (const Real value : spin_density) {
172 integrated_electrons += value;
173 }
174 }
175 integrated_electrons *=
177 if (integrated_electrons <= 0.0) {
178 return;
179 }
180 const Real scale = number_of_electrons() / integrated_electrons;
181 for (auto& spin_density : charge_density_real_) {
182 for (Real& value : spin_density) {
183 value *= scale;
184 }
185 }
186 }
187
188 /**
189 * Preload charge density from the cached charge_density_real_.
190 */
192 log::debug("Planewave preload: Transforming real charge density to G-space.");
193 int grid_points = planewave_basis_.fft_rho().total_grid_points();
194 std::vector<Real> total_charge_density_real(grid_points, 0.0);
195 for (int spin_index = 0; spin_index < number_of_spin_components(); ++spin_index) {
196 for (int grid_index = 0; grid_index < grid_points; ++grid_index) {
197 total_charge_density_real[grid_index] +=
198 charge_density_real_[spin_index][grid_index];
199 }
200 }
201 planewave_basis_.rho_r_to_g(total_charge_density_real, charge_density_g_);
202 }
203
204 // ─── Planewave accessors ─────────────────────────────────────────
205
206 /**
207 * Access the k-point grid.
208 */
209 [[nodiscard]] const KPointsGrid& kpoint_grid() const {
210 return *kpoint_grid_;
211 }
212
213 /**
214 * Access the plane-wave basis set.
215 */
216 [[nodiscard]] const PlanewaveBasis& planewave_basis() const {
217 return planewave_basis_;
218 }
219
220 /**
221 * Access the generic Kohn-Sham quantum system.
222 */
223 [[nodiscard]] const PlanewaveKohnShamQuantumSystem& quantum_system() const {
224 return quantum_system_;
225 }
226
227 /**
228 * Access a single wavefunction coefficient Ψ_{k,n}(G).
229 */
230 [[nodiscard]] Complex wavefunction(int kpoint_index, int band_index, int g_vector_index) const {
231 return quantum_system_.wavefunction(kpoint_index, band_index, g_vector_index);
232 }
233
234 /**
235 * Access eigenvalues as a Matrix (for compatibility).
236 */
237 [[nodiscard]] Matrix<Real> eigenvalues() const {
239 }
240
241 /**
242 * The eigenvalues of every k-point state in the run, in global order (Ry).
243 *
244 * eigenvalues() holds only this pool's k-point states. The rows here are
245 * spin-major over the global grid, as a serial run's are. Collective under
246 * MPI: every rank must call it.
247 */
248 [[nodiscard]] Matrix<Real> global_eigenvalues() const {
249 const Matrix<Real> local_eigenvalues = eigenvalues();
250 const int number_of_bands = static_cast<int>(local_eigenvalues.cols());
251 std::vector<Real> global_data =
256 Matrix<Real> eigenvalues_in_global_order(global_data.size() / number_of_bands,
258 eigenvalues_in_global_order.raw() = std::move(global_data);
259 return eigenvalues_in_global_order;
260 }
261
262 /**
263 * Access a single eigenvalue ε_{k,n} (Ry).
264 */
265 [[nodiscard]] Real eigenvalue(int kpoint_index, int band_index) const {
266 return quantum_system_.eigenvalue(kpoint_index, band_index);
267 }
268
269 /**
270 * Access the occupation number f_{k,n} for a given k-point state and band.
271 */
272 [[nodiscard]] Real occupation(int kpoint_index, int band_index) const {
274 band_index);
275 }
276
277 /**
278 * G-vector index mapping for all k-points: g_vector_indices[k][i] → global G-vector index.
279 */
280 [[nodiscard]] const std::vector<std::vector<int>>& g_vector_indices() const {
282 }
283
284 /**
285 * Kinetic energies |k+G|² for all k-points.
286 */
287 [[nodiscard]] const std::vector<std::vector<Real>>& kinetic_energy_squared() const {
289 }
290
291 /**
292 * Number of plane waves per k-point (full array).
293 */
294 [[nodiscard]] const std::vector<int>& number_of_plane_waves() const {
296 }
297
298 /**
299 * Number of plane waves for a specific k-point.
300 */
301 [[nodiscard]] int number_of_plane_waves(int kpoint_index) const {
303 }
304
305 /**
306 * G-vector indices for a specific k-point.
307 */
308 [[nodiscard]] const std::vector<int>& g_vector_indices(int kpoint_index) const {
310 }
311
312 /**
313 * Total number of bands (user-specified or auto-determined).
314 */
315 [[nodiscard]] int number_of_bands() const override {
316 return number_of_bands_;
317 }
318
319 /**
320 * Total number of valence electrons from all atoms.
321 */
322 [[nodiscard]] Real number_of_electrons() const override {
324 }
325
326 /**
327 * Total number of k-point states (k-points × spin components).
328 */
329 [[nodiscard]] int number_of_kpoint_states() const override {
331 }
332
333 /**
334 * Number of fully occupied bands per spin channel.
335 */
339
340 /**
341 * Spin degeneracy factor (2.0 for non-spin-polarized, 1.0 for LSDA).
342 */
343 [[nodiscard]] Real spin_degeneracy() const {
345 }
346
347 /**
348 * ∫(ρ↑−ρ↓) and ∫|ρ↑−ρ↓| for this run, in Bohr magnetons.
349 *
350 * Present only for a spin-polarized run that reached its convergence
351 * report, which is where they are integrated; empty elsewhere, so a
352 * consumer can tell "not a magnetic calculation" from "a moment that came
353 * out at zero" without asking which kind of run this was.
354 */
355 [[nodiscard]] const std::optional<Magnetization>& magnetization() const {
356 return magnetization_;
357 }
358
359 protected:
360 /// Set by report_convergence_details(); see magnetization() above.
361 std::optional<Magnetization> magnetization_;
362
363 // ─── Configuration read from KohnShamSCFIteratorConfig ──────────
364 const KPointsGrid* kpoint_grid_ = nullptr;
367 std::string diagonalization_method_ = "davidson";
368
369 /**
370 * Execution policy for pool-level collective operations.
371 * Set via set_owned_execution_policy() before run().
372 */
374
375 /**
376 * Optional owned execution policy — transfers lifetime from the factory.
377 *
378 * IMPORTANT: This member MUST be declared before planewave_basis_ and all
379 * other MPI-using members. C++ destroys members in reverse declaration order,
380 * so members declared AFTER this one are destroyed BEFORE it. The owned policy
381 * calls MPI_Finalize() in its destructor; planewave_basis_ contains MPIFFTGrid
382 * which calls fftw_destroy_plan (which internally frees MPI communicators) in
383 * its destructor. If planewave_basis_ were destroyed after MPI_Finalize, OpenMPI
384 * would report a fatal MPI_Comm_free-after-MPI_Finalize violation.
385 */
386 std::unique_ptr<execution::PlanewaveExecutionPolicy> owned_execution_policy_;
387
388 // ─── Planewave fields ────────────────────────────────────────────
391
394
395 std::unique_ptr<PlanewaveDiagonalizer> diagonalizer_;
398 /// Accumulates real-space charge density from wavefunctions each SCF iteration.
399 std::unique_ptr<PlanewaveChargeDensityAccumulator> charge_density_accumulator_;
400 /// Computes band energy and smearing entropy from the occupied Kohn-Sham states.
401 std::unique_ptr<PlanewaveKohnShamEnergyCalculator> energy_calculator_;
402
403 /// Converged output density snapshot, restored once self-consistency is reached.
404 std::vector<std::vector<Real>> convergence_output_density_;
405 /// Cached real-space ionic local potential (constant during SCF, avoids repeated FFTs).
407 /**
408 * Number of smooth G-vectors for density residual (matching QE's smooth-grid count).
409 * For dual-grid US/PAW calculations, this excludes hard G-vectors above 4*ecutwfc.
410 */
412
413 private:
414 /**
415 * Optional owned KPointsGrid — used when the factory constructs the iterator
416 * from InputData (the iterator owns the grid rather than borrowing it).
417 */
418 std::optional<KPointsGrid> owned_kpoint_grid_;
419
420 protected:
421 /**
422 * When set (by set_diagonalizer_tolerance_for_nscf), overrides the initial
423 * loose tolerance on the first call to set_diagonalization_tolerance().
424 * Stored as a pending value because the diagonalizer_ is not yet constructed
425 * when the NSCF flavor calls set_diagonalizer_tolerance_for_nscf() pre-run.
426 */
427 std::optional<Real> nscf_override_tolerance_;
428
429 /**
430 * The NSCF flavor's per-k-point Davidson cap, pending like the tolerance
431 * above and applied at the same point.
432 */
434
435 /**
436 * Set once the NSCF cap is applied: a single pass keeps whatever a solve
437 * leaves unconverged, so each such solve is counted and reported.
438 */
440
441 // ─── PW-level energy helpers ─────────────────────────────────────
442
443 [[nodiscard]] const PlanewaveKohnShamEnergies& planewave_energies() const {
444 return static_cast<const PlanewaveKohnShamEnergies&>(energies());
445 }
449
450 // ─── KohnShamSCFIterator hooks ────────────────────────────────────
451
453 // Read planewave config — skip fields already set by owned-data setters.
454 if (config().kpoint_grid != nullptr && !owned_kpoint_grid_.has_value()) {
456 }
457
462
464 Real effective_charge_density_cutoff = effective_charge_density_energy_cutoff();
465
466 /**
467 * Detect gamma-only: single k-point at Γ = (0,0,0) with no shift.
468 * This enables half-sphere G-vector storage for ~2× speedup.
469 *
470 * Under a shared pool the half sphere is rebuilt inside every transform
471 * from each G and its negative, so the FFT layout has to keep the two
472 * on one rank. The stick grid does; the slab grid it replaced did not,
473 * which is what used to force this off under MPI. The grid is asked
474 * once it exists, below, rather than assumed here.
475 */
476 bool use_gamma_only = false;
477 {
478 const auto& grid_dims = kpoint_grid().grid_dimensions();
479 const auto& grid_shifts = kpoint_grid().grid_shifts();
480 if (grid_dims[0] == 1 && grid_dims[1] == 1 && grid_dims[2] == 1 &&
481 grid_shifts[0] == 0 && grid_shifts[1] == 0 && grid_shifts[2] == 0) {
482 use_gamma_only = true;
483 log::info(" Gamma-only optimization enabled (half-sphere G-vectors)");
484 }
485 }
486 if (execution_policy_ == nullptr) {
487 owned_execution_policy_ = std::make_unique<execution::PlanewaveSerialExecutionPolicy>();
489 }
493 effective_charge_density_cutoff,
494 use_gamma_only,
497 if (use_gamma_only && !planewave_basis_.fft_wave().co_locates_mirrors()) {
498 log::info(" Gamma-only disabled: this FFT layout separates a G-vector from its "
499 "negative, so the half sphere cannot be rebuilt locally");
500 use_gamma_only = false;
504 effective_charge_density_cutoff,
505 use_gamma_only,
508 }
509
512
513 if (!diagonalizer_) {
515 (execution_policy_ != nullptr)
518 }
519 diagonalizer_->set_basis_expansion_factor(config().davidson_basis_expansion_factor);
520 const auto* saved_preloaded = quantum_system_.preloaded_quantum_system_;
521
522 /**
523 * K-points are already distributed by the factory (set_owned_kpoint_grid)
524 * and protected by the owned_kpoint_grid_.has_value() guard above.
525 */
526
532
533 if (saved_preloaded != nullptr) {
534 quantum_system_.load_quantum_system(*saved_preloaded);
535 }
536
539
542 ProfilingCounters::instance().log_resident_checkpoint("basis + wavefunctions");
543 charge_density_accumulator_ = std::make_unique<PlanewaveChargeDensityAccumulator>(
545 energy_calculator_ = std::make_unique<PlanewaveKohnShamEnergyCalculator>(
547
550
553 }
554
556 }
557
558 void initiate_mixer() override {
559 MixerEnvironment environment;
560 environment.to_reciprocal_space = [this](const std::vector<Real>& density_real,
561 std::vector<Complex>& density_g) {
562 planewave_basis_.rho_r_to_g(density_real, density_g);
563 };
564 environment.to_real_space = [this](const std::vector<Complex>& density_g,
565 std::vector<Real>& density_real) {
566 planewave_basis_.rho_g_to_r(density_g, density_real);
567 };
568
569 int number_of_g_vectors = planewave_basis_.number_of_g_vectors();
570 Real two_pi_over_lattice_parameter_squared =
572 /**
573 * Same metric definitions as the SCF dr2 (see hartree_metric_prefactor /
574 * magnetization_metric_weight) so mixer-internal norms and the SCF
575 * convergence check agree.
576 */
577 Real charge_prefactor = hartree_metric_prefactor();
581 environment.global_reduce = [this](std::vector<Real>& values) {
582 if (execution_policy_ != nullptr) {
584 }
585 };
586
587 environment.charge_metric_weights.assign(number_of_g_vectors, 0.0);
588 for (int g_index = environment.first_nonzero_g_index; g_index < number_of_g_vectors;
589 ++g_index) {
590 Real g_squared = planewave_basis_.g_vectors()[g_index].magnitude_squared;
591 environment.charge_metric_weights[g_index] = charge_prefactor / g_squared;
592 }
593
594 /**
595 * The residual preconditioner. |G|² is carried in bohr⁻² -- the stored
596 * magnitude is in units of (2π/a)² -- because every preconditioner
597 * compares it against a screening wavevector in those units.
598 */
599 PreconditionerEnvironment preconditioner_environment;
600 preconditioner_environment.g_squared.assign(number_of_g_vectors, 0.0);
601 for (int g_index = 0; g_index < number_of_g_vectors; ++g_index) {
602 preconditioner_environment.g_squared[g_index] =
603 planewave_basis_.g_vectors()[g_index].magnitude_squared *
604 two_pi_over_lattice_parameter_squared;
605 }
606 preconditioner_environment.q0_squared = mixing_q0_ * mixing_q0_;
607 preconditioner_environment.first_nonzero_g_index = environment.first_nonzero_g_index;
608 preconditioner_environment.to_reciprocal_space = environment.to_reciprocal_space;
609 preconditioner_environment.to_real_space = environment.to_real_space;
610 preconditioner_environment.global_reduce = environment.global_reduce;
611 environment.preconditioner =
612 PreconditionerFactory::create(mixing_preconditioning_, preconditioner_environment);
613
614 /**
615 * Compute the number of smooth G-vectors for dual-grid mixing (QE convention).
616 * The smooth cutoff is 4*ecutwfc (the norm-conserving default density cutoff).
617 * G-vectors above this cutoff get simple linear mixing instead of Broyden,
618 * keeping noisy high-frequency augmentation artifacts out of the Broyden subspace.
619 */
620 Real smooth_cutoff = 4.0 * wavefunction_energy_cutoff_;
622 if (dense_cutoff > smooth_cutoff + 1.0e-6) {
623 /**
624 * Count G-vectors within the smooth cutoff
625 * G-vectors are sorted by magnitude, so we can stop when we exceed the cutoff
626 */
627 Real smooth_g_squared_max = smooth_cutoff / two_pi_over_lattice_parameter_squared;
628 int smooth_count = 0;
629 for (int g_index = 0; g_index < number_of_g_vectors; ++g_index) {
630 if (planewave_basis_.g_vectors()[g_index].magnitude_squared <=
631 smooth_g_squared_max) {
632 smooth_count = g_index + 1;
633 } else {
634 break; // G-vectors are sorted by magnitude
635 }
636 }
637 environment.number_of_smooth_g_vectors = smooth_count;
638 number_of_smooth_g_vectors_ = smooth_count;
639 /**
640 * Zero metric weights for hard G-vectors so the Gram matrix and projections
641 * naturally use only smooth G-vectors, matching QE's density dot product,
642 * which is truncated at the smooth G-vector count.
643 */
644 for (int g_index = smooth_count; g_index < number_of_g_vectors; ++g_index) {
645 environment.charge_metric_weights[g_index] = 0.0;
646 }
647 log::info(" Dual-grid mixing: {} smooth / {} total G-vectors "
648 "(smooth cutoff = {:.0f} Ry, dense cutoff = {:.0f} Ry)",
649 smooth_count,
650 number_of_g_vectors,
651 smooth_cutoff,
652 dense_cutoff);
653 } else {
654 // Single grid: all G-vectors are smooth
655 environment.number_of_smooth_g_vectors = number_of_g_vectors;
656 number_of_smooth_g_vectors_ = number_of_g_vectors;
657 }
658
659 mixer_->initialize(environment);
660 }
661
662 std::vector<std::vector<Real>> input_density_for_mixing() const override {
664 }
665
667 /**
668 * Skip recomputation when V_eff was injected via load_effective_potential().
669 * This path is taken by the NSCF flavor which loads V_eff from a v2 checkpoint.
670 */
672 /**
673 * Compute total_potential_g_ by FFT-ing the spin-averaged potential.
674 * This is required by prepare_hamiltonian() (critical for ultrasoft, harmless for NC).
675 */
676 int number_of_grid_points = real_space_potential_.empty()
677 ? 0
678 : static_cast<int>(real_space_potential_[0].size());
679 if (number_of_grid_points > 0) {
680 std::vector<Real> spin_averaged_potential(number_of_grid_points, 0.0);
681 int number_of_spin_comp = static_cast<int>(real_space_potential_.size());
682 for (const auto& spin_potential : real_space_potential_) {
683 for (int grid_index = 0; grid_index < number_of_grid_points; ++grid_index) {
684 spin_averaged_potential[grid_index] +=
685 spin_potential[grid_index] / number_of_spin_comp;
686 }
687 }
688 planewave_basis_.rho_r_to_g(spin_averaged_potential, total_potential_g_);
689 /**
690 * Per-spin copies as well: a checkpoint stores only the average,
691 * but `real_space_potential_` was loaded per channel, so the
692 * nonlocal D can still be built per channel rather than from
693 * the average it would otherwise fall back to.
694 */
695 total_potential_g_by_spin_.assign(number_of_spin_comp, {});
696 for (int spin_index = 0; spin_index < number_of_spin_comp; ++spin_index)
698 total_potential_g_by_spin_[spin_index]);
699 }
700 /**
701 * Initialize the cached ionic local potential so that
702 * compute_double_counting_correction() does not crash when it is called after
703 * solve_single_particle_states(). In the SCF path this is populated lazily inside
704 * compute_effective_potential_from_density(), but that function is skipped for NSCF,
705 * leaving the cache empty.
706 */
708 const auto& local_ionic_potential = local_ionic_potential_g();
709 if (!local_ionic_potential.empty()) {
710 planewave_basis_.rho_g_to_r(local_ionic_potential,
712 }
713 }
714 return;
715 }
717 }
718
720 if (nscf_override_maximum_iterations_.has_value()) {
721 diagonalizer_->set_maximum_iterations(nscf_override_maximum_iterations_.value());
724 }
725 if (nscf_override_tolerance_.has_value()) {
726 /**
727 * NSCF mode: use the tight override tolerance set by the NSCF flavor
728 * instead of the adaptive SCF scheme. The override was requested before
729 * run() (when diagonalizer_ was still null), so it is applied here on
730 * the first call, once the diagonalizer has been constructed.
731 */
734 } else if (iteration <= 1) {
735 // Iteration 1: use initial loose threshold (matching QE's SCF setup)
737 } else {
738 /**
739 * Iteration 2+: tighten the eigenvalue threshold to stay an order of
740 * magnitude below the self-consistency error, per electron-state:
741 * tolerance <- min(tolerance, 0.1 * residual / max(1, electrons))
742 * The min keeps the schedule monotone, so a transient rise in the
743 * residual cannot loosen a threshold that has already tightened; the
744 * max(1, ...) guard covers a cell with fewer than one electron.
745 * The 0.1 margin and the 1e-13 floor applied below are heuristics --
746 * the same pair Quantum ESPRESSO uses -- and neither is observable in
747 * the converged result, which is fixed by convergence_threshold on
748 * the residual, not by this tolerance. Iteration 1's loose 1e-2 start
749 * is of the same kind: the atomic-superposition potential is far from
750 * self-consistent, so precision there is wasted.
751 */
754 0.1 * density_residual_ / std::max(1.0, number_of_electrons_));
755 }
757 }
758
759 void set_diagonalizer_tolerance(Real tolerance) override {
760 diagonalizer_tolerance_ = tolerance;
761 diagonalizer_->set_tolerance(tolerance);
762 }
763
767
777
782
784 const std::vector<std::vector<Real>>& input_density_for_mixing) const override {
786 }
787
788 protected:
789 /**
790 * Hartree-metric prefactor e² · 4π / tpiba² · V/2. The per-G charge-channel
791 * metric weight is this divided by the dimensionless (miller-basis)
792 * |G|². Single source for both the mixer environment and the SCF dr2.
793 */
799
800 /**
801 * Flat metric weight for the magnetization channel: e² · 4π / (2π)² · V/2
802 * (the constant prefactor of the spin-polarized term in QE's density dot
803 * product). Single source for both the mixer environment and the SCF dr2.
804 */
809
810 /**
811 * Compute the Hartree-metric density residual between two charge densities,
812 * synchronized across k-point pools.
813 *
814 * The raw residual (see compute_local_residual_between below) is computed
815 * independently on every rank from that rank's own rho_r_to_g transforms.
816 * FFTW plans are timing-selected per rank (FFTW_MEASURE), so bit-identical
817 * input densities yield bit-DIFFERENT residuals on different pool ranks --
818 * measured on a 4-pool Ni LSDA+smearing run: last-bit (~1e-15 relative)
819 * divergence already at iteration 1, amplified through the nonlinear
820 * mixing-history feedback to factors of 2-30x near convergence (the
821 * residual tends to zero while the per-rank noise it is measured against
822 * does not). This residual gates three branches that
823 * change the inter-pool collective call sequence: the SCF loop exit
824 * (is_iteration_converged), the skip-mixing early return, and the
825 * first-iteration re-diagonalization. If ranks decide those branches
826 * differently, their Fermi-bisection / charge-density-Allreduce /
827 * force-stress collectives pair up out of order across pools:
828 * MPI_ERR_TRUNCATE when the mismatched calls have different message
829 * sizes, silently corrupted totals when they happen to match. Reducing
830 * with MPI_MAX makes every rank branch on the same value and is
831 * conservative -- the SCF stops only when the slowest pool's estimate has
832 * converged. (Within a pool no synchronization is needed: band ranks
833 * compute the residual from pool-collectively-transformed, replicated
834 * G-space data, so they already agree bitwise.)
835 */
836 Real compute_residual_between(const std::vector<std::vector<Real>>& density_in,
837 const std::vector<std::vector<Real>>& density_out) const {
838 Real density_residual = compute_local_residual_between(density_in, density_out);
839 if (execution_policy_ != nullptr && execution_policy_->number_of_pools() > 1) {
840 execution_policy_->reduce_scalar_max(density_residual);
841 }
842 return density_residual;
843 }
844
845 /**
846 * This rank's own (unsynchronized) Hartree-metric residual between two
847 * charge densities.
848 *
849 * Uses only smooth G-vectors (up to number_of_smooth_g_vectors_), matching QE, which
850 * measures dr2 as the metric dot product of (ρ_out − ρ_in) over the smooth G-vector
851 * count only. Subclasses (e.g. ultrasoft) may call this with smooth-only
852 * densities to avoid augmentation contamination.
853 * Never branch on this value directly under k-point-pool MPI -- go through
854 * compute_residual_between so all pools agree (see its docstring).
855 */
856 Real compute_local_residual_between(const std::vector<std::vector<Real>>& density_in,
857 const std::vector<std::vector<Real>>& density_out) const {
858 int residual_g_count = (number_of_smooth_g_vectors_ > 0)
861 int first_nonzero_g_index = planewave_basis_.first_nonzero_g_index();
862
863 Real density_residual = 0.0;
864 Real gamma_factor = planewave_basis_.is_gamma_only() ? 2.0 : 1.0;
865
866 if (number_of_spin_components() == 1) {
867 std::vector<Complex> input_density_g;
868 std::vector<Complex> output_density_g;
869 planewave_basis_.rho_r_to_g(density_in[0], input_density_g);
870 planewave_basis_.rho_r_to_g(density_out[0], output_density_g);
871 for (int g_vector_index = first_nonzero_g_index; g_vector_index < residual_g_count;
872 ++g_vector_index) {
873 Complex delta = output_density_g[g_vector_index] - input_density_g[g_vector_index];
874 Real g_squared = planewave_basis_.g_vectors()[g_vector_index].magnitude_squared;
875 density_residual += std::norm(delta) / g_squared * gamma_factor;
876 }
877 return density_residual * hartree_metric_prefactor();
878 }
879
880 /**
881 * LSDA: measure the total density Δ(ρ↑+ρ↓) with the Hartree metric and
882 * the magnetization Δ(ρ↑−ρ↓) with a constant metric that INCLUDES G=0
883 * (QE's density-dot-product convention). A per-channel Hartree metric
884 * would miss a uniform electron transfer between the spin channels — it
885 * lives purely in the magnetization's G=0 component — and report
886 * convergence while the moment is still relaxing.
887 */
888 std::vector<Complex> input_up_g, input_down_g, output_up_g, output_down_g;
889 planewave_basis_.rho_r_to_g(density_in[0], input_up_g);
890 planewave_basis_.rho_r_to_g(density_in[1], input_down_g);
891 planewave_basis_.rho_r_to_g(density_out[0], output_up_g);
892 planewave_basis_.rho_r_to_g(density_out[1], output_down_g);
893
894 Real magnetization_residual = 0.0;
895 for (int g_vector_index = 0; g_vector_index < residual_g_count; ++g_vector_index) {
896 Complex delta_up = output_up_g[g_vector_index] - input_up_g[g_vector_index];
897 Complex delta_down = output_down_g[g_vector_index] - input_down_g[g_vector_index];
898 Complex delta_total = delta_up + delta_down;
899 Complex delta_magnetization = delta_up - delta_down;
900 // G=0 is stored once even on gamma-only half-sphere grids.
901 Real g_factor = (g_vector_index < first_nonzero_g_index) ? 1.0 : gamma_factor;
902 if (g_vector_index >= first_nonzero_g_index) {
903 Real g_squared = planewave_basis_.g_vectors()[g_vector_index].magnitude_squared;
904 density_residual += std::norm(delta_total) / g_squared * g_factor;
905 }
906 magnetization_residual += std::norm(delta_magnetization) * g_factor;
907 }
908
909 return density_residual * hartree_metric_prefactor() +
910 magnetization_residual * magnetization_metric_weight();
911 }
912
913 void mix_density(const std::vector<std::vector<Real>>& input_density_for_mixing) override {
915 }
916
920
924
926 /**
927 * Each rank integrates only its local real-space slabs (FFTW-MPI slab
928 * decomposition). With 1-rank-per-pool the integral is complete; with
929 * multiple ranks per pool it must be summed across the band communicator.
930 */
931 int local_grid_points = planewave_basis_.fft_rho().local_grid_points();
932 int total_grid_points = planewave_basis_.fft_rho().total_grid_points();
933 Real volume_per_grid_point = crystal_structure().cell_volume() / total_grid_points;
934 Real double_counting_correction = 0.0;
935
936 for (int spin_index = 0; spin_index < number_of_spin_components(); ++spin_index) {
937 for (int grid_index = 0; grid_index < local_grid_points; ++grid_index) {
938 double_counting_correction -= charge_density_real_[spin_index][grid_index] *
939 (real_space_potential_[spin_index][grid_index] -
941 }
942 }
943 double_counting_correction *= volume_per_grid_point;
944 if (execution_policy_ != nullptr) {
945 execution_policy_->reduce_within_pool_scalar(double_counting_correction);
946 }
947 return double_counting_correction;
948 }
949
951 /**
952 * Same slab decomposition as compute_double_counting_correction: partial
953 * real-space integral over local_grid_points, requires band-communicator
954 * reduction when multiple ranks share the pool.
955 */
956 int local_grid_points = planewave_basis_.fft_rho().local_grid_points();
957 int total_grid_points = planewave_basis_.fft_rho().total_grid_points();
958 Real volume_per_grid_point = crystal_structure().cell_volume() / total_grid_points;
959 Real delta_escf = 0.0;
960
961 /**
962 * After mixing, charge_density_real_ = rho_mixed
963 * convergence_output_density_ = rho_out (saved by snapshot_output_density())
964 * real_space_potential_ = V_Hxc from rho_mixed (set by update_effective_potential())
965 */
966 for (int spin_index = 0; spin_index < number_of_spin_components(); ++spin_index) {
967 for (int grid_index = 0; grid_index < local_grid_points; ++grid_index) {
968 Real density_difference = charge_density_real_[spin_index][grid_index] -
969 convergence_output_density_[spin_index][grid_index];
970 delta_escf -= density_difference * (real_space_potential_[spin_index][grid_index] -
972 }
973 }
974 delta_escf *= volume_per_grid_point;
975 if (execution_policy_ != nullptr) {
977 }
978 return delta_escf;
979 }
980
993
998
1003 if (execution_policy_ != nullptr) {
1005 }
1006 summary.energies = &planewave_energies();
1007 if (summary.number_of_spin_components == 2) {
1008 Real total_magnetization_sum = 0.0;
1009 Real absolute_magnetization_sum = 0.0;
1010 int local_grid_points = static_cast<int>(charge_density_real_[0].size());
1011 for (int grid_index = 0; grid_index < local_grid_points; ++grid_index) {
1012 Real spin_density =
1013 charge_density_real_[0][grid_index] - charge_density_real_[1][grid_index];
1014 total_magnetization_sum += spin_density;
1015 absolute_magnetization_sum += std::abs(spin_density);
1016 }
1017 if (execution_policy_ != nullptr) {
1018 execution_policy_->reduce_within_pool_scalar(total_magnetization_sum);
1019 execution_policy_->reduce_within_pool_scalar(absolute_magnetization_sum);
1020 }
1021 Real volume_element =
1023 summary.total_magnetization = total_magnetization_sum * volume_element;
1024 summary.absolute_magnetization = absolute_magnetization_sum * volume_element;
1025 /**
1026 * Kept here, where they are integrated for the report, rather than
1027 * re-integrated for the results file: the density this reads is
1028 * released with the rest of the SCF workspace before the flavor
1029 * assembles its result, and a second integration somewhere else
1030 * would be a second chance for the two to disagree -- the same
1031 * reason the SCF history is recorded at the line that prints it.
1032 */
1035 }
1036 /**
1037 * Report every irreducible k-point, not just this rank's pool share.
1038 *
1039 * kpoint_grid() is the DISTRIBUTED grid, so with pools it holds only
1040 * this pool's k-points -- a 60-k-point calculation on 8 pools used to
1041 * report 8, restart its labels at k_0 in each pool, and give no way to
1042 * tell which of the 60 they were. The same fixed-width pack-and-gather
1043 * the SCF flavor already uses to build results.json (see
1044 * flavors/self_consistent_field.hpp) puts them back in global order,
1045 * and it costs one Allreduce at the end of a converged run.
1046 *
1047 * Layout per k-point state: number_of_bands_ eigenvalues followed by
1048 * the three k-point coordinates. Weights are not needed for the report.
1049 * assemble_global_kpoint_state_data() is spin-major over the global
1050 * grid and returns the whole array on every rank; the serial policy
1051 * hands the input straight back, so this path is not MPI-only.
1052 */
1053 const int local_number_of_kpoints = kpoint_grid().number_of_kpoints();
1054 const int number_of_values_per_state = number_of_bands_ + 3;
1055 const int local_number_of_kpoint_states =
1056 local_number_of_kpoints * summary.number_of_spin_components;
1057 std::vector<Real> local_state_data(static_cast<std::size_t>(local_number_of_kpoint_states) *
1058 number_of_values_per_state,
1059 0.0);
1060 for (int kpoint_index_state = 0; kpoint_index_state < local_number_of_kpoint_states;
1061 ++kpoint_index_state) {
1062 const int local_physical_kpoint_index =
1063 (local_number_of_kpoints > 0) ? (kpoint_index_state % local_number_of_kpoints) : 0;
1064 const std::size_t state_offset =
1065 static_cast<std::size_t>(kpoint_index_state) * number_of_values_per_state;
1066 for (int band_index = 0; band_index < number_of_bands_; ++band_index) {
1067 local_state_data[state_offset + static_cast<std::size_t>(band_index)] =
1068 quantum_system_.eigenvalue(kpoint_index_state, band_index);
1069 }
1070 const auto& kpoint = kpoint_grid()[local_physical_kpoint_index];
1071 local_state_data[state_offset + static_cast<std::size_t>(number_of_bands_)] =
1072 kpoint.coordinates.x;
1073 local_state_data[state_offset + static_cast<std::size_t>(number_of_bands_) + 1] =
1074 kpoint.coordinates.y;
1075 local_state_data[state_offset + static_cast<std::size_t>(number_of_bands_) + 2] =
1076 kpoint.coordinates.z;
1077 }
1078
1079 std::vector<Real> global_state_data = local_state_data;
1080 if (execution_policy_ != nullptr) {
1082 local_state_data,
1083 local_number_of_kpoints,
1085 number_of_values_per_state);
1086 }
1087
1088 const int global_number_of_kpoint_states =
1089 static_cast<int>(global_state_data.size()) / number_of_values_per_state;
1090 summary.number_of_kpoints =
1091 (summary.number_of_spin_components > 0)
1092 ? global_number_of_kpoint_states / summary.number_of_spin_components
1093 : global_number_of_kpoint_states;
1094 summary.eigenvalues.resize(static_cast<std::size_t>(global_number_of_kpoint_states));
1095 summary.kpoints.assign(static_cast<std::size_t>(summary.number_of_kpoints), KPoint{});
1096 for (int kpoint_index_state = 0; kpoint_index_state < global_number_of_kpoint_states;
1097 ++kpoint_index_state) {
1098 const std::size_t state_offset =
1099 static_cast<std::size_t>(kpoint_index_state) * number_of_values_per_state;
1100 summary.eigenvalues[static_cast<std::size_t>(kpoint_index_state)].assign(
1101 global_state_data.begin() + static_cast<std::ptrdiff_t>(state_offset),
1102 global_state_data.begin() +
1103 static_cast<std::ptrdiff_t>(state_offset + number_of_bands_));
1104 const int global_physical_kpoint_index =
1105 (summary.number_of_kpoints > 0) ? (kpoint_index_state % summary.number_of_kpoints)
1106 : 0;
1107 summary.kpoints[static_cast<std::size_t>(global_physical_kpoint_index)].coordinates = {
1108 global_state_data[state_offset + static_cast<std::size_t>(number_of_bands_)],
1109 global_state_data[state_offset + static_cast<std::size_t>(number_of_bands_) + 1],
1110 global_state_data[state_offset + static_cast<std::size_t>(number_of_bands_) + 2]};
1111 }
1113
1114 /**
1115 * The counters as the SCF leaves them, which is not what the entry
1116 * point prints at exit: forces and stress add transforms of their own
1117 * afterwards, so the two figures differ, and that difference is the
1118 * reason to have this one at all.
1119 *
1120 * The Davidson timing table is deliberately not dumped here. Nothing
1121 * between this line and the entry point's closing dump touches the
1122 * accumulator, so the two were byte-identical -- the same table twice
1123 * in one run.
1124 */
1125 log::debug(" SCF profiling summary:");
1126 log::debug(" FFT calls total: {}",
1127 ProfilingCounters::instance().fft_call_count.load());
1128 log::debug(" Davidson iterations: {}",
1129 ProfilingCounters::instance().davidson_iteration_count.load());
1130 log::debug(" H|psi> applications: {}",
1131 ProfilingCounters::instance().hamiltonian_application_count.load());
1132 }
1133
1134 // ─── Abstract hooks for pseudopotential subclasses ───────────────
1135
1136 /**
1137 * Compute the effective potential from the current charge density.
1138 * Must set charge_density_g_, real_space_potential_, total_potential_g_,
1139 * exchange_correlation_potential_g_, and energy components (hartree, xc).
1140 */
1142
1143 /**
1144 * Return the local ionic potential in reciprocal space.
1145 */
1146 [[nodiscard]] virtual const std::vector<Complex>& local_ionic_potential_g() const = 0;
1147
1148 /**
1149 * Return the core charge density in reciprocal space.
1150 */
1151 [[nodiscard]] virtual const std::vector<Complex>& core_charge_density_g() const = 0;
1152
1153 /**
1154 * Hook invoked once the initial atomic-superposition charge density is ready.
1155 *
1156 * Default no-op. The ultrasoft subclass overrides it to initialise the smooth
1157 * density and projector overlap storage.
1158 */
1160
1161 // ─── Representation setup helpers ────────────────────────────────
1166
1168 Real count = 0.0;
1169 for (int atom_index = 0; atom_index < crystal_structure().number_of_atoms(); ++atom_index) {
1171 crystal_structure().species_type_index(atom_index));
1172 }
1173 /**
1174 * total_charge follows QE's tot_charge convention: positive = electrons
1175 * removed (cationic). The Ewald ion-ion sum and the Hartree G=0 skip are
1176 * both keyed only to the ionic charge (see ewald_summation.hpp), so no
1177 * separate compensating-background term is needed here -- only the
1178 * electron count itself changes.
1179 */
1180 return count - config().total_charge;
1181 }
1182
1183 /**
1184 * Bands to solve for, when the input did not say.
1185 *
1186 * Insulator: the occupied count plus four empty bands, which cost little and
1187 * give the eigensolver room to converge the top of the valence manifold.
1188 * Metal: 20% more than the occupied count, but never fewer than four extra —
1189 * a partly filled band must be resolved well above the Fermi level or the
1190 * smeared occupations are truncated and the total energy is wrong.
1191 *
1192 * The 20% and the four-band floor are heuristics, and they are the same pair
1193 * Quantum ESPRESSO uses, deliberately: for a metal the band count changes the
1194 * total energy through the fractional occupations of the extra bands, so
1195 * reproducing an upstream energy at the same input requires reproducing its
1196 * band count. That equivalence is what the three LSDA cases in
1197 * tests/validation/test_qe_energy_comparison.py check — they pass no band
1198 * count to either code. The insulating branch deliberately differs (the
1199 * upstream default adds no margin there); empty bands cannot move a
1200 * fixed-occupation total, so nothing observable turns on it.
1201 */
1203 int number_of_occupied_bands = static_cast<int>(std::round(number_of_electrons_ / 2.0));
1204 if (number_of_bands_ > 0) {
1205 if (number_of_bands_ <= number_of_occupied_bands) {
1206 log::warn("number_of_bands ({}) equals the number of occupied states ({}). "
1207 "This may cause SCF convergence issues. Consider adding extra empty "
1208 "bands (e.g., number_of_bands: {}).",
1210 number_of_occupied_bands,
1211 number_of_occupied_bands + 4);
1212 }
1213 return number_of_bands_;
1214 }
1215 bool metallic =
1217 if (metallic) {
1218 return std::max(static_cast<int>(std::round(1.2 * number_of_electrons_ / 2.0)),
1219 number_of_occupied_bands + 4);
1220 }
1221 return number_of_occupied_bands + 4;
1222 }
1223
1224 /**
1225 * Whether band distribution was abandoned during setup because some rank's
1226 * FFT slab contained no plane waves. Reported in the setup summary, since it
1227 * silently changes the parallel behaviour of the whole run.
1228 */
1230
1232 int band_size = (execution_policy_ != nullptr) ? execution_policy_->band_size() : 1;
1234
1235 /**
1236 * Safety: if band parallelism gave any rank 0 plane waves at any
1237 * k-point, fall back to replicated mode for ALL ranks in the pool.
1238 * This prevents zero-dimension BLAS crashes and asymmetric MPI reductions.
1239 */
1240 if (band_size > 1 && execution_policy_ != nullptr) {
1241 int local_minimum_plane_waves =
1243 ? 1
1244 : *std::min_element(planewave_basis_.kpoint_number_of_plane_waves().begin(),
1246 int global_minimum_plane_waves = local_minimum_plane_waves;
1247 execution_policy_->reduce_band_group_scalar_min(global_minimum_plane_waves);
1248 if (global_minimum_plane_waves == 0) {
1249 log::warn("Band parallelism: some ranks received 0 plane waves. "
1250 "Falling back to replicated G-vectors.");
1252
1253 /**
1254 * The FFT grids were built during generate(), while the group was
1255 * still distributed, so they are MPI grids whose transforms are
1256 * collective across the band group. Disabling band distribution
1257 * turns every group reduction into a no-op and leaves each rank
1258 * holding a replicated copy of the coefficients -- at which point
1259 * ranks legitimately take different paths through Davidson while
1260 * those collective transforms still expect every one of them. The
1261 * first divergence blocks inside FFTW-MPI's transpose.
1262 *
1263 * Rebuild the basis now that group_size() is 1, so the factory
1264 * hands back serial grids each rank drives alone. This repeats the
1265 * FFT planning generate() already did, which is the price of
1266 * discovering the empty slab only after the grids exist.
1267 */
1277 }
1278 }
1279 }
1280
1284
1286 const auto& number_of_plane_waves_vector = planewave_basis_.kpoint_number_of_plane_waves();
1300 /**
1301 * The irreducible count, not this pool's share. kpoint_grid() is the
1302 * distributed grid, so with 8 pools a 60-k-point calculation reported
1303 * 8 here -- next to an "IBZ reduction ... -> 60 k-points" line two
1304 * rows above, which made one of the two look wrong.
1305 *
1306 * Derived from the same assembly the convergence report uses, with a
1307 * one-value-per-state payload: its return size is the global state
1308 * count, so this costs an Allreduce of a few dozen doubles once per
1309 * run. The serial policy returns the input unchanged, which gives the
1310 * local count -- and in serial the local count IS the global one.
1311 */
1313 if (execution_policy_ != nullptr) {
1314 const int local_kpoint_states = summary.number_of_kpoints * number_of_spin_components();
1315 const std::vector<Real> kpoint_state_probe(
1316 static_cast<std::size_t>(local_kpoint_states), 0.0);
1317 const std::size_t global_kpoint_states =
1319 ->assemble_global_kpoint_state_data(kpoint_state_probe,
1320 summary.number_of_kpoints,
1322 1)
1323 .size();
1324 if (number_of_spin_components() > 0) {
1325 summary.number_of_kpoints =
1326 static_cast<int>(global_kpoint_states) / number_of_spin_components();
1327 }
1328 }
1330 number_of_plane_waves_vector.empty()
1331 ? 0
1332 : *std::max_element(number_of_plane_waves_vector.begin(),
1333 number_of_plane_waves_vector.end());
1334 /**
1335 * Each pool holds only its local k-points, so the local max may be less
1336 * than the global max. Reduce across pools to get the true maximum.
1337 */
1338 if (execution_policy_ != nullptr) {
1340 }
1344 summary.xc_functional_name = exchange_correlation_->name();
1345 summary.diagonalizer_name = diagonalizer_->name();
1348 summary.mixer_name = mixer_->name();
1349 summary.mixing_beta = mixing_beta_;
1355 if (execution_policy_ != nullptr) {
1358 /**
1359 * Ranks per pool, not band_size(). The two agree for the
1360 * reciprocal-space split, but band_size() is deliberately 1 once
1361 * bands are distributed -- reporting it there would claim one rank
1362 * per pool on a run using four.
1363 */
1364 summary.band_group_size =
1365 summary.number_of_kpoint_pools > 0
1367 : summary.number_of_processes;
1370 }
1372 }
1373
1375 /**
1376 * Each rank holds the real-space planes of the density it owns: all of
1377 * them unless reciprocal space is split within the pool, in which case
1378 * the local sum is a fraction of the charge -- about half on two ranks,
1379 * which is what this used to report as a deviation. The sum is completed
1380 * within the group before it is compared, so every rank must call this.
1381 */
1382 int local_grid_points = planewave_basis_.fft_rho().local_grid_points();
1383 int total_grid_points = planewave_basis_.fft_rho().total_grid_points();
1384 Real total_charge = 0.0;
1385 for (int spin_index = 0; spin_index < number_of_spin_components(); ++spin_index) {
1386 for (int grid_index = 0; grid_index < local_grid_points; ++grid_index) {
1387 total_charge += charge_density_real_[spin_index][grid_index];
1388 }
1389 }
1390 execution_policy_->reduce_within_group(&total_charge, 1);
1391 total_charge *= crystal_structure().cell_volume() / total_grid_points;
1392 if (execution_policy_->is_root() && std::abs(total_charge - number_of_electrons_) > 0.01) {
1393 log::warn(" Total charge {:.4f} deviates from target {} by >{:.1f}e",
1394 total_charge,
1396 std::abs(total_charge - number_of_electrons_));
1397 }
1398 }
1399};
1400
1401} // namespace q3
Real two_pi_over_lattice_parameter_squared() const
Definition crystal.hpp:199
Real valence_charge(int species_index) const
Definition crystal.hpp:317
Real cell_volume() const
Definition crystal.hpp:193
void compute_derived_quantities()
Definition crystal.hpp:156
int number_of_atoms() const
Definition crystal.hpp:209
Real lattice_parameter() const
Definition crystal.hpp:180
virtual int total_grid_points() const =0
virtual int grid_dimension_1() const =0
virtual bool co_locates_mirrors() const
Definition fft_grid.hpp:113
virtual int grid_dimension_3() const =0
virtual int grid_dimension_2() const =0
virtual int local_grid_points() const =0
int number_of_kpoints() const
const std::array< int, 3 > & grid_dimensions() const
const std::array< int, 3 > & grid_shifts() const
virtual void prepare_hamiltonian()
Definition init.hpp:585
const KohnShamSCFIteratorConfig & config() const
Definition init.hpp:256
std::string smearing_
Definition init.hpp:168
std::unique_ptr< XCFunctional > exchange_correlation_
Definition init.hpp:286
Real diagonalization_threshold() const
Definition init.hpp:301
std::vector< Complex > total_potential_g_
Definition init.hpp:263
std::unique_ptr< Mixer > mixer_
Definition init.hpp:287
std::unique_ptr< SmearingScheme > smearing_scheme_
Definition init.hpp:288
Crystal & crystal_structure() const
Definition init.hpp:219
std::vector< std::vector< Real > > charge_density_real_
Definition init.hpp:261
virtual const KohnShamEnergies & energies() const =0
bool has_preloaded_effective_potential_
Definition init.hpp:281
std::vector< Complex > charge_density_g_
Definition init.hpp:260
KohnShamSCFIteratorConfig config_
Definition init.hpp:159
virtual KohnShamEnergies & energies_mutable()=0
std::vector< std::vector< Real > > real_space_potential_
Definition init.hpp:262
std::string mixing_preconditioning_
Definition init.hpp:166
std::vector< std::vector< Complex > > total_potential_g_by_spin_
Definition init.hpp:274
std::vector< T > & raw()
Returns raw vector (useful for MPI calls that need contiguous block)
Definition matrix.hpp:83
size_t cols() const
Definition matrix.hpp:65
int number_of_g_vectors() const
Definition init.hpp:145
void rho_g_to_r(const std::vector< Complex > &charge_density_g_space, std::vector< Real > &charge_density_real_space) const
Definition init.hpp:328
const std::vector< GVector > & g_vectors() const
Definition init.hpp:148
void initialize_kpoint_data(const Crystal &crystal_structure, const KPointsGrid &kpoint_grid, int band_size=1)
Definition init.hpp:202
void rho_r_to_g(const std::vector< Real > &charge_density_real_space, std::vector< Complex > &charge_density_g_space) const
Definition init.hpp:334
int first_nonzero_g_index() const
Definition init.hpp:151
FFTGrid & fft_rho()
Definition init.hpp:286
const std::vector< int > & g_vector_indices_by_kpoint_index(int kpoint_index) const
Definition init.hpp:259
const std::vector< std::vector< int > > & kpoint_g_vector_indices() const
Definition init.hpp:272
int number_of_plane_waves_by_kpoint_index(int kpoint_index) const
Definition init.hpp:263
FFTGrid & fft_wave()
Definition init.hpp:292
void generate(const FFTGridFactory &fft_grid_factory, const Crystal &crystal_structure, Real wavefunction_energy_cutoff, Real charge_density_energy_cutoff, bool is_gamma_only=false, bool use_measured_fft_planning=true, bool use_exact_wavefunction_grid=false)
Definition init.hpp:58
const std::vector< std::vector< Real > > & kpoint_kinetic_energy_squared() const
Definition init.hpp:280
const std::vector< int > & kpoint_number_of_plane_waves() const
Definition init.hpp:276
bool is_gamma_only() const
Definition init.hpp:157
void symmetrize(const PlanewaveBasis &planewave_basis, std::vector< std::vector< Real > > &charge_density_real, int number_of_spin_components) const
Definition init.hpp:222
void build_shells(const Crystal &crystal_structure, const PlanewaveBasis &planewave_basis)
Precompute the symmetry operations and G-vector shells for the given cell/basis.
Definition init.hpp:66
Planewave Fermi energy calculator — operates on PlanewaveKohnShamQuantumSystem.
Definition init.hpp:31
Real calculate_occupations() override
Compute occupations and return the Fermi energy.
Definition init.hpp:47
void copy_quantum_states_from(const QuantumSystem< PlanewaveKohnShamQuantumState > &source) override
Definition init.hpp:231
Complex wavefunction(int kpoint_index, int band_index, int g_vector_index) const
Definition init.hpp:99
Matrix< Real > eigenvalues_matrix() const
Definition init.hpp:171
Real occupation_by_kpoint_state_index_and_band_index(int kpoint_state_index, int band_index) const
Definition init.hpp:127
void allocate_electronic_states(const std::vector< int > &number_of_plane_waves_per_kpoint)
Definition init.hpp:261
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
PlanewaveKohnShamEnergies & planewave_energies_mutable()
Definition init.hpp:446
const std::vector< std::vector< int > > & g_vector_indices() const
Definition init.hpp:280
const std::optional< Magnetization > & magnetization() const
Definition init.hpp:355
PlanewaveBasis planewave_basis_
Definition init.hpp:389
const PlanewaveKohnShamQuantumSystem & quantum_system() const
Definition init.hpp:223
const KPointsGrid & kpoint_grid() const
Definition init.hpp:209
Real compute_density_residual(const std::vector< std::vector< Real > > &input_density_for_mixing) const override
Definition init.hpp:783
virtual void release_scf_workspace()
Definition init.hpp:109
Real number_of_electrons() const override
Definition init.hpp:322
void initiate_mixer() override
Definition init.hpp:558
std::string diagonalization_method_
Definition init.hpp:367
int number_of_kpoint_states() const override
Definition init.hpp:329
PlanewaveKohnShamQuantumSystem quantum_system_
Definition init.hpp:390
void set_owned_execution_policy(std::unique_ptr< execution::PlanewaveExecutionPolicy > policy)
Definition init.hpp:84
const KPointsGrid * kpoint_grid_
Definition init.hpp:364
void restore_density_at_convergence() override
Definition init.hpp:921
Real effective_charge_density_energy_cutoff() const
Definition init.hpp:1162
Matrix< Real > eigenvalues() const
Definition init.hpp:237
std::unique_ptr< PlanewaveChargeDensityAccumulator > charge_density_accumulator_
Accumulates real-space charge density from wavefunctions each SCF iteration.
Definition init.hpp:399
void set_diagonalization_tolerance(int iteration) override
Definition init.hpp:719
const execution::PlanewaveExecutionPolicy & execution_policy() const
Definition init.hpp:119
void set_diagonalizer(std::unique_ptr< PlanewaveDiagonalizer > diagonalizer)
Definition init.hpp:96
void compute_energy_components() override
Definition init.hpp:981
Real hartree_metric_prefactor() const
Definition init.hpp:794
void update_occupations() override
Definition init.hpp:764
std::unique_ptr< execution::PlanewaveExecutionPolicy > owned_execution_policy_
Definition init.hpp:386
const std::vector< std::vector< Real > > & kinetic_energy_squared() const
Definition init.hpp:287
Real compute_double_counting_correction() const override
Definition init.hpp:925
void warn_if_total_charge_deviates() const
Definition init.hpp:1374
Real magnetization_metric_weight() const
Definition init.hpp:805
PlanewaveFermiEnergyCalculator fermi_energy_calculator_
Definition init.hpp:396
~PlanewaveKohnShamSCFIterator() override=default
Real compute_residual_between(const std::vector< std::vector< Real > > &density_in, const std::vector< std::vector< Real > > &density_out) const
Definition init.hpp:836
void set_diagonalizer_tolerance(Real tolerance) override
Definition init.hpp:759
void construct_density_from_states() override
Definition init.hpp:768
void scale_loaded_charge_density_to_electron_count()
Rescale the loaded density so it integrates to this system's electron count.
Definition init.hpp:168
std::unique_ptr< PlanewaveDiagonalizer > diagonalizer_
Definition init.hpp:395
virtual void on_charge_density_initialized()
Definition init.hpp:1159
std::vector< std::vector< Real > > input_density_for_mixing() const override
Definition init.hpp:662
int number_of_occupied_bands_per_spin() const
Definition init.hpp:336
const std::vector< int > & number_of_plane_waves() const
Definition init.hpp:294
const PlanewaveKohnShamEnergies & planewave_energies() const
Definition init.hpp:443
std::unique_ptr< PlanewaveKohnShamEnergyCalculator > energy_calculator_
Computes band energy and smearing entropy from the occupied Kohn-Sham states.
Definition init.hpp:401
virtual void compute_effective_potential_from_density()=0
const PlanewaveBasis & planewave_basis() const
Definition init.hpp:216
std::optional< int > nscf_override_maximum_iterations_
Definition init.hpp:433
int number_of_plane_waves(int kpoint_index) const
Definition init.hpp:301
PlanewaveKohnShamSCFIterator(KohnShamSCFIteratorConfig config)
Definition init.hpp:54
void log_representation_summary() const
Definition init.hpp:1285
virtual NormConservingPlanewaveKohnShamSCFState result() const =0
std::optional< Real > nscf_override_tolerance_
Definition init.hpp:427
void mix_density(const std::vector< std::vector< Real > > &input_density_for_mixing) override
Definition init.hpp:913
std::vector< std::vector< Real > > convergence_output_density_
Converged output density snapshot, restored once self-consistency is reached.
Definition init.hpp:404
std::optional< Magnetization > magnetization_
Set by report_convergence_details(); see magnetization() above.
Definition init.hpp:361
void symmetrize_density() override
Definition init.hpp:778
Matrix< Real > global_eigenvalues() const
Definition init.hpp:248
Real compute_delta_escf_correction() const override
Definition init.hpp:950
bool loaded_charge_density_fits_grid() const
Definition init.hpp:153
void update_effective_potential() override
Definition init.hpp:666
void load_quantum_system(const PlanewaveKohnShamQuantumSystem &system)
Definition init.hpp:129
std::vector< Real > cached_local_ionic_potential_real_
Cached real-space ionic local potential (constant during SCF, avoids repeated FFTs).
Definition init.hpp:406
virtual const std::vector< Complex > & local_ionic_potential_g() const =0
PlanewaveChargeDensitySymmetrizer density_symmetrizer_
Definition init.hpp:397
void set_owned_kpoint_grid(KPointsGrid kpoint_grid)
Transfer ownership of a KPointsGrid into this iterator.
Definition init.hpp:72
Real eigenvalue(int kpoint_index, int band_index) const
Definition init.hpp:265
void report_convergence_details() override
Definition init.hpp:999
void initialize_representation() override
Definition init.hpp:452
execution::PlanewaveExecutionPolicy * execution_policy_
Definition init.hpp:373
Complex wavefunction(int kpoint_index, int band_index, int g_vector_index) const
Definition init.hpp:230
virtual const std::vector< Complex > & core_charge_density_g() const =0
void report_first_iteration_details() override
Definition init.hpp:994
int number_of_bands() const override
Definition init.hpp:315
Real compute_number_of_electrons() const
Definition init.hpp:1167
const std::vector< int > & g_vector_indices(int kpoint_index) const
Definition init.hpp:308
Real occupation(int kpoint_index, int band_index) const
Definition init.hpp:272
Real compute_local_residual_between(const std::vector< std::vector< Real > > &density_in, const std::vector< std::vector< Real > > &density_out) const
Definition init.hpp:856
void snapshot_output_density() override
Definition init.hpp:917
static void report_convergence_summary(const PlanewaveKohnShamSCFConvergenceSummary &summary)
Definition reporter.hpp:303
static void report_first_iteration(Real total_energy, const PlanewaveKohnShamEnergies &energies)
Definition reporter.hpp:288
static void report_setup_summary(const PlanewaveKohnShamSCFSetupSummary &summary)
Definition reporter.hpp:180
const QuantumSystem< QuantumStateType > * preloaded_quantum_system_
void load_quantum_system(const QuantumSystem< QuantumStateType > &system)
int iteration() const
Definition init.hpp:39
int number_of_spin_components() const
Definition init.hpp:45
virtual int number_of_processes() const =0
virtual int band_distribution_size() const
Definition init.hpp:240
virtual bool is_root() const =0
virtual void reduce_within_group(ComplexValue *data, int number_of_elements) const =0
virtual void reduce_scalar_max(int &value) const =0
virtual void reduce_band_group_scalar_min(int &value) const =0
virtual void reduce_charge_density(std::vector< std::vector< Real > > &charge_density) const =0
virtual void reduce_within_pool_vector(std::vector< Real > &values) const =0
virtual int number_of_pools() const =0
virtual std::vector< Real > assemble_global_kpoint_state_data(const std::vector< Real > &local_data, int local_number_of_physical_kpoints, int number_of_spin_components, int number_of_values_per_state) const =0
virtual void reduce_within_pool_scalar(Real &value) const =0
virtual void reduce_scalar(Real &value) const =0
std::unique_ptr< Preconditioner > create(const std::string &name, const PreconditionerEnvironment &environment)
Create a residual preconditioner by name.
Definition factory.hpp:49
constexpr Real electron_charge_squared_rydberg
Definition constants.hpp:32
constexpr Real two_pi
Definition constants.hpp:25
constexpr Real four_pi
Definition constants.hpp:26
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 smearing_width_threshold
constexpr double initial_eigenvalue_tolerance
double Real
Real compute_ewald(const Crystal &crystal_structure, const PlanewaveBasis &planewave_basis)
std::complex< double > Complex
std::unique_ptr< PlanewaveDiagonalizer > create_planewave_diagonalizer(const std::string &name, const execution::ExecutionPolicy &execution_policy=PlanewaveDavidsonDiagonalizer::default_serial_execution_policy)
Definition factory.hpp:34
const KPointsGrid * kpoint_grid
Definition init.hpp:65
std::string diagonalization_method
Definition init.hpp:69
const execution::ExecutionPolicy * execution_policy
Definition init.hpp:118
int first_nonzero_g_index
Definition init.hpp:39
Real magnetization_metric_weight
Definition init.hpp:38
std::function< void(const std::vector< Complex > &, std::vector< Real > &)> to_real_space
Definition init.hpp:30
int number_of_smooth_g_vectors
Definition init.hpp:41
std::shared_ptr< Preconditioner > preconditioner
Definition init.hpp:37
std::function< void(const std::vector< Real > &, std::vector< Complex > &)> to_reciprocal_space
Definition init.hpp:29
std::function< void(std::vector< Real > &)> global_reduce
Definition init.hpp:42
std::vector< Real > charge_metric_weights
Definition init.hpp:31
int number_of_kpoints
The reporting rank's pool share under MPI, not the irreducible total.
Definition reporter.hpp:102
std::vector< std::vector< Real > > eigenvalues
eigenvalues[kpoint_state_index][band_index] in Rydberg.
Definition reporter.hpp:110
const PlanewaveKohnShamEnergies * energies
Definition reporter.hpp:107
Real total_magnetization
Globally integrated spin-density difference in Bohr magnetons per cell.
Definition reporter.hpp:115
Real absolute_magnetization
Globally integrated absolute spin-density difference in Bohr magnetons per cell.
Definition reporter.hpp:117
int number_of_kpoint_pools
Set from the execution policy so the report can say whose k-points it lists.
Definition reporter.hpp:105
std::vector< KPoint > kpoints
k-points in the same order as eigenvalues rows.
Definition reporter.hpp:112
int davidson_basis_expansion_factor
Davidson working basis as a multiple of the band count; 0 when not iterative.
Definition reporter.hpp:66
bool bands_are_distributed
True when those ranks divide the bands rather than the reciprocal-space coefficients.
Definition reporter.hpp:89
int band_group_size
Ranks sharing a k-point pool, however that pool divides its work.
Definition reporter.hpp:86
Everything any preconditioner needs from the surrounding representation.
Definition factory.hpp:31
Real q0_squared
Screening wavevector squared / vacuum floor, in bohr⁻².
Definition factory.hpp:33
std::vector< Real > g_squared
|G|² per G-vector, in bohr⁻².
Definition factory.hpp:32
std::function< void(const std::vector< Real > &, std::vector< Complex > &)> to_reciprocal_space
Definition factory.hpp:35
std::function< void(std::vector< Real > &)> global_reduce
Sums real-space integrals over the ranks sharing this pool's grid.
Definition factory.hpp:38
std::function< void(const std::vector< Complex > &, std::vector< Real > &)> to_real_space
Definition factory.hpp:36
void log_resident_checkpoint(const std::string &label)
Definition counters.hpp:152
static ProfilingCounters & instance()
Definition counters.hpp:220
void release_unused_heap(const std::string &label)
Definition counters.hpp:169