LCOV - code coverage report
Current view: top level - opes - ECVmultiThermal.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 108 113 95.6 %
Date: 2024-10-11 08:09:47 Functions: 12 13 92.3 %

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

Generated by: LCOV version 1.15