Loading...
Searching...
No Matches
init.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * PlanewaveWavefunctionProjectionCalculator — atomic-orbital projections for PDOS / Löwdin
5 * population analysis.
6 *
7 * Ties together the shared atomic-orbital G-space construction
8 * (atomic_orbital_builder.hpp) and Löwdin orthogonalization
9 * (lowdin_orthogonalization.hpp) into the actual projection weights QE's
10 * projwfc.x calls "proj": for each k-point, each band, and each ortho-atomic
11 * orbital i, computes |⟨φ'_i|S|ψ_nk⟩|². These weights are exactly what's
12 * needed for PDOS energy binning (Python side, mirroring q3.workflows.dos's
13 * existing Gaussian broadening) and Löwdin charges (Σ_nk f_nk·w_k·proj_i).
14 *
15 * S is the overlap operator of the generalized eigenproblem the run solves.
16 * It is the identity for norm-conserving pseudopotentials, where the two
17 * metrics coincide and the weight is the plain |⟨φ'_i|ψ_nk⟩|². It is not for
18 * ultrasoft and PAW: those relax norm conservation, so charge lives in the
19 * augmentation term S = 1 + Σ_ij q_ij |β_i⟩⟨β_j| as well as in the pseudo
20 * wavefunction, the Kohn-Sham states are S-orthonormal rather than
21 * orthonormal, and a projection taken in the plain metric conserves no charge
22 * -- the Löwdin charges stop summing to the valence electron count
23 * (Vanderbilt, PRB 41, 7892 (1990); Blöchl, PRB 50, 17953 (1994); Kresse &
24 * Joubert, PRB 59, 1758 (1999)). The atomic orbitals are Löwdin-orthogonalized
25 * in the same metric, ⟨φ_i|S|φ_j⟩, for the same reason. This is the convention
26 * QE's projwfc.x projects in, and the one Q3's own ortho-atomic DFT+U
27 * projectors already use.
28 *
29 * Which metric a run needs is not this class's decision: it is handed the
30 * operator (null for S = I), because building the ultrasoft one needs the live
31 * SCF state and a k-point's Kleinman-Bylander projector matrix. The same
32 * split the Wannier trial-orbital projections use.
33 *
34 * One instance is built once per calculation (tabulates the atomic-orbital
35 * radial table), then queried per k-point -- the atomic orbitals and their
36 * Löwdin orthogonalization are k-point-dependent (different structure
37 * factors / |k+G|), so must be rebuilt per k-point, unlike the projection
38 * weights which are the whole point of calling this per k-point.
39 */
40
41#include <vector>
42
53
54namespace q3 {
55
57 public:
59 const PseudopotentialSet& pseudopotential_set)
60 : orbital_labels_(enumerate_atomic_orbitals(crystal_structure, pseudopotential_set)) {
61 atomic_wavefunction_table_ = tabulate_atomic_wavefunctions_in_q_space(
62 pseudopotential_set, crystal_structure.cell_volume(), q_spacing_, number_of_q_points_);
63 }
64
65 /**
66 * Stable per-orbital (atom, shell, l, m) labels, in the same order as every
67 * projection row returned below.
68 */
69 [[nodiscard]] const std::vector<AtomicOrbitalLabel>& orbital_labels() const {
70 return orbital_labels_;
71 }
72
73 [[nodiscard]] int number_of_orbitals() const {
74 return static_cast<int>(orbital_labels_.size());
75 }
76
77 /**
78 * Compute |<phi'_i|S|psi_nk>|^2 for one k-point: projections[band_index][orbital_index].
79 * `kpoint_state_index` indexes quantum_system's flattened (spin, kpoint) storage,
80 * matching PlanewaveKohnShamQuantumSystem::wavefunction_band_data's convention.
81 * `overlap_operator` is null for norm-conserving pseudopotentials (S = I);
82 * ultrasoft and PAW callers pass create_overlap_operator()'s result for this
83 * k-point.
84 */
85 [[nodiscard]] std::vector<std::vector<Real>>
87 int kpoint_state_index,
88 int number_of_plane_waves,
89 int number_of_bands,
90 const std::vector<int>& g_vector_indices,
91 const Crystal& crystal_structure,
92 const KPointsGrid& kpoint_grid,
93 const PlanewaveBasis& planewave_basis,
94 const PseudopotentialSet& pseudopotential_set,
95 const PlanewaveKohnShamQuantumSystem& quantum_system,
96 const OverlapOperator* overlap_operator) const {
97 std::vector<std::vector<Complex>> orbitals;
99 number_of_plane_waves,
100 g_vector_indices,
101 crystal_structure,
102 kpoint_grid,
103 planewave_basis,
104 pseudopotential_set,
105 atomic_wavefunction_table_,
106 q_spacing_,
107 number_of_q_points_,
108 orbitals);
110 orbitals, compute_orbital_overlap_matrix_in_s_metric(orbitals, overlap_operator));
111
112 /**
113 * S is Hermitian, so <phi'_i|S|psi> = <S phi'_i|psi>: applying it once
114 * to the orthogonalized orbitals leaves the band loop below the same
115 * contraction it performs when S is the identity, rather than a second
116 * operator application per band.
117 */
118 std::vector<std::vector<Complex>> orbitals_in_s_metric =
119 apply_overlap_operator_to_orbitals(orbitals, overlap_operator);
120
121 int orbital_count = static_cast<int>(orbitals.size());
122 std::vector<std::vector<Real>> projections(number_of_bands,
123 std::vector<Real>(orbital_count, 0.0));
124
125 for (int band_index = 0; band_index < number_of_bands; band_index++) {
126 const Complex* band_data =
127 quantum_system.wavefunction_band_data(kpoint_state_index, band_index);
128 for (int orbital_index = 0; orbital_index < orbital_count; orbital_index++) {
129 Complex overlap(0.0, 0.0);
130 for (int g_index = 0; g_index < number_of_plane_waves; g_index++) {
131 overlap += std::conj(orbitals_in_s_metric[orbital_index][g_index]) *
132 band_data[g_index];
133 }
134 projections[band_index][orbital_index] = std::norm(overlap);
135 }
136 }
137 return projections;
138 }
139
140 private:
141 std::vector<AtomicOrbitalLabel> orbital_labels_;
142 std::vector<std::vector<std::vector<Real>>> atomic_wavefunction_table_;
143 Real q_spacing_ = 0.01;
144 int number_of_q_points_ = 900;
145};
146
147} // namespace q3
Real cell_volume() const
Definition crystal.hpp:193
Complex * wavefunction_band_data(int kpoint_state_index, int band_index)
Definition init.hpp:107
PlanewaveWavefunctionProjectionCalculator(const Crystal &crystal_structure, const PseudopotentialSet &pseudopotential_set)
Definition init.hpp:58
const std::vector< AtomicOrbitalLabel > & orbital_labels() const
Definition init.hpp:69
std::vector< std::vector< Real > > compute_projections_at_kpoint(int kpoint_index, int kpoint_state_index, int number_of_plane_waves, int number_of_bands, const std::vector< int > &g_vector_indices, const Crystal &crystal_structure, const KPointsGrid &kpoint_grid, const PlanewaveBasis &planewave_basis, const PseudopotentialSet &pseudopotential_set, const PlanewaveKohnShamQuantumSystem &quantum_system, const OverlapOperator *overlap_operator) const
Definition init.hpp:86
void build_atomic_orbitals_g_space(int kpoint_index, int number_of_plane_waves, const std::vector< int > &g_vector_indices, const Crystal &crystal_structure, const KPointsGrid &kpoint_grid, const PlanewaveBasis &planewave_basis, const PseudopotentialSet &pseudopotential_set, const std::vector< std::vector< std::vector< Real > > > &atomic_wavefunction_table, Real q_spacing, int number_of_q_points, std::vector< std::vector< Complex > > &orbitals)
double Real
std::vector< std::vector< Complex > > apply_overlap_operator_to_orbitals(const std::vector< std::vector< Complex > > &orbitals, const OverlapOperator *overlap_operator)
void lowdin_orthogonalize_orbitals(std::vector< std::vector< Complex > > &orbitals, std::vector< Complex > overlap_column_major)
std::vector< Complex > compute_orbital_overlap_matrix_in_s_metric(const std::vector< std::vector< Complex > > &orbitals, const OverlapOperator *overlap_operator)
std::vector< AtomicOrbitalLabel > enumerate_atomic_orbitals(const Crystal &crystal_structure, const PseudopotentialSet &pseudopotential_set)
std::vector< std::vector< std::vector< Real > > > tabulate_atomic_wavefunctions_in_q_space(const PseudopotentialSet &pseudopotential_set, Real cell_volume, Real q_spacing, int number_of_q_points)
std::complex< double > Complex