LCOV - code coverage report
Current view: top level - gridtools - FourierTransform.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 79 100 79.0 %
Date: 2026-03-30 13:16:06 Functions: 7 10 70.0 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2016-2023 The plumed team
       3             :    (see the PEOPLE file at the root of the distribution for a list of names)
       4             : 
       5             :    See http://www.plumed.org for more information.
       6             : 
       7             :    This file is part of plumed, version 2.
       8             : 
       9             :    plumed is free software: you can redistribute it and/or modify
      10             :    it under the terms of the GNU Lesser General Public License as published by
      11             :    the Free Software Foundation, either version 3 of the License, or
      12             :    (at your option) any later version.
      13             : 
      14             :    plumed is distributed in the hope that it will be useful,
      15             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
      16             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17             :    GNU Lesser General Public License for more details.
      18             : 
      19             :    You should have received a copy of the GNU Lesser General Public License
      20             :    along with plumed.  If not, see <http://www.gnu.org/licenses/>.
      21             : +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
      22             : #include <iostream>
      23             : #include <complex>
      24             : #include "ActionWithInputGrid.h"
      25             : #include "core/ActionRegister.h"
      26             : #ifdef __PLUMED_HAS_FFTW
      27             : #include <fftw3.h> // FFTW interface
      28             : #endif
      29             : 
      30             : namespace PLMD {
      31             : namespace gridtools {
      32             : 
      33             : //+PLUMEDOC GRIDANALYSIS FOURIER_TRANSFORM
      34             : /*
      35             : Compute the Discrete Fourier Transform (DFT) by means of FFTW of data stored on a 2D grid.
      36             : 
      37             : This action can operate on any other action that outputs scalar data on a two-dimensional grid.
      38             : 
      39             : Up to now, even if the input data are purely real the action uses a complex DFT.
      40             : 
      41             : Just as a quick reference, given a 1D array \f$\mathbf{X}\f$ of size \f$n\f$, this action computes the vector \f$\mathbf{Y}\f$ given by
      42             : 
      43             : \f[
      44             : Y_k = \sum_{j=0}^{n-1} X_j e^{2\pi\, j k \sqrt{-1}/n}.
      45             : \f]
      46             : 
      47             : This can be easily extended to more than one dimension. All the other details can be found at http://www.fftw.org/doc/What-FFTW-Really-Computes.html#What-FFTW-Really-Computes.
      48             : 
      49             : The keyword "FOURIER_PARAMETERS" deserves just a note on the usage. This keyword specifies how the Fourier transform will be normalized. The keyword takes two numerical parameters (\f$a,\,b\f$) that define the normalization according to the following expression
      50             : 
      51             : \f[
      52             : \frac{1}{n^{(1-a)/2}} \sum_{j=0}^{n-1} X_j e^{2\pi b\, j k \sqrt{-1}/n}
      53             : \f]
      54             : 
      55             : The default values of these parameters are: \f$a=1\f$ and \f$b=1\f$.
      56             : 
      57             : \par Examples
      58             : 
      59             : The following example tells Plumed to compute the complex 2D 'backward' Discrete Fourier Transform by taking the data saved on a grid called 'density', and normalizing the output by \f$ \frac{1}{\sqrt{N_x\, N_y}}\f$, where \f$N_x\f$ and \f$N_y\f$ are the number of data on the grid (it can be the case that \f$N_x\neq N_y\f$):
      60             : 
      61             : \plumedfile
      62             : FOURIER_TRANSFORM STRIDE=1 GRID=density FT_TYPE=complex FOURIER_PARAMETERS=0,-1
      63             : \endplumedfile
      64             : 
      65             : */
      66             : //+ENDPLUMEDOC
      67             : 
      68             : 
      69             : class FourierTransform : public ActionWithInputGrid {
      70             : private:
      71             :   std::string output_type;
      72             :   bool real_output, store_norm;
      73             :   std::vector<int> fourier_params;
      74             : public:
      75             :   static void registerKeywords( Keywords& keys );
      76             :   explicit FourierTransform(const ActionOptions&ao);
      77             :   void clearAverage() override;
      78             : #ifndef __PLUMED_HAS_FFTW
      79             :   void performOperations( const bool& from_update ) override {}
      80             : #else
      81             :   void performOperations( const bool& from_update ) override;
      82             : #endif
      83           0 :   void compute( const unsigned&, MultiValue& ) const override {}
      84           0 :   bool isPeriodic() override {
      85           0 :     return false;
      86             :   }
      87             : };
      88             : 
      89       13787 : PLUMED_REGISTER_ACTION(FourierTransform,"FOURIER_TRANSFORM")
      90             : 
      91           5 : void FourierTransform::registerKeywords( Keywords& keys ) {
      92           5 :   ActionWithInputGrid::registerKeywords( keys );
      93           5 :   keys.remove("BANDWIDTH");
      94           5 :   keys.remove("KERNEL");
      95          10 :   keys.add("optional","FT_TYPE","choose what kind of data you want as output on the grid. Possible values are: ABS = compute the complex modulus of Fourier coefficients (DEFAULT); NORM = compute the norm (i.e. ABS^2) of Fourier coefficients; COMPLEX = store the FFTW complex output on the grid (as a vector).");
      96          10 :   keys.add("compulsory","FOURIER_PARAMETERS","default","what kind of normalization is applied to the output and if the Fourier transform in FORWARD or BACKWARD. This keyword takes the form FOURIER_PARAMETERS=A,B, where A and B can be 0, 1 or -1. The default values are A=1 (no normalization at all) and B=1 (forward FFT). Other possible choices for A are: "
      97             :            "A=-1: normalize by the number of data, "
      98             :            "A=0: normalize by the square root of the number of data (one forward and followed by backward FFT recover the original data). ");
      99           5 : }
     100             : 
     101           1 : FourierTransform::FourierTransform(const ActionOptions&ao):
     102             :   Action(ao),
     103             :   ActionWithInputGrid(ao),
     104           1 :   real_output(true),
     105           1 :   store_norm(false),
     106           1 :   fourier_params(2) {
     107             : #ifndef __PLUMED_HAS_FFTW
     108             :   error("this feature is only available if you compile PLUMED with FFTW");
     109             : #else
     110           1 :   if( ingrid->getDimension()!=2 ) {
     111           0 :     error("fourier transform currently only works with two dimensional grids");
     112             :   }
     113             : 
     114             :   // Get the type of FT
     115           2 :   parse("FT_TYPE",output_type);
     116           1 :   if (output_type.length()==0) {
     117           0 :     log<<"  keyword FT_TYPE unset. By default output grid will contain REAL Fourier coefficients\n";
     118           2 :   } else if ( output_type=="ABS" || output_type=="abs") {
     119           0 :     log << "  keyword FT_TYPE is '"<< output_type << "' : will compute the MODULUS of Fourier coefficients\n";
     120           2 :   } else if ( output_type=="NORM" || output_type=="norm") {
     121           0 :     log << "  keyword FT_TYPE is '"<< output_type << "' : will compute the NORM of Fourier coefficients\n";
     122           0 :     store_norm=true;
     123           2 :   } else if ( output_type=="COMPLEX" || output_type=="complex" ) {
     124           1 :     log<<"  keyword FT_TYPE is '"<< output_type <<"' : output grid will contain the COMPLEX Fourier coefficients\n";
     125           1 :     real_output=false;
     126             :   } else {
     127           0 :     error("keyword FT_TYPE unrecognized!");
     128             :   }
     129             : 
     130             :   // Normalize output?
     131             :   std::string params_str;
     132           2 :   parse("FOURIER_PARAMETERS",params_str);
     133           1 :   if (params_str=="default") {
     134           0 :     fourier_params.assign( fourier_params.size(), 1 );
     135           0 :     log.printf("  default values of Fourier parameters A=%i, B=%i : the output will NOT be normalized and BACKWARD Fourier transform is computed \n", fourier_params[0],fourier_params[1]);
     136             :   } else {
     137           1 :     std::vector<std::string> fourier_str = Tools::getWords(params_str, "\t\n ,");
     138           1 :     if (fourier_str.size()>2) {
     139           0 :       error("FOURIER_PARAMETERS can take just two values");
     140             :     }
     141           3 :     for (unsigned i=0; i<fourier_str.size(); ++i) {
     142           2 :       Tools::convert(fourier_str[i],fourier_params[i]);
     143           2 :       if (fourier_params[i]>1 || fourier_params[i]<-1) {
     144           0 :         error("values accepted for FOURIER_PARAMETERS are only -1, 1 or 0");
     145             :       }
     146             :     }
     147           1 :     log.printf("  Fourier parameters are A=%i, B=%i \n", fourier_params[0],fourier_params[1]);
     148           1 :   }
     149             : 
     150             : 
     151             :   // Create the input from the old string
     152             :   std::string vstring;
     153           1 :   if (real_output) {
     154           0 :     if (!store_norm) {
     155           0 :       vstring="COMPONENTS=" + getLabel() + "_abs";
     156             :     } else {
     157           0 :       vstring="COMPONENTS=" + getLabel() + "_norm";
     158             :     }
     159             :   } else {
     160           2 :     vstring="COMPONENTS=" + getLabel() + "_real," + getLabel() + "_imag";
     161             :   }
     162             : 
     163             :   // Set COORDINATES keyword
     164           2 :   vstring += " COORDINATES=" + ingrid->getComponentName( 0 );
     165           2 :   for(unsigned i=1; i<ingrid->getDimension(); ++i) {
     166           2 :     vstring += "," + ingrid->getComponentName( i );
     167             :   }
     168             : 
     169             :   // Set PBC keyword
     170             :   vstring += " PBC=";
     171           1 :   if( ingrid->isPeriodic(0) ) {
     172             :     vstring+="T";
     173             :   } else {
     174             :     vstring+="F";
     175             :   }
     176           2 :   for(unsigned i=1; i<ingrid->getDimension(); ++i) {
     177           1 :     if( ingrid->isPeriodic(i) ) {
     178             :       vstring+=",T";
     179             :     } else {
     180             :       vstring+=",F";
     181             :     }
     182             :   }
     183             : 
     184             : 
     185             :   // Create a grid on which to store the fourier transform of the input grid
     186           1 :   auto grid=createGrid( "grid", vstring );
     187           1 :   if( ingrid->noDerivatives() ) {
     188           0 :     grid->setNoDerivatives();
     189             :   }
     190           1 :   setAveragingAction( std::move(grid), false );
     191             : 
     192           1 :   checkRead();
     193             : #endif
     194           2 : }
     195             : 
     196           1 : void FourierTransform::clearAverage() {
     197             :   std::vector<double> fspacing;
     198           1 :   std::vector<std::string> ft_min( ingrid->getMin() ), ft_max( ingrid->getMax() );
     199           3 :   for(unsigned i=0; i<ingrid->getDimension(); ++i) {
     200           2 :     Tools::convert( 0.0, ft_min[i] );
     201           2 :     Tools::convert( 2.0*pi*ingrid->getNbin()[i]/ ingrid->getGridExtent(i), ft_max[i] );
     202             :   }
     203           1 :   mygrid->setBounds( ft_min, ft_max, ingrid->getNbin(), fspacing);
     204           1 :   resizeFunctions();
     205           1 :   ActionWithAveraging::clearAverage();
     206           2 : }
     207             : 
     208             : #ifdef __PLUMED_HAS_FFTW
     209           4 : void FourierTransform::performOperations( const bool& from_update ) {
     210             : 
     211             :   // Spacing of the real grid
     212           4 :   std::vector<double> g_spacing ( ingrid->getGridSpacing() );
     213             : 
     214             :   // *** CHECK CORRECT k-GRID BOUNDARIES ***
     215             :   //log<<"Real grid boundaries: \n"
     216             :   //    <<"  min_x: "<<mygrid->getMin()[0]<<"  min_y: "<<mygrid->getMin()[1]<<"\n"
     217             :   //    <<"  max_x: "<<mygrid->getMax()[0]<<"  max_y: "<<mygrid->getMax()[1]<<"\n"
     218             :   //    <<"K-grid boundaries:"<<"\n"
     219             :   //    <<"  min_x: "<<ft_min[0]<<"  min_y: "<<ft_min[1]<<"\n"
     220             :   //    <<"  max_x: "<<ft_max[0]<<"  max_y: "<<ft_max[1]<<"\n";
     221             : 
     222             :   // Get the size of the input data arrays (to allocate FFT data)
     223           4 :   size_t fft_dimension=static_cast<size_t>( ingrid->getNumberOfPoints() );
     224           4 :   std::vector<unsigned> N_input_data( ingrid->getNbin() );
     225          12 :   for(unsigned i=0; i<N_input_data.size(); ++i)
     226           8 :     if( !ingrid->isPeriodic(i) ) {
     227           8 :       N_input_data[i]++;
     228             :     }
     229             :   // size_t fft_dimension=1; for(unsigned i=0; i<N_input_data.size(); ++i) fft_dimension*=static_cast<size_t>( N_input_data[i] );
     230             : 
     231             :   // FFT arrays
     232           4 :   std::vector<std::complex<double> > input_data(fft_dimension), fft_data(fft_dimension);
     233             : 
     234             : 
     235             :   // Fill real input with the data on the grid
     236           4 :   std::vector<unsigned> ind( ingrid->getDimension() );
     237       40808 :   for (unsigned i=0; i<ingrid->getNumberOfPoints(); ++i) {
     238             :     // Get point indices
     239       40804 :     ingrid->getIndices(i, ind);
     240             :     // Fill input data in row-major order
     241       40804 :     input_data[ind[0]*N_input_data[0]+ind[1]].real( getFunctionValue( i ) );
     242       40804 :     input_data[ind[0]*N_input_data[0]+ind[1]].imag( 0.0 );
     243             :   }
     244             : 
     245             :   // *** HERE is the only clear limitation: I'm computing explicitly a 2D FT. It should not happen to deal with other than two-dimensional grid ...
     246           4 :   fftw_plan plan_complex = fftw_plan_dft_2d(N_input_data[0], N_input_data[1], reinterpret_cast<fftw_complex*>(&input_data[0]), reinterpret_cast<fftw_complex*>(&fft_data[0]), fourier_params[1], FFTW_ESTIMATE);
     247             : 
     248             :   // Compute FT
     249           4 :   fftw_execute( plan_complex );
     250             : 
     251             :   // Compute the normalization constant
     252             :   double norm=1.0;
     253          12 :   for (unsigned i=0; i<N_input_data.size(); ++i) {
     254           8 :     norm *= pow( N_input_data[i], (1-fourier_params[0])/2 );
     255             :   }
     256             : 
     257             :   // Save FT data to output grid
     258           4 :   std::vector<unsigned> N_out_data ( mygrid->getNbin() );
     259           4 :   std::vector<unsigned> out_ind ( mygrid->getDimension() );
     260       40808 :   for(unsigned i=0; i<mygrid->getNumberOfPoints(); ++i) {
     261       40804 :     mygrid->getIndices( i, out_ind );
     262       40804 :     if (real_output) {
     263             :       double ft_value;
     264             :       // Compute abs/norm and fix normalization
     265           0 :       if (!store_norm) {
     266           0 :         ft_value=std::abs( fft_data[out_ind[0]*N_out_data[0]+out_ind[1]] / norm );
     267             :       } else {
     268           0 :         ft_value=std::norm( fft_data[out_ind[0]*N_out_data[0]+out_ind[1]] / norm );
     269             :       }
     270             :       // Set the value
     271           0 :       mygrid->setGridElement( i, 0, ft_value );
     272             :     } else {
     273             :       double ft_value_real, ft_value_imag;
     274       40804 :       ft_value_real=fft_data[out_ind[0]*N_out_data[0]+out_ind[1]].real() / norm;
     275       40804 :       ft_value_imag=fft_data[out_ind[0]*N_out_data[0]+out_ind[1]].imag() / norm;
     276             :       // Set values
     277       40804 :       mygrid->setGridElement( i, 0, ft_value_real);
     278       40804 :       mygrid->setGridElement( i, 1, ft_value_imag);
     279             :     }
     280             :   }
     281             : 
     282             :   // Free FFTW stuff
     283           4 :   fftw_destroy_plan(plan_complex);
     284             : 
     285           4 : }
     286             : #endif
     287             : 
     288             : } // end namespace 'gridtools'
     289             : } // end namespace 'PLMD'

Generated by: LCOV version 1.16