Loading...
Searching...
No Matches
fft_grid_mpi.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * MPIFFTGrid exists only when Q3 was linked against FFTW-MPI. Without it the
5 * MPI execution policy falls back to SerialFFTGrid, which supports k-point
6 * pools but not band groups -- see create_fft_grid() in
7 * layer_4_computation/iterator/kohn_sham/planewave/execution/mpi.hpp.
8 */
9#ifdef Q3_WITH_FFTW_MPI
10
11#include <cstdlib>
12#include <fftw3-mpi.h>
13#include <thread>
14#include <vector>
15
20
21namespace q3 {
22
23/**
24 * MPIFFTGrid — distributed 3D FFT using FFTW MPI.
25 *
26 * Splits the 3D grid across MPI ranks using slab decomposition
27 * (first dimension distributed). Each rank owns a contiguous range
28 * of planes [local_offset, local_offset + local_n1).
29 */
30class MPIFFTGrid : public FFTGrid {
31 public:
32 explicit MPIFFTGrid(MPI_Comm communicator) : communicator_(communicator) {}
33
34 ~MPIFFTGrid() override {
35 int finalized = 0;
36 MPI_Finalized(&finalized);
37 if (finalized) {
38 return;
39 }
40 if (plan_forward_)
41 fftw_destroy_plan(plan_forward_);
42 if (plan_inverse_)
43 fftw_destroy_plan(plan_inverse_);
44 if (plan_forward_batched_)
45 fftw_destroy_plan(plan_forward_batched_);
46 if (plan_inverse_batched_)
47 fftw_destroy_plan(plan_inverse_batched_);
48 }
49
50 // Non-copyable
51 MPIFFTGrid(const MPIFFTGrid&) = delete;
52 MPIFFTGrid& operator=(const MPIFFTGrid&) = delete;
53 MPIFFTGrid(MPIFFTGrid&&) = default;
54 MPIFFTGrid& operator=(MPIFFTGrid&&) = default;
55
56 void init(int dimension_1,
57 int dimension_2,
58 int dimension_3,
59 bool use_measured_planning = false) override {
60 grid_dimension_1_ = dimension_1;
61 grid_dimension_2_ = dimension_2;
62 grid_dimension_3_ = dimension_3;
63 total_grid_points_ = dimension_1 * dimension_2 * dimension_3;
64
65 ptrdiff_t local_n1, local_offset;
66 ptrdiff_t alloc_local = fftw_mpi_local_size_3d(
67 dimension_1, dimension_2, dimension_3, communicator_, &local_n1, &local_offset);
68
69 local_dimension_1_ = static_cast<int>(local_n1);
70 local_offset_ = static_cast<int>(local_offset);
71 local_grid_points_ = local_dimension_1_ * dimension_2 * dimension_3;
72
73 data_.resize(static_cast<std::size_t>(alloc_local));
74
75 unsigned int planning_flags = use_measured_planning ? FFTW_MEASURE : FFTW_ESTIMATE;
76
77 plan_forward_ = fftw_mpi_plan_dft_3d(dimension_1,
78 dimension_2,
79 dimension_3,
80 reinterpret_cast<fftw_complex*>(data_.data()),
81 reinterpret_cast<fftw_complex*>(data_.data()),
82 communicator_,
83 FFTW_FORWARD,
84 planning_flags);
85
86 plan_inverse_ = fftw_mpi_plan_dft_3d(dimension_1,
87 dimension_2,
88 dimension_3,
89 reinterpret_cast<fftw_complex*>(data_.data()),
90 reinterpret_cast<fftw_complex*>(data_.data()),
91 communicator_,
92 FFTW_BACKWARD,
93 planning_flags);
94 }
95
96 [[nodiscard]] int grid_dimension_1() const override {
97 return grid_dimension_1_;
98 }
99 [[nodiscard]] int grid_dimension_2() const override {
100 return grid_dimension_2_;
101 }
102 [[nodiscard]] int grid_dimension_3() const override {
103 return grid_dimension_3_;
104 }
105
106 [[nodiscard]] int total_grid_points() const override {
107 return total_grid_points_;
108 }
109
110 [[nodiscard]] int local_grid_points() const override {
111 return local_grid_points_;
112 }
113
114 /// Slabs split a plane from its mirror, which is what breaks gamma-only.
115 [[nodiscard]] bool co_locates_mirrors() const override {
116 return false;
117 }
118
119 [[nodiscard]] int local_offset() const override {
120 return local_offset_;
121 }
122
123 void reduce_g_space(Complex* data, int number_of_elements) const override {
124 execution::sum_complex_in_place(data, number_of_elements, communicator_);
125 }
126
127 Complex* input_buffer() const override {
128 return data_.data();
129 }
130
131 Complex* output_buffer() const override {
132 // In-place plan: input and output share the same buffer
133 return data_.data();
134 }
135
136 void execute_inverse_raw_on_buffers() const override {
137 ProfilingCounters::instance().fft_call_count++;
138 fftw_execute(plan_inverse_);
139 }
140
141 void execute_forward_on_buffers() const override {
142 ProfilingCounters::instance().fft_call_count++;
143 fftw_execute(plan_forward_);
144 }
145
146 void prepare_forward_from_output() const override {
147 // In-place plan: input and output are the same buffer — no copy needed
148 }
149
150 void forward(Complex* data) const override {
151 ProfilingCounters::instance().fft_call_count++;
152 std::copy(data, data + local_grid_points_, data_.begin());
153 fftw_execute(plan_forward_);
154 std::copy(data_.begin(), data_.begin() + local_grid_points_, data);
155 }
156
157 void inverse(Complex* data) const override {
158 ProfilingCounters::instance().fft_call_count++;
159 std::copy(data, data + local_grid_points_, data_.begin());
160 fftw_execute(plan_inverse_);
161
162 Real inverse_total_points = 1.0 / total_grid_points_;
163 for (int point_index = 0; point_index < local_grid_points_; point_index++) {
164 data[point_index] = data_[point_index] * inverse_total_points;
165 }
166 }
167
168 void inverse_raw(Complex* data) const override {
169 ProfilingCounters::instance().fft_call_count++;
170 std::copy(data, data + local_grid_points_, data_.begin());
171 fftw_execute(plan_inverse_);
172 std::copy(data_.begin(), data_.begin() + local_grid_points_, data);
173 }
174
175 [[nodiscard]] int
176 g_vector_to_fft_grid_index(int miller_1, int miller_2, int miller_3) const override {
177 int wrapped_index_1 = (miller_1 >= 0) ? miller_1 : miller_1 + grid_dimension_1_;
178 int wrapped_index_2 = (miller_2 >= 0) ? miller_2 : miller_2 + grid_dimension_2_;
179 int wrapped_index_3 = (miller_3 >= 0) ? miller_3 : miller_3 + grid_dimension_3_;
180
181 if (wrapped_index_1 < local_offset_ ||
182 wrapped_index_1 >= local_offset_ + local_dimension_1_) {
183 return -1;
184 }
185
186 int local_i1 = wrapped_index_1 - local_offset_;
187 return (local_i1 * grid_dimension_2_ + wrapped_index_2) * grid_dimension_3_ +
188 wrapped_index_3;
189 }
190
191 /**
192 * Returns the batch_size for which batched FFTW MPI plans are currently
193 * initialized, or 0 if no batched plans exist yet.
194 */
195 int initialized_batched_plan_size() const override {
196 return batched_count_;
197 }
198
199 /**
200 * Pre-create FFTW MPI plans for batched transforms of a given batch size.
201 *
202 * Uses fftw_mpi_plan_many_dft to create plans that transform batch_size
203 * independent 3D grids in a single MPI-coordinated call. This amortizes
204 * the MPI communication overhead across multiple transforms.
205 *
206 * If the grid has not yet been initialized via init(), this call is a no-op.
207 * Calling again with a different batch_size destroys the old plans and
208 * creates new ones.
209 */
210 void initialize_batched_plans(int batch_size,
211 bool use_measured_planning = false) const override {
212 if (grid_dimension_1_ == 0) {
213 return; // Grid not initialized yet
214 }
215
216 // Destroy previous batched plans if they exist
217 if (plan_forward_batched_) {
218 fftw_destroy_plan(plan_forward_batched_);
219 plan_forward_batched_ = nullptr;
220 }
221 if (plan_inverse_batched_) {
222 fftw_destroy_plan(plan_inverse_batched_);
223 plan_inverse_batched_ = nullptr;
224 }
225
226 batched_count_ = batch_size;
227
228 // Query allocation size for the batched layout
229 ptrdiff_t local_n1_batched, local_offset_batched;
230 const ptrdiff_t batched_dimensions[3] = {
231 grid_dimension_1_, grid_dimension_2_, grid_dimension_3_};
232 ptrdiff_t alloc_local_batched = fftw_mpi_local_size_many(3,
233 batched_dimensions,
234 static_cast<ptrdiff_t>(batch_size),
235 FFTW_MPI_DEFAULT_BLOCK,
236 communicator_,
237 &local_n1_batched,
238 &local_offset_batched);
239
240 data_batched_.resize(static_cast<std::size_t>(alloc_local_batched));
241
242 unsigned int planning_flags = use_measured_planning ? FFTW_MEASURE : FFTW_ESTIMATE;
243
244 ptrdiff_t dimensions[3] = {grid_dimension_1_, grid_dimension_2_, grid_dimension_3_};
245
246 plan_forward_batched_ =
247 fftw_mpi_plan_many_dft(3,
248 dimensions,
249 static_cast<ptrdiff_t>(batch_size),
250 FFTW_MPI_DEFAULT_BLOCK,
251 FFTW_MPI_DEFAULT_BLOCK,
252 reinterpret_cast<fftw_complex*>(data_batched_.data()),
253 reinterpret_cast<fftw_complex*>(data_batched_.data()),
254 communicator_,
255 FFTW_FORWARD,
256 planning_flags);
257
258 plan_inverse_batched_ =
259 fftw_mpi_plan_many_dft(3,
260 dimensions,
261 static_cast<ptrdiff_t>(batch_size),
262 FFTW_MPI_DEFAULT_BLOCK,
263 FFTW_MPI_DEFAULT_BLOCK,
264 reinterpret_cast<fftw_complex*>(data_batched_.data()),
265 reinterpret_cast<fftw_complex*>(data_batched_.data()),
266 communicator_,
267 FFTW_BACKWARD,
268 planning_flags);
269 }
270
271 /**
272 * Batched forward FFT using fftw_mpi_plan_many_dft.
273 *
274 * Expects batch_size independent 3D grids packed contiguously in data,
275 * each of size local_grid_points(). The batch_size must match the value
276 * passed to initialize_batched_plans().
277 *
278 * Falls back to per-transform loop if batched plans are not initialized
279 * or if batch_size does not match.
280 */
281 void forward_batched(Complex* data, int batch_size) const override {
282 if (!plan_forward_batched_ || batch_size != batched_count_) {
283 FFTGrid::forward_batched(data, batch_size);
284 return;
285 }
286 ProfilingCounters::instance().fft_call_count += batch_size;
287
288 /**
289 * Convert from contiguous layout (transform t at offset t*N) to FFTW's
290 * interleaved howmany-tuple layout (element i of transform t at offset i*B+t)
291 */
292 const int local_points = local_grid_points_;
293 std::fill(data_batched_.begin(),
294 data_batched_.begin() + static_cast<std::size_t>(local_points) * batch_size,
295 Complex(0.0, 0.0));
296 for (int transform_index = 0; transform_index < batch_size; ++transform_index) {
297 const Complex* source = data + transform_index * local_points;
298 for (int point_index = 0; point_index < local_points; ++point_index) {
299 data_batched_[point_index * batch_size + transform_index] = source[point_index];
300 }
301 }
302
303 fftw_execute(plan_forward_batched_);
304
305 // Convert back from interleaved to contiguous layout
306 for (int transform_index = 0; transform_index < batch_size; ++transform_index) {
307 Complex* destination = data + transform_index * local_points;
308 for (int point_index = 0; point_index < local_points; ++point_index) {
309 destination[point_index] =
310 data_batched_[point_index * batch_size + transform_index];
311 }
312 }
313 }
314
315 /**
316 * Batched inverse FFT (raw, no normalization) using fftw_mpi_plan_many_dft.
317 *
318 * Expects batch_size independent 3D grids packed contiguously in data,
319 * each of size local_grid_points(). The batch_size must match the value
320 * passed to initialize_batched_plans().
321 *
322 * Falls back to per-transform loop if batched plans are not initialized
323 * or if batch_size does not match.
324 */
325 void inverse_raw_batched(Complex* data, int batch_size) const override {
326 if (!plan_inverse_batched_ || batch_size != batched_count_) {
327 FFTGrid::inverse_raw_batched(data, batch_size);
328 return;
329 }
330 ProfilingCounters::instance().fft_call_count += batch_size;
331
332 // Convert from contiguous layout to FFTW's interleaved howmany-tuple layout
333 const int local_points = local_grid_points_;
334 std::fill(data_batched_.begin(),
335 data_batched_.begin() + static_cast<std::size_t>(local_points) * batch_size,
336 Complex(0.0, 0.0));
337 for (int transform_index = 0; transform_index < batch_size; ++transform_index) {
338 const Complex* source = data + transform_index * local_points;
339 for (int point_index = 0; point_index < local_points; ++point_index) {
340 data_batched_[point_index * batch_size + transform_index] = source[point_index];
341 }
342 }
343
344 fftw_execute(plan_inverse_batched_);
345
346 // Convert back from interleaved to contiguous layout
347 for (int transform_index = 0; transform_index < batch_size; ++transform_index) {
348 Complex* destination = data + transform_index * local_points;
349 for (int point_index = 0; point_index < local_points; ++point_index) {
350 destination[point_index] =
351 data_batched_[point_index * batch_size + transform_index];
352 }
353 }
354 }
355
356 private:
357 MPI_Comm communicator_;
358 int grid_dimension_1_{}, grid_dimension_2_{}, grid_dimension_3_{};
359 int total_grid_points_{};
360 int local_dimension_1_{}, local_offset_{}, local_grid_points_{};
361 fftw_plan plan_forward_{nullptr};
362 fftw_plan plan_inverse_{nullptr};
363 mutable std::vector<Complex> data_;
364
365 // Batched plan state
366 mutable fftw_plan plan_forward_batched_{nullptr};
367 mutable fftw_plan plan_inverse_batched_{nullptr};
368 mutable int batched_count_{0};
369 mutable std::vector<Complex> data_batched_;
370};
371
372} // namespace q3
373
374#endif // Q3_WITH_FFTW_MPI
void init(const std::string &log_file="")
Definition logger.hpp:59
double Real
std::complex< double > Complex