1

I am having trouble explaining/understanding the following phenomenon: To test fftw3 i am using the 2d poisson test case:

laplacian(f(x,y)) = - g(x,y) with periodic boundary conditions.

After applying the fourier transform to the equation we obtain : F(kx,ky) = G(kx,ky) /(kx² + ky²) (1)

if i take g(x,y) = sin (x) + sin(y) , (x,y) \in [0,2 \pi] i have immediately f(x,y) = g(x,y)

which is what i am trying to obtain with the fft :

i compute G from g with a forward Fourier transform

From this i can compute the Fourier transform of f with (1).

Finally, i compute f with the backward Fourier transform (without forgetting to normalize by 1/(nx*ny)).

In practice, the results are pretty bad?

(For instance, the amplitude for N = 256 is twice the amplitude obtained with N = 512)

Even worse, if i try g(x,y) = sin(x)*sin(y) , the curve has not even the same form of the solution.

(note that i must change the equation; i divide by two the laplacian in this case : (1) becomes F(kx,ky) = 2*G(kx,ky)/(kx²+ky²)

Here is the code:

/*
* fftw test -- double precision
*/
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <fftw3.h>
using namespace std;

int main()
{
 int N = 128;
 int i, j ;
 double pi = 3.14159265359;
 double *X, *Y  ; 
 X = (double*) malloc(N*sizeof(double));
   Y = (double*) malloc(N*sizeof(double));
   fftw_complex  *out1, *in2, *out2, *in1;
   fftw_plan     p1, p2;
   double L  = 2.*pi;
   double dx = L/(N - 1);

   in1 = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*(N*N) );
   out2 = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*(N*N) );
   out1 = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*(N*N) );
   in2 = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*(N*N) );

   p1 = fftw_plan_dft_2d(N, N, in1, out1, FFTW_FORWARD,FFTW_MEASURE ); 
   p2 = fftw_plan_dft_2d(N, N, in2, out2, FFTW_BACKWARD,FFTW_MEASURE);

   for(i = 0; i < N; i++){
       X[i] = -pi + i*dx ;
       for(j = 0; j < N; j++){
            Y[j] = -pi + j*dx ;
        in1[i*N + j][0] = sin(X[i]) + sin(Y[j]) ; // row major ordering
        //in1[i*N + j][0] = sin(X[i]) * sin(Y[j]) ; // 2nd test case
        in1[i*N + j][1] = 0 ; 
       }
   }

     fftw_execute(p1); // FFT forward 

     for ( i = 0; i < N; i++){   // f = g / ( kx² + ky² )  
       for( j = 0; j < N; j++){
         in2[i*N + j][0] = out1[i*N + j][0]/ (i*i+j*j+1e-16); 
         in2[i*N + j][1] = out1[i*N + j][1]/ (i*i+j*j+1e-16); 
         //in2[i*N + j][0] = 2*out1[i*N + j][0]/ (i*i+j*j+1e-16); // 2nd test case
         //in2[i*N + j][1] = 2*out1[i*N + j][1]/ (i*i+j*j+1e-16); 
       }
     }

     fftw_execute(p2); //FFT backward

     // checking the results computed

     double erl1 = 0.;
     for ( i = 0; i < N; i++) {
       for( j = 0; j < N; j++){
         erl1 += fabs( in1[i*N + j][0] -  out2[i*N + j][0]/N/N )*dx*dx; 
         cout<< i <<" "<< j<<" "<< sin(X[i])+sin(Y[j])<<" "<<  out2[i*N+j][0]/N/N <<" "<< endl; // > output
        }
      }
      cout<< erl1 << endl ;  // L1 error

      fftw_destroy_plan(p1);
      fftw_destroy_plan(p2);
      fftw_free(out1);
      fftw_free(out2);
      fftw_free(in1);
      fftw_free(in2);

      return 0;
    }

I can't find any (more) mistakes in my code (i installed the fftw3 library last week) and i don't see a problem with the maths either but i don't think it's the fft's fault. Hence my predicament. I am all out of ideas and all out of google as well.

Any help solving this puzzle would be greatly appreciated.

note :

compiling : g++ test.cpp -lfftw3 -lm

executing : ./a.out > output

and i use gnuplot in order to plot the curves : (in gnuplot ) splot "output" u 1:2:4 ( for the computed solution )

Charles P
  • 11
  • 3
  • 1
    I think your `N - 1` terms should actually be `N` ? – Paul R Jun 02 '14 at 16:18
  • it should be dx =L/N if i did "for(i = 0; i < N+1; i++)" i think, but i edited it to make it clearer (old fortran habit). i tested the change just to make sure, and no it does not change much the results. – Charles P Jun 02 '14 at 16:38
  • I deleted my answer : your formula is correct...I made a error as i tried to recompute this formula... – francis Jun 06 '14 at 03:25

1 Answers1

0

Here are some little points to be modified :

  • You need to account for all small frequencies, including the negative ones ! Index i corresponds to the frequency 2PI i/N but also to the frequency 2PI (i-N)/N. In the Fourier space, the end of the array matters as much as the beginning ! In our case, we keep the smallest frequency : it's 2PI i/N for the first half of the array, and 2PI(i-N)/N on the second half.

  • Of course, as Paul said, N-1 should be Nin double dx = L/(N - 1); => double dx = L/(N ); N-1 does not correspond to a continious periodic signal. It woud be hard to use it as a test case...

  • Scaling...I did it empirically

