Loading...
Searching...
No Matches
planewave_potential_utils.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * Shared utility functions for planewave Hamiltonian applicators.
5 *
6 * These functions are used by both the serial and GPU Hamiltonian applicators.
7 * Extracted to avoid code duplication between the two implementations.
8 */
9
10#include <algorithm>
11#include <cmath>
12#include <complex>
13#include <cstring>
14#include <vector>
15
18
19namespace q3 {
20
21/**
22 * Restrict the effective potential from the dense (charge density) FFT grid
23 * to the smooth (wavefunction) FFT grid via G-space truncation.
24 *
25 * 1. Forward FFT V(r) on the dense grid -> V(G)
26 * 2. Copy G-components that fit on the smooth grid
27 * 3. Inverse FFT on the smooth grid -> V_smooth(r)
28 *
29 * This is the single-process version (no MPI reduce). For the MPI-aware
30 * version, call this function then reduce the result across band groups.
31 */
32inline std::vector<Complex>
33compute_smooth_grid_potential_from_dense(const std::vector<Real>& dense_real_space_potential,
34 const PlanewaveBasis& planewave_basis) {
35
36 int local_dense_points = planewave_basis.fft_rho().local_grid_points();
37 int local_dense_g_points = planewave_basis.fft_rho().local_g_space_points();
38 int total_dense_points = planewave_basis.fft_rho().total_grid_points();
39 int total_smooth_points = planewave_basis.fft_wave().total_grid_points();
40
41 /**
42 * Step 1: forward-transform the dense-grid potential. The buffer holds
43 * real space going in and reciprocal space coming out, and under the
44 * stick layout those are different sizes, so it is sized for the larger
45 * and the normalisation runs over the reciprocal-space count.
46 */
47 std::vector<Complex> potential_complex(
48 static_cast<std::size_t>(std::max(local_dense_points, local_dense_g_points)),
49 Complex(0.0, 0.0));
50 for (int index = 0; index < local_dense_points; ++index) {
51 potential_complex[index] = Complex(dense_real_space_potential[index], 0.0);
52 }
53 planewave_basis.fft_rho().forward(potential_complex.data());
54
55 Real inverse_dense_points = 1.0 / total_dense_points;
56 for (int index = 0; index < local_dense_g_points; ++index) {
57 potential_complex[index] *= inverse_dense_points;
58 }
59
60 // Step 2: Copy V(G) to the smooth grid
61 std::vector<Complex> full_smooth_potential_g(total_smooth_points, Complex(0.0, 0.0));
62
63 int smooth_dimension_1 = planewave_basis.fft_wave().grid_dimension_1();
64 int smooth_dimension_2 = planewave_basis.fft_wave().grid_dimension_2();
65 int smooth_dimension_3 = planewave_basis.fft_wave().grid_dimension_3();
66
67 /**
68 * Visit each wrapped smooth-grid index exactly once. On an even grid,
69 * -N/2 and +N/2 alias to the same FFT slot. The former inclusive signed
70 * traversal visited both: serial execution happened to overwrite the slot
71 * with +N/2, while MPI ranks could own the two dense-grid coefficients
72 * separately and sum both during the subsequent reduction.
73 *
74 * Mapping wrapped indices back to signed Miller indices with +N/2 as the
75 * Nyquist representative preserves the former serial result and removes
76 * the topology-dependent double contribution.
77 */
78 for (int wrapped_1 = 0; wrapped_1 < smooth_dimension_1; ++wrapped_1) {
79 int miller_1 =
80 (wrapped_1 <= smooth_dimension_1 / 2) ? wrapped_1 : wrapped_1 - smooth_dimension_1;
81 for (int wrapped_2 = 0; wrapped_2 < smooth_dimension_2; ++wrapped_2) {
82 int miller_2 =
83 (wrapped_2 <= smooth_dimension_2 / 2) ? wrapped_2 : wrapped_2 - smooth_dimension_2;
84 for (int wrapped_3 = 0; wrapped_3 < smooth_dimension_3; ++wrapped_3) {
85 int miller_3 = (wrapped_3 <= smooth_dimension_3 / 2)
86 ? wrapped_3
87 : wrapped_3 - smooth_dimension_3;
88 int dense_index = planewave_basis.fft_rho().g_vector_to_fft_grid_index(
89 miller_1, miller_2, miller_3);
90
91 if (dense_index >= 0) {
92 int global_index =
93 (wrapped_1 * smooth_dimension_2 + wrapped_2) * smooth_dimension_3 +
94 wrapped_3;
95 full_smooth_potential_g[global_index] = potential_complex[dense_index];
96 }
97 }
98 }
99 }
100
101 return full_smooth_potential_g;
102}
103
104/**
105 * Convert full smooth-grid V(G) to real-space V_smooth(r) via inverse FFT.
106 *
107 * Handles local slab extraction for MPI-distributed grids.
108 */
109inline std::vector<Real>
110inverse_fft_smooth_potential(const std::vector<Complex>& full_smooth_potential_g,
111 const PlanewaveBasis& planewave_basis) {
112
113 int local_smooth_points = planewave_basis.fft_wave().local_grid_points();
114 int local_smooth_g_points = planewave_basis.fft_wave().local_g_space_points();
115 /**
116 * The grid knows which of the full array's points are its own -- a
117 * contiguous slab, or a set of sticks -- so it does the gathering. This
118 * used to be a memcpy from local_offset(), which encoded the slab layout.
119 */
120 std::vector<Complex> local_smooth_potential_g(
121 static_cast<std::size_t>(std::max(local_smooth_points, local_smooth_g_points)),
122 Complex(0.0, 0.0));
123 planewave_basis.fft_wave().gather_local_g_space(full_smooth_potential_g.data(),
124 local_smooth_potential_g.data());
125
126 planewave_basis.fft_wave().inverse_raw(local_smooth_potential_g.data());
127
128 std::vector<Real> smooth_real_space_potential(local_smooth_points);
129 for (int index = 0; index < local_smooth_points; ++index) {
130 smooth_real_space_potential[index] = local_smooth_potential_g[index].real();
131 }
132 return smooth_real_space_potential;
133}
134
135/**
136 * Pre-compute G-vector to smooth FFT grid index mapping.
137 *
138 * Maps each plane-wave index to its smooth FFT grid index, or -1 if
139 * the G-vector lies outside the smooth grid bounds.
140 */
141inline void compute_g_to_fft_index_mapping(int number_of_plane_waves,
142 const std::vector<int>& g_vector_indices,
143 const PlanewaveBasis& planewave_basis,
144 std::vector<int>& g_to_fft_mapping) {
145 int fft_grid_dimension_1 = planewave_basis.fft_wave().grid_dimension_1();
146 int fft_grid_dimension_2 = planewave_basis.fft_wave().grid_dimension_2();
147 int fft_grid_dimension_3 = planewave_basis.fft_wave().grid_dimension_3();
148 g_to_fft_mapping.resize(number_of_plane_waves);
149 for (int g_vector_index = 0; g_vector_index < number_of_plane_waves; g_vector_index++) {
150 const auto& g_vector = planewave_basis.g_vectors()[g_vector_indices[g_vector_index]];
151 int miller_index_1 = static_cast<int>(std::round(g_vector.miller.x));
152 int miller_index_2 = static_cast<int>(std::round(g_vector.miller.y));
153 int miller_index_3 = static_cast<int>(std::round(g_vector.miller.z));
154 if (std::abs(miller_index_1) <= fft_grid_dimension_1 / 2 &&
155 std::abs(miller_index_2) <= fft_grid_dimension_2 / 2 &&
156 std::abs(miller_index_3) <= fft_grid_dimension_3 / 2) {
157 g_to_fft_mapping[g_vector_index] =
158 planewave_basis.fft_wave().g_vector_to_fft_grid_index(
159 miller_index_1, miller_index_2, miller_index_3);
160 } else {
161 g_to_fft_mapping[g_vector_index] = -1;
162 }
163 }
164}
165
166} // namespace q3
virtual int g_vector_to_fft_grid_index(int miller_1, int miller_2, int miller_3) const =0
virtual int local_g_space_points() const
Definition fft_grid.hpp:88
virtual void gather_local_g_space(const Complex *full_g_space, Complex *local_g_space) const
Definition fft_grid.hpp:123
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
const std::vector< GVector > & g_vectors() const
Definition init.hpp:148
FFTGrid & fft_rho()
Definition init.hpp:286
FFTGrid & fft_wave()
Definition init.hpp:292
double Real
std::vector< Complex > compute_smooth_grid_potential_from_dense(const std::vector< Real > &dense_real_space_potential, const PlanewaveBasis &planewave_basis)
std::vector< Real > inverse_fft_smooth_potential(const std::vector< Complex > &full_smooth_potential_g, const PlanewaveBasis &planewave_basis)
void compute_g_to_fft_index_mapping(int number_of_plane_waves, const std::vector< int > &g_vector_indices, const PlanewaveBasis &planewave_basis, std::vector< int > &g_to_fft_mapping)
std::complex< double > Complex