5

I have a real 3D array of dimensions Nx*Ny*Nz and want to take a 2D Fourier transform for each z value using FFTW. Here the z index is the fastest varying in memory. Currently the following code works as expected:

int Nx = 16; int Ny = 8; int Nz = 3;

// allocate memory
const int dims = Nx * Ny * Nz;

// input data (pre Fourier transform)
double *input = fftw_alloc_real(dims);              

// why is this the required output size?
const int outdims = Nx * (Ny/2 + 1) * Nz;           
// we want to perform the transform out of place
// (so seperate array for output)
fftw_complex *output = fftw_alloc_complex(outdims);    


// setup "plans" for forward and backward transforms
const int rank = 2; const int howmany = Nz;
const int istride = Nz; const int ostride = Nz;
const int idist = 1; const int odist = 1;
int n[] = {Nx, Ny};
int *inembed = NULL, *onembed = NULL;


fftw_plan fp = fftw_plan_many_dft_r2c(rank, n, howmany,
        input, inembed, istride, idist,
        output, onembed, ostride, odist,
        FFTW_PATIENT);

fftw_plan bp = fftw_plan_many_dft_c2r(rank, n, howmany,
        output, onembed, ostride, odist,
        input, inembed, istride, idist,
        FFTW_PATIENT);

As I understand it, transforming a 1D sequence of length N requires (N/2 + 1) complex values so why does the above code crash if instead I set outdims = (Nx/2 + 1)*(Ny/2 + 1)*Nz as one might expect for a 2D transform?

Secondly am I right in thinking that I can access the real and imaginary parts of modes from qx = 0 to Nx/2 (inclusive) using the following:

#define outputRe(qx,qy,d) ( output[(d) + Nz * ((qy) + (Ny/2 + 1) * (qx))][0] )
#define outputIm(qx,qy,d) ( output[(d) + Nz * ((qy) + (Ny/2 + 1) * (qx))][1] )

EDIT: Full code and Makefile for those who want to play around. Assumes fftw and gsl installed.

EDIT2: If I understand correctly, the indexing (allowing positive and negative frequencies) should be (probably getting too messy for a macro!):

#define outputRe(qx,qy,d) ( output[(d) + Nz * ((qy) + (Ny/2 + 1) * ( ((qx) >= 0) ? (qx) : (Nx + (qx)) ) )  ][0] )
#define outputIm(qx,qy,d) ( output[(d) + Nz * ((qy) + (Ny/2 + 1) * ( ((qx) >= 0) ? (qx) : (Nx + (qx)) ) )  ][1] )

for (int qx = -Nx/2; qx < Nx/2; ++qx)
    for (int qy = 0; qy <= Ny/2; ++qy)
        outputRe(qx, qy, d) = ...

where outputRe(-Nx/2, qy, d) points to the same data as outputRe(Nx/2, qy, d). In practice I would probably just to loop over the first index and convert to a frequency, rather than the other way round!

Hemmer
  • 1,366
  • 1
  • 18
  • 33

2 Answers2

5

To help clarify (focusing in 2D as it easily extends to 2D transform of 3D data):

storage

An Nx * Ny array requires Nx * (Ny / 2 + 1) complex elements after a Fourier Transform.

First, in the y-direction, the negative frequencies can be reconstructed from the complex conjugate symmetry (that comes from transforming a real sequence). The y modes ky then run from 0 to Ny/2 inclusive. So for y we need Ny/2 + 1 complex values.

Next we transform in the x-direction where we cannot use the same symmetry assumption, as we are acting on complex-valued y values. Therefore we must include positive and negative frequencies, so x-modes kx run from -Nx/2 to Nx/2 inclusive. However kx = -Nx/2 and kx = Nx/2 are equivalent so only one is stored (see here). So for x we need Nx complex values.

access

As tir38 points out the x index post-transform runs from 0 to Nx-1, however this doesn't mean that modes kx run from 0 to Nx-1. FFTW packs positive frequencies in the first half of the array, then negative frequencies in the second half (in reverse order), like:

kx = 0, 1, 2, ..., Nx/2, -Nx/2 + 1, ..., -2, -1

There are two ways we can think about accessing these elements. First as tir38 suggests we can loop through in order and work out the mode kx from the index:

for (int i = 0; i < Nx; i++)
{
    // produces the list of kxs above
    int kx = (i <= Nx/2) ? i : i - Nx;

    // here we index with i, but with the knowledge that the mode is kx
    outputRe(i, ...) = some function of kx 
}

or we can loop through the modes kx and convert to an index:

for (int kx = -Nx/2; kx < Nx/2; kx++)
{
    // work out index from mode kx
    int i = (kx >= 0) ? i : Nx + i;

    // here we index with i, but with the knowledge that the mode is kx
    outputRe(i, ...) = some function of kx 
}

The two types of indexing along with the rest of the code can found here.

Hemmer
  • 1,366
  • 1
  • 18
  • 33
1

1st question:

By doing a 2D FFT for each z value you are basically doing Nz number 2D FFTs. The symmetry is only on one dimension. SO for a single [Nx x Ny] 2D FFT you will have an output that is Nx * (Ny/2 +1). So since you are doing Nz of these FFTs you will have [Nx x (Ny/2 +1+) x Nz] 3D outputs.

2nd question

yep that should be how the output is stored. I know not everybody has access to Matlab, but when I was starting out with FFTW, I was always comparing small matrices against both C++ (or C in your case) and Matlab

update (based on your second edit)

They are still indexed from zero so:

for (int qx = -Nx/2; qx < Nx/2; ++qx) => for (int gx = 0; gx < Nx; gx++)

The symmetry is over the y axis so the x data is not redundant:

output(0, a, b) != output(Nx-1, a ,b)

where a, b are some y and z values;

tir38
  • 9,810
  • 10
  • 64
  • 107
  • OK so if I understand correctly, there are `(Nx/2 - 1)(Ny/2 + 1)Nz` redundant elements of the output array (given that `qx, qy = 0 to Nx/2, Ny/2` inclusive)? – Hemmer Jun 28 '13 at 08:56
  • No, there are no redundant elements. They've all been removed (whether FFTW calculates them and then drops them or never computes them to begin with IDK). That's why its reduced size: https://en.wikipedia.org/wiki/Discrete_Fourier_transform#The_real-input_DFT – tir38 Jun 30 '13 at 22:39
  • OK that makes sense, I must have missed [this page](http://www.fftw.org/fftw3_doc/Multi_002ddimensional-Transforms.html#Multi_002ddimensional-Transforms) which basically says the same thing. Given that in the x direction we store both positive and negative frequencies, the indexing needs to be corrected. I've updated my original post, could you have a quick look? Thanks for your help! – Hemmer Jul 01 '13 at 09:28
  • I agree with that, and I clearly see that no x-data is redundant. I think we are saying the same thing: the macro I added to my post should make the reverse conversion, i.e. from `for (int gx = 0; gx < Nx; gx++)` to `for (int qx = -Nx/2; qx < Nx/2; ++qx)`. I did this just to help me understand how the positive and negative frequencies work. I've written an answer which describes this in detail (and without macros) for clarity. Thanks again for your help! – Hemmer Jul 03 '13 at 08:50