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