Loading...
Searching...
No Matches
fft_grid_sticks.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * FFT grid distributed by sticks in reciprocal space and by planes in real
5 * space, with the transpose between them owned here rather than by FFTW.
6 *
7 * A stick is the set of grid points sharing the first two indices and running
8 * the whole third dimension. Reciprocal-space data lives as whole sticks,
9 * assigned to ranks by FFTStickPartition -- mirror pairs together, balanced by
10 * weight. Real-space data lives as whole planes perpendicular to the third
11 * dimension, a contiguous range per rank. A transform is three steps:
12 *
13 * inverse: 1D transforms along the third dimension on local sticks,
14 * an all-to-all that turns sticks into planes,
15 * 2D transforms on local planes.
16 * forward: the same three in reverse.
17 *
18 * Sticks that carry no G-vectors inside the cutoff are never transported.
19 * They are zero on the way in and dropped on the way out, which is the
20 * truncation every plane-wave code applies at the cutoff sphere.
21 *
22 * The two layouts are the reason this class exists. FFTW-MPI's slab layout
23 * puts a plane and its mirror on different ranks, which is what forced the
24 * gamma-only path off under MPI; here a stick and its mirror always share a
25 * rank. It is also what lets the split balance: measured on a 72^3 grid, the
26 * slab split was uneven by 12.9x across four ranks and left ranks empty at
27 * eight, where this one is exactly balanced at every width tested.
28 *
29 * Consequences for callers, stated once:
30 * - input_buffer() is the stick layout, sized local_g_space_points();
31 * output_buffer() is the plane layout, sized local_grid_points(). They
32 * are different buffers of different sizes.
33 * - g_vector_to_fft_grid_index() indexes the stick layout, or -1 when the
34 * stick is not held here.
35 * - The pointer forms forward(data)/inverse(data) switch layout in place:
36 * the caller's buffer must hold the larger of the two counts, and after
37 * inverse() it is in plane layout, after forward() in stick layout.
38 */
39
40#include <fftw3.h>
41#include <mpi.h>
42
43#include <algorithm>
44#include <cstddef>
45#include <cstring>
46#include <numeric>
47#include <stdexcept>
48#include <vector>
49
53
54namespace q3 {
55
56class FFTGridSticks : public FFTGrid {
57 public:
58 explicit FFTGridSticks(MPI_Comm communicator) : communicator_(communicator) {
59 MPI_Comm_rank(communicator_, &rank_);
60 MPI_Comm_size(communicator_, &size_);
61 }
62
63 ~FFTGridSticks() override {
64 destroy_plans();
65 }
66
67 FFTGridSticks(const FFTGridSticks&) = delete;
69
70 /**
71 * Size the grid and, until told otherwise, treat every stick as occupied.
72 * assign_sticks() narrows that to the sticks inside a cutoff once the
73 * basis knows which those are.
74 */
75 void init(int dimension_1,
76 int dimension_2,
77 int dimension_3,
78 bool use_measured_planning = false) override {
79 grid_dimension_1_ = dimension_1;
80 grid_dimension_2_ = dimension_2;
81 grid_dimension_3_ = dimension_3;
82 use_measured_planning_ = use_measured_planning;
83 total_grid_points_ = dimension_1 * dimension_2 * dimension_3;
84
85 /**
86 * Planes along the third dimension, contiguous ranges, the remainder
87 * spread one plane at a time over the lowest ranks.
88 */
89 int base = dimension_3 / size_;
90 int remainder = dimension_3 % size_;
91 plane_counts_.assign(static_cast<std::size_t>(size_), base);
92 plane_offsets_.assign(static_cast<std::size_t>(size_), 0);
93 for (int rank = 0; rank < size_; ++rank) {
94 if (rank < remainder) {
95 plane_counts_[static_cast<std::size_t>(rank)] += 1;
96 }
97 plane_offsets_[static_cast<std::size_t>(rank)] =
98 rank == 0 ? 0
99 : plane_offsets_[static_cast<std::size_t>(rank - 1)] +
100 plane_counts_[static_cast<std::size_t>(rank - 1)];
101 }
102 local_planes_ = plane_counts_[static_cast<std::size_t>(rank_)];
103 local_plane_offset_ = plane_offsets_[static_cast<std::size_t>(rank_)];
104 local_grid_points_ = local_planes_ * dimension_1 * dimension_2;
105
106 std::vector<int> all_occupied(static_cast<std::size_t>(dimension_1) * dimension_2, 1);
107 assign_sticks(all_occupied);
108 }
109
110 /**
111 * Assign sticks to ranks from a weight per stick -- the number of
112 * G-vectors on it inside the cutoff, zero for an empty stick -- and rebuild
113 * the communication plan and the FFTW plans to match.
114 */
115 void assign_sticks(const std::vector<int>& stick_weights) override {
116 partition_.build(grid_dimension_1_, grid_dimension_2_, stick_weights, size_);
117
118 /**
119 * Every rank enumerates every rank's sticks in the same order, so the
120 * transpose can be unpacked without exchanging index lists.
121 */
122 sticks_by_rank_.assign(static_cast<std::size_t>(size_), {});
123 stick_local_index_.assign(static_cast<std::size_t>(grid_dimension_1_) * grid_dimension_2_,
124 -1);
125 for (int first = 0; first < grid_dimension_1_; ++first) {
126 for (int second = 0; second < grid_dimension_2_; ++second) {
127 int owner = partition_.owner_of(first, second);
128 if (owner == FFTStickPartition::unassigned) {
129 continue;
130 }
131 auto& list = sticks_by_rank_[static_cast<std::size_t>(owner)];
132 if (owner == rank_) {
133 stick_local_index_[static_cast<std::size_t>(first) * grid_dimension_2_ +
134 second] = static_cast<int>(list.size());
135 }
136 list.push_back(first * grid_dimension_2_ + second);
137 }
138 }
139 local_sticks_ = static_cast<int>(sticks_by_rank_[static_cast<std::size_t>(rank_)].size());
140 local_g_space_points_ = local_sticks_ * grid_dimension_3_;
141
142 /**
143 * All-to-all counts. Rank r sends, to rank q, the plane range of q for
144 * each of r's sticks; it receives from q the plane range of r for each
145 * of q's sticks.
146 */
147 send_counts_.assign(static_cast<std::size_t>(size_), 0);
148 recv_counts_.assign(static_cast<std::size_t>(size_), 0);
149 send_displacements_.assign(static_cast<std::size_t>(size_), 0);
150 recv_displacements_.assign(static_cast<std::size_t>(size_), 0);
151 for (int other = 0; other < size_; ++other) {
152 send_counts_[static_cast<std::size_t>(other)] =
153 local_sticks_ * plane_counts_[static_cast<std::size_t>(other)];
154 recv_counts_[static_cast<std::size_t>(other)] =
155 static_cast<int>(sticks_by_rank_[static_cast<std::size_t>(other)].size()) *
156 local_planes_;
157 }
158 for (int other = 1; other < size_; ++other) {
159 send_displacements_[static_cast<std::size_t>(other)] =
160 send_displacements_[static_cast<std::size_t>(other - 1)] +
161 send_counts_[static_cast<std::size_t>(other - 1)];
162 recv_displacements_[static_cast<std::size_t>(other)] =
163 recv_displacements_[static_cast<std::size_t>(other - 1)] +
164 recv_counts_[static_cast<std::size_t>(other - 1)];
165 }
166 int transpose_size = 0;
167 for (int other = 0; other < size_; ++other) {
168 transpose_size = std::max(transpose_size,
169 send_displacements_[static_cast<std::size_t>(other)] +
170 send_counts_[static_cast<std::size_t>(other)]);
171 transpose_size = std::max(transpose_size,
172 recv_displacements_[static_cast<std::size_t>(other)] +
173 recv_counts_[static_cast<std::size_t>(other)]);
174 }
175
176 std::size_t larger =
177 static_cast<std::size_t>(std::max(local_g_space_points_, local_grid_points_));
178 stick_buffer_.assign(std::max<std::size_t>(larger, 1), Complex(0.0, 0.0));
179 plane_buffer_.assign(std::max<std::size_t>(larger, 1), Complex(0.0, 0.0));
180 transpose_buffer_.assign(std::max<std::size_t>(static_cast<std::size_t>(transpose_size), 1),
181 Complex(0.0, 0.0));
182
183 create_plans();
184 }
185
186 [[nodiscard]] int grid_dimension_1() const override {
187 return grid_dimension_1_;
188 }
189 [[nodiscard]] int grid_dimension_2() const override {
190 return grid_dimension_2_;
191 }
192 [[nodiscard]] int grid_dimension_3() const override {
193 return grid_dimension_3_;
194 }
195 [[nodiscard]] int total_grid_points() const override {
196 return total_grid_points_;
197 }
198 [[nodiscard]] int local_grid_points() const override {
199 return local_grid_points_;
200 }
201 [[nodiscard]] int local_g_space_points() const override {
202 return local_g_space_points_;
203 }
204 /// First plane along the third dimension held here.
205 [[nodiscard]] int local_offset() const override {
206 return local_plane_offset_;
207 }
208 /// Local real space is [plane][i1][i2], the plane counted from local_offset().
209 [[nodiscard]] int global_real_space_index(int local_index) const override {
210 int points_per_plane = grid_dimension_1_ * grid_dimension_2_;
211 int index_3 = local_plane_offset_ + local_index / points_per_plane;
212 int index_within_plane = local_index % points_per_plane;
213 int index_1 = index_within_plane / grid_dimension_2_;
214 int index_2 = index_within_plane % grid_dimension_2_;
215 return (index_1 * grid_dimension_2_ + index_2) * grid_dimension_3_ + index_3;
216 }
217 /// No restriction along the first dimension: ownership is by stick.
218 [[nodiscard]] int slab_dimension_1() const override {
219 return grid_dimension_1_;
220 }
221 [[nodiscard]] int local_sticks() const {
222 return local_sticks_;
223 }
224 [[nodiscard]] const FFTStickPartition& partition() const {
225 return partition_;
226 }
227
228 [[nodiscard]] bool co_locates_mirrors() const override {
229 return true;
230 }
231
232 void gather_local_g_space(const Complex* full_g_space, Complex* local_g_space) const override {
233 const auto& mine = sticks_by_rank_[static_cast<std::size_t>(rank_)];
234 for (std::size_t stick = 0; stick < mine.size(); ++stick) {
235 std::memcpy(local_g_space + stick * static_cast<std::size_t>(grid_dimension_3_),
236 full_g_space + static_cast<std::size_t>(mine[stick]) * grid_dimension_3_,
237 static_cast<std::size_t>(grid_dimension_3_) * sizeof(Complex));
238 }
239 }
240
241 void scatter_local_g_space(const Complex* local_g_space, Complex* full_g_space) const override {
242 const auto& mine = sticks_by_rank_[static_cast<std::size_t>(rank_)];
243 for (std::size_t stick = 0; stick < mine.size(); ++stick) {
244 std::memcpy(full_g_space + static_cast<std::size_t>(mine[stick]) * grid_dimension_3_,
245 local_g_space + stick * static_cast<std::size_t>(grid_dimension_3_),
246 static_cast<std::size_t>(grid_dimension_3_) * sizeof(Complex));
247 }
248 }
249
250 [[nodiscard]] bool owns_stick(int miller_1, int miller_2) const override {
251 return partition_.owner_of_miller(miller_1, miller_2) == rank_;
252 }
253
254 [[nodiscard]] int
255 g_vector_to_fft_grid_index(int miller_1, int miller_2, int miller_3) const override {
256 int wrapped_1 = FFTStickPartition::wrap(miller_1, grid_dimension_1_);
257 int wrapped_2 = FFTStickPartition::wrap(miller_2, grid_dimension_2_);
258 int wrapped_3 = FFTStickPartition::wrap(miller_3, grid_dimension_3_);
259 int local_stick =
260 stick_local_index_[static_cast<std::size_t>(wrapped_1) * grid_dimension_2_ + wrapped_2];
261 if (local_stick < 0) {
262 return -1;
263 }
264 return local_stick * grid_dimension_3_ + wrapped_3;
265 }
266
267 // ─── Zero-copy buffer API ──────────────────────────────────────────
268
269 Complex* input_buffer() const override {
270 return stick_buffer_.data();
271 }
272 Complex* output_buffer() const override {
273 return plane_buffer_.data();
274 }
275
276 void execute_inverse_raw_on_buffers() const override {
277 sticks_to_planes(stick_buffer_.data(), plane_buffer_.data());
278 }
279
280 /**
281 * The interface promises the result of any transform in output_buffer().
282 * The forward transform lands in the stick buffer, so it is copied across
283 * -- one memcpy of the G-space count, the same order as the transpose pack
284 * -- rather than making every consumer learn which buffer to read.
285 */
286 void execute_forward_on_buffers() const override {
287 planes_to_sticks(plane_buffer_.data(), stick_buffer_.data());
288 std::memcpy(plane_buffer_.data(),
289 stick_buffer_.data(),
290 static_cast<std::size_t>(local_g_space_points_) * sizeof(Complex));
291 }
292
293 /// The forward transform consumes the plane buffer directly.
294 void prepare_forward_from_output() const override {}
295
296 // ─── Pointer forms ─────────────────────────────────────────────────
297
298 void forward(Complex* data) const override {
299 std::memcpy(plane_buffer_.data(), data, local_grid_points_ * sizeof(Complex));
300 planes_to_sticks(plane_buffer_.data(), stick_buffer_.data());
301 std::memcpy(data, stick_buffer_.data(), local_g_space_points_ * sizeof(Complex));
302 }
303
304 void inverse_raw(Complex* data) const override {
305 std::memcpy(stick_buffer_.data(), data, local_g_space_points_ * sizeof(Complex));
306 sticks_to_planes(stick_buffer_.data(), plane_buffer_.data());
307 std::memcpy(data, plane_buffer_.data(), local_grid_points_ * sizeof(Complex));
308 }
309
310 void inverse(Complex* data) const override {
311 inverse_raw(data);
312 Real scale = 1.0 / static_cast<Real>(total_grid_points_);
313 for (int index = 0; index < local_grid_points_; ++index) {
314 data[index] *= scale;
315 }
316 }
317
318 void reduce_g_space(Complex* data, int number_of_elements) const override {
319 MPI_Allreduce(
320 MPI_IN_PLACE, data, number_of_elements, MPI_C_DOUBLE_COMPLEX, MPI_SUM, communicator_);
321 }
322
323 private:
324 // ─── The transform ─────────────────────────────────────────────────
325
326 /**
327 * Sticks to planes: 1D along the third dimension on each local stick, then
328 * the all-to-all, then 2D on each local plane. The all-to-all sends, for
329 * each destination, its plane range of every local stick, which is a
330 * contiguous run of the stick.
331 */
332 void sticks_to_planes(Complex* sticks, Complex* planes) const {
333 if (local_sticks_ > 0) {
334 fftw_execute_dft(plan_sticks_inverse_,
335 reinterpret_cast<fftw_complex*>(sticks),
336 reinterpret_cast<fftw_complex*>(sticks));
337 }
338
339 Complex* send = transpose_buffer_.data();
340 for (int other = 0; other < size_; ++other) {
341 int plane_start = plane_offsets_[static_cast<std::size_t>(other)];
342 int plane_count = plane_counts_[static_cast<std::size_t>(other)];
343 Complex* destination = send + send_displacements_[static_cast<std::size_t>(other)];
344 for (int stick = 0; stick < local_sticks_; ++stick) {
345 std::memcpy(destination + static_cast<std::size_t>(stick) * plane_count,
346 sticks + static_cast<std::size_t>(stick) * grid_dimension_3_ +
347 plane_start,
348 static_cast<std::size_t>(plane_count) * sizeof(Complex));
349 }
350 }
351
352 std::vector<Complex>& receive = receive_buffer_;
353 receive.resize(std::max<std::size_t>(
354 static_cast<std::size_t>(recv_displacements_.back() + recv_counts_.back()), 1));
355 MPI_Alltoallv(send,
356 send_counts_.data(),
357 send_displacements_.data(),
358 MPI_C_DOUBLE_COMPLEX,
359 receive.data(),
360 recv_counts_.data(),
361 recv_displacements_.data(),
362 MPI_C_DOUBLE_COMPLEX,
363 communicator_);
364
365 /**
366 * Unpack into dense planes. Sticks nobody owns carry nothing and stay
367 * zero, which is the cutoff truncation.
368 */
369 std::fill(planes, planes + local_grid_points_, Complex(0.0, 0.0));
370 std::size_t plane_stride = static_cast<std::size_t>(grid_dimension_1_) * grid_dimension_2_;
371 for (int other = 0; other < size_; ++other) {
372 const auto& their_sticks = sticks_by_rank_[static_cast<std::size_t>(other)];
373 const Complex* source =
374 receive.data() + recv_displacements_[static_cast<std::size_t>(other)];
375 for (std::size_t stick = 0; stick < their_sticks.size(); ++stick) {
376 int position = their_sticks[stick]; // first * dimension_2 + second
377 for (int plane = 0; plane < local_planes_; ++plane) {
378 planes[static_cast<std::size_t>(plane) * plane_stride + position] =
379 source[stick * static_cast<std::size_t>(local_planes_) + plane];
380 }
381 }
382 }
383
384 if (local_planes_ > 0) {
385 fftw_execute_dft(plan_planes_inverse_,
386 reinterpret_cast<fftw_complex*>(planes),
387 reinterpret_cast<fftw_complex*>(planes));
388 }
389 }
390
391 /// Planes to sticks: the same three steps, reversed.
392 void planes_to_sticks(Complex* planes, Complex* sticks) const {
393 if (local_planes_ > 0) {
394 fftw_execute_dft(plan_planes_forward_,
395 reinterpret_cast<fftw_complex*>(planes),
396 reinterpret_cast<fftw_complex*>(planes));
397 }
398
399 /**
400 * Pack occupied sticks out of the dense planes, in the layout the
401 * receiving side unpacks from above: for each destination rank, its
402 * sticks in its order, and for each stick this rank's planes.
403 */
404 std::vector<Complex>& send = receive_buffer_;
405 send.resize(std::max<std::size_t>(
406 static_cast<std::size_t>(recv_displacements_.back() + recv_counts_.back()), 1));
407 std::size_t plane_stride = static_cast<std::size_t>(grid_dimension_1_) * grid_dimension_2_;
408 for (int other = 0; other < size_; ++other) {
409 const auto& their_sticks = sticks_by_rank_[static_cast<std::size_t>(other)];
410 Complex* destination =
411 send.data() + recv_displacements_[static_cast<std::size_t>(other)];
412 for (std::size_t stick = 0; stick < their_sticks.size(); ++stick) {
413 int position = their_sticks[stick];
414 for (int plane = 0; plane < local_planes_; ++plane) {
415 destination[stick * static_cast<std::size_t>(local_planes_) + plane] =
416 planes[static_cast<std::size_t>(plane) * plane_stride + position];
417 }
418 }
419 }
420
421 Complex* receive = transpose_buffer_.data();
422 MPI_Alltoallv(send.data(),
423 recv_counts_.data(),
424 recv_displacements_.data(),
425 MPI_C_DOUBLE_COMPLEX,
426 receive,
427 send_counts_.data(),
428 send_displacements_.data(),
429 MPI_C_DOUBLE_COMPLEX,
430 communicator_);
431
432 for (int other = 0; other < size_; ++other) {
433 int plane_start = plane_offsets_[static_cast<std::size_t>(other)];
434 int plane_count = plane_counts_[static_cast<std::size_t>(other)];
435 const Complex* source = receive + send_displacements_[static_cast<std::size_t>(other)];
436 for (int stick = 0; stick < local_sticks_; ++stick) {
437 std::memcpy(sticks + static_cast<std::size_t>(stick) * grid_dimension_3_ +
438 plane_start,
439 source + static_cast<std::size_t>(stick) * plane_count,
440 static_cast<std::size_t>(plane_count) * sizeof(Complex));
441 }
442 }
443
444 if (local_sticks_ > 0) {
445 fftw_execute_dft(plan_sticks_forward_,
446 reinterpret_cast<fftw_complex*>(sticks),
447 reinterpret_cast<fftw_complex*>(sticks));
448 }
449 }
450
451 // ─── Plans ─────────────────────────────────────────────────────────
452
453 void create_plans() {
454 destroy_plans();
455 unsigned flags = use_measured_planning_ ? FFTW_MEASURE : FFTW_ESTIMATE;
456 /**
457 * FFTW_MEASURE would overwrite the buffers while planning, so plan on
458 * the buffers we own before any data is placed in them -- which is the
459 * case here, since assign_sticks() reallocates them just above.
460 */
461 if (local_sticks_ > 0) {
462 int length = grid_dimension_3_;
463 auto* buffer = reinterpret_cast<fftw_complex*>(stick_buffer_.data());
464 plan_sticks_forward_ = fftw_plan_many_dft(1,
465 &length,
466 local_sticks_,
467 buffer,
468 nullptr,
469 1,
470 length,
471 buffer,
472 nullptr,
473 1,
474 length,
475 FFTW_FORWARD,
476 flags);
477 plan_sticks_inverse_ = fftw_plan_many_dft(1,
478 &length,
479 local_sticks_,
480 buffer,
481 nullptr,
482 1,
483 length,
484 buffer,
485 nullptr,
486 1,
487 length,
488 FFTW_BACKWARD,
489 flags);
490 }
491 if (local_planes_ > 0) {
492 int lengths[2] = {grid_dimension_1_, grid_dimension_2_};
493 int plane_points = grid_dimension_1_ * grid_dimension_2_;
494 auto* buffer = reinterpret_cast<fftw_complex*>(plane_buffer_.data());
495 plan_planes_forward_ = fftw_plan_many_dft(2,
496 lengths,
497 local_planes_,
498 buffer,
499 nullptr,
500 1,
501 plane_points,
502 buffer,
503 nullptr,
504 1,
505 plane_points,
506 FFTW_FORWARD,
507 flags);
508 plan_planes_inverse_ = fftw_plan_many_dft(2,
509 lengths,
510 local_planes_,
511 buffer,
512 nullptr,
513 1,
514 plane_points,
515 buffer,
516 nullptr,
517 1,
518 plane_points,
519 FFTW_BACKWARD,
520 flags);
521 }
522 }
523
524 void destroy_plans() {
525 for (fftw_plan* plan : {&plan_sticks_forward_,
526 &plan_sticks_inverse_,
527 &plan_planes_forward_,
528 &plan_planes_inverse_}) {
529 if (*plan != nullptr) {
530 fftw_destroy_plan(*plan);
531 *plan = nullptr;
532 }
533 }
534 }
535
536 MPI_Comm communicator_;
537 int rank_ = 0;
538 int size_ = 1;
539
540 int grid_dimension_1_ = 0;
541 int grid_dimension_2_ = 0;
542 int grid_dimension_3_ = 0;
543 int total_grid_points_ = 0;
544 bool use_measured_planning_ = false;
545
546 std::vector<int> plane_counts_;
547 std::vector<int> plane_offsets_;
548 int local_planes_ = 0;
549 int local_plane_offset_ = 0;
550 int local_grid_points_ = 0;
551
552 FFTStickPartition partition_;
553 std::vector<std::vector<int>> sticks_by_rank_;
554 std::vector<int> stick_local_index_;
555 int local_sticks_ = 0;
556 int local_g_space_points_ = 0;
557
558 std::vector<int> send_counts_, recv_counts_, send_displacements_, recv_displacements_;
559
560 mutable std::vector<Complex> stick_buffer_;
561 mutable std::vector<Complex> plane_buffer_;
562 mutable std::vector<Complex> transpose_buffer_;
563 mutable std::vector<Complex> receive_buffer_;
564
565 fftw_plan plan_sticks_forward_ = nullptr;
566 fftw_plan plan_sticks_inverse_ = nullptr;
567 fftw_plan plan_planes_forward_ = nullptr;
568 fftw_plan plan_planes_inverse_ = nullptr;
569};
570
571} // namespace q3
void assign_sticks(const std::vector< int > &stick_weights) override
void forward(Complex *data) const override
void execute_forward_on_buffers() const override
int slab_dimension_1() const override
No restriction along the first dimension: ownership is by stick.
void gather_local_g_space(const Complex *full_g_space, Complex *local_g_space) const override
void scatter_local_g_space(const Complex *local_g_space, Complex *full_g_space) const override
The reverse: place this rank's reciprocal-space points into a full array.
void inverse_raw(Complex *data) const override
bool co_locates_mirrors() const override
int global_real_space_index(int local_index) const override
Local real space is [plane][i1][i2], the plane counted from local_offset().
bool owns_stick(int miller_1, int miller_2) const override
FFTGridSticks(const FFTGridSticks &)=delete
int grid_dimension_3() const override
void reduce_g_space(Complex *data, int number_of_elements) const override
int grid_dimension_1() const override
Complex * output_buffer() const override
void inverse(Complex *data) const override
FFTGridSticks & operator=(const FFTGridSticks &)=delete
const FFTStickPartition & partition() const
int local_offset() const override
First plane along the third dimension held here.
void execute_inverse_raw_on_buffers() const override
int g_vector_to_fft_grid_index(int miller_1, int miller_2, int miller_3) const override
Complex * input_buffer() const override
void init(int dimension_1, int dimension_2, int dimension_3, bool use_measured_planning=false) override
int grid_dimension_2() const override
void prepare_forward_from_output() const override
The forward transform consumes the plane buffer directly.
FFTGridSticks(MPI_Comm communicator)
int local_grid_points() const override
int local_g_space_points() const override
int total_grid_points() const override
void build(int dimension_1, int dimension_2, const std::vector< int > &stick_weights, int number_of_ranks)
static int wrap(int miller, int dimension)
static constexpr int unassigned
Sticks with no G-vectors inside the cutoff belong to nobody.
int owner_of(int wrapped_first, int wrapped_second) const
Rank owning the stick at wrapped indices, or unassigned if empty.
int owner_of_miller(int miller_1, int miller_2) const
Rank owning the stick carrying this G-vector, by signed Miller indices.
double Real
std::complex< double > Complex