81 : crystal_structure_(crystal_structure)
82 , planewave_basis_(planewave_basis)
83 , pseudopotential_set_(pseudopotential_set) {
100 int grid_dimension_y,
101 int grid_dimension_z,
103 const std::vector<std::vector<int>>& g_vector_indices_by_kpoint,
106 int first_band_index = 0)
const {
107 return assemble(kpoint_grid,
108 {grid_dimension_x, grid_dimension_y, grid_dimension_z},
110 g_vector_indices_by_kpoint,
125 int grid_dimension_x,
126 int grid_dimension_y,
127 int grid_dimension_z,
129 const std::vector<std::vector<int>>& g_vector_indices_by_kpoint,
133 int first_band_index = 0)
const {
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.");
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,
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]);
166 return assemble(kpoint_grid,
167 {grid_dimension_x, grid_dimension_y, grid_dimension_z},
169 g_vector_indices_by_kpoint,
183 [[nodiscard]] std::vector<Vector3D>
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);
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;
203 [[nodiscard]] WannierInputs
204 assemble(
const KPointsGrid& kpoint_grid,
205 std::array<int, 3> grid_dimensions,
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.");
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.");
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.");
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.");
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() +
").");
247 if (first_band_index < 0) {
248 throw std::invalid_argument(
249 "PlanewaveWannierCapture: the first band index must not be negative.");
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.");
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).");
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);
277 KPointNeighborShells shells;
282 record.neighbor_directions = shells.neighbor_directions();
283 const int number_of_directions =
static_cast<int>(record.neighbor_directions.size());
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, {});
296 PlanewaveTrialOrbitalProjectionCalculator projection_calculator(trial_orbitals);
297 PlanewaveBlochOverlapCalculator bloch_overlap_calculator(planewave_basis_,
298 global_miller_lookup_);
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);
307 std::unique_ptr<OverlapOperator> overlap_operator;
308 if (augmentation !=
nullptr) {
311 augmentation->quantum_system,
312 augmentation->kb_projectors_by_kpoint[kpoint_index],
313 augmentation->execution_policy);
315 std::vector<std::vector<Complex>> projections =
316 projection_calculator.compute_projections_at_kpoint(
319 static_cast<int>(g_vector_indices_by_kpoint[kpoint_index].size()),
321 g_vector_indices_by_kpoint[kpoint_index],
326 overlap_operator.get(),
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,
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;
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] =
341 projectability += std::norm(amplitude);
343 record.projectabilities[kpoint_index][band_index] = projectability;
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;
352 const KPointNeighborDirection& direction =
353 record.neighbor_directions[direction_index];
354 const ResolvedKPointNeighbor resolved =
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);
364 auto band_at_neighbor = [&](
int band_index) {
365 return quantum_system.wavefunction_band_data(neighbor_kpoint_index,
366 first_band_index + band_index);
369 BlochOverlapResult result = bloch_overlap_calculator.compute(
370 g_vector_indices_by_kpoint[kpoint_index],
373 g_vector_indices_by_kpoint[neighbor_kpoint_index],
376 resolved.periodic_image_shift);
378 if (augmentation !=
nullptr) {
379 bloch_overlap_calculator.add_augmentation_contribution(
382 augmentation->ultrasoft_basis,
383 augmentation->kb_projectors_by_kpoint[kpoint_index],
384 static_cast<int>(g_vector_indices_by_kpoint[kpoint_index].size()),
387 augmentation->kb_projectors_by_kpoint[neighbor_kpoint_index],
388 static_cast<int>(g_vector_indices_by_kpoint[neighbor_kpoint_index].size()),
391 direction.reciprocal_lattice_translation);
393 overlaps_at_kpoint[direction_index] = std::move(result.values);
400 const Crystal& crystal_structure_;
401 const PlanewaveBasis& planewave_basis_;
402 const PseudopotentialSet& pseudopotential_set_;
403 MillerIndexLookup global_miller_lookup_;