Real-to-complex FFT implementation
This page details how the code performs the two-dimensional real-to-complex FFT (R2C), highlighting the differences and optimizations relative to the C2C pathway.
Forward R2C FFT
Unlike the C2C transform, which operates on complex data, the R2C variant accepts a real-valued matrix and produces only the non-redundant half of the complex spectrum. The function signature in FFT.hpp
is:
FFT.hpp |
---|
| std::vector<std::vector<std::complex<double>>>
fft2d_r2c_trim(const std::vector<std::vector<double>>& input);
|
In FFT.cpp
, fft2d_r2c_trim
is implemented by:
FFT.cpp |
---|
| std::vector<std::vector<std::complex<double>>>
fft2d_r2c_trim(const std::vector<std::vector<double>>& input)
{
std::size_t R = input.size();
std::size_t C = R ? input[0].size() : 0;
// 1) promote to complex
std::vector<std::vector<std::complex<double>>> ac(
R, std::vector<std::complex<double>>(C));
for (std::size_t i = 0; i < R; ++i)
for (std::size_t j = 0; j < C; ++j)
ac[i][j] = { input[i][j], 0.0 };
// 2) full padded forward FFT
auto full = fft2d(ac, /*invert=*/false);
std::size_t M = full.size();
std::size_t N = M ? full[0].size() : 0;
std::size_t N_half = N/2 + 1;
// 3) crop to M×(N/2+1)
std::vector<std::vector<std::complex<double>>> out(
M, std::vector<std::complex<double>>(N_half));
for (std::size_t i = 0; i < M; ++i)
for (std::size_t j = 0; j < N_half; ++j)
out[i][j] = full[i][j];
return out;
}
|
Extracting only the first N/2 + 1
columns (columns 0 through N/2
) we reduce memory and computation for real-valued data by nearly half.
Hermitian spectrum reconstruction
To invert the R2C transform, we must first recreate the full complex spectrum from the trimmed output. The function signature in FFT.hpp
is:
FFT.hpp |
---|
| std::vector<std::vector<double>>
ifft2d_c2r_trim(const std::vector<std::vector<std::complex<double>>>& R,
std::size_t orig_cols);
|
Its implementation in FFT.cpp
mirrors fft2d_r2c_trim
:
FFT.cpp |
---|
| std::vector<std::vector<double>>
ifft2d_c2r_trim(const std::vector<std::vector<std::complex<double>>>& R,
std::size_t orig_cols)
{
std::size_t M = R.size();
std::size_t N_half = M && !R.empty() ? R[0].size() : 0;
std::size_t N = 2*(N_half - 1);
// 1) reconstruct full Hermitian M×N
std::vector<std::vector<std::complex<double>>> full(
M, std::vector<std::complex<double>>(N));
for (std::size_t i = 0; i < M; ++i) {
for (std::size_t j = 0; j < N_half; ++j)
full[i][j] = R[i][j];
for (std::size_t j = N_half; j < N; ++j) {
std::size_t ii = (M - i) % M;
std::size_t jj = (N - j) % N;
full[i][j] = std::conj(R[ii][jj]);
}
}
// 2) full padded inverse FFT
auto comp = fft2d(full, /*invert=*/true);
// 3) crop to real M×orig_cols
std::vector<std::vector<double>> out(
M, std::vector<double>(orig_cols));
for (std::size_t i = 0; i < M; ++i)
for (std::size_t j = 0; j < orig_cols; ++j)
out[i][j] = comp[i][j].real();
return out;
}
|
The first step reconstructs the full complex matrix by applying Hermitian symmetry, where the second half of the spectrum is filled in by conjugating and reflecting the first half. The inverse FFT is then computed on this full matrix, and finally, we crop back to the original dimensions specified by orig_cols
.