Loading...
Searching...
No Matches
init.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * PlanewaveWannierCapture — assembles a WannierInputs from
5 * a converged plane-wave calculation's k-point grid and band data.
6 *
7 * Ties together three kernels: KPointNeighborShells (b-vectors and weights
8 * for the grid), PlanewaveBlochOverlapCalculator (M_mn(k,b)), and
9 * PlanewaveTrialOrbitalProjectionCalculator (A_mn(k) and, from it, the
10 * per-band projectabilities p_nk = sum_m |A_mn(k)|^2 that projectability
11 * disentanglement needs). None of those kernels know about each other or
12 * about WannierInputs's layout; this class is the seam that does.
13 *
14 * The trial orbitals arrive as a PlanewaveLocalOrbitalSet -- the
15 * pseudopotentials' atomic wavefunctions, hydrogenic orbitals at explicit
16 * centres, or anything else -- so there is ONE assembly path for every
17 * source of orbitals. build() covers norm-conserving pseudopotentials
18 * (S = I); build_ultrasoft() adds the ultrasoft/PAW augmentation term,
19 * threading one set of per-k-point Kleinman-Bylander projectors and
20 * OverlapOperators through both the S-metric projections and the Bloch
21 * overlap's augmentation contribution. Both entry points hand the same
22 * private assembly the same arguments plus, for ultrasoft, an augmentation
23 * context; nothing is written twice.
24 *
25 * The builder refuses, rather than approximates, in the cases where it
26 * would otherwise produce plausible-looking but wrong matrices: a
27 * spin-polarized quantum system (the record has no per-spin layout and
28 * would label one channel's matrices as the whole system), Gamma-only
29 * storage (half the G-sphere; the overlap kernel would silently see half of
30 * every state), a k-point set that is not the full grid (k+b is resolved
31 * onto another grid point by exact integer indexing), and a trial set that
32 * is empty or larger than the band count (the gauge is an SVD of the
33 * bands-by-orbitals block and cannot have more Wannier functions than
34 * bands). Putting these here rather than at the call site means every
35 * caller -- the NSCF hook, a test, a future SCF-side hook -- gets them.
36 *
37 * Takes the plane-wave state as explicit arguments (crystal, basis,
38 * pseudopotential set, k-point grid, per-k-point G-vector lists, and a
39 * PlanewaveKohnShamQuantumSystem for wavefunctions/eigenvalues/Fermi energy)
40 * rather than a live SCF/NSCF iterator reference, so it is usable both from
41 * the NSCF hook and from a unit test that builds a standalone quantum
42 * system directly.
43 *
44 * Spin-unpolarized only for now: kpoint_state_index is taken equal to the
45 * physical kpoint_index throughout, which the spin refusal above guarantees.
46 */
47
48#include <array>
49#include <complex>
50#include <cstddef>
51#include <memory>
52#include <stdexcept>
53#include <string>
54#include <utility>
55#include <vector>
56
73
74namespace q3 {
75
77 public:
78 PlanewaveWannierCapture(const Crystal& crystal_structure,
79 const PlanewaveBasis& planewave_basis,
80 const PseudopotentialSet& pseudopotential_set)
81 : crystal_structure_(crystal_structure)
82 , planewave_basis_(planewave_basis)
83 , pseudopotential_set_(pseudopotential_set) {
84 global_miller_lookup_.build(planewave_basis.g_vectors());
85 }
86
87 /**
88 * Norm-conserving pseudopotentials, S = I.
89 *
90 * `g_vector_indices_by_kpoint[kpoint_index]` and `quantum_system` must
91 * come from the same calculation -- the G-vector list a k-point's
92 * wavefunction coefficients are stored against, not a fresh
93 * recomputation, exactly as the kernels already require of their own
94 * callers. `quantum_system` must hold `number_of_bands` bands at every
95 * one of `kpoint_grid`'s physical k-points.
96 */
97 [[nodiscard]] WannierInputs
98 build(const KPointsGrid& kpoint_grid,
99 int grid_dimension_x,
100 int grid_dimension_y,
101 int grid_dimension_z,
102 int number_of_bands,
103 const std::vector<std::vector<int>>& g_vector_indices_by_kpoint,
104 const PlanewaveKohnShamQuantumSystem& quantum_system,
105 const PlanewaveLocalOrbitalSet& trial_orbitals,
106 int first_band_index = 0) const {
107 return assemble(kpoint_grid,
108 {grid_dimension_x, grid_dimension_y, grid_dimension_z},
109 number_of_bands,
110 g_vector_indices_by_kpoint,
111 quantum_system,
112 trial_orbitals,
113 nullptr,
114 first_band_index);
115 }
116
117 /**
118 * Ultrasoft/PAW: the same record with the augmentation term in M_mn(k,b)
119 * and the S metric in A_mn(k). `quantum_system` must have been through
120 * UltrasoftPlanewaveKohnShamQuantumSystem::init() so ultrasoft_basis()
121 * is populated.
122 */
123 [[nodiscard]] WannierInputs
124 build_ultrasoft(const KPointsGrid& kpoint_grid,
125 int grid_dimension_x,
126 int grid_dimension_y,
127 int grid_dimension_z,
128 int number_of_bands,
129 const std::vector<std::vector<int>>& g_vector_indices_by_kpoint,
130 const UltrasoftPlanewaveKohnShamQuantumSystem& quantum_system,
131 const PlanewaveLocalOrbitalSet& trial_orbitals,
132 const execution::PlanewaveExecutionPolicy& execution_policy,
133 int first_band_index = 0) const {
134 const int number_of_kpoints = kpoint_grid.number_of_kpoints();
135 if (static_cast<int>(g_vector_indices_by_kpoint.size()) != number_of_kpoints) {
136 throw std::invalid_argument(
137 "PlanewaveWannierCapture::build_ultrasoft: "
138 "g_vector_indices_by_kpoint must have one entry per physical k-point.");
139 }
140
141 /**
142 * Both consumers (the projections' overlap operator and the Bloch
143 * overlap's augmentation term) need every k-point's Kleinman-Bylander
144 * projector matrix -- a (k, neighbour) pair needs the neighbour's
145 * too -- so compute the whole set once up front rather than once
146 * for a k-point's own turn and again whenever another k-point's
147 * iteration needs it as a neighbour.
148 */
149 AugmentationContext augmentation{quantum_system.ultrasoft_basis(),
150 quantum_system,
151 execution_policy,
152 std::vector<Matrix<Complex>>(number_of_kpoints)};
153 for (int kpoint_index = 0; kpoint_index < number_of_kpoints; kpoint_index++) {
155 kpoint_grid[kpoint_index].coordinates,
156 crystal_structure_,
157 planewave_basis_,
158 pseudopotential_set_,
159 augmentation.ultrasoft_basis.beta_function_table(),
160 g_vector_indices_by_kpoint[kpoint_index],
161 static_cast<int>(g_vector_indices_by_kpoint[kpoint_index].size()),
162 augmentation.ultrasoft_basis.total_number_of_projectors(),
163 augmentation.kb_projectors_by_kpoint[kpoint_index]);
164 }
165
166 return assemble(kpoint_grid,
167 {grid_dimension_x, grid_dimension_y, grid_dimension_z},
168 number_of_bands,
169 g_vector_indices_by_kpoint,
170 quantum_system,
171 trial_orbitals,
172 &augmentation,
173 first_band_index);
174 }
175
176 private:
177 /**
178 * Where each trial orbital was placed, Cartesian in bohr: the frame and
179 * unit of the b-vectors, so the localiser can fix every diagonal phase's
180 * branch from a position that is known rather than from a gauge that is
181 * not yet trusted. The orbital set is the only thing that knows this.
182 */
183 [[nodiscard]] std::vector<Vector3D>
184 trial_orbital_centers_bohr(const PlanewaveLocalOrbitalSet& trial_orbitals) const {
185 /// r = f1 a1 + f2 a2 + f3 a3 is r = A^T f for a lattice whose rows are a1, a2, a3.
186 const Matrix3x3 cartesian_from_crystal =
187 crystal_structure_.lattice_vectors_bohr().transpose();
188 std::vector<Vector3D> centers_bohr;
189 for (const Vector3D& center_crystal : trial_orbitals.centers_crystal()) {
190 centers_bohr.push_back(cartesian_from_crystal * center_crystal);
191 }
192 return centers_bohr;
193 }
194
195 /// What the ultrasoft/PAW path carries beyond the norm-conserving one.
196 struct AugmentationContext {
197 const UltrasoftPlanewaveBasis& ultrasoft_basis;
198 const UltrasoftPlanewaveKohnShamQuantumSystem& quantum_system;
199 const execution::PlanewaveExecutionPolicy& execution_policy;
200 std::vector<Matrix<Complex>> kb_projectors_by_kpoint;
201 };
202
203 [[nodiscard]] WannierInputs
204 assemble(const KPointsGrid& kpoint_grid,
205 std::array<int, 3> grid_dimensions,
206 int number_of_bands,
207 const std::vector<std::vector<int>>& g_vector_indices_by_kpoint,
208 const PlanewaveKohnShamQuantumSystem& quantum_system,
209 const PlanewaveLocalOrbitalSet& trial_orbitals,
210 const AugmentationContext* augmentation,
211 int first_band_index) const {
212 const int number_of_kpoints = kpoint_grid.number_of_kpoints();
213 if (static_cast<int>(g_vector_indices_by_kpoint.size()) != number_of_kpoints) {
214 throw std::invalid_argument(
215 "PlanewaveWannierCapture: g_vector_indices_by_kpoint must have one entry "
216 "per physical k-point.");
217 }
218 if (quantum_system.number_of_spin_components != 1) {
219 throw std::invalid_argument(
220 "PlanewaveWannierCapture does not support spin-polarized systems yet: the "
221 "interface record has no per-spin layout, so the matrices would describe one spin "
222 "channel while claiming to describe the system.");
223 }
224 if (planewave_basis_.is_gamma_only()) {
225 throw std::invalid_argument(
226 "PlanewaveWannierCapture does not support Gamma-only storage: it keeps "
227 "half the G-sphere and reconstructs the rest by conjugation, which the overlap "
228 "kernel does not undo, so every overlap would be taken over half a state.");
229 }
230 const int expected_kpoints = grid_dimensions[0] * grid_dimensions[1] * grid_dimensions[2];
231 if (number_of_kpoints != expected_kpoints) {
232 throw std::invalid_argument(
233 "PlanewaveWannierCapture needs the FULL, unreduced " +
234 std::to_string(grid_dimensions[0]) + "x" + std::to_string(grid_dimensions[1]) +
235 "x" + std::to_string(grid_dimensions[2]) + " grid (" +
236 std::to_string(expected_kpoints) + " k-points) but was given " +
237 std::to_string(number_of_kpoints) +
238 ": k+b is resolved onto another grid point by exact integer indexing, which a "
239 "symmetry-reduced set cannot satisfy.");
240 }
241 const int number_of_trial_orbitals = trial_orbitals.number_of_orbitals();
242 if (number_of_trial_orbitals == 0) {
243 throw std::invalid_argument(
244 "PlanewaveWannierCapture: the trial orbital set is empty (" +
245 trial_orbitals.description() + ").");
246 }
247 if (first_band_index < 0) {
248 throw std::invalid_argument(
249 "PlanewaveWannierCapture: the first band index must not be negative.");
250 }
251 if (first_band_index + number_of_bands > quantum_system.number_of_bands()) {
252 throw std::invalid_argument(
253 "PlanewaveWannierCapture: bands " + std::to_string(first_band_index + 1) + " to " +
254 std::to_string(first_band_index + number_of_bands) +
255 " were asked for but the calculation computed only " +
256 std::to_string(quantum_system.number_of_bands()) +
257 ". Raise the run's number_of_bands to at least the range's last band.");
258 }
259 if (number_of_trial_orbitals > number_of_bands) {
260 throw std::invalid_argument(
261 "PlanewaveWannierCapture: " + trial_orbitals.description() +
262 " were given but only " + std::to_string(number_of_bands) +
263 " bands are available; the gauge cannot have more Wannier functions than bands. "
264 "Either request at least " +
265 std::to_string(number_of_trial_orbitals) +
266 " bands or choose fewer trial orbitals (control.interpolation.projection_centers "
267 "selects explicit centres in place of the pseudo-atomic set).");
268 }
269
270 WannierInputs record;
271 record.kpoint_grid_dimensions = grid_dimensions;
272 record.kpoint_coordinates.reserve(number_of_kpoints);
273 for (const KPoint& kpoint : kpoint_grid.kpoints()) {
274 record.kpoint_coordinates.push_back(kpoint.coordinates);
275 }
276
277 KPointNeighborShells shells;
278 shells.compute(crystal_structure_.reciprocal_vectors_bohr(),
279 grid_dimensions[0],
280 grid_dimensions[1],
281 grid_dimensions[2]);
282 record.neighbor_directions = shells.neighbor_directions();
283 const int number_of_directions = static_cast<int>(record.neighbor_directions.size());
284
285 record.number_of_bands = number_of_bands;
286 record.number_of_trial_orbitals = number_of_trial_orbitals;
287 record.trial_orbital_centers_bohr = trial_orbital_centers_bohr(trial_orbitals);
288 record.fermi_energy_rydberg = quantum_system.fermi_energy();
289 record.number_of_spin_components = 1;
290 record.eigenvalues_rydberg.assign(number_of_kpoints,
291 std::vector<Real>(number_of_bands, 0.0));
292 record.projectabilities.assign(number_of_kpoints, std::vector<Real>(number_of_bands, 0.0));
293 record.projection_matrices.assign(number_of_kpoints, {});
294 record.overlap_matrices.assign(number_of_kpoints, {});
295
296 PlanewaveTrialOrbitalProjectionCalculator projection_calculator(trial_orbitals);
297 PlanewaveBlochOverlapCalculator bloch_overlap_calculator(planewave_basis_,
298 global_miller_lookup_);
299
300 for (int kpoint_index = 0; kpoint_index < number_of_kpoints; kpoint_index++) {
301 for (int band_index = 0; band_index < number_of_bands; band_index++) {
302 record.eigenvalues_rydberg[kpoint_index][band_index] =
303 quantum_system.eigenvalue(kpoint_index, first_band_index + band_index);
304 }
305
306 // ── A_mn(k) and the projectabilities it defines ─────────────
307 std::unique_ptr<OverlapOperator> overlap_operator;
308 if (augmentation != nullptr) {
309 overlap_operator =
310 create_overlap_operator(crystal_structure_,
311 augmentation->quantum_system,
312 augmentation->kb_projectors_by_kpoint[kpoint_index],
313 augmentation->execution_policy);
314 }
315 std::vector<std::vector<Complex>> projections =
316 projection_calculator.compute_projections_at_kpoint(
317 kpoint_index,
318 kpoint_index,
319 static_cast<int>(g_vector_indices_by_kpoint[kpoint_index].size()),
320 number_of_bands,
321 g_vector_indices_by_kpoint[kpoint_index],
322 crystal_structure_,
323 kpoint_grid,
324 planewave_basis_,
325 quantum_system,
326 overlap_operator.get(),
327 first_band_index);
328
329 std::vector<Complex>& packed_projections = record.projection_matrices[kpoint_index];
330 packed_projections.assign(static_cast<std::size_t>(number_of_bands) *
331 number_of_trial_orbitals,
332 Complex(0.0, 0.0));
333 for (int band_index = 0; band_index < number_of_bands; band_index++) {
334 Real projectability = 0.0;
335 for (int orbital_index = 0; orbital_index < number_of_trial_orbitals;
336 orbital_index++) {
337 const Complex amplitude = projections[band_index][orbital_index];
338 packed_projections[static_cast<std::size_t>(band_index) +
339 static_cast<std::size_t>(orbital_index) * number_of_bands] =
340 amplitude;
341 projectability += std::norm(amplitude);
342 }
343 record.projectabilities[kpoint_index][band_index] = projectability;
344 }
345
346 // ── M_mn(k,b) over every neighbour direction ─────────────────
347 std::vector<std::vector<Complex>>& overlaps_at_kpoint =
348 record.overlap_matrices[kpoint_index];
349 overlaps_at_kpoint.resize(number_of_directions);
350 for (int direction_index = 0; direction_index < number_of_directions;
351 direction_index++) {
352 const KPointNeighborDirection& direction =
353 record.neighbor_directions[direction_index];
354 const ResolvedKPointNeighbor resolved =
356 direction.grid_step,
357 record.kpoint_grid_dimensions,
358 kpoint_grid.kpoints());
359 const int neighbor_kpoint_index = resolved.neighbor_kpoint_index;
360 auto band_at_kpoint = [&](int band_index) {
361 return quantum_system.wavefunction_band_data(kpoint_index,
362 first_band_index + band_index);
363 };
364 auto band_at_neighbor = [&](int band_index) {
365 return quantum_system.wavefunction_band_data(neighbor_kpoint_index,
366 first_band_index + band_index);
367 };
368
369 BlochOverlapResult result = bloch_overlap_calculator.compute(
370 g_vector_indices_by_kpoint[kpoint_index],
371 number_of_bands,
372 band_at_kpoint,
373 g_vector_indices_by_kpoint[neighbor_kpoint_index],
374 number_of_bands,
375 band_at_neighbor,
376 resolved.periodic_image_shift);
377
378 if (augmentation != nullptr) {
379 bloch_overlap_calculator.add_augmentation_contribution(
380 result,
381 crystal_structure_,
382 augmentation->ultrasoft_basis,
383 augmentation->kb_projectors_by_kpoint[kpoint_index],
384 static_cast<int>(g_vector_indices_by_kpoint[kpoint_index].size()),
385 number_of_bands,
386 band_at_kpoint,
387 augmentation->kb_projectors_by_kpoint[neighbor_kpoint_index],
388 static_cast<int>(g_vector_indices_by_kpoint[neighbor_kpoint_index].size()),
389 number_of_bands,
390 band_at_neighbor,
391 direction.reciprocal_lattice_translation);
392 }
393 overlaps_at_kpoint[direction_index] = std::move(result.values);
394 }
395 }
396
397 return record;
398 }
399
400 const Crystal& crystal_structure_;
401 const PlanewaveBasis& planewave_basis_;
402 const PseudopotentialSet& pseudopotential_set_;
403 MillerIndexLookup global_miller_lookup_;
404};
405
406} // namespace q3
Matrix3x3 reciprocal_vectors_bohr() const
Definition crystal.hpp:331
Matrix3x3 lattice_vectors_bohr() const
Definition crystal.hpp:324
static ResolvedKPointNeighbor resolve_neighbor(int starting_kpoint_index, const IntegerVector3D &grid_step, const std::array< int, 3 > &grid_dimensions, const std::vector< KPoint > &physical_kpoints)
int number_of_kpoints() const
void build(const std::vector< GVector > &g_vectors)
const std::vector< GVector > & g_vectors() const
Definition init.hpp:148
bool is_gamma_only() const
Definition init.hpp:157
WannierInputs build_ultrasoft(const KPointsGrid &kpoint_grid, int grid_dimension_x, int grid_dimension_y, int grid_dimension_z, int number_of_bands, const std::vector< std::vector< int > > &g_vector_indices_by_kpoint, const UltrasoftPlanewaveKohnShamQuantumSystem &quantum_system, const PlanewaveLocalOrbitalSet &trial_orbitals, const execution::PlanewaveExecutionPolicy &execution_policy, int first_band_index=0) const
Definition init.hpp:124
PlanewaveWannierCapture(const Crystal &crystal_structure, const PlanewaveBasis &planewave_basis, const PseudopotentialSet &pseudopotential_set)
Definition init.hpp:78
WannierInputs build(const KPointsGrid &kpoint_grid, int grid_dimension_x, int grid_dimension_y, int grid_dimension_z, int number_of_bands, const std::vector< std::vector< int > > &g_vector_indices_by_kpoint, const PlanewaveKohnShamQuantumSystem &quantum_system, const PlanewaveLocalOrbitalSet &trial_orbitals, int first_band_index=0) const
Definition init.hpp:98
const UltrasoftPlanewaveBasis & ultrasoft_basis() const
Definition init.hpp:59
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
3×3 matrix stored row-major: row i = mat[i]
Matrix3x3 transpose() const
Transpose.