Img2Num C++ (Internal Developer Docs) dev
API Documentation
Loading...
Searching...
No Matches
fft_iterative.cpp
1#include "internal/fft_iterative.h"
2
3#include <algorithm>
4#include <cmath>
5
6namespace fft {
7using std::size_t;
8
9// Check if an integer is a power of two.
10bool is_power_of_two(size_t n) {
11 return n && ((n & (n - 1)) == 0);
12}
13
14// Return the next power of two >= n.
15size_t next_power_of_two(size_t n) {
16 if (n == 0)
17 return 1;
18 --n;
19 n |= n >> 1;
20 n |= n >> 2;
21 n |= n >> 4;
22 n |= n >> 8;
23 n |= n >> 16;
24#if SIZE_MAX > UINT32_MAX
25 n |= n >> 32;
26#endif
27 return ++n;
28}
29
30/*
31 * In-place bit-reversal permutation.
32 *
33 * The DFT/FFT naturally orders outputs indexed by bit-reversed indices
34 * during the divide-and-conquer recursion. For an iterative implementation,
35 * we first reorder the input array by bit-reversed indices so subsequent
36 * "butterfly" passes combine the correct elements.
37 *
38 * a: input vector of length N = 2^log_n
39 */
40void bit_reverse_permute(std::vector<cd>& a) {
41 const size_t N = a.size();
42 int log_n = 0; // Base 2
43 while ((1u << log_n) < N)
44 ++log_n; // Set log_n to the exponent of 2 such that N = 2^log_n
45
46 // Reverse each element
47 for (size_t i = 0; i < N; ++i) {
48 // compute bit-reversed index of i with log_n bits
49 size_t rev = 0;
50 size_t x = i;
51 // Reverse element i
52 for (int b = 0; b < log_n; ++b) {
53 rev = (rev << 1) | (x & 1);
54 x >>= 1;
55 }
56 if (i < rev)
57 std::swap(a[i], a[rev]); // Avoid swapping twice
58 }
59}
60
61void pad_to_pow_two(std::vector<cd>& a, size_t& N) {
62 if (is_power_of_two(N))
63 return;
64
65 size_t N2 = next_power_of_two(N);
66 a.resize(N2, cd {0.0, 0.0});
67 N = N2;
68}
69
70/*
71 * Iterative in-place FFT.
72 *
73 * Engineering convention:
74 * - forward transform (inverse = false): uses exponent -j*2*pi*k*n/N
75 * - inverse transform (inverse = true): uses exponent +j*2*pi*k*n/N and
76 * divides final results by N (normalization).
77 *
78 * a: input buffer (modified in-place). After call it contains the transform.
79 * inverse: false => forward FFT (negative exponent), true => inverse FFT.
80 *
81 * Complexity: O(N log N) arithmetic operations, N must be a power of two (or
82 * will be padded if pad_to_pow2 is true).
83 */
84void iterative_fft(std::vector<cd>& a, bool inverse) {
85 size_t N {a.size()};
86
87 pad_to_pow_two(a, N);
88
89 // 1) Bit-reversal permutation
90 // Reorder a for step 2
91 bit_reverse_permute(a);
92
93 // 2) Iterative Danielson-Lanczos (butterfly) stages.
94 const double PI = std::acos(-1.0);
95 const double sign =
96 inverse ? +1.0 : -1.0; // -1 for forward (engineering convention), +1 for inverse
97
98 // len is the current transform length (2, 4, 8, ..., N)
99 // For a given len, we combine two sub-DFTs of size len/2 into one of size
100 // len.
101 for (size_t len = 2; len <= N; len <<= 1) {
102 // primitive len-th root of unity for this stage:
103 // w_len = exp(sign * j * 2*pi / len)
104 const double angle = sign * 2.0 * PI / double(len);
105 const cd wlen = std::polar(1.0, angle);
106
107 // iterate over blocks of size len
108 for (size_t i = 0; i < N; i += len) {
109 cd w = cd {1.0, 0.0}; // w = wlen^0 initially
110 const size_t half = len >> 1; // len/2 butterflies per block
111 for (size_t j = 0; j < half; ++j) {
112 // positions in array corresponding to the DFT indices:
113 // u = X[i + j] (upper half), v = X[i + j + half] * w (lower half times
114 // twiddle)
115 cd u = a[i + j];
116 cd v = a[i + j + half] * w;
117 a[i + j] = u + v; // combined even-frequency part
118 a[i + j + half] = u - v; // combined odd-frequency part
119 w *= wlen; // advance twiddle: w = w * wlen
120 }
121 }
122 }
123
124 // 3) If inverse transform, normalize by 1/N to get the true inverse DFT.
125 if (inverse) {
126 for (size_t i = 0; i < N; ++i)
127 a[i] /= static_cast<double>(N);
128 }
129}
130
131/*
132 * Convenience wrapper: take a const input vector and return the FFT result
133 * as a new vector. This will optionally pad to power-of-two if requested.
134 */
135std::vector<cd> fft_copy(const std::vector<cd>& input, bool inverse) {
136 std::vector<cd> a = input;
137 iterative_fft(a, inverse);
138 return a;
139}
140
141// Perform 2D FFT using your iterative FFT on rows and columns
142void iterative_fft_2d(std::vector<cd>& data, size_t width, size_t height, bool inverse) {
143 // Determine next power-of-two dimensions
144 size_t W = fft::next_power_of_two(width);
145 size_t H = fft::next_power_of_two(height);
146
147 // Pad if necessary
148 if (W != width || H != height) {
149 std::vector<cd> padded(W * H, {0.0, 0.0});
150 for (size_t y = 0; y < height; y++)
151 for (size_t x = 0; x < width; x++)
152 padded[y * W + x] = data[y * width + x];
153 data = std::move(padded);
154 width = W;
155 height = H;
156 }
157
158 // Temporary buffers for row/column FFTs
159 std::vector<cd> temp_row(width);
160 std::vector<cd> temp_col(height);
161
162 // FFT rows
163 for (size_t y = 0; y < height; y++) {
164 std::copy(data.begin() + y * width, data.begin() + (y + 1) * width, temp_row.begin());
165 fft::iterative_fft(temp_row, inverse);
166 std::copy(temp_row.begin(), temp_row.end(), data.begin() + y * width);
167 }
168
169 // FFT columns
170 for (size_t x = 0; x < width; x++) {
171 for (size_t y = 0; y < height; y++)
172 temp_col[y] = data[y * width + x];
173 fft::iterative_fft(temp_col, inverse);
174 for (size_t y = 0; y < height; y++)
175 data[y * width + x] = temp_col[y];
176 }
177}
178
179/*
180 * Convenience wrapper: take a const input vector and return the 2D FFT result
181 * as a new vector. This will optionally pad to power-of-two if requested.
182 */
183std::vector<cd>
184iterative_fft_2d_copy(const std::vector<cd>& input, size_t width, size_t height, bool inverse) {
185 std::vector<cd> a = input;
186 iterative_fft_2d(a, width, height, inverse);
187 return a;
188}
189} // namespace fft