1 /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 Copyright (c) 2012-2021 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 "KernelFunctions.h"
23 #include "IFile.h"
24 #include <iostream>
25 #include <cmath>
26
27 namespace PLMD {
28
29 //+PLUMEDOC INTERNAL kernelfunctions
30 /*
31 Functions that are used to construct histograms
32
33 Constructing histograms is something you learned to do relatively early in life. You perform an experiment a number of times,
34 count the number of times each result comes up and then draw a bar graph that describes how often each of the results came up.
35 This only works when there are a finite number of possible results. If the result a number between 0 and 1 the bar chart is
36 less easy to draw as there are as many possible results as there are numbers between zero and one - an infinite number.
37 To resolve this problem we replace probability, \f$P\f$ with probability density, \f$\pi\f$, and write the probability of getting
38 a number between \f$a\f$ and \f$b\f$ as:
39
40 \f[
41 P = \int_{a}^b \textrm{d}x \pi(x)
42 \f]
43
44 To calculate probability densities from a set of results we use a process called kernel density estimation.
45 Histograms are accumulated by adding up kernel functions, \f$K\f$, with finite spatial extent, that integrate to one.
46 These functions are centered on each of the \f$n\f$-dimensional data points, \f$\mathbf{x}_i\f$. The overall effect of this
47 is that each result we obtain in our experiments contributes to the probability density in a finite sized region of the space.
48
49 Expressing all this mathematically in kernel density estimation we write the probability density as:
50
51 \f[
52 \pi(\mathbf{x}) = \sum_i K\left[ (\mathbf{x} - \mathbf{x}_i)^T \Sigma (\mathbf{x} - \mathbf{x}_i) \right]
53 \f]
54
55 where \f$\Sigma\f$ is an \f$n \times n\f$ matrix called the bandwidth that controls the spatial extent of
56 the kernel. Whenever we accumulate a histogram (e.g. in \ref HISTOGRAM or in \ref METAD) we use this
57 technique.
58
59 There is thus some flexibility in the particular function we use for \f$K[\mathbf{r}]\f$ in the above.
60 The following variants are available.
61
62 <table align=center frame=void width=95%% cellpadding=5%%>
63 <tr>
64 <td> TYPE </td> <td> FUNCTION </td>
65 </tr> <tr>
66 <td> gaussian </td> <td> \f$f(r) = \frac{1}{(2 \pi)^{n} \sqrt{|\Sigma^{-1}|}} \exp\left(-0.5 r^2 \right)\f$ </td>
67 </tr> <tr>
68 <td> truncated-gaussian </td> <td> \f$f(r) = \frac{1}{(2 \pi)^{n} \sqrt{|\Sigma^{-1}|} \left(\frac{\mathrm{erf}(-6.25/sqrt{2}) - \mathrm{erf}(-6.25/sqrt{2})}{2}\right)^n} \exp\left(-0.5 r^2 \right)\f$ </td>
69 </tr> <tr>
70 <td> triangular </td> <td> \f$f(r) = \frac{3}{V} ( 1 - | r | )H(1-|r|) \f$ </td>
71 </tr> <tr>
72 <td> uniform </td> <td> \f$f(r) = \frac{1}{V}H(1-|r|)\f$ </td>
73 </tr>
74 </table>
75
76 In the above \f$H(y)\f$ is a function that is equal to one when \f$y>0\f$ and zero when \f$y \le 0\f$. \f$n\f$ is
77 the dimensionality of the vector \f$\mathbf{x}\f$ and \f$V\f$ is the volume of an ellipse in an \f$n\f$ dimensional
78 space which is given by:
79
80 \f{eqnarray*}{
81 V &=& | \Sigma^{-1} | \frac{ \pi^{\frac{n}{2}} }{\left( \frac{n}{2} \right)! } \qquad \textrm{for even} \quad n \\
82 V &=& | \Sigma^{-1} | \frac{ 2^{\frac{n+1}{2}} \pi^{\frac{n-1}{2}} }{ n!! }
83 \f}
84
85 In \ref METAD the normalization constants are ignored so that the value of the function at \f$r=0\f$ is equal
86 to one. In addition in \ref METAD we must be able to differentiate the bias in order to get forces. This limits
87 the kernels we can use in this method. Notice also that Gaussian kernels should have infinite support. When used
88 with grids, however, they are assumed to only be non-zero over a finite range. The difference between the
89 truncated-gaussian and regular gaussian is that the truncated gaussian is scaled so that its integral over the grid
90 is equal to one when it is normalized. The integral of a regular gaussian when it is evaluated on a grid will be
91 slightly less that one because of the truncation of a function that should have infinite support.
92 */
93 //+ENDPLUMEDOC
94
KernelFunctions(const std::string & input)95 KernelFunctions::KernelFunctions( const std::string& input ) {
96 std::vector<std::string> data=Tools::getWords(input);
97 std::string name=data[0];
98 data.erase(data.begin());
99
100 std::vector<double> at;
101 bool foundc = Tools::parseVector(data,"CENTER",at);
102 if(!foundc) plumed_merror("failed to find center keyword in definition of kernel");
103 std::vector<double> sig;
104 bool founds = Tools::parseVector(data,"SIGMA",sig);
105 if(!founds) plumed_merror("failed to find sigma keyword in definition of kernel");
106
107 bool multi=false; Tools::parseFlag(data,"MULTIVARIATE",multi);
108 bool vonmises=false; Tools::parseFlag(data,"VON-MISSES",vonmises);
109 if( center.size()==1 && multi ) plumed_merror("one dimensional kernel cannot be multivariate");
110 if( center.size()==1 && vonmises ) plumed_merror("one dimensional kernal cannot be von-misses");
111 if( center.size()==1 && sig.size()!=1 ) plumed_merror("size mismatch between center size and sigma size");
112 if( multi && center.size()>1 && sig.size()!=0.5*center.size()*(center.size()-1) ) plumed_merror("size mismatch between center size and sigma size");
113 if( !multi && center.size()>1 && sig.size()!=center.size() ) plumed_merror("size mismatch between center size and sigma size");
114
115 double h;
116 bool foundh = Tools::parse(data,"HEIGHT",h);
117 if( !foundh) h=1.0;
118
119 if( multi ) setData( at, sig, name, "MULTIVARIATE", h );
120 else if( vonmises ) setData( at, sig, name, "VON-MISSES", h );
121 else setData( at, sig, name, "DIAGONAL", h );
122 }
123
KernelFunctions(const std::vector<double> & at,const std::vector<double> & sig,const std::string & type,const std::string & mtype,const double & w)124 KernelFunctions::KernelFunctions( const std::vector<double>& at, const std::vector<double>& sig, const std::string& type, const std::string& mtype, const double& w ) {
125 setData( at, sig, type, mtype, w );
126 }
127
KernelFunctions(const KernelFunctions * in)128 KernelFunctions::KernelFunctions( const KernelFunctions* in ):
129 dtype(in->dtype),
130 ktype(in->ktype),
131 center(in->center),
132 width(in->width),
133 height(in->height)
134 {
135 }
136
setData(const std::vector<double> & at,const std::vector<double> & sig,const std::string & type,const std::string & mtype,const double & w)137 void KernelFunctions::setData( const std::vector<double>& at, const std::vector<double>& sig, const std::string& type, const std::string& mtype, const double& w ) {
138
139 height=w;
140 center.resize( at.size() ); for(unsigned i=0; i<at.size(); ++i) center[i]=at[i];
141 width.resize( sig.size() ); for(unsigned i=0; i<sig.size(); ++i) width[i]=sig[i];
142 if( mtype=="MULTIVARIATE" ) dtype=multi;
143 else if( mtype=="VON-MISSES" ) dtype=vonmises;
144 else if( mtype=="DIAGONAL" ) dtype=diagonal;
145 else plumed_merror(mtype + " is not a valid metric type");
146
147 // Setup the kernel type
148 if(type=="GAUSSIAN" || type=="gaussian" ) {
149 ktype=gaussian;
150 } else if(type=="TRUNCATED-GAUSSIAN" || type=="truncated-gaussian" ) {
151 ktype=truncatedgaussian;
152 } else if(type=="UNIFORM" || type=="uniform") {
153 ktype=uniform;
154 } else if(type=="TRIANGULAR" || type=="triangular") {
155 ktype=triangular;
156 } else {
157 plumed_merror(type+" is an invalid kernel type\n");
158 }
159 }
160
normalize(const std::vector<Value * > & myvals)161 void KernelFunctions::normalize( const std::vector<Value*>& myvals ) {
162
163 double det=1.;
164 unsigned ncv=ndim();
165 if(dtype==diagonal) {
166 for(unsigned i=0; i<width.size(); ++i) det*=width[i]*width[i];
167 } else if(dtype==multi) {
168 Matrix<double> mymatrix( getMatrix() ), myinv( ncv, ncv );
169 Invert(mymatrix,myinv); double logd;
170 logdet( myinv, logd );
171 det=std::exp(logd);
172 }
173 if( dtype==diagonal || dtype==multi ) {
174 double volume;
175 if( ktype==gaussian ) {
176 volume=pow( 2*pi, 0.5*ncv ) * pow( det, 0.5 );
177 } else if( ktype==truncatedgaussian ) {
178 // This makes it so the gaussian integrates to one over the range over which it has support
179 const double DP2CUTOFF=sqrt(6.25);
180 volume=pow( 2*pi, 0.5*ncv ) * pow( det, 0.5 ) * pow( 0.5 * ( erf(DP2CUTOFF) - erf(-DP2CUTOFF) ), ncv);
181 } else if( ktype==uniform || ktype==triangular ) {
182 if( ncv%2==1 ) {
183 double dfact=1;
184 for(unsigned i=1; i<ncv; i+=2) dfact*=static_cast<double>(i);
185 volume=( pow( pi, (ncv-1)/2 ) ) * ( pow( 2., (ncv+1)/2 ) ) / dfact;
186 } else {
187 double fact=1.;
188 for(unsigned i=1; i<ncv/2; ++i) fact*=static_cast<double>(i);
189 volume=pow( pi,ncv/2 ) / fact;
190 }
191 if(ktype==uniform) volume*=det;
192 else if(ktype==triangular) volume*=det / 3.;
193 } else {
194 plumed_merror("not a valid kernel type");
195 }
196 height /= volume;
197 return;
198 }
199 plumed_assert( dtype==vonmises && ktype==gaussian );
200 // Now calculate determinant for aperiodic variables
201 unsigned naper=0;
202 for(unsigned i=0; i<ncv; ++i) {
203 if( !myvals[i]->isPeriodic() ) naper++;
204 }
205 // Now construct sub matrix
206 double volume=1;
207 if( naper>0 ) {
208 unsigned isub=0;
209 Matrix<double> mymatrix( getMatrix() ), mysub( naper, naper );
210 for(unsigned i=0; i<ncv; ++i) {
211 if( myvals[i]->isPeriodic() ) continue;
212 unsigned jsub=0;
213 for(unsigned j=0; j<ncv; ++j) {
214 if( myvals[j]->isPeriodic() ) continue;
215 mysub( isub, jsub ) = mymatrix( i, j ); jsub++;
216 }
217 isub++;
218 }
219 Matrix<double> myisub( naper, naper ); double logd;
220 Invert( mysub, myisub ); logdet( myisub, logd );
221 det=std::exp(logd);
222 volume=pow( 2*pi, 0.5*ncv ) * pow( det, 0.5 );
223 }
224
225 // Calculate volume of periodic variables
226 unsigned nper=0;
227 for(unsigned i=0; i<ncv; ++i) {
228 if( myvals[i]->isPeriodic() ) nper++;
229 }
230
231 // Now construct sub matrix
232 if( nper>0 ) {
233 unsigned isub=0;
234 Matrix<double> mymatrix( getMatrix() ), mysub( nper, nper );
235 for(unsigned i=0; i<ncv; ++i) {
236 if( !myvals[i]->isPeriodic() ) continue;
237 unsigned jsub=0;
238 for(unsigned j=0; j<ncv; ++j) {
239 if( !myvals[j]->isPeriodic() ) continue;
240 mysub( isub, jsub ) = mymatrix( i, j ); jsub++;
241 }
242 isub++;
243 }
244 Matrix<double> eigvec( nper, nper );
245 std::vector<double> eigval( nper );
246 diagMat( mysub, eigval, eigvec );
247 unsigned iper=0; volume=1;
248 for(unsigned i=0; i<ncv; ++i) {
249 if( myvals[i]->isPeriodic() ) {
250 volume *= myvals[i]->getMaxMinusMin()*Tools::bessel0(eigval[iper])*std::exp(-eigval[iper]);
251 iper++;
252 }
253 }
254 }
255 height /= volume;
256 }
257
getCutoff(const double & width) const258 double KernelFunctions::getCutoff( const double& width ) const {
259 const double DP2CUTOFF=6.25;
260 if( ktype==gaussian || ktype==truncatedgaussian ) return sqrt(2.0*DP2CUTOFF)*width;
261 else if(ktype==triangular ) return width;
262 else if(ktype==uniform) return width;
263 else plumed_merror("No valid kernel type");
264 return 0.0;
265 }
266
getContinuousSupport() const267 std::vector<double> KernelFunctions::getContinuousSupport( ) const {
268 unsigned ncv=ndim();
269 std::vector<double> support( ncv );
270 if(dtype==diagonal) {
271 for(unsigned i=0; i<ncv; ++i) support[i]=getCutoff(width[i]);
272 } else if(dtype==multi) {
273 Matrix<double> mymatrix( getMatrix() ), myinv( ncv,ncv );
274 Invert(mymatrix,myinv);
275 Matrix<double> myautovec(ncv,ncv); std::vector<double> myautoval(ncv);
276 diagMat(myinv,myautoval,myautovec);
277 double maxautoval=0.;
278 unsigned ind_maxautoval=0;
279 for (unsigned i=0; i<ncv; i++) {
280 if(myautoval[i]>maxautoval) {maxautoval=myautoval[i]; ind_maxautoval=i;}
281 }
282 for(unsigned i=0; i<ncv; ++i) {
283 double extent=fabs(sqrt(maxautoval)*myautovec(i,ind_maxautoval));
284 support[i]=getCutoff( extent );
285 }
286 } else {
287 plumed_merror("cannot find support if metric is not multi or diagonal type");
288 }
289 return support;
290 }
291
getSupport(const std::vector<double> & dx) const292 std::vector<unsigned> KernelFunctions::getSupport( const std::vector<double>& dx ) const {
293 plumed_assert( ndim()==dx.size() );
294 std::vector<unsigned> support( dx.size() );
295 std::vector<double> vv=getContinuousSupport( );
296 for(unsigned i=0; i<dx.size(); ++i) support[i]=static_cast<unsigned>(ceil( vv[i]/dx[i] ));
297 return support;
298 }
299
evaluate(const std::vector<Value * > & pos,std::vector<double> & derivatives,bool usederiv,bool doInt,double lowI_,double uppI_) const300 double KernelFunctions::evaluate( const std::vector<Value*>& pos, std::vector<double>& derivatives, bool usederiv, bool doInt, double lowI_, double uppI_) const {
301 plumed_dbg_assert( pos.size()==ndim() && derivatives.size()==ndim() );
302 #ifndef NDEBUG
303 if( usederiv ) plumed_massert( ktype!=uniform, "step function can not be differentiated" );
304 #endif
305 if(doInt) {
306 plumed_dbg_assert(center.size()==1);
307 if(pos[0]->get()<lowI_) pos[0]->set(lowI_);
308 if(pos[0]->get()>uppI_) pos[0]->set(uppI_);
309 }
310 double r2=0;
311 if(dtype==diagonal) {
312 for(unsigned i=0; i<ndim(); ++i) {
313 derivatives[i]=-pos[i]->difference( center[i] ) / width[i];
314 r2+=derivatives[i]*derivatives[i];
315 derivatives[i] /= width[i];
316 }
317 } else if(dtype==multi) {
318 Matrix<double> mymatrix( getMatrix() );
319 for(unsigned i=0; i<mymatrix.nrows(); ++i) {
320 double dp_i, dp_j; derivatives[i]=0;
321 dp_i=-pos[i]->difference( center[i] );
322 for(unsigned j=0; j<mymatrix.ncols(); ++j) {
323 if(i==j) dp_j=dp_i;
324 else dp_j=-pos[j]->difference( center[j] );
325
326 derivatives[i]+=mymatrix(i,j)*dp_j;
327 r2+=dp_i*dp_j*mymatrix(i,j);
328 }
329 }
330 } else if(dtype==vonmises) {
331 std::vector<double> costmp( ndim() ), sintmp( ndim() ), sinout( ndim(), 0.0 );
332 for(unsigned i=0; i<ndim(); ++i) {
333 if( pos[i]->isPeriodic() ) {
334 sintmp[i]=sin( 2.*pi*(pos[i]->get() - center[i])/pos[i]->getMaxMinusMin() );
335 costmp[i]=cos( 2.*pi*(pos[i]->get() - center[i])/pos[i]->getMaxMinusMin() );
336 } else {
337 sintmp[i]=pos[i]->get() - center[i];
338 costmp[i]=1.0;
339 }
340 }
341
342 Matrix<double> mymatrix( getMatrix() );
343 for(unsigned i=0; i<mymatrix.nrows(); ++i) {
344 derivatives[i]=0;
345 if( pos[i]->isPeriodic() ) {
346 r2+=2*( 1 - costmp[i] )*mymatrix(i,i);
347 } else {
348 r2+=sintmp[i]*sintmp[i]*mymatrix(i,i);
349 }
350 for(unsigned j=0; j<mymatrix.ncols(); ++j) {
351 if( i!=j ) sinout[i]+=mymatrix(i,j)*sintmp[j];
352 }
353 derivatives[i] = mymatrix(i,i)*sintmp[i] + sinout[i]*costmp[i];
354 if( pos[i]->isPeriodic() ) derivatives[i] *= (2*pi/pos[i]->getMaxMinusMin());
355 }
356 for(unsigned i=0; i<sinout.size(); ++i) r2+=sintmp[i]*sinout[i];
357 }
358 double kderiv, kval;
359 if(ktype==gaussian || ktype==truncatedgaussian) {
360 kval=height*std::exp(-0.5*r2); kderiv=-kval;
361 } else {
362 double r=sqrt(r2);
363 if(ktype==triangular) {
364 if( r<1.0 ) {
365 if(r==0) kderiv=0;
366 kderiv=-1; kval=height*( 1. - fabs(r) );
367 } else {
368 kval=0.; kderiv=0.;
369 }
370 } else if(ktype==uniform) {
371 kderiv=0.;
372 if(r<1.0) kval=height;
373 else kval=0;
374 } else {
375 plumed_merror("Not a valid kernel type");
376 }
377 kderiv*=height / r ;
378 }
379 for(unsigned i=0; i<ndim(); ++i) derivatives[i]*=kderiv;
380 if(doInt) {
381 if((pos[0]->get() <= lowI_ || pos[0]->get() >= uppI_) && usederiv ) for(unsigned i=0; i<ndim(); ++i)derivatives[i]=0;
382 }
383 return kval;
384 }
385
read(IFile * ifile,const bool & cholesky,const std::vector<std::string> & valnames)386 std::unique_ptr<KernelFunctions> KernelFunctions::read( IFile* ifile, const bool& cholesky, const std::vector<std::string>& valnames ) {
387 double h;
388 if( !ifile->scanField("height",h) ) return NULL;;
389
390 std::string sss; ifile->scanField("multivariate",sss);
391 std::string ktype="gaussian"; if( ifile->FieldExist("kerneltype") ) ifile->scanField("kerneltype",ktype);
392 plumed_massert( sss=="false" || sss=="true" || sss=="von-misses", "multivariate flag must be either false, true or von-misses");
393
394 // Read the position of the center
395 std::vector<double> cc( valnames.size() );
396 for(unsigned i=0; i<valnames.size(); ++i) ifile->scanField(valnames[i],cc[i]);
397
398 std::vector<double> sig;
399 if( sss=="false" ) {
400 sig.resize( valnames.size() );
401 for(unsigned i=0; i<valnames.size(); ++i) {
402 ifile->scanField("sigma_"+valnames[i],sig[i]);
403 if( !cholesky ) sig[i]=sqrt(sig[i]);
404 }
405 return std::unique_ptr<KernelFunctions>(new KernelFunctions( cc, sig, ktype, "DIAGONAL", h ) );
406 }
407
408 unsigned ncv=valnames.size();
409 sig.resize( (ncv*(ncv+1))/2 );
410 Matrix<double> upper(ncv,ncv), lower(ncv,ncv), mymult( ncv, ncv ), invmatrix(ncv,ncv);
411 for(unsigned i=0; i<ncv; ++i) {
412 for(unsigned j=0; j<ncv-i; j++) {
413 ifile->scanField("sigma_" +valnames[j+i] + "_" + valnames[j], lower(j+i,j) );
414 upper(j,j+i)=lower(j+i,j); mymult(j+i,j)=mymult(j,j+i)=lower(j+i,j);
415 }
416 }
417 if( cholesky ) mult(lower,upper,mymult);
418 Invert( mymult, invmatrix );
419 unsigned k=0;
420 for(unsigned i=0; i<ncv; i++) {
421 for(unsigned j=i; j<ncv; j++) { sig[k]=invmatrix(i,j); k++; }
422 }
423 if( sss=="true" ) return std::unique_ptr<KernelFunctions>(new KernelFunctions( cc, sig, ktype, "MULTIVARIATE", h ) );
424 return std::unique_ptr<KernelFunctions>(new KernelFunctions( cc, sig, ktype, "VON-MISSES", h ) );
425 }
426
427 }
428