LCOV - code coverage report
Current view: top level - opes - ECVmultiThermal.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 107 112 95.5 %
Date: 2024-10-18 14:00:25 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_MULTITHERMAL
      26             : /*
      27             : Expand a simulation to sample multiple temperatures simultaneously.
      28             : 
      29             : The internal energy \f$U\f$ of of the system should be used as ARG.
      30             : \f[
      31             :   \Delta u_{\beta'}=(\beta'-\beta) U\, ,
      32             : \f]
      33             : where \f$\beta'\f$ are the temperatures to be sampled and \f$\beta\f$ is the temperature at which the simulation is conducted.
      34             : In case of fixed volume, the internal energy is simply the potential energy given by the \ref ENERGY colvar\f$U=E\f$, and you will run a multicanonical simulation.
      35             : If instead the simulation is at fixed pressure \f$p\f$, the contribution of the volume must be added \f$U=E+pV\f$ (see example below).
      36             : 
      37             : By defauly the needed steps in temperatures are automatically guessed from few initial unbiased MD steps, as descibed in \cite Invernizzi2020unified.
      38             : Otherwise you can manually set this number with TEMP_STEPS.
      39             : In both cases the steps will be geometrically spaced in temperature.
      40             : Use instead the keyword NO_GEOM_SPACING for a linear spacing in the inverse temperature (beta), that typically increases the focus on lower temperatures.
      41             : Finally, you can use instead the keyword TEMP_SET_ALL and explicitly provide each temperature.
      42             : 
      43             : You can reweight the resulting simulation at any temperature in the chosen range, using e.g. \ref REWEIGHT_TEMP_PRESS.
      44             : A similar target distribution can be sampled using \ref TD_MULTICANONICAL.
      45             : 
      46             : 
      47             : \par Examples
      48             : 
      49             : Fixed volume, multicanonical simulation:
      50             : 
      51             : \plumedfile
      52             : ene: ENERGY
      53             : ecv: ECV_MULTITHERMAL ARG=ene TEMP=300 TEMP_MIN=300 TEMP_MAX=800
      54             : opes: OPES_EXPANDED ARG=ecv.ene PACE=500
      55             : \endplumedfile
      56             : 
      57             : which, if your MD code passes the temperature to PLUMED, is equivalent to:
      58             : 
      59             : \plumedfile
      60             : ene: ENERGY
      61             : ecv: ECV_MULTITHERMAL ARG=ene TEMP_MAX=800
      62             : opes: OPES_EXPANDED ARG=ecv.ene PACE=500
      63             : \endplumedfile
      64             : 
      65             : If instead the pressure is fixed and the volume changes, you shuld calculate the internal energy first, \f$U=E+pV\f$
      66             : 
      67             : \plumedfile
      68             : ene: ENERGY
      69             : vol: VOLUME
      70             : intEne: CUSTOM PERIODIC=NO ARG=ene,vol FUNC=x+0.06022140857*y
      71             : ecv: ECV_MULTITHERMAL ARG=intEne TEMP_MAX=800
      72             : opes: OPES_EXPANDED ARG=ecv.intEne PACE=500
      73             : \endplumedfile
      74             : 
      75             : Notice that \f$p=0.06022140857\f$ corresponds to 1 bar when using the default PLUMED units.
      76             : 
      77             : */
      78             : //+ENDPLUMEDOC
      79             : 
      80             : class ECVmultiThermal :
      81             :   public ExpansionCVs
      82             : {
      83             : private:
      84             :   bool todoAutomatic_;
      85             :   bool geom_spacing_;
      86             :   std::vector<double> ECVs_;
      87             :   std::vector<double> derECVs_; //(beta_k-beta0) or (temp0/temp_k-1)/kbt
      88             :   void initECVs();
      89             : 
      90             : public:
      91             :   explicit ECVmultiThermal(const ActionOptions&);
      92             :   static void registerKeywords(Keywords& keys);
      93             :   void calculateECVs(const double *) override;
      94             :   const double * getPntrToECVs(unsigned) override;
      95             :   const double * getPntrToDerECVs(unsigned) override;
      96             :   std::vector<std::string> getLambdas() const override;
      97             :   void initECVs_observ(const std::vector<double>&,const unsigned,const unsigned) override;
      98             :   void initECVs_restart(const std::vector<std::string>&) override;
      99             : };
     100             : 
     101             : PLUMED_REGISTER_ACTION(ECVmultiThermal,"ECV_MULTITHERMAL")
     102             : 
     103          13 : void ECVmultiThermal::registerKeywords(Keywords& keys)
     104             : {
     105          13 :   ExpansionCVs::registerKeywords(keys);
     106          13 :   keys.remove("ARG");
     107          26 :   keys.add("compulsory","ARG","the label of the internal energy of the system. If volume is fixed it is calculated by the ENERGY colvar");
     108          26 :   keys.add("optional","TEMP_MIN","the minimum of the temperature range");
     109          26 :   keys.add("optional","TEMP_MAX","the maximum of the temperature range");
     110          26 :   keys.add("optional","TEMP_STEPS","the number of steps in temperature");
     111          26 :   keys.add("optional","TEMP_SET_ALL","manually set all the temperatures");
     112          26 :   keys.addFlag("NO_GEOM_SPACING",false,"do not use geometrical spacing in temperature, but instead linear spacing in inverse temperature");
     113          13 : }
     114             : 
     115          11 : ECVmultiThermal::ECVmultiThermal(const ActionOptions&ao)
     116             :   : Action(ao)
     117             :   , ExpansionCVs(ao)
     118          11 :   , todoAutomatic_(false)
     119             : {
     120          11 :   plumed_massert(getNumberOfArguments()==1,"only the internal energy should be given as ARG");
     121             : 
     122             : //set temp0
     123          11 :   const double temp0=kbt_/getKBoltzmann();
     124             : 
     125             : //parse temp range
     126          11 :   double temp_min=-1;
     127          11 :   double temp_max=-1;
     128          11 :   parse("TEMP_MIN",temp_min);
     129          11 :   parse("TEMP_MAX",temp_max);
     130          11 :   unsigned temp_steps=0;
     131          22 :   parse("TEMP_STEPS",temp_steps);
     132             :   std::vector<double> temps;
     133          11 :   parseVector("TEMP_SET_ALL",temps);
     134          11 :   parseFlag("NO_GEOM_SPACING",geom_spacing_);
     135          11 :   geom_spacing_=!geom_spacing_;
     136             : 
     137          11 :   checkRead();
     138             : 
     139             : //set the intermediate temperatures
     140          11 :   if(temps.size()>0)
     141             :   {
     142           2 :     plumed_massert(temp_steps==0,"cannot set both TEMP_STEPS and TEMP_SET_ALL");
     143           2 :     plumed_massert(temp_min==-1 && temp_max==-1,"cannot set both TEMP_SET_ALL and TEMP_MIN/MAX");
     144           2 :     plumed_massert(temps.size()>=2,"set at least 2 temperatures");
     145           2 :     temp_min=temps[0];
     146           2 :     temp_max=temps[temps.size()-1];
     147           2 :     derECVs_.resize(temps.size());
     148          10 :     for(unsigned k=0; k<derECVs_.size(); k++)
     149             :     {
     150           8 :       derECVs_[k]=(temp0/temps[k]-1.)/kbt_;
     151           8 :       if(k<derECVs_.size()-1)
     152           6 :         plumed_massert(temps[k]<=temps[k+1],"TEMP_SET_ALL must be properly ordered");
     153             :     }
     154             :   }
     155             :   else
     156             :   { //get TEMP_MIN and TEMP_MAX
     157           9 :     plumed_massert(temp_min!=-1 || temp_max!=-1,"TEMP_MIN, TEMP_MAX or both, should be set");
     158           9 :     if(temp_min==-1)
     159             :     {
     160           2 :       temp_min=temp0;
     161           2 :       log.printf("  no TEMP_MIN provided, using TEMP_MIN=TEMP\n");
     162             :     }
     163           9 :     if(temp_max==-1)
     164             :     {
     165           0 :       temp_max=temp0;
     166           0 :       log.printf("  no TEMP_MAX provided, using TEMP_MAX=TEMP\n");
     167             :     }
     168           9 :     plumed_massert(temp_max>=temp_min,"TEMP_MAX should be bigger than TEMP_MIN");
     169           9 :     derECVs_.resize(2);
     170           9 :     derECVs_[0]=(temp0/temp_min-1.)/kbt_;
     171           9 :     derECVs_[1]=(temp0/temp_max-1.)/kbt_;
     172           9 :     if(temp_min==temp_max && temp_steps==0)
     173           0 :       temp_steps=1;
     174           9 :     if(temp_steps>0)
     175          14 :       derECVs_=getSteps(derECVs_[0],derECVs_[1],temp_steps,"TEMP",geom_spacing_,1./kbt_);
     176             :     else
     177           2 :       todoAutomatic_=true;
     178             :   }
     179             :   const double tol=1e-3; //if temp is taken from MD engine it might be numerically slightly different
     180          11 :   if(temp0<(1-tol)*temp_min || temp0>(1+tol)*temp_max)
     181           0 :     log.printf(" +++ WARNING +++ running at TEMP=%g which is outside the chosen temperature range\n",temp0);
     182             : 
     183             : //print some info
     184          11 :   log.printf("  targeting a temperature range from TEMP_MIN=%g to TEMP_MAX=%g\n",temp_min,temp_max);
     185          11 :   if(!geom_spacing_)
     186           1 :     log.printf(" -- NO_GEOM_SPACING: inverse temperatures will be linearly spaced\n");
     187          11 : }
     188             : 
     189         586 : void ECVmultiThermal::calculateECVs(const double * ene)
     190             : {
     191        3618 :   for(unsigned k=0; k<derECVs_.size(); k++)
     192        3032 :     ECVs_[k]=derECVs_[k]*ene[0];
     193             : // derivatives never change: derECVs_k=(beta_k-beta0)
     194         586 : }
     195             : 
     196          11 : const double * ECVmultiThermal::getPntrToECVs(unsigned j)
     197             : {
     198          11 :   plumed_massert(isReady_,"cannot access ECVs before initialization");
     199          11 :   plumed_massert(j==0,getName()+" has only one CV, the ENERGY");
     200          11 :   return &ECVs_[0];
     201             : }
     202             : 
     203          11 : const double * ECVmultiThermal::getPntrToDerECVs(unsigned j)
     204             : {
     205          11 :   plumed_massert(isReady_,"cannot access ECVs before initialization");
     206          11 :   plumed_massert(j==0,getName()+" has only one CV, the ENERGY");
     207          11 :   return &derECVs_[0];
     208             : }
     209             : 
     210          11 : std::vector<std::string> ECVmultiThermal::getLambdas() const
     211             : {
     212          11 :   plumed_massert(!todoAutomatic_,"cannot access lambdas before initializing them");
     213          11 :   const double temp0=kbt_/getKBoltzmann();
     214          11 :   std::vector<std::string> lambdas(derECVs_.size());
     215          70 :   for(unsigned k=0; k<derECVs_.size(); k++)
     216             :   {
     217          59 :     std::ostringstream subs;
     218          59 :     subs<<temp0/(derECVs_[k]*kbt_+1); //temp_k
     219          59 :     lambdas[k]=subs.str();
     220          59 :   }
     221          11 :   return lambdas;
     222           0 : }
     223             : 
     224          11 : void ECVmultiThermal::initECVs()
     225             : {
     226          11 :   plumed_massert(!isReady_,"initialization should not be called twice");
     227          11 :   plumed_massert(!todoAutomatic_,"this should not happen");
     228          11 :   totNumECVs_=derECVs_.size();
     229          11 :   ECVs_.resize(derECVs_.size());
     230          11 :   isReady_=true;
     231          11 :   log.printf("  *%4lu temperatures for %s\n",derECVs_.size(),getName().c_str());
     232          11 : }
     233             : 
     234           8 : void ECVmultiThermal::initECVs_observ(const std::vector<double>& all_obs_cvs,const unsigned ncv,const unsigned index_j)
     235             : {
     236           8 :   if(todoAutomatic_) //estimate the steps in beta from observations
     237             :   {
     238           1 :     plumed_massert(all_obs_cvs.size()%ncv==0 && index_j<ncv,"initECVs_observ parameters are inconsistent");
     239           1 :     std::vector<double> obs_ene(all_obs_cvs.size()/ncv); //copy only useful observation (would be better not to copy...)
     240          11 :     for(unsigned t=0; t<obs_ene.size(); t++)
     241          10 :       obs_ene[t]=all_obs_cvs[t*ncv+index_j];
     242           1 :     const unsigned temp_steps=estimateNumSteps(derECVs_[0],derECVs_[1],obs_ene,"TEMP");
     243           1 :     log.printf("    (spacing is in beta, not in temperature)\n");
     244           2 :     derECVs_=getSteps(derECVs_[0],derECVs_[1],temp_steps,"TEMP",geom_spacing_,1./kbt_);
     245           1 :     todoAutomatic_=false;
     246             :   }
     247           8 :   initECVs();
     248           8 :   calculateECVs(&all_obs_cvs[index_j]);
     249           8 : }
     250             : 
     251           3 : void ECVmultiThermal::initECVs_restart(const std::vector<std::string>& lambdas)
     252             : {
     253           3 :   std::size_t pos=lambdas[0].find("_");
     254           3 :   plumed_massert(pos==std::string::npos,"this should not happen, only one CV is used in "+getName());
     255           3 :   if(todoAutomatic_)
     256             :   {
     257           2 :     derECVs_=getSteps(derECVs_[0],derECVs_[1],lambdas.size(),"TEMP",geom_spacing_,1./kbt_);
     258           1 :     todoAutomatic_=false;
     259             :   }
     260           3 :   std::vector<std::string> myLambdas=getLambdas();
     261           3 :   plumed_massert(myLambdas.size()==lambdas.size(),"RESTART - mismatch in number of "+getName());
     262           3 :   plumed_massert(std::equal(myLambdas.begin(),myLambdas.end(),lambdas.begin()),"RESTART - mismatch in lambda values of "+getName());
     263             : 
     264           3 :   initECVs();
     265           3 : }
     266             : 
     267             : }
     268             : }

Generated by: LCOV version 1.16