Loading...
Searching...
No Matches
accumulator.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * PlanewaveChargeDensityAccumulator — construct the smooth charge density
5 * from the occupied Kohn-Sham wavefunctions.
6 *
7 * This class holds const references to the three data sources it needs
8 * (basis, k-point grid, quantum system) and exposes a single public method
9 * `accumulate()` that writes the real-space charge density into the output
10 * array provided by the caller.
11 *
12 * Keeping this logic here removes ~75 lines of FFT bookkeeping from
13 * PlanewaveKohnShamSCFIterator, which can now simply construct an accumulator
14 * and call it.
15 */
16
17#include <algorithm>
18#include <cmath>
19#include <complex>
20#include <vector>
21
28
29namespace q3 {
30
31/**
32 * Accumulates the smooth plane-wave charge density from the occupied Kohn-Sham bands.
33 *
34 * Holds non-owning const references — all referenced objects must remain alive
35 * for the lifetime of this accumulator.
36 */
38 public:
39 /**
40 * Construct an accumulator bound to the given basis, k-point grid, and quantum system.
41 */
43 const KPointsGrid& kpoint_grid,
44 const PlanewaveKohnShamQuantumSystem& quantum_system)
45 : basis_(basis), kpoint_grid_(kpoint_grid), quantum_system_(quantum_system) {}
46
47 /**
48 * Compute the real-space charge density and write it into `charge_density_out`.
49 *
50 * Uses the smooth (wave) FFT grid for band transforms when available,
51 * then upscales to the rho grid. This is 3-4x faster than transforming
52 * each band on the full rho grid.
53 *
54 * For gamma-only calculations, uses the two-band packing trick to halve the
55 * number of FFT calls: two real bands f1, f2 are packed as f1(G) + i*f2(G),
56 * transformed together, then |f1|^2 and |f2|^2 recovered from Re/Im parts.
57 *
58 * @param number_of_bands Total number of bands per k-point state.
59 * @param number_of_spin_components 1 (non-polarized) or 2 (LSDA).
60 * @param inverse_cell_volume 1.0 / crystal cell volume in a.u.
61 * @param charge_density_out Output: [spin_index][grid_index].
62 * Resized and zeroed by this call.
63 */
64 void accumulate(int number_of_bands,
65 int number_of_spin_components,
66 Real inverse_cell_volume,
67 std::vector<std::vector<Real>>& charge_density_out) const {
68 /**
69 * The rho-grid path scatters each plane wave straight into the density
70 * grid, which is right only if this rank holds, on the density grid,
71 * every stick it holds on the wavefunction grid. Serial, pool and band
72 * layouts hold every stick. Ranks sharing a pool split the two grids
73 * by stick independently, so a plane wave whose density-grid stick
74 * belonged to another rank was dropped: silicon with the density cutoff
75 * at twice the wavefunction cutoff, where the two grids are the same
76 * size, was 0.79 Ry off on two ranks. On the exact wavefunction grid
77 * (QE-format input) they are the same size whenever the density cutoff
78 * is at most four times the wavefunction cutoff. The smooth path goes through
79 * reciprocal space and is right for any ownership. Every rank of a
80 * split grid holds less than the whole of it, so all of them choose the
81 * same path, as the collective transforms on either path require.
82 */
83 const bool density_grid_is_split =
84 basis_.fft_rho().local_grid_points() < basis_.fft_rho().total_grid_points();
85 bool use_smooth_grid = density_grid_is_split || (basis_.fft_wave().total_grid_points() <
86 basis_.fft_rho().total_grid_points());
87
88 if (use_smooth_grid) {
89 accumulate_on_smooth_grid(number_of_bands,
90 number_of_spin_components,
91 inverse_cell_volume,
92 charge_density_out);
93 } else {
94 accumulate_on_rho_grid(number_of_bands,
95 number_of_spin_components,
96 inverse_cell_volume,
97 charge_density_out);
98 }
99 }
100
101 private:
102 const PlanewaveBasis& basis_;
103 const KPointsGrid& kpoint_grid_;
104 const PlanewaveKohnShamQuantumSystem& quantum_system_;
105
106 /**
107 * Index of the first plane wave whose mirror needs filling. G = 0 is its
108 * own mirror and, on the rank that holds it, sits at index 0; on every
109 * other rank index 0 is an ordinary G with a mirror of its own.
110 */
111 [[nodiscard]] int first_mirrored_index() const {
112 return basis_.gamma_origin_is_local() ? 1 : 0;
113 }
114
115 /**
116 * Scatter wavefunction coefficients to an FFT grid using pre-computed mapping.
117 *
118 * For gamma-only scatter, also fills -G positions via conjugate symmetry.
119 * Zeros the workspace before scattering.
120 */
121 void scatter_wavefunction_to_grid(const Complex* band_data,
122 int plane_waves,
123 const std::vector<int>& g_to_mapping,
124 const std::vector<int>& minus_g_to_mapping,
125 bool scatter_minus_g,
126 std::vector<Complex>& workspace) const {
127 std::fill(workspace.begin(), workspace.end(), Complex(0.0, 0.0));
128 for (int g_index = 0; g_index < plane_waves; ++g_index) {
129 int fft_index = g_to_mapping[g_index];
130 if (fft_index >= 0) {
131 workspace[static_cast<size_t>(fft_index)] = band_data[g_index];
132 }
133 }
134 if (scatter_minus_g) {
135 for (int g_index = first_mirrored_index(); g_index < plane_waves; ++g_index) {
136 int minus_fft_index = minus_g_to_mapping[g_index];
137 if (minus_fft_index >= 0) {
138 workspace[static_cast<size_t>(minus_fft_index)] = std::conj(band_data[g_index]);
139 }
140 }
141 }
142 }
143
144 /**
145 * Pack two real-space bands into one complex FFT workspace (gamma-only two-band trick).
146 *
147 * Packs band_data_1 and band_data_2 as: f1(G) + i*f2(G).
148 * Fills -G positions with conj(f1(G)) + i*conj(f2(G)) (conjugate symmetry).
149 * If band_data_2 is nullptr, only band_data_1 is packed (zero imaginary part).
150 * Zeros the workspace before packing.
151 */
152 void scatter_two_bands_gamma(const Complex* band_data_1,
153 const Complex* band_data_2,
154 int plane_waves,
155 const std::vector<int>& g_to_mapping,
156 const std::vector<int>& minus_g_to_mapping,
157 std::vector<Complex>& workspace) const {
158 std::fill(workspace.begin(), workspace.end(), Complex(0.0, 0.0));
159 if (band_data_2 != nullptr) {
160 for (int g_index = 0; g_index < plane_waves; ++g_index) {
161 int fft_index = g_to_mapping[g_index];
162 if (fft_index >= 0) {
163 workspace[static_cast<size_t>(fft_index)] =
164 band_data_1[g_index] + Complex(0.0, 1.0) * band_data_2[g_index];
165 }
166 }
167 for (int g_index = first_mirrored_index(); g_index < plane_waves; ++g_index) {
168 int minus_fft_index = minus_g_to_mapping[g_index];
169 if (minus_fft_index >= 0) {
170 workspace[static_cast<size_t>(minus_fft_index)] =
171 std::conj(band_data_1[g_index]) +
172 Complex(0.0, 1.0) * std::conj(band_data_2[g_index]);
173 }
174 }
175 } else {
176 // Only one band: pack as f1(G) with zero imaginary part
177 for (int g_index = 0; g_index < plane_waves; ++g_index) {
178 int fft_index = g_to_mapping[g_index];
179 if (fft_index >= 0) {
180 workspace[static_cast<size_t>(fft_index)] = band_data_1[g_index];
181 }
182 }
183 for (int g_index = first_mirrored_index(); g_index < plane_waves; ++g_index) {
184 int minus_fft_index = minus_g_to_mapping[g_index];
185 if (minus_fft_index >= 0) {
186 workspace[static_cast<size_t>(minus_fft_index)] =
187 std::conj(band_data_1[g_index]);
188 }
189 }
190 }
191 }
192
193 /**
194 * Run `accumulate_band_iteration` over `number_of_iterations` band
195 * transforms, spread across threads, and fold what they produce into
196 * `density_out`.
197 *
198 * The body is handed its iteration index, a workspace of its own, the
199 * density array it should add into, and the transform slot it may drive.
200 *
201 * **This is a reduction, and at more than one thread it does not preserve
202 * the summation order.** Serially, a grid point accumulates band 0, then
203 * band 1, then band 2, and so on. Here each thread sums its own contiguous
204 * run of bands into a partial array and the partials are added in
205 * thread-index order, which regroups the same terms:
206 * `(b0+b1) + (b2+b3)` rather than `((b0+b1)+b2)+b3`. Floating-point
207 * addition is not associative, so the last bits of the density can move.
208 * The movement is regrouping and nothing else -- every term is present
209 * exactly once, in increasing band order within each partial, and the
210 * partials are combined in a fixed order, so two runs at the same thread
211 * count agree exactly.
212 *
213 * The alternative, a fixed number of partial arrays independent of the
214 * thread count, would hold the last bits still at every thread count but
215 * would allocate and combine them at one thread too -- which is the case
216 * that has to stay free. So: at one thread, `partial_densities` is empty,
217 * the body writes straight into `density_out`, and the arithmetic is
218 * exactly what the serial code did, bit for bit.
219 */
220 template <typename BandIterationBody>
221 void accumulate_bands_into(int number_of_iterations,
222 const FFTGrid& fft_grid,
223 int workspace_points,
224 int local_grid_points,
225 Real* density_out,
226 BandIterationBody accumulate_band_iteration) const {
227 int transform_slots = fft_grid.concurrent_transform_slots();
228 int band_threads =
229 (transform_slots < 2)
230 ? 1
231 : std::min(transform_slots,
232 execution::threading::thread_count_for(number_of_iterations, 1));
233
234 std::vector<std::vector<Real>> partial_densities;
235 if (band_threads > 1) {
236 partial_densities.assign(
237 static_cast<std::size_t>(band_threads - 1),
238 std::vector<Real>(static_cast<std::size_t>(local_grid_points), 0.0));
239 }
240
241#pragma omp parallel num_threads(band_threads) if (band_threads > 1)
242 {
243 int transform_slot = execution::threading::thread_index();
244 Real* density_target =
245 (transform_slot == 0)
246 ? density_out
247 : partial_densities[static_cast<std::size_t>(transform_slot - 1)].data();
248 std::vector<Complex> workspace(static_cast<std::size_t>(workspace_points));
249 long long first_iteration = 0;
250 long long last_iteration = number_of_iterations;
251 execution::threading::slice_bounds(number_of_iterations,
253 transform_slot,
254 first_iteration,
255 last_iteration);
256 for (long long iteration = first_iteration; iteration < last_iteration; ++iteration) {
257 accumulate_band_iteration(
258 static_cast<int>(iteration), workspace, density_target, transform_slot);
259 }
260 }
261
262 /**
263 * Combined by thread index, not by whichever partial finished first:
264 * the order has to be a property of the code rather than of the run,
265 * or two runs at the same thread count would disagree.
266 */
267 for (const std::vector<Real>& partial_density : partial_densities) {
268 for (int grid_index = 0; grid_index < local_grid_points; ++grid_index) {
269 density_out[grid_index] += partial_density[static_cast<std::size_t>(grid_index)];
270 }
271 }
272 }
273
274 /**
275 * Original implementation: FFT each band on the dense rho grid.
276 * Used for norm-conserving pseudopotentials where wave grid = rho grid.
277 *
278 * For gamma-only calculations uses the two-band packing trick to halve FFT count.
279 */
280 void accumulate_on_rho_grid(int number_of_bands,
281 int number_of_spin_components,
282 Real inverse_cell_volume,
283 std::vector<std::vector<Real>>& charge_density_out) const {
284 int local_grid_points = basis_.fft_rho().local_grid_points();
285 int number_of_kpoints = kpoint_grid_.number_of_kpoints();
286 int number_of_kpoint_states = number_of_kpoints * number_of_spin_components;
287
288 charge_density_out.resize(static_cast<std::size_t>(number_of_spin_components));
289 for (int spin_index = 0; spin_index < number_of_spin_components; ++spin_index) {
290 charge_density_out[static_cast<std::size_t>(spin_index)].assign(
291 static_cast<std::size_t>(local_grid_points), 0.0);
292 }
293
294 // Pre-compute FFT grid dimensions (constant for all bands)
295 int fft_dimension_1 = basis_.fft_rho().grid_dimension_1();
296 int fft_dimension_2 = basis_.fft_rho().grid_dimension_2();
297 int fft_dimension_3 = basis_.fft_rho().grid_dimension_3();
298
299 /**
300 * How large a transform workspace each band iteration needs. The array
301 * itself is allocated per thread inside accumulate_bands_into(): one
302 * shared across bands would be overwritten by whichever thread reached
303 * it first.
304 */
305 int fft_workspace_points =
306 std::max(local_grid_points, basis_.fft_rho().local_g_space_points());
307
308 for (int kpoint_index_state = 0; kpoint_index_state < number_of_kpoint_states;
309 ++kpoint_index_state) {
310 int spin_index = kpoint_index_state / number_of_kpoints;
311 int kpoint_index = kpoint_index_state % number_of_kpoints;
312 Real kpoint_weight = kpoint_grid_[kpoint_index].weight;
313 int plane_waves_at_kpoint = basis_.number_of_plane_waves_by_kpoint_index(kpoint_index);
314 const auto& kpoint_g_indices = basis_.g_vector_indices_by_kpoint_index(kpoint_index);
315
316 // Pre-compute G->FFT grid mapping for this k-point (constant across bands)
317 std::vector<int> g_to_fft_mapping(plane_waves_at_kpoint, -1);
318 for (int g_index = 0; g_index < plane_waves_at_kpoint; ++g_index) {
319 const auto& g_vector =
320 basis_.g_vectors()[kpoint_g_indices[static_cast<std::size_t>(g_index)]];
321 int miller_x = static_cast<int>(std::round(g_vector.miller.x));
322 int miller_y = static_cast<int>(std::round(g_vector.miller.y));
323 int miller_z = static_cast<int>(std::round(g_vector.miller.z));
324 if (std::abs(miller_x) <= fft_dimension_1 / 2 &&
325 std::abs(miller_y) <= fft_dimension_2 / 2 &&
326 std::abs(miller_z) <= fft_dimension_3 / 2) {
327 g_to_fft_mapping[g_index] =
328 basis_.fft_rho().g_vector_to_fft_grid_index(miller_x, miller_y, miller_z);
329 }
330 }
331
332 // Pre-compute -G FFT mapping for gamma-only scatter
333 std::vector<int> minus_g_to_fft_mapping;
334 if (basis_.is_gamma_only()) {
335 minus_g_to_fft_mapping.resize(plane_waves_at_kpoint, -1);
336 for (int g_index = 0; g_index < plane_waves_at_kpoint; ++g_index) {
337 const auto& g_vector =
338 basis_.g_vectors()[kpoint_g_indices[static_cast<std::size_t>(g_index)]];
339 int miller_x = static_cast<int>(std::round(g_vector.miller.x));
340 int miller_y = static_cast<int>(std::round(g_vector.miller.y));
341 int miller_z = static_cast<int>(std::round(g_vector.miller.z));
342 minus_g_to_fft_mapping[g_index] = basis_.fft_rho().g_vector_to_fft_grid_index(
343 -miller_x, -miller_y, -miller_z);
344 }
345 }
346
347 auto* density_out_ptr = charge_density_out[static_cast<std::size_t>(spin_index)].data();
348
349 if (basis_.is_gamma_only()) {
350 /**
351 * Two-band gamma trick: pair up occupied bands and process two per FFT.
352 * Collect occupied band indices first.
353 */
354 std::vector<int> occupied_band_indices;
355 for (int band_index = 0; band_index < number_of_bands; ++band_index) {
356 Real occupation =
358 kpoint_index_state, band_index);
359 if (occupation >= math::tolerances::occupation_threshold) {
360 occupied_band_indices.push_back(band_index);
361 }
362 }
363 // Process pairs of bands with one FFT each
364 int number_of_band_pairs = static_cast<int>((occupied_band_indices.size() + 1) / 2);
365 accumulate_bands_into(
366 number_of_band_pairs,
367 basis_.fft_rho(),
368 fft_workspace_points,
369 local_grid_points,
370 density_out_ptr,
371 [&](int pair_index,
372 std::vector<Complex>& fft_workspace,
373 Real* density_target,
374 int transform_slot) {
375 std::size_t pair_start = static_cast<std::size_t>(pair_index) * 2;
376 int band_index_1 = occupied_band_indices[pair_start];
377 bool has_second_band = (pair_start + 1 < occupied_band_indices.size());
378 int band_index_2 =
379 has_second_band ? occupied_band_indices[pair_start + 1] : -1;
380
381 const Complex* band_data_1 = quantum_system_.wavefunction_band_data(
382 kpoint_index_state, band_index_1);
383 const Complex* band_data_2 = has_second_band
384 ? quantum_system_.wavefunction_band_data(
385 kpoint_index_state, band_index_2)
386 : nullptr;
387
388 Real occupation_1 =
389 quantum_system_.occupation_by_kpoint_state_index_and_band_index(
390 kpoint_index_state, band_index_1);
391 Real occupation_2 =
392 has_second_band
393 ? quantum_system_.occupation_by_kpoint_state_index_and_band_index(
394 kpoint_index_state, band_index_2)
395 : 0.0;
396
397 scatter_two_bands_gamma(band_data_1,
398 band_data_2,
399 plane_waves_at_kpoint,
400 g_to_fft_mapping,
401 minus_g_to_fft_mapping,
402 fft_workspace);
403 basis_.fft_rho().inverse_raw_on_slot(fft_workspace.data(), transform_slot);
404
405 // psi1(r) = Re(result), psi2(r) = Im(result) - both real in gamma-only
406 Real weight_1 = kpoint_weight * occupation_1 * inverse_cell_volume;
407 Real weight_2 = kpoint_weight * occupation_2 * inverse_cell_volume;
408 for (int grid_index = 0; grid_index < local_grid_points; ++grid_index) {
409 Real psi1 = fft_workspace[static_cast<size_t>(grid_index)].real();
410 Real psi2 = fft_workspace[static_cast<size_t>(grid_index)].imag();
411 density_target[grid_index] +=
412 weight_1 * psi1 * psi1 + weight_2 * psi2 * psi2;
413 }
414 });
415 } else {
416 // Non-gamma: process each occupied band individually
417 accumulate_bands_into(
418 number_of_bands,
419 basis_.fft_rho(),
420 fft_workspace_points,
421 local_grid_points,
422 density_out_ptr,
423 [&](int band_index,
424 std::vector<Complex>& fft_workspace,
425 Real* density_target,
426 int transform_slot) {
427 Real occupation =
428 quantum_system_.occupation_by_kpoint_state_index_and_band_index(
429 kpoint_index_state, band_index);
430 if (occupation < math::tolerances::occupation_threshold) {
431 return;
432 }
433 const Complex* band_data =
434 quantum_system_.wavefunction_band_data(kpoint_index_state, band_index);
435
436 scatter_wavefunction_to_grid(band_data,
437 plane_waves_at_kpoint,
438 g_to_fft_mapping,
439 minus_g_to_fft_mapping,
440 false,
441 fft_workspace);
442 basis_.fft_rho().inverse_raw_on_slot(fft_workspace.data(), transform_slot);
443
444 Real weight_factor = kpoint_weight * occupation * inverse_cell_volume;
445 for (int grid_index = 0; grid_index < local_grid_points; ++grid_index) {
446 density_target[grid_index] +=
447 weight_factor *
448 std::norm(fft_workspace[static_cast<std::size_t>(grid_index)]);
449 }
450 });
451 }
452 }
453 }
454
455 /**
456 * Optimized implementation: FFT each band on the smaller smooth (wave) grid,
457 * accumulate |psi|^2 on the smooth grid, then upscale to the rho grid at the end.
458 *
459 * The smooth density rho_smooth(r) on the wave grid can contain aliasing because
460 * |psi|^2 has G-components up to 2*Gcut_wfc. However, for ultrasoft pseudopotentials
461 * the augmentation charge Q(G) is added separately on the full G-space, and the
462 * smooth density only needs G-vectors up to Gcut_wfc for the Hartree/XC calculation
463 * (since V_eff is smooth). The upscaling step correctly transfers only the smooth
464 * G-components to the rho grid.
465 *
466 * For gamma-only calculations uses the two-band packing trick to halve FFT count.
467 */
468 void accumulate_on_smooth_grid(int number_of_bands,
469 int number_of_spin_components,
470 Real inverse_cell_volume,
471 std::vector<std::vector<Real>>& charge_density_out) const {
472 int smooth_total_points = basis_.fft_wave().total_grid_points();
473 int smooth_local_points = basis_.fft_wave().local_grid_points();
474 int rho_total_points = basis_.fft_rho().total_grid_points();
475 int rho_local_points = basis_.fft_rho().local_grid_points();
476 int number_of_kpoints = kpoint_grid_.number_of_kpoints();
477 int number_of_kpoint_states = number_of_kpoints * number_of_spin_components;
478
479 // Precompute G->smooth FFT mapping (constant across all bands and k-points)
480 int wave_dim_1 = basis_.fft_wave().grid_dimension_1();
481 int wave_dim_2 = basis_.fft_wave().grid_dimension_2();
482 int wave_dim_3 = basis_.fft_wave().grid_dimension_3();
483
484 // Accumulate |psi|^2 on the smooth grid per spin
485 std::vector<std::vector<Real>> smooth_density(
486 static_cast<std::size_t>(number_of_spin_components));
487 for (int spin_index = 0; spin_index < number_of_spin_components; ++spin_index) {
488 smooth_density[static_cast<std::size_t>(spin_index)].assign(
489 static_cast<std::size_t>(smooth_local_points), 0.0);
490 }
491
492 /// Per-thread workspace size; see the rho-grid path for why it is not shared.
493 int fft_workspace_points =
494 std::max(smooth_local_points, basis_.fft_wave().local_g_space_points());
495
496 for (int kpoint_index_state = 0; kpoint_index_state < number_of_kpoint_states;
497 ++kpoint_index_state) {
498 int spin_index = kpoint_index_state / number_of_kpoints;
499 int kpoint_index = kpoint_index_state % number_of_kpoints;
500 Real kpoint_weight = kpoint_grid_[kpoint_index].weight;
501 int plane_waves_at_kpoint = basis_.number_of_plane_waves_by_kpoint_index(kpoint_index);
502
503 // Pre-build G->smooth-grid mapping for this k-point
504 const auto& kpoint_g_indices = basis_.g_vector_indices_by_kpoint_index(kpoint_index);
505 std::vector<int> g_to_smooth_mapping(plane_waves_at_kpoint, -1);
506 for (int g_index = 0; g_index < plane_waves_at_kpoint; ++g_index) {
507 const auto& g_vector =
508 basis_.g_vectors()[kpoint_g_indices[static_cast<std::size_t>(g_index)]];
509 int miller_x = static_cast<int>(std::round(g_vector.miller.x));
510 int miller_y = static_cast<int>(std::round(g_vector.miller.y));
511 int miller_z = static_cast<int>(std::round(g_vector.miller.z));
512 if (std::abs(miller_x) <= wave_dim_1 / 2 && std::abs(miller_y) <= wave_dim_2 / 2 &&
513 std::abs(miller_z) <= wave_dim_3 / 2) {
514 g_to_smooth_mapping[g_index] =
515 basis_.fft_wave().g_vector_to_fft_grid_index(miller_x, miller_y, miller_z);
516 }
517 }
518
519 // Pre-compute -G FFT mapping for gamma-only scatter on smooth grid
520 std::vector<int> minus_g_to_smooth_mapping;
521 if (basis_.is_gamma_only()) {
522 minus_g_to_smooth_mapping.resize(plane_waves_at_kpoint, -1);
523 for (int g_index = 0; g_index < plane_waves_at_kpoint; ++g_index) {
524 const auto& g_vector =
525 basis_.g_vectors()[kpoint_g_indices[static_cast<std::size_t>(g_index)]];
526 int miller_x = static_cast<int>(std::round(g_vector.miller.x));
527 int miller_y = static_cast<int>(std::round(g_vector.miller.y));
528 int miller_z = static_cast<int>(std::round(g_vector.miller.z));
529 if (std::abs(miller_x) <= wave_dim_1 / 2 &&
530 std::abs(miller_y) <= wave_dim_2 / 2 &&
531 std::abs(miller_z) <= wave_dim_3 / 2) {
532 minus_g_to_smooth_mapping[g_index] =
534 -miller_x, -miller_y, -miller_z);
535 }
536 }
537 }
538
539 auto* smooth_density_ptr = smooth_density[static_cast<std::size_t>(spin_index)].data();
540
541 if (basis_.is_gamma_only()) {
542 // Two-band gamma trick: process pairs of occupied bands with one FFT each.
543 std::vector<int> occupied_band_indices;
544 for (int band_index = 0; band_index < number_of_bands; ++band_index) {
545 Real occupation =
547 kpoint_index_state, band_index);
548 if (occupation >= math::tolerances::occupation_threshold) {
549 occupied_band_indices.push_back(band_index);
550 }
551 }
552 int number_of_band_pairs = static_cast<int>((occupied_band_indices.size() + 1) / 2);
553 accumulate_bands_into(
554 number_of_band_pairs,
555 basis_.fft_wave(),
556 fft_workspace_points,
557 smooth_local_points,
558 smooth_density_ptr,
559 [&](int pair_index,
560 std::vector<Complex>& fft_workspace,
561 Real* density_target,
562 int transform_slot) {
563 std::size_t pair_start = static_cast<std::size_t>(pair_index) * 2;
564 int band_index_1 = occupied_band_indices[pair_start];
565 bool has_second_band = (pair_start + 1 < occupied_band_indices.size());
566 int band_index_2 =
567 has_second_band ? occupied_band_indices[pair_start + 1] : -1;
568
569 const Complex* band_data_1 = quantum_system_.wavefunction_band_data(
570 kpoint_index_state, band_index_1);
571 const Complex* band_data_2 = has_second_band
572 ? quantum_system_.wavefunction_band_data(
573 kpoint_index_state, band_index_2)
574 : nullptr;
575
576 Real occupation_1 =
577 quantum_system_.occupation_by_kpoint_state_index_and_band_index(
578 kpoint_index_state, band_index_1);
579 Real occupation_2 =
580 has_second_band
581 ? quantum_system_.occupation_by_kpoint_state_index_and_band_index(
582 kpoint_index_state, band_index_2)
583 : 0.0;
584
585 scatter_two_bands_gamma(band_data_1,
586 band_data_2,
587 plane_waves_at_kpoint,
588 g_to_smooth_mapping,
589 minus_g_to_smooth_mapping,
590 fft_workspace);
591 basis_.fft_wave().inverse_raw_on_slot(fft_workspace.data(), transform_slot);
592
593 Real weight_1 = kpoint_weight * occupation_1 * inverse_cell_volume;
594 Real weight_2 = kpoint_weight * occupation_2 * inverse_cell_volume;
595 for (int grid_index = 0; grid_index < smooth_local_points; ++grid_index) {
596 Real psi1 = fft_workspace[static_cast<size_t>(grid_index)].real();
597 Real psi2 = fft_workspace[static_cast<size_t>(grid_index)].imag();
598 density_target[grid_index] +=
599 weight_1 * psi1 * psi1 + weight_2 * psi2 * psi2;
600 }
601 });
602 } else {
603 // Non-gamma: process each occupied band individually
604 accumulate_bands_into(
605 number_of_bands,
606 basis_.fft_wave(),
607 fft_workspace_points,
608 smooth_local_points,
609 smooth_density_ptr,
610 [&](int band_index,
611 std::vector<Complex>& fft_workspace,
612 Real* density_target,
613 int transform_slot) {
614 Real occupation =
615 quantum_system_.occupation_by_kpoint_state_index_and_band_index(
616 kpoint_index_state, band_index);
617 if (occupation < math::tolerances::occupation_threshold) {
618 return;
619 }
620 const Complex* band_data =
621 quantum_system_.wavefunction_band_data(kpoint_index_state, band_index);
622
623 scatter_wavefunction_to_grid(band_data,
624 plane_waves_at_kpoint,
625 g_to_smooth_mapping,
626 minus_g_to_smooth_mapping,
627 false,
628 fft_workspace);
629 basis_.fft_wave().inverse_raw_on_slot(fft_workspace.data(), transform_slot);
630
631 Real weight_factor = kpoint_weight * occupation * inverse_cell_volume;
632 for (int grid_index = 0; grid_index < smooth_local_points; ++grid_index) {
633 density_target[grid_index] +=
634 weight_factor *
635 std::norm(fft_workspace[static_cast<std::size_t>(grid_index)]);
636 }
637 });
638 }
639 }
640
641 // Upscale smooth density to rho grid via G-space
642 charge_density_out.resize(static_cast<std::size_t>(number_of_spin_components));
643 for (int spin_index = 0; spin_index < number_of_spin_components; ++spin_index) {
644 // Forward FFT on smooth grid: rho(r) -> rho(G)
645 std::vector<Complex> density_g_smooth(
646 static_cast<std::size_t>(
647 std::max(smooth_local_points, basis_.fft_wave().local_g_space_points())),
648 Complex(0.0, 0.0));
649 for (int grid_index = 0; grid_index < smooth_local_points; ++grid_index) {
650 density_g_smooth[static_cast<std::size_t>(grid_index)] =
651 Complex(smooth_density[static_cast<std::size_t>(spin_index)]
652 [static_cast<std::size_t>(grid_index)],
653 0.0);
654 }
655 basis_.fft_wave().forward(density_g_smooth.data());
656
657 // Normalize forward FFT and make G-space redundant
658 Real inverse_smooth = 1.0 / smooth_total_points;
659 for (auto& value : density_g_smooth) {
660 value *= inverse_smooth;
661 }
662 /**
663 * A redundant rho(G) is needed to scatter it onto the rho grid. The
664 * grid places its own reciprocal-space points into the full array --
665 * a slab or a set of sticks, it knows which -- and the pool-wide
666 * reduction fills in the rest.
667 */
668 std::vector<Complex> density_g_smooth_redundant(smooth_total_points, Complex(0.0, 0.0));
669 basis_.fft_wave().scatter_local_g_space(density_g_smooth.data(),
670 density_g_smooth_redundant.data());
671 basis_.fft_wave().reduce_g_space(density_g_smooth_redundant.data(),
672 smooth_total_points);
673
674 /**
675 * Scatter smooth G-components to rho grid (redundant rho(G) -> local rho(G) on rho
676 * grid)
677 */
678 std::vector<Complex> density_g_rho(
679 static_cast<std::size_t>(
680 std::max(rho_local_points, basis_.fft_rho().local_g_space_points())),
681 Complex(0.0, 0.0));
682 for (int m1 = -(wave_dim_1 / 2); m1 <= wave_dim_1 / 2; ++m1) {
683 for (int m2 = -(wave_dim_2 / 2); m2 <= wave_dim_2 / 2; ++m2) {
684 for (int m3 = -(wave_dim_3 / 2); m3 <= wave_dim_3 / 2; ++m3) {
685 int smooth_index_global =
686 (m1 < 0 ? m1 + wave_dim_1 : m1) * wave_dim_2 * wave_dim_3 +
687 (m2 < 0 ? m2 + wave_dim_2 : m2) * wave_dim_3 +
688 (m3 < 0 ? m3 + wave_dim_3 : m3);
689 int rho_index_local =
690 basis_.fft_rho().g_vector_to_fft_grid_index(m1, m2, m3);
691
692 if (rho_index_local >= 0) {
693 density_g_rho[static_cast<std::size_t>(rho_index_local)] =
694 density_g_smooth_redundant[static_cast<std::size_t>(
695 smooth_index_global)];
696 }
697 }
698 }
699 }
700
701 // Backward FFT on rho grid: rho(G) -> rho(r)
702 basis_.fft_rho().inverse_raw(density_g_rho.data());
703
704 // Extract real part
705 charge_density_out[static_cast<std::size_t>(spin_index)].resize(
706 static_cast<std::size_t>(rho_local_points));
707 for (int grid_index = 0; grid_index < rho_local_points; ++grid_index) {
708 charge_density_out[static_cast<std::size_t>(spin_index)]
709 [static_cast<std::size_t>(grid_index)] =
710 density_g_rho[static_cast<std::size_t>(grid_index)].real();
711 }
712 }
713 }
714};
715
716} // namespace q3
virtual int g_vector_to_fft_grid_index(int miller_1, int miller_2, int miller_3) const =0
virtual void inverse_raw_on_slot(Complex *data, int) const
Definition fft_grid.hpp:278
virtual void scatter_local_g_space(const Complex *local_g_space, Complex *full_g_space) const
The reverse: place this rank's reciprocal-space points into a full array.
Definition fft_grid.hpp:133
virtual int local_g_space_points() const
Definition fft_grid.hpp:88
virtual void reduce_g_space(Complex *data, int number_of_elements) const =0
virtual void forward(Complex *data) const =0
virtual int total_grid_points() const =0
virtual int grid_dimension_1() const =0
virtual void inverse_raw(Complex *data) const =0
virtual int grid_dimension_3() const =0
virtual int grid_dimension_2() const =0
virtual int local_grid_points() const =0
int number_of_kpoints() const
const std::vector< GVector > & g_vectors() const
Definition init.hpp:148
FFTGrid & fft_rho()
Definition init.hpp:286
const std::vector< int > & g_vector_indices_by_kpoint_index(int kpoint_index) const
Definition init.hpp:259
int number_of_plane_waves_by_kpoint_index(int kpoint_index) const
Definition init.hpp:263
FFTGrid & fft_wave()
Definition init.hpp:292
bool gamma_origin_is_local() const
Definition init.hpp:172
bool is_gamma_only() const
Definition init.hpp:157
PlanewaveChargeDensityAccumulator(const PlanewaveBasis &basis, const KPointsGrid &kpoint_grid, const PlanewaveKohnShamQuantumSystem &quantum_system)
void accumulate(int number_of_bands, int number_of_spin_components, Real inverse_cell_volume, std::vector< std::vector< Real > > &charge_density_out) const
Real occupation_by_kpoint_state_index_and_band_index(int kpoint_state_index, int band_index) const
Definition init.hpp:127
Complex * wavefunction_band_data(int kpoint_state_index, int band_index)
Definition init.hpp:107
int thread_count_for(long long work_items, long long minimum_items_per_thread)
void slice_bounds(long long work_items, int number_of_slices, int slice_index, long long &first, long long &last)
constexpr double occupation_threshold
double Real
std::complex< double > Complex