The result i obtain is closer to the expected one, for both cases. Here is the code :

    /*
 * fftw test -- double precision
 */
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <fftw3.h>
using namespace std;

int main()
{
    int N = 128;
    int i, j ;
    double pi = 3.14159265359;
    double *X, *Y  ; 
    X = (double*) malloc(N*sizeof(double));
    Y = (double*) malloc(N*sizeof(double));
    fftw_complex  *out1, *in2, *out2, *in1;
    fftw_plan     p1, p2;
    double L  = 2.*pi;
    double dx = L/(N );


    in1 = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*(N*N) );
    out2 = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*(N*N) );
    out1 = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*(N*N) );
    in2 = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*(N*N) );

    p1 = fftw_plan_dft_2d(N, N, in1, out1, FFTW_FORWARD,FFTW_MEASURE ); 
    p2 = fftw_plan_dft_2d(N, N, in2, out2, FFTW_BACKWARD,FFTW_MEASURE);

    for(i = 0; i < N; i++){
        X[i] = -pi + i*dx ;
        for(j = 0; j < N; j++){
            Y[j] = -pi + j*dx ;
            in1[i*N + j][0] = sin(X[i]) + sin(Y[j]) ; // row major ordering
            //  in1[i*N + j][0] = sin(X[i]) * sin(Y[j]) ; // 2nd test case
            in1[i*N + j][1] = 0 ; 
        }
    }

    fftw_execute(p1); // FFT forward 

    for ( i = 0; i < N; i++){   // f = g / ( kx² + ky² )  
        for( j = 0; j < N; j++){
            double fact=0;
            in2[i*N + j][0]=0;
            in2[i*N + j][1]=0;
            if(2*i<N){
                fact=((double)i*i);
            }else{
                fact=((double)(N-i)*(N-i));
            }
            if(2*j<N){
                fact+=((double)j*j);
            }else{
                fact+=((double)(N-j)*(N-j));
            }
            if(fact!=0){
                in2[i*N + j][0] = out1[i*N + j][0]/fact;
                in2[i*N + j][1] = out1[i*N + j][1]/fact;
            }else{
                in2[i*N + j][0] = 0;
                in2[i*N + j][1] = 0;
            }
            //in2[i*N + j][0] = out1[i*N + j][0];
            //in2[i*N + j][1] = out1[i*N + j][1];
            //  in2[i*N + j][0] = out1[i*N + j][0]*(1.0/(i*i+1e-16)+1.0/(j*j+1e-16)+1.0/((N-i)*(N-i)+1e-16)+1.0/((N-j)*(N-j)+1e-16))*N*N; 
            //  in2[i*N + j][1] = out1[i*N + j][1]*(1.0/(i*i+1e-16)+1.0/(j*j+1e-16)+1.0/((N-i)*(N-i)+1e-16)+1.0/((N-j)*(N-j)+1e-16))*N*N; 
            //in2[i*N + j][0] = 2*out1[i*N + j][0]/ (i*i+j*j+1e-16); // 2nd test case
            //in2[i*N + j][1] = 2*out1[i*N + j][1]/ (i*i+j*j+1e-16); 
        }
    }

    fftw_execute(p2); //FFT backward

    // checking the results computed

    double erl1 = 0.;
    for ( i = 0; i < N; i++) {
        for( j = 0; j < N; j++){
            erl1 += fabs( in1[i*N + j][0] -  out2[i*N + j][0]/(N*N))*dx*dx; 
            cout<< i <<" "<< j<<" "<< sin(X[i])+sin(Y[j])<<" "<<  out2[i*N+j][0]/(N*N) <<" "<< endl; // > output
            //  cout<< i <<" "<< j<<" "<< sin(X[i])*sin(Y[j])<<" "<<  out2[i*N+j][0]/(N*N) <<" "<< endl; // > output
        }
    }
    cout<< erl1 << endl ;  // L1 error

    fftw_destroy_plan(p1);
    fftw_destroy_plan(p2);
    fftw_free(out1);
    fftw_free(out2);
    fftw_free(in1);
    fftw_free(in2);

    return 0;
}

This code is far from being perfect, it is neither optimized nor beautiful. But it gives almost what is expected.

Bye,

francis
  • 9,525
  • 2
  • 25
  • 41
  • Hello and thank you for your answer. It does work as far as i tested it but i don't understand why it does ^^" . Concerning the scaling i think you took care of it by multiplying "fact" by NxN . I am a bit confused for "N-1 should be N", my point of view is, if you have 3 points you only need 2 steps (but if i change your code it does not work so there must be smth wrong with me).Also it did seem odd to me to not take the negative modes into account, but how do you justify adding it to "fact" ? Once again, thank you very much for your help. – Charles P Jun 04 '14 at 10:20
  • I think i get now why N instead of N-1 : in real space sampling you cannot repeat the first and last point which is why we stop at (N-1)/N*L, is that correct? I am still stuck as to why ( 1/(i*i) + 1/ ( (n-i)*(n-i) ) any link ? ( everything i have read so far on the subject gives me ( kx²+ky² ) with kx = 2 pi / Lx * i – Charles P Jun 04 '14 at 16:22
  • You gave us the correct formula and i apologize for my first answer : i tried to recompute it and i made a mistake. I changed my answer to provide a better solution (a correct one...) – francis Jun 06 '14 at 04:10