LCOV - code coverage report
Current view: top level - opes - ExpansionCVs.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 102 111 91.9 %
Date: 2024-10-11 08:09:47 Functions: 9 10 90.0 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2020-2021 of Michele Invernizzi.
       3             : 
       4             :    This file is part of the OPES plumed module.
       5             : 
       6             :    The OPES plumed module is free software: you can redistribute it and/or modify
       7             :    it under the terms of the GNU Lesser General Public License as published by
       8             :    the Free Software Foundation, either version 3 of the License, or
       9             :    (at your option) any later version.
      10             : 
      11             :    The OPES plumed module is distributed in the hope that it will be useful,
      12             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
      13             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14             :    GNU Lesser General Public License for more details.
      15             : 
      16             :    You should have received a copy of the GNU Lesser General Public License
      17             :    along with plumed.  If not, see <http://www.gnu.org/licenses/>.
      18             : +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
      19             : #include "ExpansionCVs.h"
      20             : 
      21             : #include "tools/OpenMP.h"
      22             : #include "core/PlumedMain.h"
      23             : #include "core/Atoms.h"
      24             : 
      25             : namespace PLMD {
      26             : namespace opes {
      27             : 
      28          43 : void ExpansionCVs::registerKeywords(Keywords& keys)
      29             : {
      30          43 :   Action::registerKeywords(keys);
      31          43 :   ActionWithValue::registerKeywords(keys);
      32          43 :   ActionWithArguments::registerKeywords(keys);
      33          43 :   ActionWithValue::useCustomisableComponents(keys);
      34          86 :   keys.add("compulsory","TEMP","-1","temperature. If not specified tries to get it from MD engine");
      35          43 : }
      36             : 
      37          37 : ExpansionCVs::ExpansionCVs(const ActionOptions&ao)
      38             :   : Action(ao)
      39             :   , ActionWithValue(ao)
      40             :   , ActionWithArguments(ao)
      41          37 :   , isReady_(false)
      42          37 :   , totNumECVs_(0)
      43             : {
      44             : //set kbt_
      45          37 :   const double kB=plumed.getAtoms().getKBoltzmann();
      46          37 :   kbt_=plumed.getAtoms().getKbT();
      47          37 :   double temp=-1;
      48          37 :   parse("TEMP",temp);
      49          37 :   if(temp>0)
      50             :   {
      51          37 :     if(kbt_>0 && std::abs(kbt_-kB*temp)>1e-4)
      52           0 :       log.printf(" +++ WARNING +++ using TEMP=%g while MD engine uses %g\n",temp,kbt_/kB);
      53          37 :     kbt_=kB*temp;
      54             :   }
      55          37 :   plumed_massert(kbt_>0,"your MD engine does not pass the temperature to plumed, you must specify it using TEMP");
      56          37 :   log.printf("  temperature = %g, beta = %g\n",kbt_/kB,1./kbt_);
      57             : 
      58             : //set components
      59          37 :   plumed_massert( getNumberOfArguments()!=0, "you must specify the underlying CV");
      60          90 :   for(unsigned j=0; j<getNumberOfArguments(); j++)
      61             :   {
      62          53 :     std::string name_j=getPntrToArgument(j)->getName();
      63          53 :     ActionWithValue::addComponentWithDerivatives(name_j);
      64          53 :     getPntrToComponent(j)->resizeDerivatives(1);
      65          53 :     if(getPntrToArgument(j)->isPeriodic()) //it should not be necessary, but why not
      66             :     {
      67             :       std::string min,max;
      68          17 :       getPntrToArgument(j)->getDomain(min,max);
      69          17 :       getPntrToComponent(j)->setDomain(min,max);
      70             :     }
      71             :     else
      72          36 :       getPntrToComponent(j)->setNotPeriodic();
      73             :   }
      74          37 :   plumed_massert((int)getNumberOfArguments()==getNumberOfComponents(),"Expansion CVs have same number of arguments and components");
      75          37 : }
      76             : 
      77        1847 : void ExpansionCVs::calculate()
      78             : {
      79        1847 :   std::vector<double> args(getNumberOfArguments());
      80        4470 :   for(unsigned j=0; j<getNumberOfArguments(); j++)
      81             :   {
      82        2623 :     args[j]=getArgument(j);
      83        2623 :     getPntrToComponent(j)->set(args[j]); //components are equal to arguments
      84        2623 :     getPntrToComponent(j)->addDerivative(0,1.); //the derivative of the identity is 1
      85             :   }
      86        1847 :   if(isReady_)
      87        1417 :     calculateECVs(&args[0]);
      88        1847 : }
      89             : 
      90        1847 : void ExpansionCVs::apply()
      91             : {
      92        4470 :   for(unsigned j=0; j<getNumberOfArguments(); j++)
      93             :   {
      94        2623 :     std::vector<double> force_j(1);
      95        2623 :     if(getPntrToComponent(j)->applyForce(force_j)) //a bias is applied?
      96        2623 :       getPntrToArgument(j)->addForce(force_j[0]); //just tell it to the CV!
      97             :   }
      98        1847 : }
      99             : 
     100          26 : std::vector< std::vector<unsigned> > ExpansionCVs::getIndex_k() const
     101             : {
     102          26 :   plumed_massert(isReady_ && totNumECVs_>0,"cannot access getIndex_k() of ECV before initialization");
     103          26 :   std::vector< std::vector<unsigned> > index_k(totNumECVs_,std::vector<unsigned>(getNumberOfArguments()));
     104         869 :   for(unsigned k=0; k<totNumECVs_; k++)
     105        2391 :     for(unsigned j=0; j<getNumberOfArguments(); j++)
     106        1548 :       index_k[k][j]=k; //each CV gives rise to the same number of ECVs
     107          26 :   return index_k;
     108           0 : }
     109             : 
     110             : //following methods are meant to be used only in case of linear expansions
     111          24 : std::vector<double> ExpansionCVs::getSteps(double lambda_min,double lambda_max,const unsigned lambda_steps,const std::string& msg,const bool geom_spacing, const double shift) const
     112             : {
     113          24 :   plumed_massert(!(lambda_min==lambda_max && lambda_steps>1),"cannot have multiple "+msg+"_STEPS if "+msg+"_MIN=="+msg+"_MAX");
     114          24 :   std::vector<double> lambda(lambda_steps);
     115          24 :   if(lambda_steps==1)
     116             :   {
     117           0 :     lambda[0]=(lambda_min+lambda_max)/2.;
     118           0 :     log.printf(" +++ WARNING +++ using one single %s as target = %g\n",msg.c_str(),lambda[0]);
     119             :   }
     120             :   else
     121             :   {
     122          24 :     if(geom_spacing) //geometric spacing
     123             :     { //this way lambda[k]/lambda[k+1] is constant
     124          14 :       lambda_min+=shift;
     125          14 :       lambda_max+=shift;
     126          14 :       plumed_massert(lambda_min>0,"cannot use GEOM_SPACING when %s_MIN is not greater than zero");
     127          14 :       plumed_massert(lambda_max>0,"cannot use GEOM_SPACING when %s_MAX is not greater than zero");
     128          14 :       const double log_lambda_min=std::log(lambda_min);
     129          14 :       const double log_lambda_max=std::log(lambda_max);
     130         196 :       for(unsigned k=0; k<lambda.size(); k++)
     131         182 :         lambda[k]=std::exp(log_lambda_min+k*(log_lambda_max-log_lambda_min)/(lambda_steps-1))-shift;
     132             :     }
     133             :     else //linear spacing
     134         108 :       for(unsigned k=0; k<lambda.size(); k++)
     135          98 :         lambda[k]=lambda_min+k*(lambda_max-lambda_min)/(lambda_steps-1);
     136             :   }
     137          24 :   return lambda;
     138             : }
     139             : 
     140           6 : unsigned ExpansionCVs::estimateNumSteps(const double left_side,const double right_side,const std::vector<double>& obs,const std::string& msg) const
     141             : { //for linear expansions only, it uses effective sample size (Neff) to estimate the grid spacing
     142           6 :   if(left_side==0 && right_side==0)
     143             :   {
     144           0 :     log.printf(" +++ WARNING +++ %s_MIN and %s_MAX are equal to %s, using single step\n",msg.c_str(),msg.c_str(),msg.c_str());
     145           0 :     return 1;
     146             :   }
     147           9 :   auto get_neff_HWHM=[](const double side,const std::vector<double>& obs) //HWHM = half width at half maximum. neff is in general not symmetric
     148             :   {
     149             :     //func: Neff/N-0.5 is a function between -0.5 and 0.5
     150         109 :     auto func=[](const double delta,const std::vector<double>& obs)
     151             :     {
     152             :       double sum_w=0;
     153             :       double sum_w2=0;
     154             :       //we could avoid recomputing safe_shift every time, but here speed is not a concern
     155         109 :       const double safe_shift=delta<0?*std::max_element(obs.begin(),obs.end()):*std::min_element(obs.begin(),obs.end());
     156         899 :       for(unsigned t=0; t<obs.size(); t++)
     157             :       {
     158         790 :         const double w=std::exp(-delta*(obs[t]-safe_shift)); //robust to overflow
     159         790 :         sum_w+=w;
     160         790 :         sum_w2+=w*w;
     161             :       }
     162         109 :       return sum_w*sum_w/sum_w2/obs.size()-0.5;
     163             :     };
     164             :     //here we find the root of func using the regula falsi (false position) method
     165             :     //but any method would be OK, not much precision is needed. src/tools/RootFindingBase.h looked complicated
     166             :     const double tolerance=1e-4; //seems to be a good default
     167             :     double a=0; //default is right side case
     168             :     double func_a=0.5;
     169             :     double b=side;
     170           9 :     double func_b=func(side,obs);
     171           9 :     if(func_b>=0)
     172             :       return 0.0; //no zero is present!
     173           9 :     if(b<0) //left side case
     174             :     {
     175             :       std::swap(a,b);
     176             :       std::swap(func_a,func_b);
     177             :     }
     178             :     double c=a;
     179             :     double func_c=func_a;
     180         109 :     while(std::abs(func_c)>tolerance)
     181             :     {
     182         100 :       if(func_a*func_c>0)
     183             :       {
     184             :         a=c;
     185             :         func_a=func_c;
     186             :       }
     187             :       else
     188             :       {
     189             :         b=c;
     190             :         func_b=func_c;
     191             :       }
     192         100 :       c=(a*func_b-b*func_a)/(func_b-func_a);
     193         100 :       func_c=func(c,obs); //func is evaluated only here, it might be expensive
     194             :     }
     195           9 :     return std::abs(c);
     196             :   };
     197             : 
     198             : //estimation
     199             :   double left_HWHM=0;
     200           6 :   if(left_side!=0)
     201           4 :     left_HWHM=get_neff_HWHM(left_side,obs);
     202             :   double right_HWHM=0;
     203           6 :   if(right_side!=0)
     204           5 :     right_HWHM=get_neff_HWHM(right_side,obs);
     205           6 :   if(left_HWHM==0)
     206             :   {
     207           2 :     right_HWHM*=2;
     208           2 :     if(left_side==0)
     209           2 :       log.printf(" --- %s_MIN is equal to %s\n",msg.c_str(),msg.c_str());
     210             :     else
     211           0 :       log.printf(" +++ WARNING +++ %s_MIN is very close to %s\n",msg.c_str(),msg.c_str());
     212             :   }
     213           6 :   if(right_HWHM==0)
     214             :   {
     215           1 :     left_HWHM*=2;
     216           1 :     if(right_side==0)
     217           1 :       log.printf(" --- %s_MAX is equal to %s\n",msg.c_str(),msg.c_str());
     218             :     else
     219           0 :       log.printf(" +++ WARNING +++ %s_MAX is very close to %s\n",msg.c_str(),msg.c_str());
     220             :   }
     221           6 :   const double grid_spacing=left_HWHM+right_HWHM;
     222           6 :   log.printf("   estimated %s spacing = %g\n",msg.c_str(),grid_spacing);
     223           6 :   unsigned steps=std::ceil(std::abs(right_side-left_side)/grid_spacing);
     224           6 :   if(steps<2 || grid_spacing==0)
     225             :   {
     226           0 :     log.printf(" +++ WARNING +++ %s range is very narrow, using %s_MIN and %s_MAX as only steps\n",msg.c_str(),msg.c_str(),msg.c_str());
     227             :     steps=2;
     228             :   }
     229             :   return steps;
     230             : }
     231             : 
     232             : }
     233             : }

Generated by: LCOV version 1.15