Line data Source code
1 : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 : Copyright (c) 2016-2019 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 FILE=fourier.dat
63 : \endplumedfile
64 :
65 : */
66 : //+ENDPLUMEDOC
67 :
68 :
69 0 : class FourierTransform : public ActionWithInputGrid {
70 : private:
71 : std::string output_type;
72 : bool real_output, store_norm;
73 : std::vector<unsigned> gdirs;
74 : std::vector<int> fourier_params;
75 : public:
76 : static void registerKeywords( Keywords& keys );
77 : explicit FourierTransform(const ActionOptions&ao);
78 : #ifndef __PLUMED_HAS_FFTW
79 : void performOperations( const bool& from_update ) {}
80 : #else
81 : void performOperations( const bool& from_update );
82 : #endif
83 0 : void compute( const unsigned&, MultiValue& ) const {}
84 0 : bool isPeriodic() { return false; }
85 : };
86 :
87 6452 : PLUMED_REGISTER_ACTION(FourierTransform,"FOURIER_TRANSFORM")
88 :
89 1 : void FourierTransform::registerKeywords( Keywords& keys ) {
90 3 : ActionWithInputGrid::registerKeywords( keys ); keys.remove("BANDWIDTH"); keys.remove("KERNEL");
91 4 : 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).");
92 5 : 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: "
93 : "A=-1: normalize by the number of data, "
94 : "A=0: normalize by the square root of the number of data (one forward and followed by backward FFT recover the original data). ");
95 1 : }
96 :
97 0 : FourierTransform::FourierTransform(const ActionOptions&ao):
98 : Action(ao),
99 : ActionWithInputGrid(ao),
100 : real_output(true),
101 : store_norm(false),
102 0 : fourier_params(2)
103 : {
104 : #ifndef __PLUMED_HAS_FFTW
105 : error("this feature is only available if you compile PLUMED with FFTW");
106 : #else
107 0 : if( ingrid->getDimension()!=2 ) error("fourier transform currently only works with two dimensional grids");
108 :
109 : // Get the type of FT
110 0 : parse("FT_TYPE",output_type);
111 0 : if (output_type.length()==0) {
112 0 : log<<" keyword FT_TYPE unset. By default output grid will contain REAL Fourier coefficients\n";
113 0 : } else if ( output_type=="ABS" || output_type=="abs") {
114 0 : log << " keyword FT_TYPE is '"<< output_type << "' : will compute the MODULUS of Fourier coefficients\n";
115 0 : } else if ( output_type=="NORM" || output_type=="norm") {
116 0 : log << " keyword FT_TYPE is '"<< output_type << "' : will compute the NORM of Fourier coefficients\n";
117 0 : store_norm=true;
118 0 : } else if ( output_type=="COMPLEX" || output_type=="complex" ) {
119 0 : log<<" keyword FT_TYPE is '"<< output_type <<"' : output grid will contain the COMPLEX Fourier coefficients\n";
120 0 : real_output=false;
121 0 : } else error("keyword FT_TYPE unrecognized!");
122 :
123 : // Normalize output?
124 0 : std::string params_str; parse("FOURIER_PARAMETERS",params_str);
125 0 : if (params_str=="default") {
126 0 : fourier_params.assign( fourier_params.size(), 1 );
127 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]);
128 : } else {
129 0 : std::vector<std::string> fourier_str = Tools::getWords(params_str, "\t\n ,");
130 0 : if (fourier_str.size()>2) error("FOURIER_PARAMETERS can take just two values");
131 0 : for (unsigned i=0; i<fourier_str.size(); ++i) {
132 0 : Tools::convert(fourier_str[i],fourier_params[i]);
133 0 : if (fourier_params[i]>1 || fourier_params[i]<-1) error("values accepted for FOURIER_PARAMETERS are only -1, 1 or 0");
134 : }
135 0 : log.printf(" Fourier parameters are A=%i, B=%i \n", fourier_params[0],fourier_params[1]);
136 : }
137 :
138 :
139 : // Create the input from the old string
140 : std::string vstring;
141 0 : unsigned n=0; gdirs.resize( ingrid->getDimension() );
142 0 : for(unsigned i=0; i<ingrid->getDimension(); ++i) {
143 0 : gdirs[n]=i; n++;
144 : }
145 :
146 0 : plumed_assert( n==ingrid->getDimension() );
147 :
148 0 : if (real_output) {
149 0 : if (!store_norm) vstring="COMPONENTS=" + getLabel() + "_abs";
150 0 : else vstring="COMPONENTS=" + getLabel() + "_norm";
151 0 : } else vstring="COMPONENTS=" + getLabel() + "_real," + getLabel() + "_imag";
152 :
153 : // Set COORDINATES keyword
154 0 : vstring += " COORDINATES=" + ingrid->getComponentName( gdirs[0] );
155 0 : for(unsigned i=1; i<gdirs.size(); ++i) vstring += "," + ingrid->getComponentName( gdirs[i] );
156 :
157 : // Set PBC keyword
158 : vstring += " PBC=";
159 0 : if( ingrid->isPeriodic(gdirs[0]) ) vstring+="T"; else vstring+="F";
160 0 : for(unsigned i=1; i<gdirs.size(); ++i) {
161 0 : if( ingrid->isPeriodic(gdirs[i]) ) vstring+=",T"; else vstring+=",F";
162 : }
163 :
164 :
165 : // Create a grid on which to store the fourier transform of the input grid
166 0 : createGrid( "grid", vstring );
167 0 : if( ingrid->noDerivatives() ) mygrid->setNoDerivatives();
168 0 : setAveragingAction( mygrid, false );
169 :
170 0 : checkRead();
171 : #endif
172 0 : }
173 :
174 : #ifdef __PLUMED_HAS_FFTW
175 0 : void FourierTransform::performOperations( const bool& from_update ) {
176 :
177 : // Spacing of the real grid
178 0 : std::vector<double> g_spacing ( ingrid->getGridSpacing() );
179 : // Spacing of the k-grid
180 : std::vector<double> ft_spacing;
181 : // Extents of the k-grid
182 0 : std::vector<std::string> ft_min( ingrid->getMin() ), ft_max( ingrid->getMax() );
183 : // Number of bins in the k-grid (equal to the number of bins in the real grid)
184 0 : std::vector<unsigned> ft_bins ( ingrid->getNbin() );
185 :
186 0 : for (unsigned i=0; i<ingrid->getDimension(); ++i) {
187 : // Check PBC in current grid dimension
188 0 : if( !ingrid->isPeriodic(i) ) ft_bins[i]++;
189 : // Compute k-grid extents
190 : double dmin, dmax;
191 0 : Tools::convert(ft_min[i],dmin); Tools::convert(ft_max[i],dmax);
192 : // We want to have the min of k-grid at point (0,0)
193 0 : dmin=0.0;
194 0 : dmax=2.0*pi*ft_bins[i]/( ingrid->getGridExtent(i) );
195 0 : Tools::convert(dmin,ft_min[i]); Tools::convert(dmax,ft_max[i]);
196 : }
197 :
198 : // This is the actual setup of the k-grid
199 0 : mygrid->setBounds( ft_min, ft_max, ft_bins, ft_spacing); resizeFunctions();
200 :
201 : // *** CHECK CORRECT k-GRID BOUNDARIES ***
202 : //log<<"Real grid boundaries: \n"
203 : // <<" min_x: "<<mygrid->getMin()[0]<<" min_y: "<<mygrid->getMin()[1]<<"\n"
204 : // <<" max_x: "<<mygrid->getMax()[0]<<" max_y: "<<mygrid->getMax()[1]<<"\n"
205 : // <<"K-grid boundaries:"<<"\n"
206 : // <<" min_x: "<<ft_min[0]<<" min_y: "<<ft_min[1]<<"\n"
207 : // <<" max_x: "<<ft_max[0]<<" max_y: "<<ft_max[1]<<"\n";
208 :
209 :
210 :
211 : // Get the size of the input data arrays (to allocate FFT data)
212 0 : std::vector<unsigned> N_input_data( ingrid->getNbin() );
213 0 : 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] );
214 :
215 : // FFT arrays
216 0 : std::vector<std::complex<double> > input_data(fft_dimension), fft_data(fft_dimension);
217 :
218 :
219 : // Fill real input with the data on the grid
220 0 : std::vector<unsigned> ind( ingrid->getDimension() );
221 0 : for (unsigned i=0; i<ingrid->getNumberOfPoints(); ++i) {
222 : // Get point indices
223 0 : ingrid->getIndices(i, ind);
224 : // Fill input data in row-major order
225 0 : input_data[ind[0]*N_input_data[0]+ind[1]].real( getFunctionValue( i ) );
226 0 : input_data[ind[0]*N_input_data[0]+ind[1]].imag( 0.0 );
227 : }
228 :
229 : // *** 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 ...
230 0 : 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);
231 :
232 : // Compute FT
233 0 : fftw_execute( plan_complex );
234 :
235 : // Compute the normalization constant
236 : double norm=1.0;
237 0 : for (unsigned i=0; i<N_input_data.size(); ++i) {
238 0 : norm *= pow( N_input_data[i], (1-fourier_params[0])/2 );
239 : }
240 :
241 : // Save FT data to output grid
242 0 : std::vector<unsigned> N_out_data ( mygrid->getNbin() );
243 0 : std::vector<unsigned> out_ind ( mygrid->getDimension() );
244 0 : for(unsigned i=0; i<mygrid->getNumberOfPoints(); ++i) {
245 0 : mygrid->getIndices( i, out_ind );
246 0 : if (real_output) {
247 : double ft_value;
248 : // Compute abs/norm and fix normalization
249 0 : if (!store_norm) ft_value=std::abs( fft_data[out_ind[0]*N_out_data[0]+out_ind[1]] / norm );
250 0 : else ft_value=std::norm( fft_data[out_ind[0]*N_out_data[0]+out_ind[1]] / norm );
251 : // Set the value
252 0 : mygrid->setGridElement( i, 0, ft_value );
253 : } else {
254 : double ft_value_real, ft_value_imag;
255 0 : ft_value_real=fft_data[out_ind[0]*N_out_data[0]+out_ind[1]].real() / norm;
256 0 : ft_value_imag=fft_data[out_ind[0]*N_out_data[0]+out_ind[1]].imag() / norm;
257 : // Set values
258 0 : mygrid->setGridElement( i, 0, ft_value_real);
259 0 : mygrid->setGridElement( i, 1, ft_value_imag);
260 : }
261 : }
262 :
263 : // Free FFTW stuff
264 0 : fftw_destroy_plan(plan_complex);
265 :
266 0 : }
267 : #endif
268 :
269 : } // end namespace 'gridtools'
270 4839 : } // end namespace 'PLMD'
|