LCOV - code coverage report
Current view: top level - opes - ECVlinear.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 113 118 95.8 %
Date: 2025-03-25 09:33:27 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             : #include "core/ActionRegister.h"
      21             : 
      22             : namespace PLMD {
      23             : namespace opes {
      24             : 
      25             : //+PLUMEDOC OPES_EXPANSION_CV ECV_LINEAR
      26             : /*
      27             : Linear expansion, according to a parameter lambda.
      28             : 
      29             : This can be used e.g. for thermodynamic integration, or for multibaric simulations, in which case lambda=pressure.
      30             : It can also be used for multithermal simulations, but for simplicity it is more convenient to use \ref ECV_MULTITHERMAL.
      31             : 
      32             : The difference in Hamiltonian \f$\Delta U\f$ is expected as ARG.
      33             : \f[
      34             :   \Delta u_\lambda=\beta \lambda \Delta U\, .
      35             : \f]
      36             : Use the DIMENSIONLESS flag to avoid multiplying for the inverse temperature \f$\beta\f$.
      37             : 
      38             : By defauly the needed steps in lambda are automatically guessed from few initial unbiased MD steps, as descibed in \cite Invernizzi2020unified.
      39             : Otherwise one can set this number with LAMBDA_STEPS.
      40             : In both cases the steps will be uniformly distriuted.
      41             : Finally, one can use instead the keyword LAMBDA_SET_ALL and explicitly provide each lambda value.
      42             : 
      43             : \par Examples
      44             : 
      45             : Typical multibaric simulation:
      46             : 
      47             : \plumedfile
      48             : vol: VOLUME
      49             : ecv: ECV_LINEAR ...
      50             :   ARG=vol
      51             :   TEMP=300
      52             :   LAMBDA=0.06022140857*2000 #2 kbar
      53             :   LAMBDA_MIN=0.06022140857  #1 bar
      54             :   LAMBDA_MAX=0.06022140857*4000 #4 kbar
      55             : ...
      56             : opes: OPES_EXPANDED ARG=ecv.vol PACE=500
      57             : \endplumedfile
      58             : 
      59             : Typical thermodynamic integration:
      60             : 
      61             : \plumedfile
      62             : DeltaU: EXTRACV NAME=energy_difference
      63             : ecv: ECV_LINEAR ARG=DeltaU TEMP=300
      64             : opes: OPES_EXPANDED ARG=ecv.* PACE=100
      65             : \endplumedfile
      66             : 
      67             : Notice that by defauly LAMBDA=0, LAMBDA_MIN=0 and LAMBDA_MAX=1, which is the typical case for thermodynamic integration.
      68             : 
      69             : */
      70             : //+ENDPLUMEDOC
      71             : 
      72             : class ECVlinear :
      73             :   public ExpansionCVs {
      74             : private:
      75             :   bool todoAutomatic_;
      76             :   bool geom_spacing_;
      77             :   double beta0_;
      78             :   double lambda0_;
      79             :   std::vector<double> ECVs_;
      80             :   std::vector<double> derECVs_; //beta0*(lambda_k-lambda0)
      81             :   void initECVs();
      82             : 
      83             : public:
      84             :   explicit ECVlinear(const ActionOptions&);
      85             :   static void registerKeywords(Keywords& keys);
      86             :   void calculateECVs(const double *) override;
      87             :   const double * getPntrToECVs(unsigned) override;
      88             :   const double * getPntrToDerECVs(unsigned) override;
      89             :   std::vector<std::string> getLambdas() const override;
      90             :   void initECVs_observ(const std::vector<double>&,const unsigned,const unsigned) override;
      91             :   void initECVs_restart(const std::vector<std::string>&) override;
      92             : };
      93             : 
      94             : PLUMED_REGISTER_ACTION(ECVlinear,"ECV_LINEAR")
      95             : 
      96           6 : void ECVlinear::registerKeywords(Keywords& keys) {
      97           6 :   ExpansionCVs::registerKeywords(keys);
      98          12 :   keys.addInputKeyword("compulsory","ARG","scalar","the label of the Hamiltonian difference. Delta U");
      99           6 :   keys.add("compulsory","LAMBDA","0","the lambda at which the underlying simulation runs");
     100           6 :   keys.add("optional","LAMBDA_MIN","( default=0 ) the minimum of the lambda range");
     101           6 :   keys.add("optional","LAMBDA_MAX","( default=1 ) the maximum of the lambda range");
     102           6 :   keys.add("optional","LAMBDA_STEPS","uniformly place the lambda values, for a total of LAMBDA_STEPS");
     103           6 :   keys.add("optional","LAMBDA_SET_ALL","manually set all the lamdbas");
     104           6 :   keys.addFlag("DIMENSIONLESS",false,"ARG is considered dimensionless rather than an energy, thus is not multiplied by beta");
     105           6 :   keys.addFlag("GEOM_SPACING",false,"use geometrical spacing in lambda instead of linear spacing");
     106           6 : }
     107             : 
     108           4 : ECVlinear::ECVlinear(const ActionOptions&ao)
     109             :   : Action(ao)
     110             :   , ExpansionCVs(ao)
     111           4 :   , todoAutomatic_(false)
     112           4 :   , beta0_(1./kbt_) {
     113           4 :   plumed_massert(getNumberOfArguments()==1,"only DeltaU should be given as ARG");
     114             : 
     115             : //set beta0_
     116             :   bool dimensionless;
     117           4 :   parseFlag("DIMENSIONLESS",dimensionless);
     118           4 :   if(dimensionless) {
     119           1 :     beta0_=1;
     120             :   }
     121             : 
     122             : //parse lambda info
     123           4 :   parse("LAMBDA",lambda0_);
     124             :   const double myNone=std::numeric_limits<double>::lowest(); //quiet_NaN is not supported by some intel compiler
     125           4 :   double lambda_min=myNone;
     126           4 :   double lambda_max=myNone;
     127           4 :   parse("LAMBDA_MIN",lambda_min);
     128           4 :   parse("LAMBDA_MAX",lambda_max);
     129           4 :   unsigned lambda_steps=0;
     130           8 :   parse("LAMBDA_STEPS",lambda_steps);
     131             :   std::vector<double> lambdas;
     132           4 :   parseVector("LAMBDA_SET_ALL",lambdas);
     133           4 :   parseFlag("GEOM_SPACING",geom_spacing_);
     134             : 
     135           4 :   checkRead();
     136             : 
     137             : //set the diff vector using lambdas
     138           4 :   if(lambdas.size()>0) {
     139           1 :     plumed_massert(lambda_steps==0,"cannot set both LAMBDA_STEPS and LAMBDA_SET_ALL");
     140           1 :     plumed_massert(lambda_min==myNone && lambda_max==myNone,"cannot set both LAMBDA_SET_ALL and LAMBDA_MIN/MAX");
     141           1 :     plumed_massert(lambdas.size()>=2,"set at least 2 lambdas with LAMBDA_SET_ALL");
     142           4 :     for(unsigned k=0; k<lambdas.size()-1; k++) {
     143           3 :       plumed_massert(lambdas[k]<=lambdas[k+1],"LAMBDA_SET_ALL must be properly ordered");
     144             :     }
     145           1 :     lambda_min=lambdas[0];
     146           1 :     lambda_max=lambdas[lambdas.size()-1];
     147           1 :     derECVs_.resize(lambdas.size());
     148           5 :     for(unsigned k=0; k<derECVs_.size(); k++) {
     149           4 :       derECVs_[k]=beta0_*(lambdas[k]-lambda0_);
     150             :     }
     151             :   } else {
     152             :     //get LAMBDA_MIN and LAMBDA_MAX
     153           3 :     if(lambda_min==myNone) {
     154           0 :       lambda_min=0;
     155           0 :       log.printf("  no LAMBDA_MIN provided, using LAMBDA_MIN = %g\n",lambda_min);
     156             :     }
     157           3 :     if(lambda_max==myNone) {
     158           1 :       lambda_max=1;
     159           1 :       log.printf("  no LAMBDA_MAX provided, using LAMBDA_MAX = %g\n",lambda_max);
     160             :     }
     161           3 :     plumed_massert(lambda_max>=lambda_min,"LAMBDA_MAX should be bigger than LAMBDA_MIN");
     162           3 :     derECVs_.resize(2);
     163           3 :     derECVs_[0]=beta0_*(lambda_min-lambda0_);
     164           3 :     derECVs_[1]=beta0_*(lambda_max-lambda0_);
     165           3 :     if(lambda_min==lambda_max && lambda_steps==0) {
     166           0 :       lambda_steps=1;
     167             :     }
     168           3 :     if(lambda_steps>0) {
     169           2 :       derECVs_=getSteps(derECVs_[0],derECVs_[1],lambda_steps,"LAMBDA",geom_spacing_,beta0_*lambda0_);
     170             :     } else {
     171           2 :       todoAutomatic_=true;
     172             :     }
     173             :   }
     174           4 :   if(lambda0_<lambda_min || lambda0_>lambda_max) {
     175           1 :     log.printf(" +++ WARNING +++ running at LAMBDA=%g which is outside the chosen lambda range\n",lambda0_);
     176             :   }
     177             : 
     178             : //print some info
     179           4 :   log.printf("  running at LAMBDA=%g\n",lambda0_);
     180           4 :   log.printf("  targeting a lambda range from LAMBDA_MIN=%g to LAMBDA_MAX=%g\n",lambda_min,lambda_max);
     181           4 :   if(dimensionless) {
     182           1 :     log.printf(" -- DIMENSIONLESS: the ARG is not multiplied by beta\n");
     183             :   }
     184           4 :   if(geom_spacing_) {
     185           1 :     log.printf(" -- GEOM_SPACING: lambdas will be geometrically spaced\n");
     186             :   }
     187           4 : }
     188             : 
     189         182 : void ECVlinear::calculateECVs(const double * DeltaU) {
     190        1587 :   for(unsigned k=0; k<derECVs_.size(); k++) {
     191        1405 :     ECVs_[k]=derECVs_[k]*DeltaU[0];
     192             :   }
     193             : // derivatives never change: derECVs_k=beta0*(lambda_k-lambda0)
     194         182 : }
     195             : 
     196           4 : const double * ECVlinear::getPntrToECVs(unsigned j) {
     197           4 :   plumed_massert(isReady_,"cannot access ECVs before initialization");
     198           4 :   plumed_massert(j==0,getName()+" has only one CV, the DeltaU");
     199           4 :   return &ECVs_[0];
     200             : }
     201             : 
     202           4 : const double * ECVlinear::getPntrToDerECVs(unsigned j) {
     203           4 :   plumed_massert(isReady_,"cannot access ECVs before initialization");
     204           4 :   plumed_massert(j==0,getName()+" has only one CV, the DeltaU");
     205           4 :   return &derECVs_[0];
     206             : }
     207             : 
     208           4 : std::vector<std::string> ECVlinear::getLambdas() const {
     209           4 :   plumed_massert(!todoAutomatic_,"cannot access lambdas before initializing them");
     210           4 :   std::vector<std::string> lambdas(derECVs_.size());
     211          35 :   for(unsigned k=0; k<derECVs_.size(); k++) {
     212          31 :     std::ostringstream subs;
     213          31 :     subs<<derECVs_[k]/beta0_+lambda0_; //lambda_k
     214          31 :     lambdas[k]=subs.str();
     215          31 :   }
     216           4 :   return lambdas;
     217           0 : }
     218             : 
     219           4 : void ECVlinear::initECVs() {
     220           4 :   plumed_massert(!isReady_,"initialization should not be called twice");
     221           4 :   plumed_massert(!todoAutomatic_,"this should not happen");
     222           4 :   totNumECVs_=derECVs_.size();
     223           4 :   ECVs_.resize(derECVs_.size());
     224           4 :   isReady_=true;
     225           4 :   log.printf("  *%4lu lambdas for %s\n",derECVs_.size(),getName().c_str());
     226           4 : }
     227             : 
     228           3 : void ECVlinear::initECVs_observ(const std::vector<double>& all_obs_cvs,const unsigned ncv,const unsigned index_j) {
     229           3 :   if(todoAutomatic_) { //estimate the steps in lambda from observations
     230           1 :     plumed_massert(all_obs_cvs.size()%ncv==0 && index_j<ncv,"initECVs_observ parameters are inconsistent");
     231           1 :     std::vector<double> obs_cv(all_obs_cvs.size()/ncv); //copy only useful observation (would be better not to copy...)
     232          11 :     for(unsigned t=0; t<obs_cv.size(); t++) {
     233          10 :       obs_cv[t]=all_obs_cvs[t*ncv+index_j];
     234             :     }
     235           1 :     const unsigned lambda_steps=estimateNumSteps(derECVs_[0],derECVs_[1],obs_cv,"LAMBDA");
     236           1 :     if(beta0_!=1) {
     237           0 :       log.printf("    (spacing is in beta0 units)\n");
     238             :     }
     239           2 :     derECVs_=getSteps(derECVs_[0],derECVs_[1],lambda_steps,"LAMBDA",geom_spacing_,beta0_*lambda0_);
     240           1 :     todoAutomatic_=false;
     241             :   }
     242           3 :   initECVs();
     243           3 :   calculateECVs(&all_obs_cvs[index_j]);
     244           3 : }
     245             : 
     246           1 : void ECVlinear::initECVs_restart(const std::vector<std::string>& lambdas) {
     247           1 :   std::size_t pos=lambdas[0].find("_");
     248           1 :   plumed_massert(pos==std::string::npos,"this should not happen, only one CV is used in "+getName());
     249           1 :   if(todoAutomatic_) {
     250           2 :     derECVs_=getSteps(derECVs_[0],derECVs_[1],lambdas.size(),"LAMBDA",geom_spacing_,beta0_*lambda0_);
     251           1 :     todoAutomatic_=false;
     252             :   }
     253           1 :   std::vector<std::string> myLambdas=getLambdas();
     254           1 :   plumed_massert(myLambdas.size()==lambdas.size(),"RESTART - mismatch in number of "+getName());
     255           1 :   plumed_massert(std::equal(myLambdas.begin(),myLambdas.end(),lambdas.begin()),"RESTART - mismatch in lambda values of "+getName());
     256             : 
     257           1 :   initECVs();
     258           1 : }
     259             : 
     260             : }
     261             : }

Generated by: LCOV version 1.16