4

I am trying to understand how to utilize the fft to process data I capture with a software defined radio. I discovered C++'s fftw3 library and attempted to look up some documentation or tutorials, and found there wasn't a whole ton to go off of.

I did however find this tutorial. The code to do the fft/ifft/etc. compiles and works just fine for me, but when I attempted to implement the fftShift function I get the following compiler error:

In file included from /usr/include/c++/9/algorithm:62,
                 from CppFFTW.cpp:13:
/usr/include/c++/9/bits/stl_algo.h: In instantiation of ‘_RandomAccessIterator std::_V2::__rotate(_RandomAccessIterator, _RandomAccessIterator, _RandomAccessIterator, std::random_access_iterator_tag) [with _RandomAccessIterator = double (*)[2]]’:
/usr/include/c++/9/bits/stl_algo.h:1449:27:   required from ‘_FIter std::_V2::rotate(_FIter, _FIter, _FIter) [with _FIter = double (*)[2]]’
CppFFTW.cpp:76:54:   required from here
/usr/include/c++/9/bits/stl_algo.h:1371:16: error: array must be initialized with a brace-enclosed initializer
 1371 |     _ValueType __t = _GLIBCXX_MOVE(*__p);
      |                ^~~
/usr/include/c++/9/bits/stl_algo.h:1373:22: error: invalid array assignment
 1373 |     *(__p + __n - 1) = _GLIBCXX_MOVE(__t);
      |                      ^
/usr/include/c++/9/bits/stl_algo.h:1394:16: error: array must be initialized with a brace-enclosed initializer
 1394 |     _ValueType __t = _GLIBCXX_MOVE(*(__p + __n - 1));
      |                ^~~
/usr/include/c++/9/bits/stl_algo.h:1396:10: error: invalid array assignment
 1396 |     *__p = _GLIBCXX_MOVE(__t);
      |          ^

Here is the line I use to compile the code: g++ CppFFTW.cpp -lfftw3 -lm
Output for g++ --version: 'g++ (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0'

This error message didn't make much sense to me, but I figured it meant that algorithm's std::rotate function didn't play nice with the fftw_complex data type anymore for some reason.

This left me with a few questions

  1. How come this method worked in the tutorial video, but not for me?
  2. What are some ways to go about understanding what this error message means?
  3. How can I implement a working fftShift function?
  4. Is there a different compiler version I should be using?

What fftShift is supposed to do is shift the zero-components to the center like in this example

When # elements odd:
{a, b, c, d, e, f, g} -> {e, f, g, a, b, c, d} 
When # elements even:
{a, b, c, d, e, f, g, h} -> {e, f, g, h, a, b, c, d}

Here is the fftShift definition definition: Edit: The original (broken) fftShift

// FFT shift for complex data 
void fftShift(fftw_complex *data)
{
    // even number of elements
    if (N % 2 == 0) {
        std::rotate(&data[0], &data[N >> 1], &data[N]);
    }
    // odd number of elements
    else {
        std::rotate(&data[0], &data[(N >> 1) + 1], &data[N]);
    }
}

Here is the rest of the code (functions/calls not relevant to the question were omitted): Edit: Added #include and fixed fftShift functions as per Ted's contribution.

