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 "SwitchingFunction.h"
23 #include "Tools.h"
24 #include "Keywords.h"
25 #include "OpenMP.h"
26 #include <vector>
27 #include <limits>
28 
29 #define PI 3.14159265358979323846
30 
31 using namespace std;
32 namespace PLMD {
33 
34 //+PLUMEDOC INTERNAL switchingfunction
35 /*
36 Functions that measure whether values are less than a certain quantity.
37 
38 Switching functions \f$s(r)\f$ take a minimum of one input parameter \f$r_0\f$.
39 For \f$r \le d_0 \quad s(r)=1.0\f$ while for \f$r > d_0\f$ the function decays smoothly to 0.
40 The various switching functions available in plumed differ in terms of how this decay is performed.
41 
42 Where there is an accepted convention in the literature (e.g. \ref COORDINATION) on the form of the
43 switching function we use the convention as the default.  However, the flexibility to use different
44 switching functions is always present generally through a single keyword. This keyword generally
45 takes an input with the following form:
46 
47 \verbatim
48 KEYWORD={TYPE <list of parameters>}
49 \endverbatim
50 
51 The following table contains a list of the various switching functions that are available in plumed 2
52 together with an example input.
53 
54 <table align=center frame=void width=95%% cellpadding=5%%>
55 <tr>
56 <td> TYPE </td> <td> FUNCTION </td> <td> EXAMPLE INPUT </td> <td> DEFAULT PARAMETERS </td>
57 </tr> <tr> <td>RATIONAL </td> <td>
58 \f$
59 s(r)=\frac{ 1 - \left(\frac{ r - d_0 }{ r_0 }\right)^{n} }{ 1 - \left(\frac{ r - d_0 }{ r_0 }\right)^{m} }
60 \f$
61 </td> <td>
62 {RATIONAL R_0=\f$r_0\f$ D_0=\f$d_0\f$ NN=\f$n\f$ MM=\f$m\f$}
63 </td> <td> \f$d_0=0.0\f$, \f$n=6\f$, \f$m=2n\f$ </td>
64 </tr> <tr>
65 <td> EXP </td> <td>
66 \f$
67 s(r)=\exp\left(-\frac{ r - d_0 }{ r_0 }\right)
68 \f$
69 </td> <td>
70 {EXP  R_0=\f$r_0\f$ D_0=\f$d_0\f$}
71 </td> <td> \f$d_0=0.0\f$ </td>
72 </tr> <tr>
73 <td> GAUSSIAN </td> <td>
74 \f$
75 s(r)=\exp\left(-\frac{ (r - d_0)^2 }{ 2r_0^2 }\right)
76 \f$
77 </td> <td>
78 {GAUSSIAN R_0=\f$r_0\f$ D_0=\f$d_0\f$}
79 </td> <td> \f$d_0=0.0\f$ </td>
80 </tr> <tr>
81 <td> SMAP </td> <td>
82 \f$
83 s(r) = \left[ 1 + ( 2^{a/b} -1 )\left( \frac{r-d_0}{r_0} \right)^a \right]^{-b/a}
84 \f$
85 </td> <td>
86 {SMAP R_0=\f$r_0\f$ D_0=\f$d_0\f$ A=\f$a\f$ B=\f$b\f$}
87 </td> <td> \f$d_0=0.0\f$ </td>
88 </tr> <tr>
89 <td> Q </td> <td>
90 \f$
91 s(r) = \frac{1}{1 + \exp(\beta(r_{ij} - \lambda r_{ij}^0))}
92 \f$
93 </td> <td>
94 {Q REF=\f$r_{ij}^0\f$ BETA=\f$\beta\f$ LAMBDA=\f$\lambda\f$ }
95 </td> <td> \f$\lambda=1.8\f$,  \f$\beta=50 nm^-1\f$ (all-atom)<br/>\f$\lambda=1.5\f$,  \f$\beta=50 nm^-1\f$ (coarse-grained)  </td>
96 </tr> <tr>
97 <td> CUBIC </td> <td>
98 \f$
99 s(r) = (y-1)^2(1+2y) \qquad \textrm{where} \quad y = \frac{r - r_1}{r_0-r_1}
100 \f$
101 </td> <td>
102 {CUBIC D_0=\f$r_1\f$ D_MAX=\f$r_0\f$}
103 </td> <td> </td>
104 </tr> <tr>
105 <td> TANH </td> <td>
106 \f$
107 s(r) = 1 - \tanh\left( \frac{ r - d_0 }{ r_0 } \right)
108 \f$
109 </td> <td>
110 {TANH R_0=\f$r_0\f$ D_0=\f$d_0\f$}
111 </td> <td> </td>
112 </tr> <tr>
113 <td> COSINUS </td> <td>
114 \f$
115 s(r) &= 1  & if r<=d0
116 s(r) &= 0.5 \left( \cos ( \frac{ r - d_0 }{ r_0 } * PI ) + 1 \right) & if d0<r<=d0+r0
117 s(r) &= 0  & if r> d0+r0
118 \f$
119 </td> <td>
120 {COSINUS R_0=\f$r_0\f$ D_0=\f$d_0\f$}
121 </td> <td> </td>
122 </tr> <tr>
123 <td> CUSTOM </td> <td>
124 \f$
125 s(r) = FUNC
126 \f$
127 </td> <td>
128 {CUSTOM FUNC=1/(1+x^6) R_0=\f$r_0\f$ D_0=\f$d_0\f$}
129 </td> <td> </td>
130 </tr>
131 </table>
132 
133 Notice that for backward compatibility we allow using `MATHEVAL` instead of `CUSTOM`.
134 Also notice that if the a `CUSTOM` switching function only depends on even powers of `x` it can be
135 made faster by using `x2` as a variable. For instance
136 \verbatim
137 {CUSTOM FUNC=1/(1+x2^3) R_0=0.3}
138 \endverbatim
139 is equivalent to
140 \verbatim
141 {CUSTOM FUNC=1/(1+x^6) R_0=0.3}
142 \endverbatim
143 but runs faster. The reason is that there is an expensive square root calculation that can be optimized out.
144 
145 
146 \attention
147 With the default implementation CUSTOM is slower than other functions
148 (e.g., it is slower than an equivalent RATIONAL function by approximately a factor 2).
149 Checkout page \ref Lepton to see how to improve its performance.
150 
151 For all the switching functions in the above table one can also specify a further (optional) parameter using the parameter
152 keyword D_MAX to assert that for \f$r>d_{\textrm{max}}\f$ the switching function can be assumed equal to zero.
153 In this case the function is brought smoothly to zero by stretching and shifting it.
154 \verbatim
155 KEYWORD={RATIONAL R_0=1 D_MAX=3}
156 \endverbatim
157 the resulting switching function will be
158 \f$
159 s(r) = \frac{s'(r)-s'(d_{max})}{s'(0)-s'(d_{max})}
160 \f$
161 where
162 \f$
163 s'(r)=\frac{1-r^6}{1-r^{12}}
164 \f$
165 Since PLUMED 2.2 this is the default. The old behavior (no stretching) can be obtained with the
166 NOSTRETCH flag. The NOSTRETCH keyword is only provided for backward compatibility and might be
167 removed in the future. Similarly, the STRETCH keyword is still allowed but has no effect.
168 
169 Notice that switching functions defined with the simplified syntax are never stretched
170 for backward compatibility. This might change in the future.
171 
172 */
173 //+ENDPLUMEDOC
174 
registerKeywords(Keywords & keys)175 void SwitchingFunction::registerKeywords( Keywords& keys ) {
176   keys.add("compulsory","R_0","the value of R_0 in the switching function");
177   keys.add("compulsory","D_0","0.0","the value of D_0 in the switching function");
178   keys.add("optional","D_MAX","the value at which the switching function can be assumed equal to zero");
179   keys.add("compulsory","NN","6","the value of n in the switching function (only needed for TYPE=RATIONAL)");
180   keys.add("compulsory","MM","0","the value of m in the switching function (only needed for TYPE=RATIONAL); 0 implies 2*NN");
181   keys.add("compulsory","A","the value of a in the switching function (only needed for TYPE=SMAP)");
182   keys.add("compulsory","B","the value of b in the switching function (only needed for TYPE=SMAP)");
183 }
184 
set(const std::string & definition,std::string & errormsg)185 void SwitchingFunction::set(const std::string & definition,std::string& errormsg) {
186   vector<string> data=Tools::getWords(definition);
187   if( data.size()<1 ) {
188     errormsg="missing all input for switching function";
189     return;
190   }
191   string name=data[0];
192   data.erase(data.begin());
193   invr0=0.0;
194   invr0_2=0.0;
195   d0=0.0;
196   dmax=std::numeric_limits<double>::max();
197   dmax_2=std::numeric_limits<double>::max();
198   stretch=1.0;
199   shift=0.0;
200   init=true;
201 
202   bool present;
203 
204   present=Tools::findKeyword(data,"D_0");
205   if(present && !Tools::parse(data,"D_0",d0)) errormsg="could not parse D_0";
206 
207   present=Tools::findKeyword(data,"D_MAX");
208   if(present && !Tools::parse(data,"D_MAX",dmax)) errormsg="could not parse D_MAX";
209   if(dmax<std::sqrt(std::numeric_limits<double>::max())) dmax_2=dmax*dmax;
210   bool dostretch=false;
211   Tools::parseFlag(data,"STRETCH",dostretch); // this is ignored now
212   dostretch=true;
213   bool dontstretch=false;
214   Tools::parseFlag(data,"NOSTRETCH",dontstretch); // this is ignored now
215   if(dontstretch) dostretch=false;
216   double r0;
217   if(name=="CUBIC") {
218     r0 = dmax - d0;
219   } else {
220     bool found_r0=Tools::parse(data,"R_0",r0);
221     if(!found_r0) errormsg="R_0 is required";
222   }
223   invr0=1.0/r0;
224   invr0_2=invr0*invr0;
225 
226   if(name=="RATIONAL") {
227     type=rational;
228     nn=6;
229     mm=0;
230     present=Tools::findKeyword(data,"NN");
231     if(present && !Tools::parse(data,"NN",nn)) errormsg="could not parse NN";
232     present=Tools::findKeyword(data,"MM");
233     if(present && !Tools::parse(data,"MM",mm)) errormsg="could not parse MM";
234     if(mm==0) mm=2*nn;
235     fastrational=(nn%2==0 && mm%2==0 && d0==0.0);
236   } else if(name=="SMAP") {
237     type=smap;
238     present=Tools::findKeyword(data,"A");
239     if(present && !Tools::parse(data,"A",a)) errormsg="could not parse A";
240     present=Tools::findKeyword(data,"B");
241     if(present && !Tools::parse(data,"B",b)) errormsg="could not parse B";
242     c=pow(2., static_cast<double>(a)/static_cast<double>(b) ) - 1;
243     d = -static_cast<double>(b) / static_cast<double>(a);
244   }
245   else if(name=="Q") {
246     type=nativeq;
247     beta = 50.0;  // nm-1
248     lambda = 1.8; // unitless
249     present=Tools::findKeyword(data,"BETA");
250     if(present && !Tools::parse(data, "BETA", beta)) errormsg="could not parse BETA";
251     present=Tools::findKeyword(data,"LAMBDA");
252     if(present && !Tools::parse(data, "LAMBDA", lambda)) errormsg="could not parse LAMBDA";
253     bool found_ref=Tools::parse(data,"REF",ref); // nm
254     if(!found_ref) errormsg="REF (reference disatance) is required for native Q";
255 
256   }
257   else if(name=="EXP") type=exponential;
258   else if(name=="GAUSSIAN") type=gaussian;
259   else if(name=="CUBIC") type=cubic;
260   else if(name=="TANH") type=tanh;
261   else if(name=="COSINUS") type=cosinus;
262   else if((name=="MATHEVAL" || name=="CUSTOM")) {
263     type=leptontype;
264     std::string func;
265     Tools::parse(data,"FUNC",func);
266     lepton::ParsedExpression pe=lepton::Parser::parse(func).optimize(lepton::Constants());
267     lepton_func=func;
268     expression.resize(OpenMP::getNumThreads());
269     for(auto & e : expression) e=pe.createCompiledExpression();
270     lepton_ref.resize(expression.size());
271     for(unsigned t=0; t<lepton_ref.size(); t++) {
272       try {
273         lepton_ref[t]=&const_cast<lepton::CompiledExpression*>(&expression[t])->getVariableReference("x");
274       } catch(const PLMD::lepton::Exception& exc) {
275         try {
276           lepton_ref[t]=&const_cast<lepton::CompiledExpression*>(&expression[t])->getVariableReference("x2");
277           leptonx2=true;
278         } catch(const PLMD::lepton::Exception& exc) {
279 // this is necessary since in some cases lepton things a variable is not present even though it is present
280 // e.g. func=0*x
281           lepton_ref[t]=nullptr;
282         }
283       }
284     }
285     std::string arg="x";
286     if(leptonx2) arg="x2";
287     lepton::ParsedExpression ped=lepton::Parser::parse(func).differentiate(arg).optimize(lepton::Constants());
288     expression_deriv.resize(OpenMP::getNumThreads());
289     for(auto & e : expression_deriv) e=ped.createCompiledExpression();
290     lepton_ref_deriv.resize(expression_deriv.size());
291     for(unsigned t=0; t<lepton_ref_deriv.size(); t++) {
292       try {
293         lepton_ref_deriv[t]=&const_cast<lepton::CompiledExpression*>(&expression_deriv[t])->getVariableReference(arg);
294       } catch(const PLMD::lepton::Exception& exc) {
295 // this is necessary since in some cases lepton things a variable is not present even though it is present
296 // e.g. func=3*x
297         lepton_ref_deriv[t]=nullptr;
298       }
299     }
300 
301   }
302   else errormsg="cannot understand switching function type '"+name+"'";
303   if( !data.empty() ) {
304     errormsg="found the following rogue keywords in switching function input : ";
305     for(unsigned i=0; i<data.size(); ++i) errormsg = errormsg + data[i] + " ";
306   }
307 
308   if(dostretch && dmax!=std::numeric_limits<double>::max()) {
309     double dummy;
310     double s0=calculate(0.0,dummy);
311     double sd=calculate(dmax,dummy);
312     stretch=1.0/(s0-sd);
313     shift=-sd*stretch;
314   }
315   plumed_assert(!(leptonx2 && d0!=0.0)) << "You cannot use lepton x2 optimization with d0!=0.0 (d0=" << d0 <<")\n"
316                                         << "Please rewrite your function using x as a variable";
317 }
318 
description() const319 std::string SwitchingFunction::description() const {
320   std::ostringstream ostr;
321   ostr<<1./invr0<<".  Using ";
322   if(type==rational) {
323     ostr<<"rational";
324   } else if(type==exponential) {
325     ostr<<"exponential";
326   } else if(type==nativeq) {
327     ostr<<"nativeq";
328   } else if(type==gaussian) {
329     ostr<<"gaussian";
330   } else if(type==smap) {
331     ostr<<"smap";
332   } else if(type==cubic) {
333     ostr<<"cubic";
334   } else if(type==tanh) {
335     ostr<<"tanh";
336   } else if(type==cosinus) {
337     ostr<<"cosinus";
338   } else if(type==leptontype) {
339     ostr<<"lepton";
340   } else {
341     plumed_merror("Unknown switching function type");
342   }
343   ostr<<" switching function with parameters d0="<<d0;
344   if(type==rational) {
345     ostr<<" nn="<<nn<<" mm="<<mm;
346   } else if(type==nativeq) {
347     ostr<<" beta="<<beta<<" lambda="<<lambda<<" ref="<<ref;
348   } else if(type==smap) {
349     ostr<<" a="<<a<<" b="<<b;
350   } else if(type==cubic) {
351     ostr<<" dmax="<<dmax;
352   } else if(type==leptontype) {
353     ostr<<" func="<<lepton_func;
354 
355   }
356   return ostr.str();
357 }
358 
do_rational(double rdist,double & dfunc,int nn,int mm) const359 double SwitchingFunction::do_rational(double rdist,double&dfunc,int nn,int mm)const {
360   double result;
361   if(2*nn==mm) {
362 // if 2*N==M, then (1.0-rdist^N)/(1.0-rdist^M) = 1.0/(1.0+rdist^N)
363     double rNdist=Tools::fastpow(rdist,nn-1);
364     double iden=1.0/(1+rNdist*rdist);
365     dfunc = -nn*rNdist*iden*iden;
366     result = iden;
367   } else {
368     if(rdist>(1.-100.0*epsilon) && rdist<(1+100.0*epsilon)) {
369       result=nn/mm;
370       dfunc=0.5*nn*(nn-mm)/mm;
371     } else {
372       double rNdist=Tools::fastpow(rdist,nn-1);
373       double rMdist=Tools::fastpow(rdist,mm-1);
374       double num = 1.-rNdist*rdist;
375       double iden = 1./(1.-rMdist*rdist);
376       double func = num*iden;
377       result = func;
378       dfunc = ((-nn*rNdist*iden)+(func*(iden*mm)*rMdist));
379     }
380   }
381   return result;
382 }
383 
calculateSqr(double distance2,double & dfunc) const384 double SwitchingFunction::calculateSqr(double distance2,double&dfunc)const {
385   if(fastrational) {
386     if(distance2>dmax_2) {
387       dfunc=0.0;
388       return 0.0;
389     }
390     const double rdist_2 = distance2*invr0_2;
391     double result=do_rational(rdist_2,dfunc,nn/2,mm/2);
392 // chain rule:
393     dfunc*=2*invr0_2;
394 // stretch:
395     result=result*stretch+shift;
396     dfunc*=stretch;
397     return result;
398   } else if(leptonx2) {
399     if(distance2>dmax_2) {
400       dfunc=0.0;
401       return 0.0;
402     }
403     const unsigned t=OpenMP::getThreadNum();
404     const double rdist_2 = distance2*invr0_2;
405     plumed_assert(t<expression.size());
406     if(lepton_ref[t]) *lepton_ref[t]=rdist_2;
407     if(lepton_ref_deriv[t]) *lepton_ref_deriv[t]=rdist_2;
408     double result=expression[t].evaluate();
409     dfunc=expression_deriv[t].evaluate();
410 // chain rule:
411     dfunc*=2*invr0_2;
412 // stretch:
413     result=result*stretch+shift;
414     dfunc*=stretch;
415     return result;
416   } else {
417     double distance=std::sqrt(distance2);
418     return calculate(distance,dfunc);
419   }
420 }
421 
calculate(double distance,double & dfunc) const422 double SwitchingFunction::calculate(double distance,double&dfunc)const {
423   plumed_massert(init,"you are trying to use an unset SwitchingFunction");
424   if(distance>dmax) {
425     dfunc=0.0;
426     return 0.0;
427   }
428 // in this case, the lepton object stores only the calculateSqr function
429 // so we have to implement calculate in terms of calculateSqr
430   if(leptonx2) {
431     return calculateSqr(distance*distance,dfunc);
432   }
433   const double rdist = (distance-d0)*invr0;
434   double result;
435 
436   if(rdist<=0.) {
437     result=1.;
438     dfunc=0.0;
439   } else {
440     if(type==smap) {
441       double sx=c*Tools::fastpow( rdist, a );
442       result=pow( 1.0 + sx, d );
443       dfunc=-b*sx/rdist*result/(1.0+sx);
444     } else if(type==rational) {
445       result=do_rational(rdist,dfunc,nn,mm);
446     } else if(type==exponential) {
447       result=exp(-rdist);
448       dfunc=-result;
449     } else if(type==nativeq) {
450       double rdist2 = beta*(distance - lambda * ref);
451       double exprdist=exp(rdist2);
452       double exprmdist=1.0/exprdist;
453       result=1./(1.+exprdist);
454       dfunc=-1.0/(exprmdist+1.0)/(1.+exprdist);
455     } else if(type==gaussian) {
456       result=exp(-0.5*rdist*rdist);
457       dfunc=-rdist*result;
458     } else if(type==cubic) {
459       double tmp1=rdist-1, tmp2=(1+2*rdist);
460       result=tmp1*tmp1*tmp2;
461       dfunc=2*tmp1*tmp2 + 2*tmp1*tmp1;
462     } else if(type==tanh) {
463       double tmp1=std::tanh(rdist);
464       result = 1.0 - tmp1;
465       dfunc=-(1-tmp1*tmp1);
466     } else if(type==cosinus) {
467       if(rdist<=0.0) {
468 // rdist = (r-r1)/(r2-r1) ; rdist<=0.0 if r <=r1
469         result=1.;
470         dfunc=0.0;
471       } else if(rdist<=1.0) {
472 // rdist = (r-r1)/(r2-r1) ; 0.0<=rdist<=1.0 if r1 <= r <=r2; (r2-r1)/(r2-r1)=1
473         double tmpcos = cos ( rdist * PI );
474         double tmpsin = sin ( rdist * PI );
475         result = 0.5 * (tmpcos + 1.0);
476         dfunc=-0.5 * PI * tmpsin * invr0;
477       } else {
478         result=0.;
479         dfunc=0.0;
480       }
481     } else if(type==leptontype) {
482       const unsigned t=OpenMP::getThreadNum();
483       plumed_assert(t<expression.size());
484       if(lepton_ref[t]) *lepton_ref[t]=rdist;
485       if(lepton_ref_deriv[t]) *lepton_ref_deriv[t]=rdist;
486       result=expression[t].evaluate();
487       dfunc=expression_deriv[t].evaluate();
488     } else plumed_merror("Unknown switching function type");
489 // this is for the chain rule:
490     dfunc*=invr0;
491 // this is because calculate() sets dfunc to the derivative divided times the distance.
492 // (I think this is misleading and I would like to modify it - GB)
493     dfunc/=distance;
494   }
495 
496   result=result*stretch+shift;
497   dfunc*=stretch;
498 
499   return result;
500 }
501 
set(int nn,int mm,double r0,double d0)502 void SwitchingFunction::set(int nn,int mm,double r0,double d0) {
503   init=true;
504   type=rational;
505   if(mm==0) mm=2*nn;
506   this->nn=nn;
507   this->mm=mm;
508   this->invr0=1.0/r0;
509   this->invr0_2=this->invr0*this->invr0;
510   this->d0=d0;
511   this->dmax=d0+r0*pow(0.00001,1./(nn-mm));
512   this->dmax_2=this->dmax*this->dmax;
513   this->leptonx2=false;
514   this->fastrational=(nn%2==0 && mm%2==0 && d0==0.0);
515 
516   double dummy;
517   double s0=calculate(0.0,dummy);
518   double sd=calculate(dmax,dummy);
519   stretch=1.0/(s0-sd);
520   shift=-sd*stretch;
521 }
522 
get_r0() const523 double SwitchingFunction::get_r0() const {
524   return 1./invr0;
525 }
526 
get_d0() const527 double SwitchingFunction::get_d0() const {
528   return d0;
529 }
530 
get_dmax() const531 double SwitchingFunction::get_dmax() const {
532   return dmax;
533 }
534 
get_dmax2() const535 double SwitchingFunction::get_dmax2() const {
536   return dmax_2;
537 }
538 
539 }
540 
541 
542 
543