LCOV - code coverage report
Current view: top level - fourier - FourierTransform.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 99 120 82.5 %
Date: 2025-04-08 21:11:17 Functions: 6 9 66.7 %

          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 "gridtools/ActionWithGrid.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 fourier {
      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 takes a function on a two-dimensional grid as input and computes a fourier transform upon the input function using [FFTW](https://www.fftw.org).
      38             : Currently, this actions performs a complex fourier transition even if the input data is real.  The functionality here was developed and used in the paper cited
      39             : below. The following input was used in that paper:
      40             : 
      41             : ```plumed
      42             : UNITS NATURAL
      43             : 
      44             : # These two commands calculate one symmetry function for each atom.  These
      45             : # symmetry functions tell us whether the environment around each atom resembles
      46             : # the environment in the solid or the environment in the liquid.
      47             : fcc: FCCUBIC SPECIES=1-20736 SWITCH={CUBIC D_0=1.2 D_MAX=1.5} ALPHA=27
      48             : smapfcc: MORE_THAN ARG=fcc SWITCH={SMAP R_0=0.5 A=8 B=8}
      49             : smapfcc_grp: GROUP ATOMS=1-20736
      50             : # This calculates the position of the center of the solid region of the simulation box.  What we are computing here a weighted average position
      51             : # the weights are the order parameters computed using the two commands above.
      52             : center: CENTER PHASES ATOMS=fcc WEIGHTS=smapfcc
      53             : # This calculates the phase field that tells us whether the structure in each part of the simulation box is solid-like or liquid like.
      54             : dens: MULTICOLVARDENS DATA=smapfcc ORIGIN=center DIR=xyz NBINS=50,80,80 BANDWIDTH=1.0,1.0,1.0 GRID_MIN=0.0,auto,auto GRID_MAX=20.0,auto,auto STRIDE=1 CLEAR=1
      55             : # This finds the instantaneous location of the interface between the solid and liquid phases
      56             : contour: FIND_CONTOUR_SURFACE ARG=dens CONTOUR=0.5 SEARCHDIR=dens_dist.x
      57             : DUMPGRID ARG=contour FILE=contour.dat
      58             : # This does the fourier transform of the location of the interface.  We can extract the interfacial stiffness from the average of this fourier transform
      59             : ft: FOURIER_TRANSFORM ARG=contour FT_TYPE=norm FOURIER_PARAMETERS=-1,1
      60             : DUMPGRID ARG=ft FILE=fourier.dat STRIDE=10
      61             : ```
      62             : 
      63             : We do not think this action has been used in any other paper. If you are interested in using the functionality here we would recommend you carefully
      64             : read the documentation for the FFTW library.  You may find it necessary to modify the code in this action for your particular purpose.
      65             : 
      66             : To what FFTW computes we would recommend reading [this page](http://www.fftw.org/doc/What-FFTW-Really-Computes.html#What-FFTW-Really-Computes).
      67             : 
      68             : Notice that the keyword "FOURIER_PARAMETERS" specifies how the Fourier transform will be normalized. The keyword takes two numerical parameters $a$ and $b$ that are both set equal to one by default.
      69             : The normalization is then defined as:
      70             : 
      71             : $$
      72             : \frac{1}{n^{(1-a)/2}} \sum_{j=0}^{n-1} X_j e^{2\pi b\, j k \sqrt{-1}/n}
      73             : $$
      74             : 
      75             : */
      76             : //+ENDPLUMEDOC
      77             : 
      78             : 
      79             : class FourierTransform : public gridtools::ActionWithGrid {
      80             : private:
      81             :   bool firsttime;
      82             :   std::string output_type;
      83             :   bool real_output, store_norm;
      84             :   std::vector<int> fourier_params;
      85             :   gridtools::GridCoordinatesObject gridcoords;
      86             : public:
      87             :   static void registerKeywords( Keywords& keys );
      88             :   explicit FourierTransform(const ActionOptions&ao);
      89           0 :   void setupOnFirstStep( const bool incalc ) override {
      90           0 :     plumed_error();
      91             :   }
      92             :   unsigned getNumberOfDerivatives() override ;
      93             :   const gridtools::GridCoordinatesObject& getGridCoordinatesObject() const override ;
      94             :   std::vector<std::string> getGridCoordinateNames() const override ;
      95           0 :   void performTask( const unsigned& current, MultiValue& myvals ) const override {
      96           0 :     plumed_error();
      97             :   }
      98             :   void calculate() override ;
      99             : };
     100             : 
     101             : PLUMED_REGISTER_ACTION(FourierTransform,"FOURIER_TRANSFORM")
     102             : 
     103           3 : void FourierTransform::registerKeywords( Keywords& keys ) {
     104           3 :   ActionWithGrid::registerKeywords( keys );
     105           6 :   keys.addInputKeyword("compulsory","ARG","grid","the label of the grid that you want to fourer transform");
     106           3 :   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).");
     107           3 :   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: "
     108             :            "A=-1: normalize by the number of data, "
     109             :            "A=0: normalize by the square root of the number of data (one forward and followed by backward FFT recover the original data). ");
     110           6 :   keys.addOutputComponent("real","FT_TYPE","grid","the real part of the function");
     111           6 :   keys.addOutputComponent("imag","FT_TYPE","grid","the imaginary part of the function");
     112           6 :   keys.setValueDescription("grid","the fourier transform of the input grid");
     113           3 :   keys.addDOI("10.1088/1361-648X/aa893d");
     114           3 : }
     115             : 
     116           1 : FourierTransform::FourierTransform(const ActionOptions&ao):
     117             :   Action(ao),
     118             :   ActionWithGrid(ao),
     119           1 :   firsttime(true),
     120           1 :   real_output(true),
     121           1 :   store_norm(false),
     122           1 :   fourier_params(2) {
     123           1 :   if( getPntrToArgument(0)->getRank()!=2 ) {
     124           0 :     error("fourier transform currently only works with two dimensional grids");
     125             :   }
     126             : 
     127             :   // Get the type of FT
     128           2 :   parse("FT_TYPE",output_type);
     129           1 :   if (output_type.length()==0) {
     130           0 :     log<<"  keyword FT_TYPE unset. By default output grid will contain REAL Fourier coefficients\n";
     131           2 :   } else if ( output_type=="ABS" || output_type=="abs") {
     132           0 :     log << "  keyword FT_TYPE is '"<< output_type << "' : will compute the MODULUS of Fourier coefficients\n";
     133           2 :   } else if ( output_type=="NORM" || output_type=="norm") {
     134           0 :     log << "  keyword FT_TYPE is '"<< output_type << "' : will compute the NORM of Fourier coefficients\n";
     135           0 :     store_norm=true;
     136           2 :   } else if ( output_type=="COMPLEX" || output_type=="complex" ) {
     137           1 :     log<<"  keyword FT_TYPE is '"<< output_type <<"' : output grid will contain the COMPLEX Fourier coefficients\n";
     138           1 :     real_output=false;
     139             :   } else {
     140           0 :     error("keyword FT_TYPE unrecognized!");
     141             :   }
     142             : 
     143             :   // Normalize output?
     144             :   std::string params_str;
     145           2 :   parse("FOURIER_PARAMETERS",params_str);
     146           1 :   if (params_str=="default") {
     147           0 :     fourier_params.assign( fourier_params.size(), 1 );
     148           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]);
     149             :   } else {
     150           1 :     std::vector<std::string> fourier_str = Tools::getWords(params_str, "\t\n ,");
     151           1 :     if (fourier_str.size()>2) {
     152           0 :       error("FOURIER_PARAMETERS can take just two values");
     153             :     }
     154           3 :     for (unsigned i=0; i<fourier_str.size(); ++i) {
     155           2 :       Tools::convert(fourier_str[i],fourier_params[i]);
     156           2 :       if (fourier_params[i]>1 || fourier_params[i]<-1) {
     157           0 :         error("values accepted for FOURIER_PARAMETERS are only -1, 1 or 0");
     158             :       }
     159             :     }
     160           1 :     log.printf("  Fourier parameters are A=%i, B=%i \n", fourier_params[0],fourier_params[1]);
     161           1 :   }
     162             : 
     163           1 :   std::vector<unsigned> shape( getPntrToArgument(0)->getRank() );
     164           1 :   if (real_output) {
     165           0 :     addValueWithDerivatives( shape );
     166             :   } else {
     167           1 :     addComponentWithDerivatives( "real", shape );
     168           2 :     addComponentWithDerivatives( "imag", shape );
     169             :   }
     170             : 
     171             :   unsigned dimension = getPntrToArgument(0)->getRank();
     172           1 :   gridtools::ActionWithGrid* ag=dynamic_cast<gridtools::ActionWithGrid*>( getPntrToArgument(0)->getPntrToAction() );
     173           1 :   if( !ag ) {
     174           0 :     error("input action should be a grid");
     175             :   }
     176           1 :   const gridtools::GridCoordinatesObject & gcoords( ag->getGridCoordinatesObject() );
     177           2 :   if( gcoords.getGridType()=="fibonacci" ) {
     178           0 :     error("cannot fourier transform fibonacci grids");
     179             :   }
     180           1 :   std::vector<bool> ipbc( dimension );
     181           3 :   for(unsigned i=0; i<dimension; ++i) {
     182           2 :     ipbc[i] = gcoords.isPeriodic(i);
     183             :   }
     184           1 :   gridcoords.setup( "flat", ipbc, 0, 0.0 );
     185           1 :   checkRead();
     186             : #ifndef __PLUMED_HAS_FFTW
     187             :   error("this feature is only available if you compile PLUMED with FFTW");
     188             : #endif
     189           1 : }
     190             : 
     191           4 : unsigned FourierTransform::getNumberOfDerivatives() {
     192           4 :   return 2;
     193             : }
     194             : 
     195           7 : const gridtools::GridCoordinatesObject& FourierTransform::getGridCoordinatesObject() const {
     196           7 :   return gridcoords;
     197             : }
     198             : 
     199           2 : std::vector<std::string> FourierTransform::getGridCoordinateNames() const {
     200           2 :   gridtools::ActionWithGrid* ag=dynamic_cast<gridtools::ActionWithGrid*>( getPntrToArgument(0)->getPntrToAction() );
     201           2 :   return ag->getGridCoordinateNames();
     202             : }
     203             : 
     204           1 : void FourierTransform::calculate() {
     205           1 :   if( firsttime ) {
     206           1 :     gridtools::ActionWithGrid* ag=dynamic_cast<gridtools::ActionWithGrid*>( getPntrToArgument(0)->getPntrToAction() );
     207           1 :     const gridtools::GridCoordinatesObject & gcoords( ag->getGridCoordinatesObject() );
     208             :     std::vector<double> fspacing;
     209           1 :     std::vector<unsigned> snbins( getGridCoordinatesObject().getDimension() );
     210           1 :     std::vector<std::string> smin( gcoords.getDimension() ), smax( gcoords.getDimension() );
     211           3 :     for(unsigned i=0; i<getGridCoordinatesObject().getDimension(); ++i) {
     212           4 :       smin[i]=gcoords.getMin()[i];
     213           4 :       smax[i]=gcoords.getMax()[i];
     214             :       // Compute k-grid extents
     215             :       double dmin, dmax;
     216           2 :       snbins[i]=gcoords.getNbin(false)[i];
     217           2 :       Tools::convert(smin[i],dmin);
     218           2 :       Tools::convert(smax[i],dmax);
     219           2 :       dmax=2.0*pi*snbins[i]/( dmax - dmin );
     220           2 :       dmin=0.0;
     221           2 :       Tools::convert(dmin,smin[i]);
     222           2 :       Tools::convert(dmax,smax[i]);
     223             :     }
     224           1 :     gridcoords.setBounds( smin, smax, snbins, fspacing );
     225           1 :     firsttime=false;
     226           3 :     for(unsigned i=0; i<getNumberOfComponents(); ++i) {
     227           4 :       getPntrToComponent(i)->setShape( gcoords.getNbin(true) );
     228             :     }
     229           1 :   }
     230             : 
     231             : #ifdef __PLUMED_HAS_FFTW
     232             :   // *** CHECK CORRECT k-GRID BOUNDARIES ***
     233             :   //log<<"Real grid boundaries: \n"
     234             :   //    <<"  min_x: "<<mygrid->getMin()[0]<<"  min_y: "<<mygrid->getMin()[1]<<"\n"
     235             :   //    <<"  max_x: "<<mygrid->getMax()[0]<<"  max_y: "<<mygrid->getMax()[1]<<"\n"
     236             :   //    <<"K-grid boundaries:"<<"\n"
     237             :   //    <<"  min_x: "<<ft_min[0]<<"  min_y: "<<ft_min[1]<<"\n"
     238             :   //    <<"  max_x: "<<ft_max[0]<<"  max_y: "<<ft_max[1]<<"\n";
     239             : 
     240             :   // Get the size of the input data arrays (to allocate FFT data)
     241           1 :   std::vector<unsigned> N_input_data( gridcoords.getNbin(true) );
     242             :   size_t fft_dimension=1;
     243           3 :   for(unsigned i=0; i<N_input_data.size(); ++i) {
     244           2 :     fft_dimension*=static_cast<size_t>( N_input_data[i] );
     245             :   }
     246             :   // FFT arrays
     247           1 :   std::vector<std::complex<double> > input_data(fft_dimension), fft_data(fft_dimension);
     248             : 
     249             :   // Fill real input with the data on the grid
     250             :   Value* arg=getPntrToArgument(0);
     251           1 :   unsigned nargs=arg->getNumberOfValues();
     252           1 :   std::vector<unsigned> ind( arg->getRank() );
     253       10202 :   for (unsigned i=0; i<arg->getNumberOfValues(); ++i) {
     254             :     // Get point indices
     255       10201 :     gridcoords.getIndices(i, ind);
     256             :     // Fill input data in row-major order
     257       10201 :     input_data[ind[0]*N_input_data[0]+ind[1]].real( arg->get( i ) );
     258       10201 :     input_data[ind[0]*N_input_data[0]+ind[1]].imag( 0.0 );
     259             :   }
     260             : 
     261             :   // *** 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 ...
     262           1 :   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);
     263             : 
     264             :   // Compute FT
     265           1 :   fftw_execute( plan_complex );
     266             : 
     267             :   // Compute the normalization constant
     268             :   double norm=1.0;
     269           3 :   for (unsigned i=0; i<N_input_data.size(); ++i) {
     270           2 :     norm *= pow( N_input_data[i], (1-fourier_params[0])/2 );
     271             :   }
     272             : 
     273             :   // Save FT data to output grid
     274           1 :   std::vector<unsigned> N_out_data ( getGridCoordinatesObject().getNbin(true) );
     275           1 :   std::vector<unsigned> out_ind ( getPntrToArgument(0)->getRank() );
     276       10202 :   for(unsigned i=0; i<getPntrToArgument(0)->getNumberOfValues(); ++i) {
     277       10201 :     gridcoords.getIndices( i, out_ind );
     278       10201 :     if (real_output) {
     279             :       double ft_value;
     280             :       // Compute abs/norm and fix normalization
     281           0 :       if (!store_norm) {
     282           0 :         ft_value=std::abs( fft_data[out_ind[0]*N_out_data[0]+out_ind[1]] / norm );
     283             :       } else {
     284           0 :         ft_value=std::norm( fft_data[out_ind[0]*N_out_data[0]+out_ind[1]] / norm );
     285             :       }
     286             :       // Set the value
     287           0 :       getPntrToComponent(0)->set( i, ft_value);
     288             :     } else {
     289             :       double ft_value_real, ft_value_imag;
     290       10201 :       ft_value_real=fft_data[out_ind[0]*N_out_data[0]+out_ind[1]].real() / norm;
     291       10201 :       ft_value_imag=fft_data[out_ind[0]*N_out_data[0]+out_ind[1]].imag() / norm;
     292             :       // Set values
     293       10201 :       getPntrToComponent(0)->set( i, ft_value_real );
     294       10201 :       getPntrToComponent(1)->set( i, ft_value_imag );
     295             :     }
     296             :   }
     297             : 
     298             :   // Free FFTW stuff
     299           1 :   fftw_destroy_plan(plan_complex);
     300             : #endif
     301           1 : }
     302             : 
     303             : } // end namespace 'gridtools'
     304             : } // end namespace 'PLMD'

Generated by: LCOV version 1.16