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

Generated by: LCOV version 1.16