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'
|