Loading...
Searching...
No Matches
serial_pocket_fft_grid.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <cstddef>
4#include <cstdlib>
5#include <cstring>
6#include <vector>
7
8#include <pocketfft_hdronly.h>
9
13
14namespace q3 {
15
16/**
17 * SerialPocketFFTGrid — node-local 3D FFT implementation using PocketFFT.
18 *
19 * PocketFFT (BSD-3-Clause, header-only; the C++ library NumPy and SciPy use)
20 * lets a build ship without any GPL-licensed FFT library. It has the same
21 * contract as the FFTW-backed serial grid: row-major [i1][i2][i3] storage,
22 * an exp(-iGr) forward transform, unnormalized forward and raw-inverse
23 * transforms, and out-of-place execution from input_buffer() to
24 * output_buffer().
25 *
26 * PocketFFT has no planning step to tune, so use_measured_planning is
27 * accepted and ignored: every transform is deterministic by construction.
28 * Twiddle tables are cached by the library itself (POCKETFFT_CACHE_SIZE,
29 * set by the build). Threads follow OMP_NUM_THREADS, as the FFTW grid's do.
30 */
32 public:
33 void init(int dimension_1,
34 int dimension_2,
35 int dimension_3,
36 bool /*use_measured_planning*/ = false) override {
37 grid_dimension_1_ = dimension_1;
38 grid_dimension_2_ = dimension_2;
39 grid_dimension_3_ = dimension_3;
40 const auto total_points = static_cast<std::size_t>(total_grid_points());
41 work_.assign(total_points, Complex(0.0, 0.0));
42 output_buffer_.assign(total_points, Complex(0.0, 0.0));
43
44 shape_ = {static_cast<std::size_t>(dimension_1),
45 static_cast<std::size_t>(dimension_2),
46 static_cast<std::size_t>(dimension_3)};
47 const auto element_bytes = static_cast<std::ptrdiff_t>(sizeof(Complex));
48 strides_ = {
49 element_bytes * dimension_2 * dimension_3, element_bytes * dimension_3, element_bytes};
50
51 number_of_threads_ = 1;
52 const char* omp_threads_env = std::getenv("OMP_NUM_THREADS");
53 if (omp_threads_env != nullptr && std::atoi(omp_threads_env) > 1) {
54 number_of_threads_ = static_cast<std::size_t>(std::atoi(omp_threads_env));
55 }
56 }
57
58 [[nodiscard]] int grid_dimension_1() const override {
59 return grid_dimension_1_;
60 }
61 [[nodiscard]] int grid_dimension_2() const override {
62 return grid_dimension_2_;
63 }
64 [[nodiscard]] int grid_dimension_3() const override {
65 return grid_dimension_3_;
66 }
67 [[nodiscard]] int total_grid_points() const override {
68 return grid_dimension_1_ * grid_dimension_2_ * grid_dimension_3_;
69 }
70
71 void forward(Complex* data) const override {
73 transform(data, data, /*is_forward=*/true);
74 }
75
76 void inverse(Complex* data) const override {
78 transform(data, data, /*is_forward=*/false, 1.0 / total_grid_points());
79 }
80
81 void inverse_raw(Complex* data) const override {
83 transform(data, data, /*is_forward=*/false);
84 }
85
86 [[nodiscard]] int
87 g_vector_to_fft_grid_index(int miller_1, int miller_2, int miller_3) const override {
88 int wrapped_index_1 = (miller_1 >= 0) ? miller_1 : miller_1 + grid_dimension_1_;
89 int wrapped_index_2 = (miller_2 >= 0) ? miller_2 : miller_2 + grid_dimension_2_;
90 int wrapped_index_3 = (miller_3 >= 0) ? miller_3 : miller_3 + grid_dimension_3_;
91 return (wrapped_index_1 * grid_dimension_2_ + wrapped_index_2) * grid_dimension_3_ +
92 wrapped_index_3;
93 }
94
95 [[nodiscard]] int local_grid_points() const override {
96 return total_grid_points();
97 }
98
99 [[nodiscard]] int local_offset() const override {
100 return 0;
101 }
102
103 void reduce_g_space(Complex* /*data*/, int /*number_of_elements*/) const override {
104 // No-op for serial: data is already redundant
105 }
106
107 Complex* input_buffer() const override {
108 return work_.data();
109 }
110
111 Complex* output_buffer() const override {
112 return output_buffer_.data();
113 }
114
115 void execute_inverse_raw_on_buffers() const override {
117 transform(work_.data(), output_buffer_.data(), /*is_forward=*/false);
118 }
119
120 void execute_forward_on_buffers() const override {
122 transform(work_.data(), output_buffer_.data(), /*is_forward=*/true);
123 }
124
125 void prepare_forward_from_output() const override {
126 std::memcpy(work_.data(),
127 output_buffer_.data(),
128 static_cast<std::size_t>(total_grid_points()) * sizeof(Complex));
129 }
130
131 private:
132 /// One 3D complex transform over all three axes; input and output may alias.
133 void transform(const Complex* input, Complex* output, bool is_forward, Real scale = 1.0) const {
134 pocketfft::c2c(shape_,
135 strides_,
136 strides_,
137 axes_,
138 is_forward,
139 input,
140 output,
141 scale,
142 number_of_threads_);
143 }
144
145 int grid_dimension_1_{}, grid_dimension_2_{}, grid_dimension_3_{};
146 pocketfft::shape_t shape_;
147 pocketfft::stride_t strides_;
148 pocketfft::shape_t axes_{0, 1, 2};
149 std::size_t number_of_threads_{1};
150 mutable std::vector<Complex> work_;
151 mutable std::vector<Complex> output_buffer_;
152};
153
154} // namespace q3
Complex * input_buffer() const override
int grid_dimension_3() const override
int local_offset() const override
int grid_dimension_2() const override
void forward(Complex *data) const override
void reduce_g_space(Complex *, int) const override
Complex * output_buffer() const override
int grid_dimension_1() const override
void init(int dimension_1, int dimension_2, int dimension_3, bool=false) override
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
void inverse(Complex *data) const override
void execute_forward_on_buffers() const override
void prepare_forward_from_output() const override
int local_grid_points() const override
void inverse_raw(Complex *data) const override
int total_grid_points() const override
double Real
std::complex< double > Complex
std::atomic< long long > fft_call_count
Definition counters.hpp:22
static ProfilingCounters & instance()
Definition counters.hpp:220