Quantum 3xplorer (Q3)
An AI-native quantum simulation engine
Loading...
Searching...
No Matches
overlaps.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * PlanewaveBlochOverlapCalculator — M_mn(k,b) = <u_mk|u_n,k+b>, the
5 * neighbor overlap between Bloch periodic parts that Wannier localization
6 * and disentanglement (Marzari & Vanderbilt, PRB 56, 12847 (1997)) are
7 * built from.
8 *
9 * The plane-wave part exploits that b is always an exact grid-step
10 * multiple (KPointNeighborShells::resolve_neighbor()): k+b equals some
11 * OTHER physical k-point k' on the same grid plus an integer
12 * reciprocal-lattice (Miller-index) shift G0, so
13 *
14 * c_{n,k+b}(G) = c_{n,k'}(G + G0)
15 *
16 * exactly -- no interpolation, no recomputation of anything at an
17 * off-grid k. M_mn(k,b) = sum_G conj(c_mk(G)) * c_{n,k'}(G + G0) is then a
18 * plain dot product over k's own G-set, matched Miller-index-by-Miller-index
19 * against k' 's G-set via the shared MillerIndexLookup, once per starting
20 * G-vector -- both codes' G-sets live in the same global charge-density
21 * sphere (PlanewaveBasis::g_vectors()), which is what makes this matching
22 * well-defined and MillerIndexLookup reusable across every (k,b) pair.
23 *
24 * For norm-conserving pseudopotentials S = I, so this plane-wave dot
25 * product is the whole answer: compute() alone. add_augmentation_contribution()
26 * adds the ultrasoft/PAW term -- Q_ij(b) between the beta projectors at k
27 * and k' -- on top of an already-computed compute() result.
28 *
29 * Normalization (resolved empirically, see this file's unit tests):
30 * UltrasoftPlanewaveBasis::compute_q_g_single()'s general (nonzero-argument)
31 * branch is normalized for its existing charge-density consumer
32 * (add_augmentation_charges(), via the identically-normalized
33 * precompute_q_functions_g() table -- neither includes a cell-volume
34 * factor), not for the S-operator/overlap convention this augmentation
35 * term needs (Q_ij = the cell-size-independent real-space integral
36 * integral Q_ij(r) d^3r, exactly what the function's own b=0 special
37 * branch returns). Measured on a real Si ultrasoft pseudopotential: the
38 * general branch is smaller than the special branch by exactly the cell
39 * volume, to 8 significant figures -- so add_augmentation_contribution()
40 * multiplies compute_q_g_single()'s result by cell_volume(). This is
41 * unconditionally correct for real Wannier neighbor directions (b is
42 * never exactly zero there), but would be wrong applied to the b=0
43 * special branch, so this method refuses b_cartesian = (0,0,0).
44 */
45
46#include <complex>
47#include <stdexcept>
48#include <unordered_map>
49#include <vector>
50
60
61namespace q3 {
62
63/**
64 * Per-band-pair Bloch overlaps for one (k, k') pair, column-major (LAPACK
65 * convention -- band_index_at_kpoint varies fastest, matching how the
66 * localizer's dense linear algebra and the WannierInputs it feeds
67 * both expect it):
68 * values[band_index_at_kpoint + band_index_at_neighbor * number_of_bands_at_kpoint].
69 * `missing_g_vector_count` is the number of k-side plane waves whose
70 * shifted Miller triple fell outside the shared charge-density sphere, or
71 * inside it but outside k' 's own selected G-set -- a real, expected
72 * outcome at the cutoff-sphere edge (see the file header), never an
73 * error; the caller decides whether to log or gate on it.
74 */
75struct [[nodiscard]] BlochOverlapResult {
76 std::vector<Complex> values;
77 int number_of_bands_at_kpoint = 0;
78 int number_of_bands_at_neighbor = 0;
79 int missing_g_vector_count = 0;
80
81 [[nodiscard]] const Complex& operator()(int band_index_at_kpoint,
82 int band_index_at_neighbor) const {
83 return values[static_cast<std::size_t>(band_index_at_neighbor) * number_of_bands_at_kpoint +
84 band_index_at_kpoint];
85 }
86};
87
89 public:
90 /**
91 * `global_miller_lookup` must already be built over
92 * `planewave_basis.g_vectors()` -- the same lookup is reused across
93 * every (k,b) pair in a calculation, so building it is the caller's
94 * responsibility, not this calculator's.
95 */
96 explicit PlanewaveBlochOverlapCalculator(const PlanewaveBasis& planewave_basis,
97 const MillerIndexLookup& global_miller_lookup)
98 : planewave_basis_(planewave_basis), global_miller_lookup_(global_miller_lookup) {}
99
100 /**
101 * Compute M_mn(k,b) for one (k, neighbor) pair.
102 *
103 * `wavefunction_at_kpoint(band_index)` and
104 * `wavefunction_at_neighbor(band_index)` return a pointer to that
105 * band's plane-wave coefficients, contiguous over its own
106 * `g_vector_indices_*` list (the PlanewaveKohnShamQuantumSystem
107 * convention) -- passed as callables rather than a quantum system
108 * reference so this kernel stays independent of the SCF/NSCF state
109 * shape and is directly unit-testable against synthetic data.
110 */
111 template <typename WavefunctionAtKpoint, typename WavefunctionAtNeighbor>
112 [[nodiscard]] BlochOverlapResult compute(const std::vector<int>& g_vector_indices_at_kpoint,
113 int number_of_bands_at_kpoint,
114 WavefunctionAtKpoint wavefunction_at_kpoint,
115 const std::vector<int>& g_vector_indices_at_neighbor,
116 int number_of_bands_at_neighbor,
117 WavefunctionAtNeighbor wavefunction_at_neighbor,
118 const IntegerVector3D& periodic_image_shift) const {
119 const std::vector<GVector>& global_g_vectors = planewave_basis_.g_vectors();
120 int number_of_plane_waves_at_kpoint = static_cast<int>(g_vector_indices_at_kpoint.size());
121 int number_of_plane_waves_at_neighbor =
122 static_cast<int>(g_vector_indices_at_neighbor.size());
123
124 /**
125 * Local position within the neighbor's own G-set, keyed by the
126 * GLOBAL charge-density-sphere index -- built once per (k,b) pair
127 * here; a caller driving many pairs against the same neighbor may
128 * cache this externally, but correctness does not depend on it.
129 */
130 std::unordered_map<int, int> neighbor_local_position_by_global_index;
131 neighbor_local_position_by_global_index.reserve(number_of_plane_waves_at_neighbor);
132 for (int local_index = 0; local_index < number_of_plane_waves_at_neighbor; local_index++) {
133 neighbor_local_position_by_global_index[g_vector_indices_at_neighbor[local_index]] =
134 local_index;
135 }
136
137 /**
138 * For every plane wave in k's own set, resolve which local
139 * position (if any) in the neighbor's set holds the same physical
140 * plane wave once the periodic-image shift is applied.
141 */
142 std::vector<int> neighbor_local_position_for_kpoint_plane_wave(
143 number_of_plane_waves_at_kpoint, -1);
144 int missing_count = 0;
145 for (int g_index = 0; g_index < number_of_plane_waves_at_kpoint; g_index++) {
146 const IntegerVector3D& miller =
147 global_g_vectors[g_vector_indices_at_kpoint[g_index]].miller;
148 IntegerVector3D shifted_miller(miller.x + periodic_image_shift.x,
149 miller.y + periodic_image_shift.y,
150 miller.z + periodic_image_shift.z);
151 int global_index_of_shifted = global_miller_lookup_.find(shifted_miller);
152 if (global_index_of_shifted < 0) {
153 missing_count++;
154 continue;
155 }
156 auto iterator = neighbor_local_position_by_global_index.find(global_index_of_shifted);
157 if (iterator == neighbor_local_position_by_global_index.end()) {
158 missing_count++;
159 continue;
160 }
161 neighbor_local_position_for_kpoint_plane_wave[g_index] = iterator->second;
162 }
163
164 BlochOverlapResult result;
165 result.number_of_bands_at_kpoint = number_of_bands_at_kpoint;
166 result.number_of_bands_at_neighbor = number_of_bands_at_neighbor;
167 result.missing_g_vector_count = missing_count;
168 result.values.assign(static_cast<std::size_t>(number_of_bands_at_kpoint) *
169 number_of_bands_at_neighbor,
170 Complex(0.0, 0.0));
171
172 std::vector<const Complex*> band_data_at_kpoint(number_of_bands_at_kpoint);
173 for (int band_index = 0; band_index < number_of_bands_at_kpoint; band_index++) {
174 band_data_at_kpoint[band_index] = wavefunction_at_kpoint(band_index);
175 }
176 std::vector<const Complex*> band_data_at_neighbor(number_of_bands_at_neighbor);
177 for (int band_index = 0; band_index < number_of_bands_at_neighbor; band_index++) {
178 band_data_at_neighbor[band_index] = wavefunction_at_neighbor(band_index);
179 }
180
181 for (int band_index_at_neighbor = 0; band_index_at_neighbor < number_of_bands_at_neighbor;
182 band_index_at_neighbor++) {
183 for (int band_index_at_kpoint = 0; band_index_at_kpoint < number_of_bands_at_kpoint;
184 band_index_at_kpoint++) {
185 Complex overlap(0.0, 0.0);
186 for (int g_index = 0; g_index < number_of_plane_waves_at_kpoint; g_index++) {
187 int neighbor_local_position =
188 neighbor_local_position_for_kpoint_plane_wave[g_index];
189 if (neighbor_local_position < 0) {
190 continue;
191 }
192 overlap +=
193 std::conj(band_data_at_kpoint[band_index_at_kpoint][g_index]) *
194 band_data_at_neighbor[band_index_at_neighbor][neighbor_local_position];
195 }
196 result.values[static_cast<std::size_t>(band_index_at_neighbor) *
197 number_of_bands_at_kpoint +
198 band_index_at_kpoint] = overlap;
199 }
200 }
201 return result;
202 }
203
204 /**
205 * Add the ultrasoft/PAW augmentation contribution to an already-computed
206 * NC-only `result`, in place:
207 *
208 * M_mn(k,b) += sum_I sum_ij <psi_mk|beta_i^I(k)> * Q_ij^I(b) *
209 * e^{-i*b.tau_I} * <beta_j^I(k+b)|psi_n,k+b>
210 *
211 * `projectors_at_kpoint`/`projectors_at_neighbor` are
212 * compute_kleinman_bylander_projectors()'s output at k and at the
213 * neighbor k' -- already carry each their own atomic structure factor
214 * e^{-i(k+G).tau_I}, so only the *translation* phase for Q_ij (which is
215 * defined atom-origin-centred, like compute_q_g_single itself) needs to
216 * be applied here. `b_cartesian` is the *unwrapped* translation (i.e.
217 * KPointNeighborDirection::reciprocal_lattice_translation, not folded
218 * by the periodic-image shift used for the plane-wave G-matching in
219 * compute() above), a physical wavevector in Bohr^-1 (tpiba-scaled --
220 * built from Crystal::reciprocal_vectors_bohr(), NOT the same
221 * convention as the raw GVector::g_cart field: g_cart is left
222 * unscaled by tpiba precisely so that structure-factor code elsewhere
223 * in this codebase can multiply it by an explicit 2*pi against a
224 * dimensionless alat-fraction position; b_cartesian already carries
225 * that 2*pi, so its own translation phase below multiplies the
226 * position by the plain lattice parameter instead, not by 2*pi again
227 * -- doing both would double the factor). See
228 * atomic_position_alat's own definition below for the matching
229 * position-side convention.
230 *
231 * `b_cartesian` must not be exactly (0,0,0) -- see the cell-volume
232 * scaling note below; every real Wannier neighbor direction
233 * (KPointNeighborShells::neighbor_directions()) already satisfies
234 * this, since the origin is never itself a shell member.
235 */
236 template <typename WavefunctionAtKpoint, typename WavefunctionAtNeighbor>
238 const Crystal& crystal_structure,
239 const UltrasoftPlanewaveBasis& ultrasoft_basis,
240 const Matrix<Complex>& projectors_at_kpoint,
241 int number_of_plane_waves_at_kpoint,
242 int number_of_bands_at_kpoint,
243 WavefunctionAtKpoint wavefunction_at_kpoint,
244 const Matrix<Complex>& projectors_at_neighbor,
245 int number_of_plane_waves_at_neighbor,
246 int number_of_bands_at_neighbor,
247 WavefunctionAtNeighbor wavefunction_at_neighbor,
248 const Vector3D& b_cartesian) const {
250 throw std::invalid_argument(
251 "PlanewaveBlochOverlapCalculator::add_augmentation_contribution: |b_cartesian| "
252 "is below compute_q_g_single's zero-vector threshold, where it takes its b=0 "
253 "special branch. That branch is already correctly scaled and must not be "
254 "multiplied by cell_volume(), which this method does unconditionally for its "
255 "(always nonzero, in real use) general-branch case.");
256 }
257 int total_number_of_projectors = projectors_at_kpoint.rows();
258
259 // amplitude_at_kpoint[projector][band] = <beta_i^I(k)|psi_mk>
260 Matrix<Complex> amplitude_at_kpoint(static_cast<std::size_t>(total_number_of_projectors),
261 number_of_bands_at_kpoint);
262 for (int band_index = 0; band_index < number_of_bands_at_kpoint; band_index++) {
263 const Complex* band_data = wavefunction_at_kpoint(band_index);
264 for (int projector_index = 0; projector_index < total_number_of_projectors;
265 projector_index++) {
266 Complex sum(0.0, 0.0);
267 const Complex* projector_row = projectors_at_kpoint[projector_index];
268 for (int g_index = 0; g_index < number_of_plane_waves_at_kpoint; g_index++) {
269 sum += std::conj(projector_row[g_index]) * band_data[g_index];
270 }
271 amplitude_at_kpoint[projector_index][band_index] = sum;
272 }
273 }
274
275 // amplitude_at_neighbor[projector][band] = <beta_j^I(k')|psi_n,k'>
276 Matrix<Complex> amplitude_at_neighbor(static_cast<std::size_t>(total_number_of_projectors),
277 number_of_bands_at_neighbor);
278 for (int band_index = 0; band_index < number_of_bands_at_neighbor; band_index++) {
279 const Complex* band_data = wavefunction_at_neighbor(band_index);
280 for (int projector_index = 0; projector_index < total_number_of_projectors;
281 projector_index++) {
282 Complex sum(0.0, 0.0);
283 const Complex* projector_row = projectors_at_neighbor[projector_index];
284 for (int g_index = 0; g_index < number_of_plane_waves_at_neighbor; g_index++) {
285 sum += std::conj(projector_row[g_index]) * band_data[g_index];
286 }
287 amplitude_at_neighbor[projector_index][band_index] = sum;
288 }
289 }
290
291 Real two_pi_over_lattice_parameter = crystal_structure.two_pi_over_lattice_parameter();
292
293 /**
294 * compute_q_g_single()'s general (nonzero-argument) branch is
295 * normalized for its existing charge-density consumer
296 * (add_augmentation_charges() / precompute_q_functions_g(), which
297 * use the identical interpolate_augmentation_function()-based formula with no
298 * volume factor of their own) -- not for the S-operator/overlap
299 * convention this augmentation term needs, where Q_ij must be the
300 * cell-size-independent real-space integral
301 * augmentation_overlap_integrals(i,j) = integral Q_ij(r) d^3r
302 * (exactly what its own b=0 special branch returns, unscaled).
303 * Measured empirically (see this file's unit tests): the general
304 * branch is smaller than the special branch by exactly a factor
305 * of the cell volume, to 8 significant figures on a real Si
306 * ultrasoft pseudopotential -- so the general branch must be
307 * multiplied by cell_volume() to convert it to the overlap
308 * convention. b is never exactly zero for a real Wannier neighbor
309 * direction (KPointNeighborShells never enumerates the origin),
310 * so this multiplication is applied unconditionally; it would be
311 * wrong at b = 0 exactly, where compute_q_g_single() takes its
312 * already-correctly-scaled special branch instead -- callers must
313 * not invoke this method with b_cartesian = (0,0,0).
314 */
315 Real cell_volume = crystal_structure.cell_volume();
316
317 /**
318 * compute_q_g_single() takes its wavevector in the plane-wave
319 * basis's own 2*pi/alat units (it multiplies by
320 * two_pi_over_lattice_parameter internally before interpolating
321 * the radial table), while b_cartesian arrives in physical
322 * bohr^-1 from KPointNeighborShells. Convert once here; passing
323 * b_cartesian directly would sample Q_ij at |b| * tpiba instead
324 * of |b| -- too small by tpiba (0.6 for silicon), so an
325 * over-estimated augmentation term that can push the overlap's
326 * singular values above 1.
327 */
328 const Vector3D b_in_lattice_units = b_cartesian * (1.0 / two_pi_over_lattice_parameter);
329
330 int projector_offset = 0;
331 for (int atom_index = 0; atom_index < crystal_structure.number_of_atoms(); atom_index++) {
332 int species_type_index = crystal_structure.species_type_index(atom_index);
333 int number_of_channels =
334 ultrasoft_basis.number_of_projector_channels(species_type_index);
335 if (number_of_channels == 0) {
336 continue;
337 }
338
339 /**
340 * convert_from_crystal_to_cartesian() returns a position in
341 * alat units (dimensionless, 1.0 = one lattice parameter), not
342 * Bohr -- multiply by lattice_parameter() to get a physical
343 * Bohr position matching b_cartesian's own physical Bohr^-1
344 * units, so the dot product is already a phase angle in
345 * radians with no separate 2*pi factor (unlike the g_cart-based
346 * structure factors elsewhere in this codebase, which multiply
347 * an explicit 2*pi against a *dimensionless* reduced G-vector
348 * instead -- a different, not-tpiba-scaled convention).
349 */
350 Vector3D atomic_position_alat =
351 crystal_structure.convert_from_crystal_to_cartesian(atom_index);
352 Real translation_phase_angle =
353 b_cartesian.dot(atomic_position_alat) * crystal_structure.lattice_parameter();
354 Complex translation_phase = compute_phase_factor(translation_phase_angle);
355
356 for (int channel_i = 0; channel_i < number_of_channels; channel_i++) {
357 int projector_i = projector_offset + channel_i;
358 for (int channel_j = 0; channel_j < number_of_channels; channel_j++) {
359 int projector_j = projector_offset + channel_j;
360
361 Complex q_ij =
362 cell_volume *
363 ultrasoft_basis.compute_q_g_single(species_type_index,
364 channel_i,
365 channel_j,
366 b_in_lattice_units,
367 two_pi_over_lattice_parameter) *
368 translation_phase;
369 if (q_ij == Complex(0.0, 0.0)) {
370 continue;
371 }
372
373 for (int band_index_at_neighbor = 0;
374 band_index_at_neighbor < number_of_bands_at_neighbor;
375 band_index_at_neighbor++) {
376 Complex right =
377 q_ij * amplitude_at_neighbor[projector_j][band_index_at_neighbor];
378 for (int band_index_at_kpoint = 0;
379 band_index_at_kpoint < number_of_bands_at_kpoint;
380 band_index_at_kpoint++) {
381 /**
382 * amplitude_at_kpoint stores <beta_i|psi_mk>; the formula above
383 * needs <psi_mk|beta_i>, its conjugate.
384 */
385 result.values[static_cast<std::size_t>(band_index_at_neighbor) *
386 number_of_bands_at_kpoint +
387 band_index_at_kpoint] +=
388 std::conj(amplitude_at_kpoint[projector_i][band_index_at_kpoint]) *
389 right;
390 }
391 }
392 }
393 }
394 projector_offset += number_of_channels;
395 }
396 }
397
398 private:
399 const PlanewaveBasis& planewave_basis_;
400 const MillerIndexLookup& global_miller_lookup_;
401};
402
403} // namespace q3
Vector3D convert_from_crystal_to_cartesian(int atom_index) const
Definition crystal.hpp:225
Real cell_volume() const
Definition crystal.hpp:193
Real two_pi_over_lattice_parameter() const
Definition crystal.hpp:196
int species_type_index(int atom_index) const
Definition crystal.hpp:273
int number_of_atoms() const
Definition crystal.hpp:209
Real lattice_parameter() const
Definition crystal.hpp:180
size_t rows() const
Definition matrix.hpp:62
int find(const IntegerVector3D &miller) const
int number_of_projector_channels(int species_type) const
Definition init.hpp:203
const std::vector< GVector > & g_vectors() const
Definition init.hpp:148
BlochOverlapResult compute(const std::vector< int > &g_vector_indices_at_kpoint, int number_of_bands_at_kpoint, WavefunctionAtKpoint wavefunction_at_kpoint, const std::vector< int > &g_vector_indices_at_neighbor, int number_of_bands_at_neighbor, WavefunctionAtNeighbor wavefunction_at_neighbor, const IntegerVector3D &periodic_image_shift) const
Definition overlaps.hpp:112
PlanewaveBlochOverlapCalculator(const PlanewaveBasis &planewave_basis, const MillerIndexLookup &global_miller_lookup)
Definition overlaps.hpp:96
void add_augmentation_contribution(BlochOverlapResult &result, const Crystal &crystal_structure, const UltrasoftPlanewaveBasis &ultrasoft_basis, const Matrix< Complex > &projectors_at_kpoint, int number_of_plane_waves_at_kpoint, int number_of_bands_at_kpoint, WavefunctionAtKpoint wavefunction_at_kpoint, const Matrix< Complex > &projectors_at_neighbor, int number_of_plane_waves_at_neighbor, int number_of_bands_at_neighbor, WavefunctionAtNeighbor wavefunction_at_neighbor, const Vector3D &b_cartesian) const
Definition overlaps.hpp:237
Complex compute_q_g_single(int species_type_index, int projector_index_i, int projector_index_j, const Vector3D &g_cartesian, Real two_pi_over_lattice_parameter) const
Definition init.hpp:288
constexpr double zero_vector_magnitude_threshold
double Real
Complex compute_phase_factor(Real phase)
Compute e^{-iφ} = cos(φ) - i·sin(φ) (standard structure factor phase).
std::complex< double > Complex
const Complex & operator()(int band_index_at_kpoint, int band_index_at_neighbor) const
Definition overlaps.hpp:81
std::vector< Complex > values
Definition overlaps.hpp:76
Integer 3-vector for Miller indices / FFT grid indices.
Real norm() const
Definition vector_3d.hpp:68
Real dot(const Vector3D &other) const
Definition vector_3d.hpp:59