Loading...
Searching...
No Matches
direct.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * PlanewaveDirectDiagonalizer — dense LAPACK-based planewave eigenvalue solver.
5 *
6 * Builds the full Hamiltonian matrix H(G,G') and solves via LAPACK's
7 * ZHEEV/ZHEGV. O(N_pw³) cost, but exact — used as the reference solver
8 * for small systems and for validating iterative solvers.
9 */
10
11#include <algorithm>
12#include <cassert>
13#include <cmath>
14#include <complex>
15#include <vector>
16
17#include "io/logger.hpp"
25
26namespace q3 {
27
28/**
29 * Dense (direct) diagonalization using LAPACK.
30 * The OverlapOperator selects the standard (NC) or generalized (US) eigenproblem.
31 */
33 public:
35
38 : subspace_diagonalizer_(SubspaceDiagonalizerFactory::create(execution_policy))
39 , execution_policy_(execution_policy) {}
40 void solve(int kpoint_index_state,
41 int kpoint_index,
42 int number_of_plane_waves,
43 int number_of_bands,
44 const std::vector<int>& g_vector_indices,
45 const std::vector<Real>& kinetic_energy_squared,
46 const std::vector<Real>& real_space_potential,
47 const Crystal& crystal_structure,
48 const KPointsGrid& kpoint_grid,
49 const PlanewaveBasis& planewave_basis,
50 const NormConservingPlanewaveKohnShamQuantumSystem& pseudopotential_quantum_system,
51 PlanewaveKohnShamQuantumSystem& quantum_system) override {
52 // Build full Hamiltonian matrix H[i + j*number_of_plane_waves] (column-major for LAPACK)
53 std::vector<Complex> hamiltonian_matrix(number_of_plane_waves * number_of_plane_waves,
54 Complex(0.0, 0.0));
55
56 // Kinetic energy (diagonal)
57 for (int g_index = 0; g_index < number_of_plane_waves; g_index++) {
58 hamiltonian_matrix[g_index + g_index * number_of_plane_waves] =
59 kinetic_energy_squared[g_index];
60 }
61
62 // Local potential via FFT
63 {
64 int number_of_grid_points = planewave_basis.fft_rho().total_grid_points();
65 int fft_dimension_x = planewave_basis.fft_rho().grid_dimension_1();
66 int fft_dimension_y = planewave_basis.fft_rho().grid_dimension_2();
67 int fft_dimension_z = planewave_basis.fft_rho().grid_dimension_3();
68 for (int plane_wave_index_jg = 0; plane_wave_index_jg < number_of_plane_waves;
69 plane_wave_index_jg++) {
70 std::vector<Complex> psi_g(number_of_grid_points, Complex(0.0, 0.0));
71 const auto& g_vector_j =
72 planewave_basis.g_vectors()[g_vector_indices[plane_wave_index_jg]];
73 int miller_x_j = static_cast<int>(std::round(g_vector_j.miller.x));
74 int miller_y_j = static_cast<int>(std::round(g_vector_j.miller.y));
75 int miller_z_j = static_cast<int>(std::round(g_vector_j.miller.z));
76 if (std::abs(miller_x_j) <= fft_dimension_x / 2 &&
77 std::abs(miller_y_j) <= fft_dimension_y / 2 &&
78 std::abs(miller_z_j) <= fft_dimension_z / 2) {
79 int fft_grid_index = planewave_basis.fft_rho().g_vector_to_fft_grid_index(
80 miller_x_j, miller_y_j, miller_z_j);
81 psi_g[fft_grid_index] = Complex(1.0, 0.0);
82 }
83
84 planewave_basis.fft_rho().inverse(psi_g.data());
85
86 for (int grid_point_index = 0; grid_point_index < number_of_grid_points;
87 grid_point_index++) {
88 psi_g[grid_point_index] = Complex(real_space_potential[grid_point_index], 0.0) *
89 psi_g[grid_point_index];
90 }
91
92 planewave_basis.fft_rho().forward(psi_g.data());
93
94 for (int g_index = 0; g_index < number_of_plane_waves; g_index++) {
95 const auto& g_vector_i = planewave_basis.g_vectors()[g_vector_indices[g_index]];
96 int miller_x_i = static_cast<int>(std::round(g_vector_i.miller.x));
97 int miller_y_i = static_cast<int>(std::round(g_vector_i.miller.y));
98 int miller_z_i = static_cast<int>(std::round(g_vector_i.miller.z));
99 if (std::abs(miller_x_i) <= fft_dimension_x / 2 &&
100 std::abs(miller_y_i) <= fft_dimension_y / 2 &&
101 std::abs(miller_z_i) <= fft_dimension_z / 2) {
102 int fft_grid_index_i = planewave_basis.fft_rho().g_vector_to_fft_grid_index(
103 miller_x_i, miller_y_i, miller_z_i);
104 hamiltonian_matrix[g_index + plane_wave_index_jg * number_of_plane_waves] +=
105 psi_g[fft_grid_index_i];
106 }
107 }
108 }
109 }
110
111 /**
112 * Which spin channel this call belongs to; see the same derivation in
113 * the Davidson solver. The nonlocal D is per channel for ultrasoft and
114 * PAW, and `real_space_potential` above is already this channel's.
115 */
116 int spin_channel = kpoint_grid.number_of_kpoints() > 0
117 ? kpoint_index_state / kpoint_grid.number_of_kpoints()
118 : 0;
119
120 // Nonlocal potential contribution — use cached projectors if available
121 Matrix<Complex> computed_projectors;
122 const Matrix<Complex>* kleinman_bylander_potential_ptr = nullptr;
123 if (kpoint_index < static_cast<int>(cached_projector_map_.size()) &&
124 cached_projector_map_[kpoint_index] != nullptr) {
125 kleinman_bylander_potential_ptr = cached_projector_map_[kpoint_index];
126 } else {
127 Vector3D kpoint_coordinates = kpoint_grid[kpoint_index].coordinates;
129 kpoint_coordinates,
130 crystal_structure,
131 planewave_basis,
132 pseudopotential_quantum_system.pseudopotential_basis().pseudopotential_set(),
133 pseudopotential_quantum_system.pseudopotential_basis().beta_function_table(),
134 g_vector_indices,
135 number_of_plane_waves,
136 pseudopotential_quantum_system.pseudopotential_basis().total_number_of_projectors(),
137 computed_projectors);
138 kleinman_bylander_potential_ptr = &computed_projectors;
139 }
140 const auto& kleinman_bylander_potential = *kleinman_bylander_potential_ptr;
141
142 int combined_projector_index = 0;
143 for (int atom_index = 0; atom_index < crystal_structure.number_of_atoms(); atom_index++) {
144 int species_type_index = crystal_structure.species_type_index(atom_index);
145 int number_of_projectors =
146 pseudopotential_quantum_system.pseudopotential_basis().number_of_projectors(
147 species_type_index);
148
149 int projector_index_start = combined_projector_index;
150 std::vector<int> projector_angular_momenta, projector_magnetic_projections,
151 projector_beta_indices;
152 for (int beta_index = 0; beta_index < number_of_projectors; beta_index++) {
153 int angular_momentum =
154 pseudopotential_quantum_system.pseudopotential_basis().beta_angular_momentum(
155 species_type_index, beta_index);
156 for (int magnetic_projection : harmonic_order_sequence(angular_momentum)) {
157 projector_angular_momenta.push_back(angular_momentum);
158 projector_magnetic_projections.push_back(magnetic_projection);
159 projector_beta_indices.push_back(beta_index);
160 combined_projector_index++;
161 }
162 }
163 int number_of_projectors_for_atom = static_cast<int>(projector_angular_momenta.size());
164
165 for (int projector_index_a = 0; projector_index_a < number_of_projectors_for_atom;
166 projector_index_a++) {
167 for (int projector_index_b = 0; projector_index_b < number_of_projectors_for_atom;
168 projector_index_b++) {
169 Real d_matrix_element_value =
170 pseudopotential_quantum_system.d_matrix_element(atom_index,
171 species_type_index,
172 projector_index_a,
173 projector_index_b,
174 spin_channel);
175 if (std::abs(d_matrix_element_value) <
177 continue;
178
179 int combined_projector_index_a = projector_index_start + projector_index_a;
180 int combined_projector_index_b = projector_index_start + projector_index_b;
181
182 for (int g_index = 0; g_index < number_of_plane_waves; g_index++) {
183 for (int plane_wave_index_jg = 0;
184 plane_wave_index_jg < number_of_plane_waves;
185 plane_wave_index_jg++) {
186 hamiltonian_matrix[g_index +
187 plane_wave_index_jg * number_of_plane_waves] +=
188 d_matrix_element_value *
189 kleinman_bylander_potential[combined_projector_index_a][g_index] *
190 std::conj(kleinman_bylander_potential[combined_projector_index_b]
191 [plane_wave_index_jg]);
192 }
193 }
194 }
195 }
196 }
197
198 // The overlap operator selects the standard (NC, S=I) or generalized (US) eigenproblem.
199 auto overlap_operator = create_overlap_operator(crystal_structure,
200 pseudopotential_quantum_system,
201 kleinman_bylander_potential,
202 execution_policy_);
203
204 std::vector<Complex> overlap_matrix;
205 overlap_operator->build_dense_matrix(number_of_plane_waves, overlap_matrix);
206
207 /**
208 * Reduce Hamiltonian and overlap matrices across band group.
209 * No-op when group_size == 1 (serial); MPI_Allreduce when distributed.
210 */
211 execution_policy_.reduce_within_group(hamiltonian_matrix.data(),
212 number_of_plane_waves * number_of_plane_waves);
213 execution_policy_.reduce_within_group(overlap_matrix.data(),
214 number_of_plane_waves * number_of_plane_waves);
215
216 std::vector<Real> lapack_eigenvalues(number_of_plane_waves);
217 std::vector<Complex> eigenvectors(number_of_plane_waves * number_of_plane_waves);
218
219 /**
220 * Direct diagonalizer solves the FULL matrix problem.
221 * We reuse the SubspaceDiagonalizer interface here as well.
222 * NOTE: always pass is_gamma_only=false because the direct diag builds a standard
223 * complex Hermitian H matrix via explicit G-space convolution — the imaginary
224 * part of H[i,j] = V(G_i - G_j) is non-zero even for gamma-only calculations.
225 * The real-symmetric solver (dsygv) would discard the imaginary part and produce
226 * incorrect eigenvalues.
227 */
228 subspace_diagonalizer_->solve(number_of_plane_waves,
229 hamiltonian_matrix.data(),
230 overlap_matrix.data(),
231 lapack_eigenvalues.data(),
232 eigenvectors.data(),
233 false);
234
235 for (int band_index = 0; band_index < number_of_bands; band_index++) {
236 quantum_system.set_eigenvalue(
237 kpoint_index_state, band_index, lapack_eigenvalues[band_index]);
238 Complex* band_data =
239 quantum_system.wavefunction_band_data(kpoint_index_state, band_index);
240 for (int g_index = 0; g_index < number_of_plane_waves; g_index++) {
241 band_data[g_index] = eigenvectors[band_index * number_of_plane_waves + g_index];
242 }
243 }
244 }
245
246 [[nodiscard]] std::string name() const override {
247 return "direct";
248 }
249
250 void set_cached_projectors(int kpoint_index, const q3::Matrix<Complex>* projectors) override {
251 if (kpoint_index >= static_cast<int>(cached_projector_map_.size())) {
252 cached_projector_map_.resize(kpoint_index + 1, nullptr);
253 }
254 cached_projector_map_[kpoint_index] = projectors;
255 }
256
257 private:
258 std::unique_ptr<SubspaceDiagonalizer> subspace_diagonalizer_;
259 const execution::ExecutionPolicy& execution_policy_;
260 std::vector<const q3::Matrix<Complex>*> cached_projector_map_;
261};
262
263} // namespace q3
int species_type_index(int atom_index) const
Definition crystal.hpp:273
int number_of_atoms() const
Definition crystal.hpp:209
virtual int g_vector_to_fft_grid_index(int miller_1, int miller_2, int miller_3) const =0
virtual void forward(Complex *data) const =0
virtual int total_grid_points() const =0
virtual int grid_dimension_1() const =0
virtual int grid_dimension_3() const =0
virtual void inverse(Complex *data) const =0
virtual int grid_dimension_2() const =0
int number_of_kpoints() const
const BetaFunctionTable & beta_function_table() const
Definition init.hpp:284
int total_number_of_projectors() const
Definition init.hpp:181
int number_of_projectors(int species_type) const
Definition init.hpp:188
int beta_angular_momentum(int species_type, int projector_index) const
Definition init.hpp:195
const PseudopotentialSet & pseudopotential_set() const
Definition init.hpp:156
const NormConservingPlanewaveBasis & pseudopotential_basis() const
Definition init.hpp:65
virtual Real d_matrix_element(int atom_index, int species_type, int projector_index_i, int projector_index_j, int spin=0) const
Definition init.hpp:84
const std::vector< GVector > & g_vectors() const
Definition init.hpp:148
FFTGrid & fft_rho()
Definition init.hpp:286
std::string name() const override
Definition direct.hpp:246
void set_cached_projectors(int kpoint_index, const q3::Matrix< Complex > *projectors) override
Definition direct.hpp:250
PlanewaveDirectDiagonalizer(const execution::ExecutionPolicy &execution_policy=default_serial_execution_policy)
Definition direct.hpp:36
void solve(int kpoint_index_state, int kpoint_index, int number_of_plane_waves, int number_of_bands, const std::vector< int > &g_vector_indices, const std::vector< Real > &kinetic_energy_squared, const std::vector< Real > &real_space_potential, const Crystal &crystal_structure, const KPointsGrid &kpoint_grid, const PlanewaveBasis &planewave_basis, const NormConservingPlanewaveKohnShamQuantumSystem &pseudopotential_quantum_system, PlanewaveKohnShamQuantumSystem &quantum_system) override
Definition direct.hpp:40
static const execution::SerialExecutionPolicy default_serial_execution_policy
Definition direct.hpp:34
void set_eigenvalue(int kpoint_state_index, int band_index, Real value)
Definition init.hpp:121
Complex * wavefunction_band_data(int kpoint_state_index, int band_index)
Definition init.hpp:107
virtual void reduce_within_group(ComplexValue *data, int number_of_elements) const =0
constexpr double d_matrix_element_threshold
double Real
std::unique_ptr< OverlapOperator > create_overlap_operator(const Crystal &crystal_structure, const NormConservingPlanewaveKohnShamQuantumSystem &quantum_system, const Matrix< Complex > &kleinman_bylander_potential, const execution::ExecutionPolicy &execution_policy)
Definition factory.hpp:23
void compute_kleinman_bylander_projectors(const Vector3D &k_crystal, const Crystal &crystal_structure, const PlanewaveBasis &planewave_basis, const PseudopotentialSet &pseudopotential_set, const BetaFunctionTable &beta_function_table, const std::vector< int > &g_vector_indices, int number_of_plane_waves, int total_number_of_projectors, Matrix< Complex > &kleinman_bylander_potential)
Definition projector.hpp:36
std::complex< double > Complex
std::vector< int > harmonic_order_sequence(int degree)
Definition harmonics.hpp:40