LCOV - code coverage report
Current view: top level - opes - ECVlinear.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 114 119 95.8 %
Date: 2025-11-25 13:55:50 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           6 :   keys.remove("ARG");
      99          12 :   keys.add("compulsory","ARG","the label of the Hamiltonian difference. Delta U");
     100          12 :   keys.add("compulsory","LAMBDA","0","the lambda at which the underlying simulation runs");
     101          12 :   keys.add("optional","LAMBDA_MIN","( default=0 ) the minimum of the lambda range");
     102          12 :   keys.add("optional","LAMBDA_MAX","( default=1 ) the maximum of the lambda range");
     103          12 :   keys.add("optional","LAMBDA_STEPS","uniformly place the lambda values, for a total of LAMBDA_STEPS");
     104          12 :   keys.add("optional","LAMBDA_SET_ALL","manually set all the lamdbas");
     105          12 :   keys.addFlag("DIMENSIONLESS",false,"ARG is considered dimensionless rather than an energy, thus is not multiplied by beta");
     106          12 :   keys.addFlag("GEOM_SPACING",false,"use geometrical spacing in lambda instead of linear spacing");
     107           6 : }
     108             : 
     109           4 : ECVlinear::ECVlinear(const ActionOptions&ao)
     110             :   : Action(ao)
     111             :   , ExpansionCVs(ao)
     112           4 :   , todoAutomatic_(false)
     113           4 :   , beta0_(1./kbt_) {
     114           4 :   plumed_massert(getNumberOfArguments()==1,"only DeltaU should be given as ARG");
     115             : 
     116             : //set beta0_
     117             :   bool dimensionless;
     118           4 :   parseFlag("DIMENSIONLESS",dimensionless);
     119           4 :   if(dimensionless) {
     120           1 :     beta0_=1;
     121             :   }
     122             : 
     123             : //parse lambda info
     124           4 :   parse("LAMBDA",lambda0_);
     125             :   const double myNone=std::numeric_limits<double>::lowest(); //quiet_NaN is not supported by some intel compiler
     126           4 :   double lambda_min=myNone;
     127           4 :   double lambda_max=myNone;
     128           4 :   parse("LAMBDA_MIN",lambda_min);
     129           4 :   parse("LAMBDA_MAX",lambda_max);
     130           4 :   unsigned lambda_steps=0;
     131           8 :   parse("LAMBDA_STEPS",lambda_steps);
     132             :   std::vector<double> lambdas;
     133           4 :   parseVector("LAMBDA_SET_ALL",lambdas);
     134           4 :   parseFlag("GEOM_SPACING",geom_spacing_);
     135             : 
     136           4 :   checkRead();
     137             : 
     138             : //set the diff vector using lambdas
     139           4 :   if(lambdas.size()>0) {
     140           1 :     plumed_massert(lambda_steps==0,"cannot set both LAMBDA_STEPS and LAMBDA_SET_ALL");
     141           1 :     plumed_massert(lambda_min==myNone && lambda_max==myNone,"cannot set both LAMBDA_SET_ALL and LAMBDA_MIN/MAX");
     142           1 :     plumed_massert(lambdas.size()>=2,"set at least 2 lambdas with LAMBDA_SET_ALL");
     143           4 :     for(unsigned k=0; k<lambdas.size()-1; k++) {
     144           3 :       plumed_massert(lambdas[k]<=lambdas[k+1],"LAMBDA_SET_ALL must be properly ordered");
     145             :     }
     146           1 :     lambda_min=lambdas[0];
     147           1 :     lambda_max=lambdas[lambdas.size()-1];
     148           1 :     derECVs_.resize(lambdas.size());
     149           5 :     for(unsigned k=0; k<derECVs_.size(); k++) {
     150           4 :       derECVs_[k]=beta0_*(lambdas[k]-lambda0_);
     151             :     }
     152             :   } else {
     153             :     //get LAMBDA_MIN and LAMBDA_MAX
     154           3 :     if(lambda_min==myNone) {
     155           0 :       lambda_min=0;
     156           0 :       log.printf("  no LAMBDA_MIN provided, using LAMBDA_MIN = %g\n",lambda_min);
     157             :     }
     158           3 :     if(lambda_max==myNone) {
     159           1 :       lambda_max=1;
     160           1 :       log.printf("  no LAMBDA_MAX provided, using LAMBDA_MAX = %g\n",lambda_max);
     161             :     }
     162           3 :     plumed_massert(lambda_max>=lambda_min,"LAMBDA_MAX should be bigger than LAMBDA_MIN");
     163           3 :     derECVs_.resize(2);
     164           3 :     derECVs_[0]=beta0_*(lambda_min-lambda0_);
     165           3 :     derECVs_[1]=beta0_*(lambda_max-lambda0_);
     166           3 :     if(lambda_min==lambda_max && lambda_steps==0) {
     167           0 :       lambda_steps=1;
     168             :     }
     169           3 :     if(lambda_steps>0) {
     170           2 :       derECVs_=getSteps(derECVs_[0],derECVs_[1],lambda_steps,"LAMBDA",geom_spacing_,beta0_*lambda0_);
     171             :     } else {
     172           2 :       todoAutomatic_=true;
     173             :     }
     174             :   }
     175           4 :   if(lambda0_<lambda_min || lambda0_>lambda_max) {
     176           1 :     log.printf(" +++ WARNING +++ running at LAMBDA=%g which is outside the chosen lambda range\n",lambda0_);
     177             :   }
     178             : 
     179             : //print some info
     180           4 :   log.printf("  running at LAMBDA=%g\n",lambda0_);
     181           4 :   log.printf("  targeting a lambda range from LAMBDA_MIN=%g to LAMBDA_MAX=%g\n",lambda_min,lambda_max);
     182           4 :   if(dimensionless) {
     183           1 :     log.printf(" -- DIMENSIONLESS: the ARG is not multiplied by beta\n");
     184             :   }
     185           4 :   if(geom_spacing_) {
     186           1 :     log.printf(" -- GEOM_SPACING: lambdas will be geometrically spaced\n");
     187             :   }
     188           4 : }
     189             : 
     190         182 : void ECVlinear::calculateECVs(const double * DeltaU) {
     191        1587 :   for(unsigned k=0; k<derECVs_.size(); k++) {
     192        1405 :     ECVs_[k]=derECVs_[k]*DeltaU[0];
     193             :   }
     194             : // derivatives never change: derECVs_k=beta0*(lambda_k-lambda0)
     195         182 : }
     196             : 
     197           4 : const double * ECVlinear::getPntrToECVs(unsigned j) {
     198           4 :   plumed_massert(isReady_,"cannot access ECVs before initialization");
     199           4 :   plumed_massert(j==0,getName()+" has only one CV, the DeltaU");
     200           4 :   return &ECVs_[0];
     201             : }
     202             : 
     203           4 : const double * ECVlinear::getPntrToDerECVs(unsigned j) {
     204           4 :   plumed_massert(isReady_,"cannot access ECVs before initialization");
     205           4 :   plumed_massert(j==0,getName()+" has only one CV, the DeltaU");
     206           4 :   return &derECVs_[0];
     207             : }
     208             : 
     209           4 : std::vector<std::string> ECVlinear::getLambdas() const {
     210           4 :   plumed_massert(!todoAutomatic_,"cannot access lambdas before initializing them");
     211           4 :   std::vector<std::string> lambdas(derECVs_.size());
     212          35 :   for(unsigned k=0; k<derECVs_.size(); k++) {
     213          31 :     std::ostringstream subs;
     214          31 :     subs<<derECVs_[k]/beta0_+lambda0_; //lambda_k
     215          31 :     lambdas[k]=subs.str();
     216          31 :   }
     217           4 :   return lambdas;
     218           0 : }
     219             : 
     220           4 : void ECVlinear::initECVs() {
     221           4 :   plumed_massert(!isReady_,"initialization should not be called twice");
     222           4 :   plumed_massert(!todoAutomatic_,"this should not happen");
     223           4 :   totNumECVs_=derECVs_.size();
     224           4 :   ECVs_.resize(derECVs_.size());
     225           4 :   isReady_=true;
     226           4 :   log.printf("  *%4lu lambdas for %s\n",derECVs_.size(),getName().c_str());
     227           4 : }
     228             : 
     229           3 : void ECVlinear::initECVs_observ(const std::vector<double>& all_obs_cvs,const unsigned ncv,const unsigned index_j) {
     230           3 :   if(todoAutomatic_) { //estimate the steps in lambda from observations
     231           1 :     plumed_massert(all_obs_cvs.size()%ncv==0 && index_j<ncv,"initECVs_observ parameters are inconsistent");
     232           1 :     std::vector<double> obs_cv(all_obs_cvs.size()/ncv); //copy only useful observation (would be better not to copy...)
     233          11 :     for(unsigned t=0; t<obs_cv.size(); t++) {
     234          10 :       obs_cv[t]=all_obs_cvs[t*ncv+index_j];
     235             :     }
     236           1 :     const unsigned lambda_steps=estimateNumSteps(derECVs_[0],derECVs_[1],obs_cv,"LAMBDA");
     237           1 :     if(beta0_!=1) {
     238           0 :       log.printf("    (spacing is in beta0 units)\n");
     239             :     }
     240           2 :     derECVs_=getSteps(derECVs_[0],derECVs_[1],lambda_steps,"LAMBDA",geom_spacing_,beta0_*lambda0_);
     241           1 :     todoAutomatic_=false;
     242             :   }
     243           3 :   initECVs();
     244           3 :   calculateECVs(&all_obs_cvs[index_j]);
     245           3 : }
     246             : 
     247           1 : void ECVlinear::initECVs_restart(const std::vector<std::string>& lambdas) {
     248           1 :   std::size_t pos=lambdas[0].find("_");
     249           1 :   plumed_massert(pos==std::string::npos,"this should not happen, only one CV is used in "+getName());
     250           1 :   if(todoAutomatic_) {
     251           2 :     derECVs_=getSteps(derECVs_[0],derECVs_[1],lambdas.size(),"LAMBDA",geom_spacing_,beta0_*lambda0_);
     252           1 :     todoAutomatic_=false;
     253             :   }
     254           1 :   std::vector<std::string> myLambdas=getLambdas();
     255           1 :   plumed_massert(myLambdas.size()==lambdas.size(),"RESTART - mismatch in number of "+getName());
     256           1 :   plumed_massert(std::equal(myLambdas.begin(),myLambdas.end(),lambdas.begin()),"RESTART - mismatch in lambda values of "+getName());
     257             : 
     258           1 :   initECVs();
     259           1 : }
     260             : 
     261             : }
     262             : }

Generated by: LCOV version 1.16