LCOV - code coverage report
Current view: top level - ves - VesDeltaF.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 348 358 97.2 %
Date: 2026-03-30 13:16:06 Functions: 10 11 90.9 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2016-2021 The VES code team
       3             :    (see the PEOPLE-VES file at the root of this folder for a list of names)
       4             : 
       5             :    See http://www.ves-code.org for more information.
       6             : 
       7             :    This file is part of VES code module.
       8             : 
       9             :    The VES code module is free software: you can redistribute it and/or modify
      10             :    it under the terms of the GNU Lesser General Public License as published by
      11             :    the Free Software Foundation, either version 3 of the License, or
      12             :    (at your option) any later version.
      13             : 
      14             :    The VES code module is distributed in the hope that it will be useful,
      15             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
      16             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17             :    GNU Lesser General Public License for more details.
      18             : 
      19             :    You should have received a copy of the GNU Lesser General Public License
      20             :    along with the VES code module.  If not, see <http://www.gnu.org/licenses/>.
      21             : +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
      22             : 
      23             : #include "bias/Bias.h"
      24             : #include "core/PlumedMain.h"
      25             : #include "core/ActionRegister.h"
      26             : #include "core/Atoms.h"
      27             : #include "tools/Communicator.h"
      28             : #include "tools/Grid.h"
      29             : #include "tools/File.h"
      30             : //#include <algorithm> //std::fill
      31             : 
      32             : namespace PLMD {
      33             : namespace ves {
      34             : 
      35             : //+PLUMEDOC VES_BIAS VES_DELTA_F
      36             : /*
      37             : Implementation of VES Delta F method
      38             : 
      39             : Implementation of VES\f$\Delta F\f$ method \cite Invernizzi2019vesdeltaf (step two only).
      40             : 
      41             : \warning
      42             :   Notice that this is a stand-alone bias Action, it does not need any of the other VES module components
      43             : 
      44             : First you should create some estimate of the local free energy basins of your system,
      45             : using e.g. multiple \ref METAD short runs, and combining them with the \ref sum_hills utility.
      46             : Once you have them, you can use this bias Action to perform the VES optimization part of the method.
      47             : 
      48             : These \f$N+1\f$ local basins are used to model the global free energy.
      49             : In particular, given the conditional probabilities \f$P(\mathbf{s}|i)\propto e^{-\beta F_i(\mathbf{s})}\f$
      50             : and the probabilities of being in a given basin \f$P_i\f$, we can write:
      51             : \f[
      52             :   e^{-\beta F(\mathbf{s})}\propto P(\mathbf{s})=\sum_{i=0}^N P(\mathbf{s}|i)P_i \, .
      53             : \f]
      54             : We use this free energy model and the chosen bias factor \f$\gamma\f$ to build the bias potential:
      55             : \f$V(\mathbf{s})=-(1-1/\gamma)F(\mathbf{s})\f$.
      56             : Or, more explicitly:
      57             : \f[
      58             :   V(\mathbf{s})=(1-1/\gamma)\frac{1}{\beta}\log\left[e^{-\beta F_0(\mathbf{s})}
      59             :   +\sum_{i=1}^{N} e^{-\beta F_i(\mathbf{s})} e^{-\beta \alpha_i}\right] \, ,
      60             : \f]
      61             : where the parameters \f$\boldsymbol{\alpha}\f$ are the \f$N\f$ free energy differences (see below) from the \f$F_0\f$ basin.
      62             : 
      63             : By default the \f$F_i(\mathbf{s})\f$ are shifted so that \f$\min[F_i(\mathbf{s})]=0\f$ for all \f$i=\{0,...,N\}\f$.
      64             : In this case the optimization parameters \f$\alpha_i\f$ are the difference in height between the minima of the basins.
      65             : Using the keyword `NORMALIZE`, you can also decide to normalize the local free energies so that
      66             : \f$\int d\mathbf{s}\, e^{-\beta F_i(\mathbf{s})}=1\f$.
      67             : In this case the parameters will represent not the difference in height (which depends on the chosen CVs),
      68             : but the actual free energy difference, \f$\alpha_i=\Delta F_i\f$.
      69             : 
      70             : However, as discussed in Ref. \cite Invernizzi2019vesdeltaf, a better estimate of \f$\Delta F_i\f$ should be obtained through the reweighting procedure.
      71             : 
      72             : \par Examples
      73             : 
      74             : The following performs the optimization of the free energy difference between two metastable basins:
      75             : 
      76             : \plumedfile
      77             : cv: DISTANCE ATOMS=1,2
      78             : ves: VES_DELTA_F ...
      79             :   ARG=cv
      80             :   TEMP=300
      81             :   FILE_F0=fesA.data
      82             :   FILE_F1=fesB.data
      83             :   BIASFACTOR=10.0
      84             :   M_STEP=0.1
      85             :   AV_STRIDE=500
      86             :   PRINT_STRIDE=100
      87             : ...
      88             : PRINT FMT=%g STRIDE=500 FILE=Colvar.data ARG=cv,ves.bias,ves.rct
      89             : \endplumedfile
      90             : 
      91             : The local FES files can be obtained as described in Sec. 4.2 of Ref. \cite Invernizzi2019vesdeltaf, i.e. for example:
      92             : - run 4 independent metad runs, all starting from basin A, and kill them as soon as they make the first transition (see e.g. \ref COMMITTOR)
      93             : - \verbatim cat HILLS* > all_HILLS \endverbatim
      94             : - \verbatim plumed sum_hills --hills all_HILLS --outfile all_fesA.dat --mintozero --min 0 --max 1 --bin 100 \endverbatim
      95             : - \verbatim awk -v n_rep=4 '{if($1!="#!" && $1!="") {for(i=1+(NF-1)/2; i<=NF; i++) $i/=n_rep;} print $0}' all_fesA.dat > fesA.data \endverbatim
      96             : 
      97             : The header of both FES files must be identical, and should be similar to the following:
      98             : 
      99             : \auxfile{fesA.data}
     100             : #! FIELDS cv file.free der_cv
     101             : #! SET min_cv 0
     102             : #! SET max_cv 1
     103             : #! SET nbins_cv  100
     104             : #! SET periodic_cv false
     105             : 0 0 0
     106             : \endauxfile
     107             : \auxfile{fesB.data}
     108             : #! FIELDS cv file.free der_cv
     109             : #! SET min_cv 0
     110             : #! SET max_cv 1
     111             : #! SET nbins_cv  100
     112             : #! SET periodic_cv false
     113             : 0 0 0
     114             : \endauxfile
     115             : 
     116             : */
     117             : //+ENDPLUMEDOC
     118             : 
     119             : class VesDeltaF : public bias::Bias {
     120             : 
     121             : private:
     122             :   double beta_;
     123             :   unsigned NumParallel_;
     124             :   unsigned rank_;
     125             :   unsigned NumWalkers_;
     126             :   bool isFirstStep_;
     127             :   bool afterCalculate_;
     128             : 
     129             : //prob
     130             :   double tot_prob_;
     131             :   std::vector<double> prob_;
     132             :   std::vector< std::vector<double> > der_prob_;
     133             : 
     134             : //local basins
     135             :   std::vector< std::unique_ptr<Grid> > grid_p_; //pointers because of GridBase::create
     136             :   std::vector<double> norm_;
     137             : 
     138             : //optimizer-related stuff
     139             :   long long unsigned mean_counter_;
     140             :   unsigned mean_weight_tau_;
     141             :   unsigned alpha_size_;
     142             :   unsigned sym_alpha_size_;
     143             :   std::vector<double> mean_alpha_;
     144             :   std::vector<double> inst_alpha_;
     145             :   std::vector<double> past_increment2_;
     146             :   double minimization_step_;
     147             :   bool damping_off_;
     148             : //'tg' -> 'target distribution'
     149             :   double inv_gamma_;
     150             :   unsigned tg_counter_;
     151             :   unsigned tg_stride_;
     152             :   std::vector<double> tg_dV_dAlpha_;
     153             :   std::vector<double> tg_d2V_dAlpha2_;
     154             : //'av' -> 'ensemble average'
     155             :   unsigned av_counter_;
     156             :   unsigned av_stride_;
     157             :   std::vector<double> av_dV_dAlpha_;
     158             :   std::vector<double> av_dV_dAlpha_prod_;
     159             :   std::vector<double> av_d2V_dAlpha2_;
     160             : //printing
     161             :   unsigned print_stride_;
     162             :   OFile alphaOfile_;
     163             : //other
     164             :   std::vector<double> exp_alpha_;
     165             :   std::vector<double> prev_exp_alpha_;
     166             :   double work_;
     167             : 
     168             : //functions
     169             :   void update_alpha();
     170             :   void update_tg_and_rct();
     171             :   inline unsigned get_index(const unsigned, const unsigned) const;
     172             : 
     173             : public:
     174             :   explicit VesDeltaF(const ActionOptions&);
     175             :   void calculate() override;
     176             :   void update() override;
     177             :   static void registerKeywords(Keywords& keys);
     178             : };
     179             : 
     180       13793 : PLUMED_REGISTER_ACTION(VesDeltaF,"VES_DELTA_F")
     181             : 
     182           8 : void VesDeltaF::registerKeywords(Keywords& keys) {
     183           8 :   Bias::registerKeywords(keys);
     184           8 :   keys.use("ARG");
     185          16 :   keys.add("optional","TEMP","temperature is compulsory, but it can be sometimes fetched from the MD engine");
     186             : //local free energies
     187          16 :   keys.add("numbered","FILE_F","names of files containing local free energies and derivatives. "
     188             :            "The first one, FILE_F0, is used as reference for all the free energy differences.");
     189          16 :   keys.reset_style("FILE_F","compulsory");
     190          16 :   keys.addFlag("NORMALIZE",false,"normalize all local free energies so that alpha will be (approx) Delta F");
     191          16 :   keys.addFlag("NO_MINTOZERO",false,"leave local free energies as provided, without shifting them to zero min");
     192             : //target distribution
     193          16 :   keys.add("compulsory","BIASFACTOR","0","the gamma bias factor used for well-tempered target p(s)."
     194             :            " Set to 0 for non-tempered flat target");
     195          16 :   keys.add("optional","TG_STRIDE","( default=1 ) number of AV_STRIDE between updates"
     196             :            " of target p(s) and reweighing factor c(t)");
     197             : //optimization
     198          16 :   keys.add("compulsory","M_STEP","1.0","the mu step used for the Omega functional minimization");
     199          16 :   keys.add("compulsory","AV_STRIDE","500","number of simulation steps between alpha updates");
     200          16 :   keys.add("optional","TAU_MEAN","exponentially decaying average for alpha (rescaled using AV_STRIDE)."
     201             :            " Should be used only in very specific cases");
     202          16 :   keys.add("optional","INITIAL_ALPHA","( default=0 ) an initial guess for the bias potential parameter alpha");
     203          16 :   keys.addFlag("DAMPING_OFF",false,"do not use an AdaGrad-like term to rescale M_STEP");
     204             : //output parameters file
     205          16 :   keys.add("compulsory","ALPHA_FILE","ALPHA","file name for output minimization parameters");
     206          16 :   keys.add("optional","PRINT_STRIDE","( default=10 ) stride for printing to ALPHA_FILE");
     207          16 :   keys.add("optional","FMT","specify format for ALPHA_FILE");
     208             : //debug flags
     209          16 :   keys.addFlag("SERIAL",false,"perform the calculation in serial even if multiple tasks are available");
     210          16 :   keys.addFlag("MULTIPLE_WALKERS",false,"use multiple walkers connected via MPI for the optimization");
     211           8 :   keys.use("RESTART");
     212             : 
     213             : //output components
     214           8 :   componentsAreNotOptional(keys);
     215          16 :   keys.addOutputComponent("rct","default","the reweighting factor c(t)");
     216          16 :   keys.addOutputComponent("work","default","the work done by the bias in one AV_STRIDE");
     217           8 : }
     218             : 
     219           4 : VesDeltaF::VesDeltaF(const ActionOptions&ao)
     220             :   : PLUMED_BIAS_INIT(ao)
     221           4 :   , isFirstStep_(true)
     222           4 :   , afterCalculate_(false)
     223           4 :   , mean_counter_(0)
     224           4 :   , av_counter_(0)
     225           4 :   , work_(0) {
     226             : //set beta
     227           4 :   const double Kb=plumed.getAtoms().getKBoltzmann();
     228           4 :   double temp=0;
     229           4 :   parse("TEMP",temp);
     230           4 :   double KbT=Kb*temp;
     231           4 :   if(KbT==0) {
     232           0 :     KbT=plumed.getAtoms().getKbT();
     233           0 :     plumed_massert(KbT>0,"your MD engine does not pass the temperature to plumed, you must specify it using TEMP");
     234             :   }
     235           4 :   beta_=1.0/KbT;
     236             : 
     237             : //initialize probability grids using local free energies
     238             :   bool spline=true;
     239             :   bool sparsegrid=false;
     240           4 :   std::string funcl="file.free"; //typical name given by sum_hills
     241             : 
     242             :   std::vector<std::string> fes_names;
     243           8 :   for(unsigned n=0;; n++) { //NB: here we start from FILE_F0 not from FILE_F1
     244             :     std::string filename;
     245          24 :     if(!parseNumbered("FILE_F",n,filename)) {
     246             :       break;
     247             :     }
     248           8 :     fes_names.push_back(filename);
     249           8 :     IFile gridfile;
     250           8 :     gridfile.open(filename);
     251           8 :     auto g=GridBase::create(funcl,getArguments(),gridfile,sparsegrid,spline,true);
     252             : // we assume this cannot be sparse. in case we want it to be sparse, some of the methods
     253             : // that are available only in Grid should be ported to GridBase
     254           8 :     auto gg=dynamic_cast<Grid*>(g.get());
     255             : // if this throws, g is deleted
     256           8 :     plumed_assert(gg);
     257             : // release ownership in order to transfer it to emplaced pointer
     258             :     g.release();
     259           8 :     grid_p_.emplace_back(gg);
     260          16 :   }
     261           4 :   plumed_massert(grid_p_.size()>1,"at least 2 basins must be defined, starting from FILE_F0");
     262           4 :   alpha_size_=grid_p_.size()-1;
     263           4 :   sym_alpha_size_=alpha_size_*(alpha_size_+1)/2; //useful for symmetric matrix [alpha_size_]x[alpha_size_]
     264             :   //check for consistency with first local free energy
     265           8 :   for(unsigned n=1; n<grid_p_.size(); n++) {
     266           8 :     std::string error_tag="FILE_F"+std::to_string(n)+" '"+fes_names[n]+"' not compatible with reference one, FILE_F0";
     267           4 :     plumed_massert(grid_p_[n]->getSize()==grid_p_[0]->getSize(),error_tag);
     268           4 :     plumed_massert(grid_p_[n]->getMin()==grid_p_[0]->getMin(),error_tag);
     269           4 :     plumed_massert(grid_p_[n]->getMax()==grid_p_[0]->getMax(),error_tag);
     270           4 :     plumed_massert(grid_p_[n]->getBinVolume()==grid_p_[0]->getBinVolume(),error_tag);
     271             :   }
     272             : 
     273           4 :   bool no_mintozero=false;
     274           4 :   parseFlag("NO_MINTOZERO",no_mintozero);
     275           4 :   if(!no_mintozero) {
     276           6 :     for(unsigned n=0; n<grid_p_.size(); n++) {
     277           4 :       grid_p_[n]->setMinToZero();
     278             :     }
     279             :   }
     280           4 :   bool normalize=false;
     281           4 :   parseFlag("NORMALIZE",normalize);
     282           4 :   norm_.resize(grid_p_.size(),0);
     283           4 :   std::vector<double> c_norm(grid_p_.size());
     284             :   //convert the FESs to probability distributions
     285             :   //NB: the spline interpolation will be done on the probability distributions, not on the given FESs
     286             :   const unsigned ncv=getNumberOfArguments(); //just for ease
     287          12 :   for(unsigned n=0; n<grid_p_.size(); n++) {
     288         808 :     for(Grid::index_t t=0; t<grid_p_[n]->getSize(); t++) {
     289         800 :       std::vector<double> der(ncv);
     290         800 :       const double val=std::exp(-beta_*grid_p_[n]->getValueAndDerivatives(t,der));
     291        1600 :       for(unsigned s=0; s<ncv; s++) {
     292         800 :         der[s]*=-beta_*val;
     293             :       }
     294         800 :       grid_p_[n]->setValueAndDerivatives(t,val,der);
     295         800 :       norm_[n]+=val;
     296             :     }
     297           8 :     c_norm[n]=1./beta_*std::log(norm_[n]);
     298           8 :     if(normalize) {
     299           4 :       grid_p_[n]->scaleAllValuesAndDerivatives(1./norm_[n]);
     300           4 :       norm_[n]=1;
     301             :     }
     302             :   }
     303             : 
     304             : //get target
     305           4 :   double biasfactor=0;
     306           4 :   parse("BIASFACTOR",biasfactor);
     307           4 :   plumed_massert(biasfactor==0 || biasfactor>1,"BIASFACTOR must be zero (for uniform target) or greater than one");
     308           4 :   if(biasfactor==0) {
     309           2 :     inv_gamma_=0;
     310             :   } else {
     311           2 :     inv_gamma_=1./biasfactor;
     312             :   }
     313           4 :   tg_counter_=0;
     314           4 :   tg_stride_=1;
     315           4 :   parse("TG_STRIDE",tg_stride_);
     316           4 :   tg_dV_dAlpha_.resize(alpha_size_,0);
     317           4 :   tg_d2V_dAlpha2_.resize(sym_alpha_size_,0);
     318             : 
     319             : //setup optimization stuff
     320           4 :   minimization_step_=1;
     321           4 :   parse("M_STEP",minimization_step_);
     322             : 
     323           4 :   av_stride_=500;
     324           4 :   parse("AV_STRIDE",av_stride_);
     325           4 :   av_dV_dAlpha_.resize(alpha_size_,0);
     326           4 :   av_dV_dAlpha_prod_.resize(sym_alpha_size_,0);
     327           4 :   av_d2V_dAlpha2_.resize(sym_alpha_size_,0);
     328             : 
     329           4 :   mean_weight_tau_=0;
     330           4 :   parse("TAU_MEAN",mean_weight_tau_);
     331           4 :   if(mean_weight_tau_!=1) { //set it to 1 for basic SGD
     332           4 :     plumed_massert((mean_weight_tau_==0 || mean_weight_tau_>av_stride_),"TAU_MEAN is rescaled with AV_STRIDE, so it has to be greater");
     333           4 :     mean_weight_tau_/=av_stride_; //this way you can look at the number of simulation steps to choose TAU_MEAN
     334             :   }
     335             : 
     336           8 :   parseVector("INITIAL_ALPHA",mean_alpha_);
     337           4 :   if(mean_alpha_.size()>0) {
     338           2 :     plumed_massert(mean_alpha_.size()==alpha_size_,"provide one INITIAL_ALPHA for each basin beyond the first one");
     339             :   } else {
     340           2 :     mean_alpha_.resize(alpha_size_,0);
     341             :   }
     342           4 :   inst_alpha_=mean_alpha_;
     343           4 :   exp_alpha_.resize(alpha_size_);
     344           8 :   for(unsigned i=0; i<alpha_size_; i++) {
     345           4 :     exp_alpha_[i]=std::exp(-beta_*mean_alpha_[i]);
     346             :   }
     347           4 :   prev_exp_alpha_=exp_alpha_;
     348             : 
     349           4 :   damping_off_=false;
     350           4 :   parseFlag("DAMPING_OFF",damping_off_);
     351           4 :   if(damping_off_) {
     352           2 :     past_increment2_.resize(alpha_size_,1);
     353             :   } else {
     354           2 :     past_increment2_.resize(alpha_size_,0);
     355             :   }
     356             : 
     357             : //file printing options
     358           4 :   std::string alphaFileName("ALPHA");
     359           4 :   parse("ALPHA_FILE",alphaFileName);
     360           4 :   print_stride_=10;
     361           8 :   parse("PRINT_STRIDE",print_stride_);
     362             :   std::string fmt;
     363           4 :   parse("FMT",fmt);
     364             : 
     365             : //other flags, mainly for debugging
     366           4 :   NumParallel_=comm.Get_size();
     367           4 :   rank_=comm.Get_rank();
     368           4 :   bool serial=false;
     369           4 :   parseFlag("SERIAL",serial);
     370           4 :   if(serial) {
     371           2 :     log.printf(" -- SERIAL: running without loop parallelization\n");
     372           2 :     NumParallel_=1;
     373           2 :     rank_=0;
     374             :   }
     375             : 
     376           4 :   bool multiple_walkers=false;
     377           4 :   parseFlag("MULTIPLE_WALKERS",multiple_walkers);
     378           4 :   if(!multiple_walkers) {
     379           2 :     NumWalkers_=1;
     380             :   } else {
     381           2 :     if(comm.Get_rank()==0) { //multi_sim_comm works well on first rank only
     382           2 :       NumWalkers_=multi_sim_comm.Get_size();
     383             :     }
     384           2 :     if(comm.Get_size()>1) { //if each walker has more than one processor update them all
     385           0 :       comm.Bcast(NumWalkers_,0);
     386             :     }
     387             :   }
     388             : 
     389           4 :   checkRead();
     390             : 
     391             : //restart if needed
     392           4 :   if(getRestart()) {
     393           2 :     IFile ifile;
     394           2 :     ifile.link(*this);
     395           2 :     if(NumWalkers_>1) {
     396           4 :       ifile.enforceSuffix("");
     397             :     }
     398           2 :     if(ifile.FileExist(alphaFileName)) {
     399           2 :       log.printf("  Restarting from: %s\n",alphaFileName.c_str());
     400           2 :       log.printf("    all options (also PRINT_STRIDE) must be consistent!\n");
     401           2 :       log.printf("    any INITIAL_ALPHA will be overwritten\n");
     402           2 :       ifile.open(alphaFileName);
     403             :       double time;
     404           2 :       std::vector<double> damping(alpha_size_);
     405          20 :       while(ifile.scanField("time",time)) { //room for improvements: only last line is important
     406          16 :         for(unsigned i=0; i<alpha_size_; i++) {
     407           8 :           const std::string index(std::to_string(i+1));
     408           8 :           prev_exp_alpha_[i]=std::exp(-beta_*mean_alpha_[i]);
     409          16 :           ifile.scanField("alpha_"+index,mean_alpha_[i]);
     410          16 :           ifile.scanField("auxiliary_"+index,inst_alpha_[i]);
     411          16 :           ifile.scanField("damping_"+index,damping[i]);
     412             :         }
     413           8 :         ifile.scanField();
     414           8 :         mean_counter_+=print_stride_;
     415             :       }
     416           4 :       for(unsigned i=0; i<alpha_size_; i++) {
     417           2 :         exp_alpha_[i]=std::exp(-beta_*mean_alpha_[i]);
     418           2 :         past_increment2_[i]=damping[i]*damping[i];
     419             :       }
     420             :       //sync all walkers and treads. Not sure is mandatory but is no harm
     421           2 :       comm.Barrier();
     422           2 :       if(comm.Get_rank()==0) {
     423           2 :         multi_sim_comm.Barrier();
     424             :       }
     425             :     } else {
     426           0 :       log.printf("  -- WARNING: restart requested, but no '%s' file found!\n",alphaFileName.c_str());
     427             :     }
     428           2 :   }
     429             : 
     430             : //setup output file with Alpha values
     431           4 :   alphaOfile_.link(*this);
     432           4 :   if(NumWalkers_>1) {
     433           2 :     if(comm.Get_rank()==0 && multi_sim_comm.Get_rank()>0) {
     434             :       alphaFileName="/dev/null";  //only first walker writes on file
     435             :     }
     436           4 :     alphaOfile_.enforceSuffix("");
     437             :   }
     438           4 :   alphaOfile_.open(alphaFileName);
     439           4 :   if(fmt.length()>0) {
     440           8 :     alphaOfile_.fmtField(" "+fmt);
     441             :   }
     442             : 
     443             : //add other output components
     444           4 :   addComponent("rct");
     445           4 :   componentIsNotPeriodic("rct");
     446           4 :   addComponent("work");
     447           4 :   componentIsNotPeriodic("work");
     448             : 
     449             : //print some info
     450           4 :   log.printf("  Temperature T: %g\n",1./(Kb*beta_));
     451           4 :   log.printf("  Beta (1/Kb*T): %g\n",beta_);
     452           4 :   log.printf("  Local free energy basins files and normalization constants:\n");
     453          12 :   for(unsigned n=0; n<grid_p_.size(); n++) {
     454           8 :     log.printf("    F_%d filename: %s  c_%d=%g\n",n,fes_names[n].c_str(),n,c_norm[n]);
     455             :   }
     456           4 :   if(no_mintozero) {
     457           2 :     log.printf(" -- NO_MINTOZERO: local free energies are not shifted to be zero at minimum\n");
     458             :   }
     459           4 :   if(normalize) {
     460           2 :     log.printf(" -- NORMALIZE: F_n+=c_n, alpha=DeltaF\n");
     461             :   }
     462           4 :   log.printf("  Using target distribution with 1/gamma = %g\n",inv_gamma_);
     463           4 :   log.printf("    and updated with stride %d\n",tg_stride_);
     464           4 :   log.printf("  Step for the minimization algorithm: %g\n",minimization_step_);
     465           4 :   log.printf("  Stride for the ensemble average: %d\n",av_stride_);
     466           4 :   if(mean_weight_tau_>1) {
     467           2 :     log.printf("  Exponentially decaying average with weight=tau/av_stride=%d\n",mean_weight_tau_);
     468             :   }
     469           4 :   if(mean_weight_tau_==1) {
     470           0 :     log.printf(" +++ WARNING +++ setting TAU_MEAN=1 is equivalent to use simple SGD, without mean alpha nor hessian contribution\n");
     471             :   }
     472           4 :   log.printf("  Initial guess for alpha:\n");
     473           8 :   for(unsigned i=0; i<alpha_size_; i++) {
     474           4 :     log.printf("    alpha_%d = %g\n",i+1,mean_alpha_[i]);
     475             :   }
     476           4 :   if(damping_off_) {
     477           2 :     log.printf(" -- DAMPING_OFF: the minimization step will NOT become smaller as the simulation goes on\n");
     478             :   }
     479           4 :   log.printf("  Printing on file %s with stride %d\n",alphaFileName.c_str(),print_stride_);
     480           4 :   if(serial) {
     481           2 :     log.printf(" -- SERIAL: running without loop parallelization\n");
     482             :   }
     483           4 :   if(NumParallel_>1) {
     484           2 :     log.printf("  Using multiple threads per simulation: %d\n",NumParallel_);
     485             :   }
     486           4 :   if(multiple_walkers) {
     487           2 :     log.printf(" -- MULTIPLE_WALKERS: multiple simulations will combine statistics for the optimization\n");
     488           2 :     if(NumWalkers_>1) {
     489           2 :       log.printf("    number of walkers: %d\n",NumWalkers_);
     490           2 :       log.printf("    walker rank: %d\n",multi_sim_comm.Get_rank()); //only comm.Get_rank()=0 prints, so this is fine
     491             :     } else {
     492           0 :       log.printf(" +++ WARNING +++ only one replica found: are you sure you are running MPI-connected simulations?\n");
     493             :     }
     494             :   }
     495           4 :   log.printf(" Bibliography ");
     496           8 :   log<<plumed.cite("Invernizzi and Parrinello, J. Chem. Theory Comput. 15, 2187-2194 (2019)");
     497           8 :   log<<plumed.cite("Valsson and Parrinello, Phys. Rev. Lett. 113, 090601 (2014)");
     498           4 :   if(inv_gamma_>0) {
     499           4 :     log<<plumed.cite("Valsson and Parrinello, J. Chem. Theory Comput. 11, 1996-2002 (2015)");
     500             :   }
     501             : 
     502             : //last initializations
     503           4 :   prob_.resize(grid_p_.size());
     504           4 :   der_prob_.resize(grid_p_.size(),std::vector<double>(getNumberOfArguments()));
     505           4 :   update_tg_and_rct();
     506           8 : }
     507             : 
     508         804 : void VesDeltaF::calculate() {
     509             : //get CVs
     510         804 :   const unsigned ncv=getNumberOfArguments(); //just for ease
     511         804 :   std::vector<double> cv(ncv);
     512        1608 :   for(unsigned s=0; s<ncv; s++) {
     513         804 :     cv[s]=getArgument(s);
     514             :   }
     515             : //get probabilities for each basin, and total one
     516        2412 :   for(unsigned n=0; n<grid_p_.size(); n++) {
     517        1608 :     prob_[n]=grid_p_[n]->getValueAndDerivatives(cv,der_prob_[n]);
     518             :   }
     519         804 :   tot_prob_=prob_[0];
     520        1608 :   for(unsigned i=0; i<alpha_size_; i++) {
     521         804 :     tot_prob_+=prob_[i+1]*exp_alpha_[i];
     522             :   }
     523             : 
     524             : //update bias and forces: V=-(1-inv_gamma_)*fes
     525         804 :   setBias((1-inv_gamma_)/beta_*std::log(tot_prob_));
     526        1608 :   for(unsigned s=0; s<ncv; s++) {
     527         804 :     double dProb_dCV_s=der_prob_[0][s];
     528        1608 :     for(unsigned i=0; i<alpha_size_; i++) {
     529         804 :       dProb_dCV_s+=der_prob_[i+1][s]*exp_alpha_[i];
     530             :     }
     531         804 :     setOutputForce(s,-(1-inv_gamma_)/beta_/tot_prob_*dProb_dCV_s);
     532             :   }
     533         804 :   afterCalculate_=true;
     534         804 : }
     535             : 
     536         804 : void VesDeltaF::update() {
     537             : //skip first step to sync getTime() and av_counter_, as in METAD
     538         804 :   if(isFirstStep_) {
     539           4 :     isFirstStep_=false;
     540           4 :     return;
     541             :   }
     542         800 :   plumed_massert(afterCalculate_,"VesDeltaF::update() must be called after VesDeltaF::calculate() to work properly");
     543         800 :   afterCalculate_=false;
     544             : 
     545             : //calculate derivatives for ensemble averages
     546         800 :   std::vector<double> dV_dAlpha(alpha_size_);
     547         800 :   std::vector<double> d2V_dAlpha2(sym_alpha_size_);
     548        1600 :   for(unsigned i=0; i<alpha_size_; i++) {
     549         800 :     dV_dAlpha[i]=-(1-inv_gamma_)/tot_prob_*prob_[i+1]*exp_alpha_[i];
     550             :   }
     551        1600 :   for(unsigned i=0; i<alpha_size_; i++) {
     552         800 :     d2V_dAlpha2[get_index(i,i)]=-beta_*dV_dAlpha[i];
     553        1600 :     for(unsigned j=i; j<alpha_size_; j++) {
     554         800 :       d2V_dAlpha2[get_index(i,j)]-=beta_/(1-inv_gamma_)*dV_dAlpha[i]*dV_dAlpha[j];
     555             :     }
     556             :   }
     557             : //update ensemble averages
     558         800 :   av_counter_++;
     559        1600 :   for(unsigned i=0; i<alpha_size_; i++) {
     560         800 :     av_dV_dAlpha_[i]+=(dV_dAlpha[i]-av_dV_dAlpha_[i])/av_counter_;
     561        1600 :     for(unsigned j=i; j<alpha_size_; j++) {
     562         800 :       const unsigned ij=get_index(i,j);
     563         800 :       av_dV_dAlpha_prod_[ij]+=(dV_dAlpha[i]*dV_dAlpha[j]-av_dV_dAlpha_prod_[ij])/av_counter_;
     564         800 :       av_d2V_dAlpha2_[ij]+=(d2V_dAlpha2[ij]-av_d2V_dAlpha2_[ij])/av_counter_;
     565             :     }
     566             :   }
     567             : //update work
     568         800 :   double prev_tot_prob=prob_[0];
     569        1600 :   for(unsigned i=0; i<alpha_size_; i++) {
     570         800 :     prev_tot_prob+=prob_[i+1]*prev_exp_alpha_[i];
     571             :   }
     572         800 :   work_+=(1-inv_gamma_)/beta_*std::log(tot_prob_/prev_tot_prob);
     573             : 
     574             : //update coefficients
     575         800 :   if(av_counter_==av_stride_) {
     576          16 :     update_alpha();
     577          16 :     tg_counter_++;
     578          16 :     if(tg_counter_==tg_stride_) {
     579          12 :       update_tg_and_rct();
     580          12 :       tg_counter_=0;
     581             :     }
     582             :     //reset the ensemble averages
     583          16 :     av_counter_=0;
     584             :     std::fill(av_dV_dAlpha_.begin(),av_dV_dAlpha_.end(),0);
     585             :     std::fill(av_dV_dAlpha_prod_.begin(),av_dV_dAlpha_prod_.end(),0);
     586             :     std::fill(av_d2V_dAlpha2_.begin(),av_d2V_dAlpha2_.end(),0);
     587             :   }
     588             : }
     589             : 
     590          16 : void VesDeltaF::update_tg_and_rct() {
     591             : //calculate target averages
     592          16 :   double Z_0=norm_[0];
     593          32 :   for(unsigned i=0; i<alpha_size_; i++) {
     594          16 :     Z_0+=norm_[i+1]*exp_alpha_[i];
     595             :   }
     596          16 :   double Z_tg=0;
     597             :   std::fill(tg_dV_dAlpha_.begin(),tg_dV_dAlpha_.end(),0);
     598             :   std::fill(tg_d2V_dAlpha2_.begin(),tg_d2V_dAlpha2_.end(),0);
     599        1116 :   for(Grid::index_t t=rank_; t<grid_p_[0]->getSize(); t+=NumParallel_) {
     600             :     //TODO can we recycle some code?
     601        1100 :     std::vector<double> prob(grid_p_.size());
     602        3300 :     for(unsigned n=0; n<grid_p_.size(); n++) {
     603        2200 :       prob[n]=grid_p_[n]->getValue(t);
     604             :     }
     605        1100 :     double tot_prob=prob[0];
     606        2200 :     for(unsigned i=0; i<alpha_size_; i++) {
     607        1100 :       tot_prob+=prob[i+1]*exp_alpha_[i];
     608             :     }
     609        1100 :     std::vector<double> dV_dAlpha(alpha_size_);
     610        1100 :     std::vector<double> d2V_dAlpha2(sym_alpha_size_);
     611        2200 :     for(unsigned i=0; i<alpha_size_; i++) {
     612        1100 :       dV_dAlpha[i]=-(1-inv_gamma_)/tot_prob*prob[i+1]*exp_alpha_[i];
     613             :     }
     614        2200 :     for(unsigned i=0; i<alpha_size_; i++) {
     615        1100 :       d2V_dAlpha2[get_index(i,i)]=-beta_*dV_dAlpha[i];
     616        2200 :       for(unsigned j=i; j<alpha_size_; j++) {
     617        1100 :         d2V_dAlpha2[get_index(i,j)]-=beta_/(1-inv_gamma_)*dV_dAlpha[i]*dV_dAlpha[j];
     618             :       }
     619             :     }
     620        1100 :     const double unnorm_tg_p=std::pow(tot_prob,inv_gamma_);
     621        1100 :     Z_tg+=unnorm_tg_p;
     622        2200 :     for(unsigned i=0; i<alpha_size_; i++) {
     623        1100 :       tg_dV_dAlpha_[i]+=unnorm_tg_p*dV_dAlpha[i];
     624             :     }
     625        2200 :     for(unsigned ij=0; ij<sym_alpha_size_; ij++) {
     626        1100 :       tg_d2V_dAlpha2_[ij]+=unnorm_tg_p*d2V_dAlpha2[ij];
     627             :     }
     628             :   }
     629          16 :   if(NumParallel_>1) {
     630          10 :     comm.Sum(Z_tg);
     631          10 :     comm.Sum(tg_dV_dAlpha_);
     632          10 :     comm.Sum(tg_d2V_dAlpha2_);
     633             :   }
     634          32 :   for(unsigned i=0; i<alpha_size_; i++) {
     635          16 :     tg_dV_dAlpha_[i]/=Z_tg;
     636             :   }
     637          32 :   for(unsigned ij=0; ij<sym_alpha_size_; ij++) {
     638          16 :     tg_d2V_dAlpha2_[ij]/=Z_tg;
     639             :   }
     640          16 :   getPntrToComponent("rct")->set(-1./beta_*std::log(Z_tg/Z_0)); //Z_tg is the best available estimate of Z_V
     641          16 : }
     642             : 
     643          16 : void VesDeltaF::update_alpha() {
     644             : //combining the averages of multiple walkers
     645          16 :   if(NumWalkers_>1) {
     646           8 :     if(comm.Get_rank()==0) { //sum only once: in the first rank of each walker
     647           8 :       multi_sim_comm.Sum(av_dV_dAlpha_);
     648           8 :       multi_sim_comm.Sum(av_dV_dAlpha_prod_);
     649           8 :       multi_sim_comm.Sum(av_d2V_dAlpha2_);
     650          16 :       for(unsigned i=0; i<alpha_size_; i++) {
     651           8 :         av_dV_dAlpha_[i]/=NumWalkers_;
     652             :       }
     653          16 :       for(unsigned ij=0; ij<sym_alpha_size_; ij++) {
     654           8 :         av_dV_dAlpha_prod_[ij]/=NumWalkers_;
     655           8 :         av_d2V_dAlpha2_[ij]/=NumWalkers_;
     656             :       }
     657             :     }
     658           8 :     if(comm.Get_size()>1) { //if there are more ranks for each walker, everybody has to know
     659           0 :       comm.Bcast(av_dV_dAlpha_,0);
     660           0 :       comm.Bcast(av_dV_dAlpha_prod_,0);
     661           0 :       comm.Bcast(av_d2V_dAlpha2_,0);
     662             :     }
     663             :   }
     664             :   //set work and reset it
     665          16 :   getPntrToComponent("work")->set(work_);
     666          16 :   work_=0;
     667             : 
     668             : //build the gradient and the Hessian of the functional
     669          16 :   std::vector<double> grad_omega(alpha_size_);
     670          16 :   std::vector<double> hess_omega(sym_alpha_size_);
     671          32 :   for(unsigned i=0; i<alpha_size_; i++) {
     672          16 :     grad_omega[i]=tg_dV_dAlpha_[i]-av_dV_dAlpha_[i];
     673          32 :     for(unsigned j=i; j<alpha_size_; j++) {
     674          16 :       const unsigned ij=get_index(i,j);
     675          16 :       hess_omega[ij]=beta_*(av_dV_dAlpha_prod_[ij]-av_dV_dAlpha_[i]*av_dV_dAlpha_[j])+tg_d2V_dAlpha2_[ij]-av_d2V_dAlpha2_[ij];
     676             :     }
     677             :   }
     678             : //calculate the increment and update alpha
     679          16 :   mean_counter_++;
     680             :   long long unsigned mean_weight=mean_counter_;
     681          16 :   if(mean_weight_tau_>0 && mean_weight_tau_<mean_counter_) {
     682             :     mean_weight=mean_weight_tau_;
     683             :   }
     684          16 :   std::vector<double> damping(alpha_size_);
     685          32 :   for(unsigned i=0; i<alpha_size_; i++) {
     686          16 :     double increment_i=grad_omega[i];
     687          32 :     for(unsigned j=0; j<alpha_size_; j++) {
     688          16 :       increment_i+=hess_omega[get_index(i,j)]*(inst_alpha_[j]-mean_alpha_[j]);
     689             :     }
     690          16 :     if(!damping_off_) {
     691           8 :       past_increment2_[i]+=increment_i*increment_i;
     692             :     }
     693          16 :     damping[i]=std::sqrt(past_increment2_[i]);
     694          16 :     prev_exp_alpha_[i]=std::exp(-beta_*mean_alpha_[i]);
     695          16 :     inst_alpha_[i]-=minimization_step_/damping[i]*increment_i;
     696          16 :     mean_alpha_[i]+=(inst_alpha_[i]-mean_alpha_[i])/mean_weight;
     697          16 :     exp_alpha_[i]=std::exp(-beta_*mean_alpha_[i]);
     698             :   }
     699             : 
     700             : //update the Alpha file
     701          16 :   if(mean_counter_%print_stride_==0) {
     702          16 :     alphaOfile_.printField("time",getTime());
     703          32 :     for(unsigned i=0; i<alpha_size_; i++) {
     704          16 :       const std::string index(std::to_string(i+1));
     705          32 :       alphaOfile_.printField("alpha_"+index,mean_alpha_[i]);
     706          32 :       alphaOfile_.printField("auxiliary_"+index,inst_alpha_[i]);
     707          32 :       alphaOfile_.printField("damping_"+index,damping[i]);
     708             :     }
     709          16 :     alphaOfile_.printField();
     710             :   }
     711          16 : }
     712             : 
     713             : //mapping of a [alpha_size_]x[alpha_size_] symmetric matrix into a vector of size sym_alpha_size_, useful for the communicator
     714        4632 : inline unsigned VesDeltaF::get_index(const unsigned i, const unsigned j) const {
     715        4632 :   if(i<=j) {
     716        4632 :     return j+i*(alpha_size_-1)-i*(i-1)/2;
     717             :   } else {
     718           0 :     return get_index(j,i);
     719             :   }
     720             : }
     721             : 
     722             : }
     723             : }

Generated by: LCOV version 1.16