Loading...
Searching...
No Matches
fft_grid.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <cstring>
4#include <vector>
5
7
8namespace q3 {
9
10/**
11 * FFTGrid — Abstract interface for 3D FFT grids.
12 *
13 * Provides a unified contract for real <-> reciprocal space transformations,
14 * shielding consumers from the details of memory distribution (serial vs. MPI).
15 */
16class FFTGrid {
17 public:
18 virtual ~FFTGrid() = default;
19
20 /**
21 * Initialize with grid dimensions (dimension_1 x dimension_2 x dimension_3).
22 */
23 virtual void
24 init(int dimension_1, int dimension_2, int dimension_3, bool use_measured_planning = false) = 0;
25
26 /**
27 * Grid size along the first dimension.
28 */
29 [[nodiscard]] virtual int grid_dimension_1() const = 0;
30 /**
31 * Grid size along the second dimension.
32 */
33 [[nodiscard]] virtual int grid_dimension_2() const = 0;
34 /**
35 * Grid size along the third dimension.
36 */
37 [[nodiscard]] virtual int grid_dimension_3() const = 0;
38
39 /**
40 * Total number of real-space grid points across all processes.
41 */
42 [[nodiscard]] virtual int total_grid_points() const = 0;
43
44 /**
45 * Number of real-space grid points stored on the local rank.
46 */
47 [[nodiscard]] virtual int local_grid_points() const = 0;
48
49 /**
50 * Offset into the global grid for the local segment (e.g., start of slab).
51 */
52 [[nodiscard]] virtual int local_offset() const = 0;
53
54 /**
55 * Where this rank's real-space point `local_index` sits in the whole grid,
56 * as the flat index i1·d2·d3 + i2·d3 + i3 that a serial grid stores it at.
57 *
58 * Code that needs the position of a grid point -- a multipole moment, a
59 * kernel evaluated at r -- must ask this rather than add local_offset()
60 * planes: that is right for a serial grid and a slab along the first
61 * dimension, which is this default, and wrong for the stick layout, which
62 * holds planes along the third dimension and overrides it.
63 */
64 [[nodiscard]] virtual int global_real_space_index(int local_index) const {
65 return local_offset() * grid_dimension_2() * grid_dimension_3() + local_index;
66 }
67
68 /**
69 * Number of first-dimension planes owned by this rank for G-vector partitioning.
70 *
71 * In serial mode this equals grid_dimension_1(). In MPI mode this
72 * returns the slab size (local_n1 from FFTW's decomposition), which
73 * may differ from local_grid_points() / (dim2*dim3) when the FFT
74 * operates on the full grid (Allreduce mode).
75 */
76 [[nodiscard]] virtual int slab_dimension_1() const {
77 return grid_dimension_1(); // Default: full grid (serial)
78 }
79
80 /**
81 * Number of reciprocal-space points this rank holds.
82 *
83 * Equal to local_grid_points() for every layout in which G-space and
84 * real space share a buffer. The stick decomposition holds G-space as
85 * whole sticks and real space as whole planes, so the two counts differ,
86 * and a caller sizing a G-space scatter must ask this rather than assume.
87 */
88 [[nodiscard]] virtual int local_g_space_points() const {
89 return local_grid_points();
90 }
91
92 /**
93 * Whether this rank holds the G-vectors sharing the first two Miller
94 * indices. True everywhere for a layout that does not distribute G-space
95 * by stick; the plane-wave selector asks this to decide what to keep.
96 */
97 [[nodiscard]] virtual bool owns_stick(int /*miller_1*/, int /*miller_2*/) const {
98 return true;
99 }
100
101 /**
102 * Assign reciprocal-space ownership from a weight per stick -- the number
103 * of G-vectors on it inside the cutoff. Only a layout that distributes by
104 * stick has anything to do with it; everything else ignores it.
105 */
106 virtual void assign_sticks(const std::vector<int>& /*stick_weights*/) {}
107
108 /**
109 * Whether a G-vector and its negative are always held by the same rank.
110 * True for a serial grid and for the stick layout; false for slabs, which
111 * is what forced the gamma-only path off under MPI.
112 */
113 [[nodiscard]] virtual bool co_locates_mirrors() const {
114 return true;
115 }
116
117 /**
118 * Copy this rank's reciprocal-space points out of a full, replicated
119 * G-space array laid out [i1][i2][i3], into the local G-space layout.
120 * The slab layout is a contiguous run at local_offset(); the stick layout
121 * is not, so consumers must go through this rather than a memcpy.
122 */
123 virtual void gather_local_g_space(const Complex* full_g_space, Complex* local_g_space) const {
124 std::size_t offset = static_cast<std::size_t>(local_offset()) *
125 static_cast<std::size_t>(grid_dimension_2()) *
126 static_cast<std::size_t>(grid_dimension_3());
127 std::memcpy(local_g_space,
128 full_g_space + offset,
129 static_cast<std::size_t>(local_g_space_points()) * sizeof(Complex));
130 }
131
132 /// The reverse: place this rank's reciprocal-space points into a full array.
133 virtual void scatter_local_g_space(const Complex* local_g_space, Complex* full_g_space) const {
134 std::size_t offset = static_cast<std::size_t>(local_offset()) *
135 static_cast<std::size_t>(grid_dimension_2()) *
136 static_cast<std::size_t>(grid_dimension_3());
137 std::memcpy(full_g_space + offset,
138 local_g_space,
139 static_cast<std::size_t>(local_g_space_points()) * sizeof(Complex));
140 }
141
142 /**
143 * Forward FFT: real-space -> reciprocal-space (unnormalized).
144 */
145 virtual void forward(Complex* data) const = 0;
146
147 /**
148 * Inverse FFT: reciprocal-space -> real-space (includes 1/N normalization).
149 */
150 virtual void inverse(Complex* data) const = 0;
151
152 /**
153 * Inverse FFT without 1/N normalization (raw FFTW backward).
154 */
155 virtual void inverse_raw(Complex* data) const = 0;
156
157 /**
158 * Map a G-vector (integer Miller indices) to FFT array index.
159 * Handles negative indices via wrapping.
160 */
161 [[nodiscard]] virtual int
162 g_vector_to_fft_grid_index(int miller_1, int miller_2, int miller_3) const = 0;
163
164 /**
165 * Reduce G-space data across the pool to make it redundant on all ranks.
166 *
167 * Serial: no-op.
168 * MPI: MPI_Allreduce(MPI_IN_PLACE, ..., MPI_SUM, pool_comm).
169 */
170 virtual void reduce_g_space(Complex* data, int number_of_elements) const = 0;
171
172 /**
173 * Access the FFT grid's internal input buffer for zero-copy transforms.
174 *
175 * Callers scatter G-space data directly into this buffer, then call
176 * execute_inverse_raw_on_buffers() to transform in-place. This eliminates
177 * the memcpy overhead of inverse_raw() / forward().
178 *
179 * The buffer has at least local_grid_points() elements of usable space.
180 */
181 virtual Complex* input_buffer() const = 0;
182
183 /**
184 * Access the FFT grid's internal output buffer for zero-copy transforms.
185 *
186 * After execute_inverse_raw_on_buffers(), real-space data is here.
187 * After execute_forward_on_buffers(), G-space data is here.
188 *
189 * For in-place plans (MPI FFT), this returns the same pointer as input_buffer().
190 * For out-of-place plans (Serial FFT), this is a separate buffer.
191 */
192 virtual Complex* output_buffer() const = 0;
193
194 /**
195 * Execute a raw inverse FFT on the internal buffers (no copy, no normalization).
196 *
197 * Caller must have populated input_buffer() before calling.
198 * Result is available in output_buffer() after this call.
199 */
200 virtual void execute_inverse_raw_on_buffers() const = 0;
201
202 /**
203 * Execute a forward FFT on the internal buffers (no copy).
204 *
205 * Caller must have populated input_buffer() before calling.
206 * Result is available in output_buffer() after this call.
207 */
208 virtual void execute_forward_on_buffers() const = 0;
209
210 /**
211 * Prepare the input buffer for a forward transform after modifying the output buffer.
212 *
213 * For out-of-place plans (Serial FFT), this copies output_buffer → input_buffer.
214 * For in-place plans (MPI FFT), this is a no-op (same buffer).
215 *
216 * Call this between the real-space multiply and execute_forward_on_buffers().
217 */
218 virtual void prepare_forward_from_output() const = 0;
219
220 /**
221 * How many transforms this grid can have in flight at once.
222 *
223 * The zero-copy buffer API above is a single set of scratch arrays, so two
224 * threads transforming through it at the same time would overwrite each
225 * other. A grid that can hand out independent scratch reports more than
226 * one slot here and honours the `*_on_slot` calls below; a grid that
227 * cannot leaves this at 1 and callers keep their band loops serial.
228 *
229 * Slot 0 is always the buffers `input_buffer()` and `output_buffer()`
230 * return, so a caller that asks for one slot and a caller that never heard
231 * of slots drive exactly the same arrays through exactly the same plan.
232 * That is what keeps a one-thread run bit-identical to a build with no
233 * threading at all.
234 */
235 [[nodiscard]] virtual int concurrent_transform_slots() const {
236 return 1;
237 }
238
239 /**
240 * The input buffer of transform slot `slot`, the slot-aware form of
241 * `input_buffer()`.
242 */
243 [[nodiscard]] virtual Complex* slot_input_buffer(int /*slot*/) const {
244 return input_buffer();
245 }
246
247 /**
248 * The output buffer of transform slot `slot`, the slot-aware form of
249 * `output_buffer()`.
250 */
251 [[nodiscard]] virtual Complex* slot_output_buffer(int /*slot*/) const {
252 return output_buffer();
253 }
254
255 /// Inverse transform on slot `slot`, the slot-aware `execute_inverse_raw_on_buffers()`.
256 virtual void execute_inverse_raw_on_slot(int /*slot*/) const {
258 }
259
260 /// Forward transform on slot `slot`, the slot-aware `execute_forward_on_buffers()`.
261 virtual void execute_forward_on_slot(int /*slot*/) const {
263 }
264
265 /// Output-to-input copy on slot `slot`, the slot-aware `prepare_forward_from_output()`.
266 virtual void prepare_forward_from_output_on_slot(int /*slot*/) const {
268 }
269
270 /**
271 * In-place inverse transform of a caller-owned array through slot `slot`,
272 * the slot-aware form of `inverse_raw()`.
273 *
274 * The array belongs to the caller, so two threads passing different arrays
275 * collide only over the grid's own scratch -- which is what the slot
276 * separates.
277 */
278 virtual void inverse_raw_on_slot(Complex* data, int /*slot*/) const {
279 inverse_raw(data);
280 }
281
282 /**
283 * Pre-create FFTW plans for batched transforms of a given batch size.
284 *
285 * Must be called before forward_batched() or inverse_raw_batched() with the
286 * corresponding batch_size. Calling with a different batch_size replaces
287 * the existing batched plans.
288 *
289 * The default implementation is a no-op (the fallback batched methods loop
290 * over single-transform calls, which need no separate plan).
291 */
292 virtual void initialize_batched_plans(int /*batch_size*/,
293 bool /*use_measured_planning*/ = false) const {
294 // Default: no-op — batched methods fall back to per-transform loops.
295 }
296
297 /**
298 * Returns the batch_size for which batched plans are currently initialized,
299 * or 0 if no batched plans have been created.
300 *
301 * Callers can use this to determine whether to zero-pad a smaller batch
302 * to the pre-initialized plan size (avoiding a plan recreation that would
303 * require a collective MPI call).
304 */
305 virtual int initialized_batched_plan_size() const {
306 return 0; // Default: no batched plan
307 }
308
309 /**
310 * Batched forward FFT: real-space -> reciprocal-space (unnormalized).
311 *
312 * Transforms batch_size independent 3D grids packed contiguously:
313 * data[0 .. local_grid_points-1] = transform 0
314 * data[local_grid_points .. 2*local_grid_points-1] = transform 1
315 * ...
316 *
317 * The default implementation loops over forward() for each transform.
318 */
319 virtual void forward_batched(Complex* data, int batch_size) const {
320 const int stride = local_grid_points();
321 for (int transform_index = 0; transform_index < batch_size; ++transform_index) {
322 forward(data + transform_index * stride);
323 }
324 }
325
326 /**
327 * Batched inverse FFT without 1/N normalization (raw FFTW backward).
328 *
329 * Transforms batch_size independent 3D grids packed contiguously
330 * (same layout as forward_batched).
331 *
332 * The default implementation loops over inverse_raw() for each transform.
333 */
334 virtual void inverse_raw_batched(Complex* data, int batch_size) const {
335 const int stride = local_grid_points();
336 for (int transform_index = 0; transform_index < batch_size; ++transform_index) {
337 inverse_raw(data + transform_index * stride);
338 }
339 }
340};
341
342} // namespace q3
virtual void assign_sticks(const std::vector< int > &)
Definition fft_grid.hpp:106
virtual Complex * slot_input_buffer(int) const
Definition fft_grid.hpp:243
virtual int g_vector_to_fft_grid_index(int miller_1, int miller_2, int miller_3) const =0
virtual void initialize_batched_plans(int, bool=false) const
Definition fft_grid.hpp:292
virtual void inverse_raw_on_slot(Complex *data, int) const
Definition fft_grid.hpp:278
virtual void execute_forward_on_buffers() const =0
virtual int concurrent_transform_slots() const
Definition fft_grid.hpp:235
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 Complex * slot_output_buffer(int) const
Definition fft_grid.hpp:251
virtual void gather_local_g_space(const Complex *full_g_space, Complex *local_g_space) const
Definition fft_grid.hpp:123
virtual void prepare_forward_from_output_on_slot(int) const
Output-to-input copy on slot slot, the slot-aware prepare_forward_from_output().
Definition fft_grid.hpp:266
virtual void execute_inverse_raw_on_slot(int) const
Inverse transform on slot slot, the slot-aware execute_inverse_raw_on_buffers().
Definition fft_grid.hpp:256
virtual int initialized_batched_plan_size() const
Definition fft_grid.hpp:305
virtual void reduce_g_space(Complex *data, int number_of_elements) const =0
virtual void prepare_forward_from_output() const =0
virtual void forward_batched(Complex *data, int batch_size) const
Definition fft_grid.hpp:319
virtual Complex * input_buffer() const =0
virtual void forward(Complex *data) const =0
virtual int global_real_space_index(int local_index) const
Definition fft_grid.hpp:64
virtual int total_grid_points() const =0
virtual int grid_dimension_1() const =0
virtual bool owns_stick(int, int) const
Definition fft_grid.hpp:97
virtual bool co_locates_mirrors() const
Definition fft_grid.hpp:113
virtual int slab_dimension_1() const
Definition fft_grid.hpp:76
virtual ~FFTGrid()=default
virtual void inverse_raw(Complex *data) const =0
virtual int grid_dimension_3() const =0
virtual void inverse_raw_batched(Complex *data, int batch_size) const
Definition fft_grid.hpp:334
virtual int local_offset() const =0
virtual Complex * output_buffer() const =0
virtual void inverse(Complex *data) const =0
virtual void execute_inverse_raw_on_buffers() const =0
virtual int grid_dimension_2() const =0
virtual void init(int dimension_1, int dimension_2, int dimension_3, bool use_measured_planning=false)=0
virtual void execute_forward_on_slot(int) const
Forward transform on slot slot, the slot-aware execute_forward_on_buffers().
Definition fft_grid.hpp:261
virtual int local_grid_points() const =0
std::complex< double > Complex