sandbox/acastillo/output_fields/spectra/spectra_shell.h

    Shell-averaged spectra of a plane

    The transform and binning behind spectra.h. A plane sampled by spectra_sample.h is transformed with GSL and reduced to E(k) by averaging over shells of constant |k|.

    Bin 0 holds the squared mean, so \sum_k E(k) is the mean square of the plane and \sum_{k>0} E(k) its variance – an exact Parseval check. Bins beyond m/2 are only partly populated, since they exist only in the corners of the k plane; they are kept so that the sum stays exact.

    #include <gsl/gsl_fft_complex.h>
    #pragma autolink -lgsl -lgslcblas
    
    #ifndef REAL // also defined by the Dimonte initial-condition headers
      #define REAL(z,i) ((z)[2*(i)])
      #define IMAG(z,i) ((z)[2*(i)+1])
    #endif
    
    #include "spectra_sample.h"

    Forward twin of fft2D() in initial_conditions_dimonte_fft2.h. Mixed radix, so any transform length.

    static void fft2D_forward (double * data, int n0, int n1)
    {
      gsl_fft_complex_wavetable * wt = gsl_fft_complex_wavetable_alloc (n1);
      gsl_fft_complex_workspace * ws = gsl_fft_complex_workspace_alloc (n1);
      for (int i = 0; i < n0; i++)
        gsl_fft_complex_forward (&REAL(data, i*n1), 1, n1, wt, ws);
      gsl_fft_complex_wavetable_free (wt);
      gsl_fft_complex_workspace_free (ws);
    
      wt = gsl_fft_complex_wavetable_alloc (n0);
      ws = gsl_fft_complex_workspace_alloc (n0);
      double * col = malloc (2*n0*sizeof(double));
      for (int j = 0; j < n1; j++) {
        for (int i = 0; i < n0; i++) {
          REAL(col,i) = REAL(data, i*n1 + j);
          IMAG(col,i) = IMAG(data, i*n1 + j);
        }
        gsl_fft_complex_forward (col, 1, n0, wt, ws);
        for (int i = 0; i < n0; i++) {
          REAL(data, i*n1 + j) = REAL(col,i);
          IMAG(data, i*n1 + j) = IMAG(col,i);
        }
      }
      free (col);
      gsl_fft_complex_wavetable_free (wt);
      gsl_fft_complex_workspace_free (ws);
    }
    
    // Number of shells for an m1 x m2 lattice: the corner of the k plane.
    static int nshells (int m1, int m2)
    {
      return (int) (sqrt (sq (m1/2.) + sq (m2/2.)) + 0.5) + 1;
    }

    Shell-average |F|^2/(m_1m_2)^2 into unit-width bins in |k|, with k in units of 2\pi/L_0. GSL’s transform is unnormalised, hence the 1/(m_1m_2)^2.

    static void shell_average (double * data, int m1, int m2, double * E, int nk)
    {
      for (int b = 0; b < nk; b++)
        E[b] = 0.;
      double norm = 1./sq ((double) m1*m2);
      for (int i = 0; i < m1; i++) {
        double kx = i <= m1/2 ? i : i - m1;
        for (int j = 0; j < m2; j++) {
          double ky = j <= m2/2 ? j : j - m2;
          int b = (int) (sqrt (sq (kx) + sq (ky)) + 0.5);
          if (b < nk)
            E[b] += (sq (REAL(data, i*m2 + j)) + sq (IMAG(data, i*m2 + j)))*norm;
        }
      }
    }

    Sample the plane at z = h and fill E with one shell-averaged spectrum per field, laid out as E[is*nk + b] for field is in bin b. The caller allocates len*nk doubles, nk from nshells(). Returns the number of lattice points no rank owned, 0 for a complete plane.

    m must be the finest grid size, or the plane is not sampled on cell centres. Only rank 0 transforms, since the sampled plane is replicated everywhere.

    int spectrum_plane (scalar * list, double * E, double h,
                        double xmin, double xmax, double ymin, double ymax, int m)
    {
      int len = list_len (list), nk = nshells (m, m);
      size_t npt = (size_t) m*m;
      double * plane = malloc (npt*len*sizeof(double));
      int holes = sample_scalar_plane (list, plane, h,
                                       xmin, xmax, ymin, ymax, m, m);
      if (pid() == 0) {
        double * data = malloc (2*npt*sizeof(double));
        for (int is = 0; is < len; is++) {
          for (size_t i = 0; i < npt; i++) {
            REAL(data,i) = plane[i*len + is];
            IMAG(data,i) = 0.;
          }
          fft2D_forward (data, m, m);
          shell_average (data, m, m, E + (size_t) is*nk, nk);
        }
        free (data);
      }
      free (plane);
      return holes/len;   // per lattice point, not per stored value
    }