fft_2x4.cpp#
The file fft_2x4.cpp
shows a 2D complex time, complex frequency FFT.
It also illustrates passing a Factory (which is owned by a unique pointer)
to a function.
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* Copyright (C) 2023--2024, High Performance Kernels LLC *
* *
* This software and the related documents are provided as is, WITHOUT ANY *
* WARRANTY, without even the implied warranty of MERCHANTABILITY or FITNESS *
* FOR A PARTICULAR PURPOSE. *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <cassert>
#include <iostream>
#include <string>
#include <hpk/fft/makeFactory.hpp>
// Prints data formatted into rows and columns, with line continuations if
// necessary. It assumes that real and imaginary elements (of type 'fp_t')
// are interleaved in memory.
template<typename fp_t>
void printComplexData(std::string label, const fp_t* data, int rows, int cols) {
std::cout << label << ":\n";
for (int i = 0; i < rows; ++i) {
std::cout << " ";
for (int j = 0; j < cols; ++j) {
if (j % 8 == 0 && j > 0) std::cout << " \\\n ";
std::cout << data[(2 * cols * i) + (2 * j) + 0] << std::showpos
<< data[(2 * cols * i) + (2 * j) + 1] << "i "
<< std::noshowpos;
}
std::cout << '\n';
}
std::cout << std::endl;
}
// This demonstrates how a factory can be passed to a function, with the
// function taking ownership of the factory.
void compute2x4(std::unique_ptr<hpk::fft::FactoryCC<float>> factory) {
// This is just a toy problem. Data layout is 2x4.
constexpr long kRows = 2;
constexpr long kCols = 4;
// First, do the problem using an out-of-place 2D transform.
// This is the fastest (and easiest) way to go.
std::cout << "Example #1: Two dimensional, Out-of-place FFT.\n"
<< "~~~~~~~~~~ Rows=" << kRows << ", Cols=" << kCols << '\n';
const float in[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f,
1.0f, 1.0f, 2.0f, 2.0f, 1.0f, 1.0f, 2.0f, 2.0f};
float out[2 * kRows * kCols];
printComplexData("in", in, kRows, kCols);
auto fft_2D = factory->makeOoplace<2>({kRows, kCols});
assert(fft_2D && "Error: makeOoplace() failed for fft_2D.");
std::cout << *fft_2D << '\n';
fft_2D->forwardCopy(in, out);
printComplexData("out", out, kRows, kCols);
// Now redo the problem using only 1D unit stride FFTs.
std::cout << "Example #2: Same problem as in the previous example,\n"
<< "~~~~~~~~~~ but for fun we'll do it the slow way.\n";
printComplexData("in", in, kRows, kCols);
auto fft_rows = factory->makeOoplace<1>({kCols}, /*batch=*/kRows);
assert(fft_rows && "Error: makeOoplace() failed for fft_rows.");
std::cout << *fft_rows << '\n';
fft_rows->forwardCopy(in, out);
std::cout << "Transpose to a temporary array\n";
float transposed[2 * kRows * kCols];
for (int i = 0; i < kRows; ++i) {
for (int j = 0; j < kCols; ++j) {
transposed[2 * (j * kRows + i) + 0] = out[2 * (i * kCols + j) + 0];
transposed[2 * (j * kRows + i) + 1] = out[2 * (i * kCols + j) + 1];
}
}
auto fft_colsAsRows = factory->makeInplace<1>({kRows}, /*batch=*/kCols);
assert(fft_colsAsRows && "Error: makeInplace() failed for fft_colsAsRows.");
std::cout << *fft_colsAsRows << '\n';
fft_colsAsRows->forward(transposed);
std::cout << "Transpose to the out array\n";
for (int i = 0; i < kRows; ++i) {
for (int j = 0; j < kCols; ++j) {
out[2 * (i * kCols + j) + 0] = transposed[2 * (j * kRows + i) + 0];
out[2 * (i * kCols + j) + 1] = transposed[2 * (j * kRows + i) + 1];
}
}
printComplexData("out", out, kRows, kCols);
// Now re-do the problem yet again using 1D unit stride FFTs for the rows
// and 1D strided FFTs for the columns.
std::cout << "Example #3: Same problem as in the previous example,\n"
<< "~~~~~~~~~~ but avoiding the transposes.\n";
printComplexData("in", in, kRows, kCols);
// We will reuse fft_rows, which was made in the previous example.
std::cout << *fft_rows << '\n';
fft_rows->forwardCopy(in, out);
// Now, make a strided 1D FFT where the distance between points in the
// transform is the length of a row, that is 2 * kCols.
// The batch here can be thought of as a SIMD vector of complex numbers.
// The vector length (the batch size) is kCols, and the stride from one
// batch element to the next is 2.
// Recall that strides are measured in terms of real values (floats).
auto fft_cols = factory->makeInplace<1>({{kRows, 2 * kCols}}, {kCols, 2});
assert(fft_cols && "Error: makeInplace() failed for fft_cols.");
std::cout << *fft_cols << '\n';
fft_cols->forward(out);
printComplexData("out", out, kRows, kCols);
}
int main() {
// Make a factory for complex single precision time and frequency domains
// that uses only one thread.
auto factory = hpk::fft::makeFactory<float>({{hpk::Parameter::threads, 1}});
if (factory) {
std::cout << "Using " << *factory << "\n\n";
} else {
std::cout << "Error: makeFactory<float>() failed" << std::endl;
return -1;
}
// Transfer ownership of the factory to the compute2x4() function, which
// will do some FFTs and print the results.
compute2x4(std::move(factory));
}