* 
 Example code for how to utilize the FFTW libs
 Adapted from damian-dz C++ Tutorial
 https://github.com/damian-dz/CppFFTW/tree/master/CppFFTW
 (https://www.youtube.com/watch?v=geYbCA137PU&t=0s)
 To compile, run: g++ CppFFTW.cpp -lfftw3 -lm
 -lfftw3 -lm links the code to the fftw3 library
*/

#include <fftw3.h>
#include <iostream>
#include <cmath>
#include <algorithm>
#include <complex> // Added post feedback from Ted

// macros for real & imaginary parts
#define REAL 0
#define IMAG 1

// length of the complex arrays
#define N 8

/* Computres the 1-D Fast Fourier Transform. */
void fft(fftw_complex *in, fftw_complex *out)
{
    // create a DFT plan
    fftw_plan plan = fftw_plan_dft_1d(N, in, out, FFTW_FORWARD, FFTW_ESTIMATE);
    // execute the plan
    fftw_execute(plan);
    // clean up
    fftw_destroy_plan(plan);
    fftw_cleanup();
}

/* Displays complex numbers in the form a +/- bi */
void displayComplex(fftw_complex *y)
{
    for (int idx = 0; idx < N; ++idx) {
        if (y[idx][IMAG] < 0) {
            std::cout << y[idx][REAL] << " - " << abs(y[idx][IMAG]) << "i" << std::endl;
        } else {
            std::cout << y[idx][REAL] << " + " << abs(y[idx][IMAG]) << "i" << std::endl;
        }
    }
}

// Edit: Adjusted fftShift function per Ted's feedback
void fftShift(std::complex<double>* data) {
    static_assert(sizeof(fftw_complex) == sizeof(std::complex<double>));

    // even number of elements
    if(N % 2 == 0) {
        std::rotate(&data[0], &data[N >> 1], &data[N]);
    }
    // odd number of elements
    else {
        std::rotate(&data[0], &data[(N >> 1) + 1], &data[N]);
    }
}
// Edit: Accompanying fftShift to re-cast data
void fftShift(fftw_complex* data) {
    fftShift(reinterpret_cast<std::complex<double>*>(data));
}

// Original (broken) FFT shift for complex data 
void fftShift(fftw_complex *data)
{
    // even number of elements
    if (N % 2 == 0) {
        std::rotate(&data[0], &data[N >> 1], &data[N]);
    }
    // odd number of elements
    else {
        std::rotate(&data[0], &data[(N >> 1) + 1], &data[N]);
    }
}

/* Test */
int main()
{
    // input array
    fftw_complex x[N];

    // output array
    fftw_complex y[N];

    // fill the first array with some numbers
    for (int idx = 0; idx < N; ++idx) {
        x[idx][REAL] = idx + 1;
        x[idx][IMAG] = 0;
    }
    
    // compute the FFT of x and store results in y
    fft(x, y);
    // display the results
    std::cout << "FFT =" << std::endl;
    displayComplex(y);

    // "shifted" results
    fftShift(y);
    std::cout << "\nFFT shifted =" << std::endl;
    displayComplex(y);
    
    return 0;
}

Didn't see any obvious existing questions that I could see applying to my case (could be wrong, I am still learning C++), so I made an account and this is my first question here. Feel free to provide feedback so that I can make my question easier to understand.

  • If you look at the video in detail, the author changes the function to `void fftShift(double *data)` it happens very quickly. I sort of works accidentally. – alfC Mar 15 '23 at 22:52
  • @alfC You are right - I didn't see that before. I wonder why though? It actually compiles without changing it. At least in VS2022. – Ted Lyngmo Mar 15 '23 at 22:54
  • @TedLyngmo, maybe VS2022 doesn't give the error for a function that is not used? is that what you are wondering about? – alfC Mar 15 '23 at 23:04
  • @alfC No, I wonder why they change to `double*` when `fftw_complex*` works in MSVC. Edit: Ah, wait... they have the other overload still there ... ok, got it. the `double*` overload is not used. :-) – Ted Lyngmo Mar 15 '23 at 23:04
  • It seems this question is about to be closed for lack of detail. I thought it became perfectly clear after the edit. – Ted Lyngmo Mar 15 '23 at 23:09
  • PSA, use `std::abs` not `abs`, otherwise it could be calling the integer version of `abs`. I get this with `-Wfatal-errors -Werror -Wall -Wextra -Wpedantic` – alfC Mar 15 '23 at 23:17
  • Do you want to use my library to simplify array manipulation and use of fftw? https://gitlab.com/correaa/boost-multi/-/blob/fftw_so_example/include/multi/adaptors/fftw/test/so_shift.cpp – alfC Mar 15 '23 at 23:50
  • @alfC I'll take a look tomorrow at some point. Might use it for learning material. – OzzlyOsborne Mar 16 '23 at 01:51
  • @OzzlyOsborne, no problem, use this link, the above is broken: https://gitlab.com/correaa/boost-multi/-/blob/master/include/multi/adaptors/fftw/test/so_shift.cpp – alfC Mar 16 '23 at 03:13

1 Answers1

5

std::complex:

For any object z of type std::complex<T>, reinterpret_cast<T(&)[2]>(z)[0] is the real part of z and reinterpret_cast<T(&)[2]>(z)[1] is the imaginary part of z.

The intent of this requirement is to preserve binary compatibility between the C++ library complex number types and the C language complex number types (and arrays thereof), which have an identical object representation requirement.

Now, fftw_complex isn't necessarily a complex number type defined in the C language - but you will likely get away with doing a similar cast. In fact, the comment in the fftw3.h header suggests it will most probably be ok:

/* If <complex.h> is included, use the C99 complex type.  Otherwise
   define a type bit-compatible with C99 complex */
#if !defined(FFTW_NO_Complex) && defined(_Complex_I) && defined(complex) && defined(I)
#  define FFTW_DEFINE_COMPLEX(R, C) typedef R _Complex C
#else
#  define FFTW_DEFINE_COMPLEX(R, C) typedef R C[2]
#endif

The C++ definition of fftw_complex becomes double[2] which is also why std::rotate fails - you can't assign arrays to arrays.

So I'd define my function taking a fftw_complex* like this:

void fftShift(std::complex<double>* data) {
    static_assert(sizeof(fftw_complex) == sizeof(std::complex<double>));

    // even number of elements
    if(N % 2 == 0) {
        std::rotate(&data[0], &data[N >> 1], &data[N]);
    }
    // odd number of elements
    else {
        std::rotate(&data[0], &data[(N >> 1) + 1], &data[N]);
    }
}

void fftShift(fftw_complex* data) {
    fftShift(reinterpret_cast<std::complex<double>*>(data));
}

int main() {
    fftw_complex data[N];
    fftShift(data);
}

A nicer alternative would be to use std::complex<double>s in your C++ code and only cast to fftw_complex* when calling fftw functions.

std::vector<std::complex<double>> wave;

fftw_function( reinterpret_cast<fftw_complex*>(wave.data()), wave.size() );

  1. How come this method worked in the tutorial video, but not for me?

It's because they use Visual Studio in the tutorial and the std::rotate implementation in that version of the standard library uses std::iter_swap that is actually capable of swapping whole arrays. If iter_swap is mandated by the standard to be able to do that, I don't know, but iter_swap in the g++, clang++, icx and MSVC implementations are all capable of doing that. However, g++, clang++ and icx do not use their own iter_swaps in their rotate implementations - at least not in this case.

We can borrow a rotate implementation from rotate @ cppreference that actually uses std::iter_swap and then all four can rotate your fftw_complexs - using their own iter_swaps. Demo

  1. What are some ways to go about understanding what this error message means?

It means that std::rotate tries to assign a double[2] to another double[2], as-if by doing:

double a[2]{};
double b[2]{};
a = b;          // error: invalid array assignment
  1. How can I implement a working fftShift function?

See above.

  1. Is there a different compiler version I should be using?

No, even newer versions of g++ and clang++ would complain about the same issue. You could use Visual Studio or a different implementation of rotate (like the one on cppreference.com) - but I suggest just adapting and use std::complex<double>s that you cast to fftw_complexs when needed.

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • Appreciate the feedback! Your suggested code worked for me (after adding #include at the top of my code block). Understanding the more complex data types and how to use them is still a bit confusing for me. – OzzlyOsborne Mar 15 '23 at 22:34
  • @OzzlyOsborne You're welcome! I had to dig in the Visual Studio library to find out why it actually worked in the tutorial. Explanation added to the answer. – Ted Lyngmo Mar 15 '23 at 22:46
  • I am confused. Even if iter_swap, or whatever, can swap the "arrays" directly, it wouldn't help here and give an erroneous result, because the actual data should be swapped (the two double elements at a time). There is no actual useful pointer to swap. – alfC Mar 15 '23 at 23:53
  • @alfC The actual data _is_ swapped so it does the right thing. However, the code I showed above and claimed didn't work in g++ and clang++ actually works in both [example](https://godbolt.org/z/MKoj9r963) so I'm not sure where my tired brain got that idea from Yesterday night. I'll remove those claims later when I get home. – Ted Lyngmo Mar 16 '23 at 11:24
  • Ok. But wait, now that I see your code from FFTW, I notice that if the type is defined as `_Complex` (and not as `double[2]`) swap can work. (in other words, `_Complex` is a value type, `double[2]` is just an abomination (not a value). – alfC Mar 16 '23 at 11:27
  • 1
    @alfC Yes, that's what the comment under the fftw code block is aiming at too. The g++ and clang++ versions try to assign an array to another, which fails - but since both implementations have an `iter_swap` that is capable of actually swapping the values in the two arrays, there seems to be something more going on. Right now I can't check but I suspect those implementations of `std::rotate` end up _not_ using `std::iter_swap` that would have made it work. – Ted Lyngmo Mar 16 '23 at 11:48
  • Yes, algorithms tend to use `iter_swap` which in turn one needs to end up customizing (although it is not technically allowed) to swap reference-like types. As I did many times. – alfC Mar 16 '23 at 12:16
  • @alfC Updated the answer to remove the erroneous claim about `iter_swap` not being capable in the g++ and clang++ implementations. It's fully capable - but they are not using it (at least not for this case) inside `rotate`. Replacing `rotate` with a version that actually uses the internal `iter_swap` makes all compilers happy. – Ted Lyngmo Mar 16 '23 at 18:13
  • @TedLyngmo, IMO, `fftw_complex` shouldn't be used at all, except when with reinterpret cast when calling fftw functions. – alfC Mar 16 '23 at 18:46
  • @alfC 100% agreement. That's why I suggested using `std::complex`s in the C++ program and only do the cast to `fftw_complex*` when calling the `fftw` functions. – Ted Lyngmo Mar 16 '23 at 18:51