Loading...
Searching...
No Matches
init.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * UltrasoftPlanewavePseudopotentialKohnShamSCFIterator — ultrasoft plane-wave PSP SCF (layer 4).
5 *
6 * Extends the norm-conserving pseudopotential iterator with the ultrasoft-only
7 * machinery: the projector overlap coefficients, the augmentation charges
8 * added to the smooth density, the effective D-matrix used by the Hamiltonian, and
9 * the smooth (pre-augmentation) charge density that is mixed and snapshotted.
10 */
11
12#include <algorithm>
13#include <chrono>
14#include <vector>
15
17
23
24namespace q3 {
25using namespace q3::blas;
26
29 public:
33
34 [[nodiscard]] const Matrix<Real>& projector_overlap_coefficients() const override {
36 }
37
41 /**
42 * Copy the converged effective D-matrix into the snapshot's owned storage.
43 * Without this, d_matrix_element() falls back to bare_d_matrix_element() which
44 * uses channel indices (0..number_of_channels-1) on the radial d_ion_matrix
45 * (size nbeta × nbeta), causing an out-of-bounds access → SIGSEGV for USPP.
46 */
47 auto& ultrasoft_snapshot =
52 return data;
53 }
54
55 protected:
57
58 /**
59 * The same coefficients per spin channel, un-summed.
60 *
61 * `projector_overlap_coefficients_` above is accumulated over
62 * `kpoint_index_state`, which folds spin in, so by the time it is read the
63 * two channels are indistinguishable. That is what the augmentation charge
64 * on the grid wants — it is a total density — but the PAW one-center terms
65 * need ρ↑ and ρ↓ apart, because exchange-correlation is nonlinear in the
66 * spin density. Kept as a parallel array rather than reshaping the primary
67 * one so every existing ultrasoft consumer is untouched.
68 */
69 std::vector<Matrix<Real>> projector_overlap_coefficients_by_spin_;
71
72 /**
73 * Smooth (pre-augmentation) charge density used by ultrasoft pseudopotential_set,
74 * plus its converged snapshot restored at self-consistency.
75 */
76 std::vector<std::vector<Real>> charge_density_real_smooth_;
77 std::vector<std::vector<Real>> convergence_output_density_smooth_;
78
79 /**
80 * Cached G-space augmented density from construct_density_from_states.
81 * Used by symmetrize_density to avoid a redundant R→G FFT.
82 */
83 std::vector<std::vector<Complex>> cached_augmented_density_g_;
84
85 /**
86 * Access the ultrasoft quantum system (downcast).
87 */
92
94 return static_cast<UltrasoftPlanewaveKohnShamQuantumSystem&>(*quantum_system_snapshot_);
95 }
96
97 /**
98 * The nonlocal D matrix is built per spin channel.
99 *
100 * D_ij = D_ion + ∫v_eff·Q_ij, and for a spin-polarized system v_eff differs
101 * between channels, so D does too. It used to be built once from the spin
102 * average and shared: exact for a non-magnetic cell, and for a magnetic one
103 * it collapsed the moment — nickel with a PAW dataset converged to 1e-4
104 * Bohr magnetons against a PBE moment near 0.64, which the norm-conserving
105 * path reproduces at 0.605. Norm-conserving was never affected because its
106 * D_ion is a constant with no v_eff dependence, which is why Q3's LSDA
107 * validation — all of it norm-conserving — passed and said nothing about
108 * this.
109 *
110 * `nonlocal_potentials_by_spin()` returns one entry per channel, or a
111 * single averaged entry when the per-spin copies are unavailable, so the
112 * unpolarized path is bit-for-bit what it was.
113 */
114
115 /**
116 * The potential each spin channel's D should be built from.
117 *
118 * Falls back to a single entry holding the spin average whenever the
119 * per-spin copies are absent, which keeps every non-spin-polarized path and
120 * every path that predates them behaving exactly as before.
121 */
122 [[nodiscard]] std::vector<std::vector<Complex>> nonlocal_potentials_by_spin() const {
123 if (!total_potential_g_by_spin_.empty())
125 return {total_potential_g_};
126 }
127
128 void prepare_hamiltonian() override {
129 const auto& ultrasoft_system = ultrasoft_quantum_system();
132 ultrasoft_system.ultrasoft_basis(),
134
135 // Wire the D-matrix into the quantum system for d_matrix_element() dispatch
138 }
139
141 auto timer_us_start = std::chrono::high_resolution_clock::now();
143 auto timer_us_base = std::chrono::high_resolution_clock::now();
145 auto timer_us_snap = std::chrono::high_resolution_clock::now();
147 auto timer_us_projector_overlaps = std::chrono::high_resolution_clock::now();
149 auto timer_us_aug = std::chrono::high_resolution_clock::now();
151
152 log::debug(" US density sub-timing:");
154 " base_accumulate: {:.1f} ms",
155 std::chrono::duration<double, std::milli>(timer_us_base - timer_us_start).count());
157 " snapshot_smooth: {:.1f} ms",
158 std::chrono::duration<double, std::milli>(timer_us_snap - timer_us_base).count());
160 " projector_overlaps:{:.1f} ms",
161 std::chrono::duration<double, std::milli>(timer_us_projector_overlaps - timer_us_snap)
162 .count());
164 " augmentation: {:.1f} ms",
165 std::chrono::duration<double, std::milli>(timer_us_aug - timer_us_projector_overlaps)
166 .count());
167 }
168
169 void symmetrize_density() override {
170 /**
171 * If augmentation cached the G-space density, use the G-space symmetrizer
172 * to avoid a redundant R→G FFT. This saves ~100ms per iteration.
173 */
174 if (!cached_augmented_density_g_.empty()) {
180 return;
181 }
182 // Fallback to base class R-space symmetrization
184 }
185
186 std::vector<std::vector<Real>> input_density_for_mixing() const override {
188 }
189
191 for (int spin_index = 0; spin_index < number_of_spin_components(); ++spin_index) {
192 charge_density_real_smooth_[spin_index] = charge_density_real_[spin_index];
193 }
194 }
195
199 for (int spin_index = 0; spin_index < number_of_spin_components(); ++spin_index) {
200 charge_density_real_smooth_[spin_index] = charge_density_real_[spin_index];
201 }
202 }
203
208
213
214 // ─── Ultrasoft density construction ──────────────────────────────
215 /// Allocate and zero the per-atom projector overlap matrix when augmentation is required.
217 const auto& pseudopotential_snapshot = *quantum_system_snapshot_;
218 int number_of_atoms = crystal_structure().number_of_atoms();
219 int maximum_projector_channels = 0;
220 for (int species_index = 0; species_index < crystal_structure().number_of_species();
221 ++species_index) {
222 maximum_projector_channels = std::max(
223 maximum_projector_channels,
224 pseudopotential_snapshot.pseudopotential_basis().number_of_projector_channels(
225 species_index));
226 }
228 number_of_atoms, maximum_projector_channels * maximum_projector_channels, 0.0);
230 for (auto& per_spin : projector_overlap_coefficients_by_spin_)
231 per_spin.assign(
232 number_of_atoms, maximum_projector_channels * maximum_projector_channels, 0.0);
233 }
234
237 for (int spin_index = 0; spin_index < number_of_spin_components(); ++spin_index) {
238 charge_density_real_smooth_[spin_index] = charge_density_real_[spin_index];
239 }
240 }
241
242 /**
243 * Accumulate the per-atom projector overlap coefficients from the bands.
244 *
245 * Uses BLAS zgemm to compute all <beta|psi> overlaps in a single call
246 * instead of per-projector-per-band zdotc calls.
247 */
248 /**
249 * Virtual so PAW can damp the result before it is used.
250 *
251 * The coefficients feed two things — the augmentation charge on the grid and, for
252 * PAW, the on-site D matrix — and the second closes a loop that the
253 * density mixer cannot reach: ρ_ij is recomputed from fresh ψ every step,
254 * so its feedback gain is 1 no matter how small the mixing beta. That is
255 * harmless while the on-site terms are small, and not harmless for a
256 * dataset with a stiff channel, which is why PAW overrides this.
257 */
259 const auto& pseudopotential_snapshot = *quantum_system_snapshot_;
261
262 int number_of_kpoints = kpoint_grid().number_of_kpoints();
263 int number_of_kpoint_states = number_of_kpoints * number_of_spin_components();
264 int total_projectors =
265 pseudopotential_snapshot.pseudopotential_basis().total_number_of_projectors();
266
267 for (int kpoint_index_state = 0; kpoint_index_state < number_of_kpoint_states;
268 ++kpoint_index_state) {
269 int kpoint_index = kpoint_index_state % number_of_kpoints;
270 Real kpoint_weight = kpoint_grid()[kpoint_index].weight;
271 int plane_waves_at_kpoint = number_of_plane_waves(kpoint_index);
272
273 // Use cached KB projectors (computed once in solve_single_particle_states)
274 const auto& kleinman_bylander_projectors =
276
277 // Gather occupied band data and weights
278 std::vector<int> occupied_band_indices;
279 std::vector<Real> occupied_band_weights;
280 for (int band_index = 0; band_index < number_of_bands_; ++band_index) {
282 kpoint_index_state, band_index);
284 continue;
285 }
286 occupied_band_indices.push_back(band_index);
287 occupied_band_weights.push_back(kpoint_weight * occupation);
288 }
289 int number_of_occupied_bands = static_cast<int>(occupied_band_indices.size());
290 if (number_of_occupied_bands == 0) {
291 continue;
292 }
293
294 // Gather wavefunctions into a contiguous matrix: psi(pw, occupied_bands)
295 std::vector<Complex> psi_matrix(static_cast<size_t>(plane_waves_at_kpoint) *
296 number_of_occupied_bands);
297 for (int occupied_index = 0; occupied_index < number_of_occupied_bands;
298 ++occupied_index) {
300 kpoint_index_state, occupied_band_indices[occupied_index]);
301 std::copy(band_data,
302 band_data + plane_waves_at_kpoint,
303 psi_matrix.data() +
304 static_cast<size_t>(occupied_index) * plane_waves_at_kpoint);
305 }
306
307 // Compute projector_overlaps(total_projectors, occupied_bands) = beta^H × psi
308 std::vector<Complex> projector_overlaps(static_cast<size_t>(total_projectors) *
309 number_of_occupied_bands,
310 Complex(0.0, 0.0));
311
313 int double_number_of_plane_waves = 2 * plane_waves_at_kpoint;
314 double alpha_two = 2.0;
315 double double_beta_zero = 0.0;
316
317 std::vector<double> real_projector_overlaps(
318 static_cast<size_t>(total_projectors) * number_of_occupied_bands, 0.0);
319 const double* real_kleinman_bylander_projectors =
320 reinterpret_cast<const double*>(kleinman_bylander_projectors.data());
321 const double* real_psi_matrix = reinterpret_cast<const double*>(psi_matrix.data());
322
323 dgemm_("T",
324 "N",
325 &total_projectors,
326 &number_of_occupied_bands,
327 &double_number_of_plane_waves,
328 &alpha_two,
329 real_kleinman_bylander_projectors,
330 &double_number_of_plane_waves,
331 real_psi_matrix,
332 &double_number_of_plane_waves,
333 &double_beta_zero,
334 real_projector_overlaps.data(),
335 &total_projectors);
336
337 /**
338 * G=0 correction. The 2 Re sum above counts the origin twice, so
339 * the rank whose plane-wave list begins with G = 0 removes one
340 * copy. The density list cannot answer that: it is complete on
341 * every rank, so asking it whether G = 0 comes first says yes on
342 * every rank of a shared pool, and each rank without the origin
343 * subtracted its first stored plane wave instead.
344 */
345 bool has_zero_g_vector = planewave_basis_.gamma_origin_is_local();
346 if (has_zero_g_vector) {
347 for (int occupied_index = 0; occupied_index < number_of_occupied_bands;
348 ++occupied_index) {
349 Complex psi_g0 =
350 psi_matrix[static_cast<size_t>(occupied_index) * plane_waves_at_kpoint];
351 for (int projector_index = 0; projector_index < total_projectors;
352 ++projector_index) {
353 Complex kleinman_bylander_g0 =
354 kleinman_bylander_projectors[projector_index][0];
355 size_t index = static_cast<size_t>(projector_index) +
356 static_cast<size_t>(occupied_index) * total_projectors;
357 real_projector_overlaps[index] -=
358 (std::conj(kleinman_bylander_g0) * psi_g0).real();
359 }
360 }
361 }
362
363 for (size_t index = 0; index < real_projector_overlaps.size(); ++index) {
364 projector_overlaps[index] = Complex(real_projector_overlaps[index], 0.0);
365 }
366 } else {
367 Complex alpha_blas(1.0, 0.0);
368 Complex beta_blas(0.0, 0.0);
369 auto* cast_alpha = reinterpret_cast<const std::complex<double>*>(&alpha_blas);
370 auto* cast_beta_coeff = reinterpret_cast<const std::complex<double>*>(&beta_blas);
371 auto* cast_kb = reinterpret_cast<const std::complex<double>*>(
372 kleinman_bylander_projectors.data());
373 auto* cast_psi = reinterpret_cast<const std::complex<double>*>(psi_matrix.data());
374 auto* cast_projector_overlaps =
375 reinterpret_cast<std::complex<double>*>(projector_overlaps.data());
376
377 zgemm_("C",
378 "N",
379 &total_projectors,
380 &number_of_occupied_bands,
381 &plane_waves_at_kpoint,
382 cast_alpha,
383 cast_kb,
384 &plane_waves_at_kpoint,
385 cast_psi,
386 &plane_waves_at_kpoint,
387 cast_beta_coeff,
388 cast_projector_overlaps,
389 &total_projectors);
390 }
391
392 /**
393 * Band-parallel ranks own disjoint G-vector slabs, so BLAS has
394 * produced only each rank's partial <beta|psi>. Complete those
395 * amplitudes before forming rho_ij = <psi|beta_i><beta_j|psi>.
396 * Reducing rho_ij afterward is not equivalent because it omits
397 * cross-rank products. The later reduce_vector remains the
398 * separate across-pool sum over distinct k-points.
399 */
400 if (execution_policy_ != nullptr) {
401 execution_policy_->reduce_within_group(projector_overlaps.data(),
402 static_cast<int>(projector_overlaps.size()));
403 }
404
405 // Accumulate the projector overlap coefficients from projector_overlaps
406 int current_projector_index = 0;
407 for (int atom_index = 0; atom_index < crystal_structure().number_of_atoms();
408 ++atom_index) {
409 int species_type = crystal_structure().species_type_index(atom_index);
410 int number_of_projectors_on_atom =
411 pseudopotential_snapshot.pseudopotential_basis().number_of_projector_channels(
412 species_type);
413
414 for (int projector_a_index = 0; projector_a_index < number_of_projectors_on_atom;
415 ++projector_a_index) {
416 for (int projector_b_index = 0;
417 projector_b_index < number_of_projectors_on_atom;
418 ++projector_b_index) {
419 Real sum = 0.0;
420 int global_a = current_projector_index + projector_a_index;
421 int global_b = current_projector_index + projector_b_index;
422 for (int occupied_index = 0; occupied_index < number_of_occupied_bands;
423 ++occupied_index) {
424 /**
425 * projector_overlaps is column-major (total_projectors ×
426 * occupied_bands)
427 */
428 Complex projector_overlaps_a =
429 projector_overlaps[static_cast<size_t>(global_a) +
430 static_cast<size_t>(occupied_index) *
431 total_projectors];
432 Complex projector_overlaps_b =
433 projector_overlaps[static_cast<size_t>(global_b) +
434 static_cast<size_t>(occupied_index) *
435 total_projectors];
436 sum +=
437 occupied_band_weights[occupied_index] *
438 std::real(std::conj(projector_overlaps_a) * projector_overlaps_b);
439 }
440 int coefficient_index =
441 projector_a_index * number_of_projectors_on_atom + projector_b_index;
442 projector_overlap_coefficients_[atom_index][coefficient_index] += sum;
443 projector_overlap_coefficients_by_spin_[kpoint_index_state /
444 number_of_kpoints][atom_index]
445 [coefficient_index] += sum;
446 }
447 }
448 current_projector_index += number_of_projectors_on_atom;
449 }
450 }
451
452 // Reduce projector overlap coefficients across all ranks
453 if (execution_policy_ != nullptr) {
455 for (auto& per_spin : projector_overlap_coefficients_by_spin_)
456 execution_policy_->reduce_vector(per_spin.raw());
457 }
458 }
459
460 /**
461 * Add the compensation charge n̂ to the smooth density, per spin channel.
462 *
463 * The augmentation charge is built from ρ_ij, and ρ_ij is spin-resolved, so
464 * each channel gets its own n̂ from its own occupancies. That matters far
465 * more than it sounds: in a 3d metal almost the entire moment lives inside
466 * the augmentation spheres, so how n̂ is divided between the channels *is*
467 * the magnetization.
468 *
469 * This used to add one total n̂ and split it by the ratio of the smooth
470 * densities, which forces the on-site polarization to match the
471 * interstitial polarization — small, and nearly unpolarized. Nickel came
472 * out at 1e-4 Bohr magnetons against a PBE moment near 0.6.
473 */
475 const auto& ultrasoft_system = ultrasoft_quantum_system();
476
477 // Reuse cached structure factors from the D-matrix computation
478 const auto* structure_factor_cache =
480 ? nullptr
482
483 int number_of_spins = number_of_spin_components();
484 cached_augmented_density_g_.resize(number_of_spins);
485 for (int spin_index = 0; spin_index < number_of_spins; ++spin_index) {
486 std::vector<Complex> density_g;
487 planewave_basis_.rho_r_to_g(charge_density_real_[spin_index], density_g);
488
489 /**
490 * `projector_overlap_coefficients_by_spin_` is empty only on paths
491 * that never accumulated it; falling back to the spin-summed array
492 * then reproduces the old single-channel behaviour rather than
493 * adding nothing.
494 */
495 const Matrix<Real>& occupancies =
496 spin_index < static_cast<int>(projector_overlap_coefficients_by_spin_.size())
499
500 ultrasoft_system.ultrasoft_basis().add_augmentation_charges(
501 crystal_structure(), occupancies, density_g, structure_factor_cache);
502
503 planewave_basis_.rho_g_to_r(density_g, charge_density_real_[spin_index]);
504 cached_augmented_density_g_[spin_index] = std::move(density_g);
505 }
506 }
507};
508
509} // namespace q3
int number_of_species() const
Definition crystal.hpp:212
int species_type_index(int atom_index) const
Definition crystal.hpp:273
int number_of_atoms() const
Definition crystal.hpp:209
int number_of_kpoints() const
virtual void restore_density_at_convergence()=0
const KohnShamSCFIteratorConfig & config() const
Definition init.hpp:256
virtual void symmetrize_density()=0
std::vector< Complex > total_potential_g_
Definition init.hpp:263
Crystal & crystal_structure() const
Definition init.hpp:219
std::vector< std::vector< Real > > charge_density_real_
Definition init.hpp:261
virtual void snapshot_output_density()=0
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
void assign(size_t rows, size_t cols, const T &initial_value)
Definition matrix.hpp:38
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
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
bool gamma_origin_is_local() const
Definition init.hpp:172
bool is_gamma_only() const
Definition init.hpp:157
void symmetrize_from_g_space(const PlanewaveBasis &planewave_basis, std::vector< std::vector< Complex > > &charge_density_g_components, std::vector< std::vector< Real > > &charge_density_real, int number_of_spin_components) const
Symmetrize the spin-resolved real-space charge density in place.
Definition init.hpp:173
Real occupation_by_kpoint_state_index_and_band_index(int kpoint_state_index, int band_index) const
Definition init.hpp:127
Complex * wavefunction_band_data(int kpoint_state_index, int band_index)
Definition init.hpp:107
PlanewaveBasis planewave_basis_
Definition init.hpp:389
const KPointsGrid & kpoint_grid() const
Definition init.hpp:209
int number_of_kpoint_states() const override
Definition init.hpp:329
PlanewaveKohnShamQuantumSystem quantum_system_
Definition init.hpp:390
void warn_if_total_charge_deviates() const
Definition init.hpp:1374
const std::vector< int > & number_of_plane_waves() const
Definition init.hpp:294
PlanewaveChargeDensitySymmetrizer density_symmetrizer_
Definition init.hpp:397
execution::PlanewaveExecutionPolicy * execution_policy_
Definition init.hpp:373
Real occupation(int kpoint_index, int band_index) const
Definition init.hpp:272
std::shared_ptr< NormConservingPlanewaveKohnShamQuantumSystem > quantum_system_snapshot_
Definition init.hpp:372
std::vector< q3::Matrix< Complex > > cached_kleinman_bylander_projectors_
Cached KB projectors per k-point (reused by the projector overlap accumulation).
Definition init.hpp:408
NormConservingPlanewaveKohnShamSCFState result() const override
Definition init.hpp:83
int number_of_spin_components() const
Definition init.hpp:45
const std::vector< std::vector< Complex > > & cached_structure_factors() const
Definition init.hpp:431
void compute_by_spin(const Crystal &crystal_structure, const PlanewaveBasis &planewave_basis, const UltrasoftPlanewaveBasis &ultrasoft_pseudopotential_basis, const std::vector< std::vector< Complex > > &total_potential_g_by_spin)
Definition init.hpp:57
void set_nonlocal_effective_potential(const UltrasoftPlanewaveEffectivePotential &nonlocal_effective_potential)
Definition init.hpp:86
std::vector< std::vector< Real > > charge_density_real_smooth_
Definition init.hpp:76
void reset_projector_overlap_coefficients()
Allocate and zero the per-atom projector overlap matrix when augmentation is required.
Definition init.hpp:216
std::vector< Matrix< Real > > projector_overlap_coefficients_by_spin_
Definition init.hpp:69
std::vector< std::vector< Real > > input_density_for_mixing() const override
Definition init.hpp:186
const UltrasoftPlanewaveKohnShamQuantumSystem & ultrasoft_quantum_system() const
Definition init.hpp:88
NormConservingPlanewaveKohnShamSCFState result() const override
Definition init.hpp:38
std::vector< std::vector< Complex > > cached_augmented_density_g_
Definition init.hpp:83
UltrasoftPlanewavePseudopotentialKohnShamSCFIterator(KohnShamSCFIteratorConfig config)
Definition init.hpp:30
UltrasoftPlanewaveKohnShamQuantumSystem & mutable_ultrasoft_quantum_system()
Definition init.hpp:93
std::vector< std::vector< Real > > convergence_output_density_smooth_
Definition init.hpp:77
const Matrix< Real > & projector_overlap_coefficients() const override
Definition init.hpp:34
std::vector< std::vector< Complex > > nonlocal_potentials_by_spin() const
Definition init.hpp:122
UltrasoftPlanewaveEffectivePotential nonlocal_effective_potential_
Definition init.hpp:70
virtual void reduce_within_group(ComplexValue *data, int number_of_elements) const =0
virtual void reduce_vector(std::vector< Real > &values) const =0
void dgemm_(const char *transa, const char *transb, const int *m, const int *n, const int *k, const double *alpha, const double *a, const int *lda, const double *b, const int *ldb, const double *beta, double *c, const int *ldc)
void zgemm_(const char *transa, const char *transb, const int *m, const int *n, const int *k, const std::complex< double > *alpha, const std::complex< double > *a, const int *lda, const std::complex< double > *b, const int *ldb, const std::complex< double > *beta, std::complex< double > *c, const int *ldc)
void debug(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:179
constexpr double occupation_threshold
double Real
std::complex< double > Complex
std::vector< Matrix< Real > > projector_overlap_coefficients_by_spin
Definition init.hpp:59
std::shared_ptr< NormConservingPlanewaveKohnShamQuantumSystem > quantum_system_snapshot_
Definition init.hpp:44