LCOV - code coverage report
Current view: top level - bias - MetaDRestart.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 176 185 95.1 %
Date: 2026-11-05 10:19:04 Functions: 11 11 100.0 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2011-2026 The plumed team
       3             :    (see the PEOPLE file at the root of the distribution for a list of names)
       4             : 
       5             :    See http://www.plumed.org for more information.
       6             : 
       7             :    This file is part of plumed, version 2.
       8             : 
       9             :    plumed 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             :    plumed 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 plumed.  If not, see <http://www.gnu.org/licenses/>.
      21             : +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
      22             : #include "MetaDRestart.h"
      23             : #include "MetaDGrid.h"
      24             : #include "MetaDHills.h"
      25             : #include "core/Action.h"
      26             : #include "tools/Communicator.h"
      27             : #include "tools/File.h"
      28             : #include "tools/Keywords.h"
      29             : #include "tools/Log.h"
      30             : #include <exception>
      31             : #include <filesystem>
      32             : #include <limits>
      33             : #include <numeric>
      34             : #include <vector>
      35             : 
      36             : namespace PLMD {
      37             : namespace bias {
      38             : 
      39         359 : void MetaDWalkers::registerKeywords(Keywords& keys) {
      40         359 :   keys.add("optional","WALKERS_ID", "walker id");
      41         359 :   keys.add("optional","WALKERS_N", "number of walkers");
      42         359 :   keys.add("optional","WALKERS_DIR", "shared directory with the hills files from all the walkers");
      43         359 :   keys.add("optional","WALKERS_RSTRIDE","stride for reading hills files");
      44         359 :   keys.addFlag("WALKERS_MPI",false,"Switch on MPI version of multiple walkers - not compatible with WALKERS_* options other than WALKERS_DIR");
      45         359 : }
      46             : 
      47         337 : void MetaDWalkers::parse(Action& action) {
      48         337 :   action.parse("WALKERS_N",n);
      49         337 :   action.parse("WALKERS_ID",id);
      50         337 :   if(n<1) {
      51           0 :     action.error("the number of walkers should be positive");
      52             :   }
      53         337 :   if(id<0 || n<=id) {
      54           1 :     action.error("walker ID should be a numerical value between 0 and the total number of walkers minus one");
      55             :   }
      56         336 :   action.parse("WALKERS_DIR",dir);
      57         336 :   action.parse("WALKERS_RSTRIDE",rstride);
      58         336 :   if(rstride<=0) {
      59           1 :     action.error("WALKERS_RSTRIDE should be positive");
      60             :   }
      61         335 :   action.parseFlag("WALKERS_MPI",mpi);
      62             :   // if PLUMED is not compiled with MPI the user is informed and we exit gracefully
      63         335 :   if(mpi) {
      64         107 :     if(!Communicator::plumedHasMPI()) {
      65           0 :       action.error("Invalid walkers configuration: WALKERS_MPI flag requires MPI compilation");
      66             :     }
      67         107 :     if(!Communicator::initialized()) {
      68           1 :       action.error("Invalid walkers configuration: WALKERS_MPI needs the communicator correctly initialized.");
      69             :     }
      70             :   }
      71         334 : }
      72             : 
      73         334 : void MetaDWalkers::setup(Action& action) {
      74         334 :   Log& log=action.log;
      75         334 :   if(n>1) {
      76          22 :     if(mpi) {
      77           0 :       action.error("MPI version of multiple walkers is not compatible with filesystem version of multiple walkers");
      78             :     }
      79          22 :     log.printf("  %d multiple walkers active\n",n);
      80          22 :     log.printf("  walker id %d\n",id);
      81          22 :     log.printf("  reading stride %d\n",rstride);
      82          22 :     if(dir!="") {
      83           3 :       log.printf("  directory with hills files %s\n",dir.c_str());
      84             :     }
      85         312 :   } else if(mpi) {
      86         106 :     log.printf("  Multiple walkers active using MPI communication\n");
      87         106 :     if(dir!="") {
      88           0 :       log.printf("  directory with hills files %s\n",dir.c_str());
      89             :     }
      90         106 :     mpi_nw=action.getNumberOfReplicas();
      91         106 :     mpi_id=action.getReplicaIndex();
      92             :   }
      93         334 : }
      94             : 
      95         468 : std::string MetaDWalkers::getFileName(const std::string& name, int walker) const {
      96         468 :   return MetaDHillsFiles::getWalkerFileName(name,dir,n,walker,mpi);
      97             : }
      98             : 
      99         106 : std::string MetaDWalkers::firstReplicaDirectory(Action& action) const {
     100         106 :   std::string cwd=std::filesystem::current_path().string();
     101             :   // the paths of the replicas can have different lengths
     102         106 :   if(action.comm.Get_rank()==0) {
     103          61 :     bcastString(action.multi_sim_comm,cwd);
     104             :   }
     105         106 :   bcastString(action.comm,cwd);
     106         106 :   return cwd;
     107             : }
     108             : 
     109         666 : void MetaDWalkers::checkSameChoice(Action& action, bool choice, const std::string& what) const {
     110         666 :   if(!action.getRestart() || !mpi) {
     111         640 :     return;
     112             :   }
     113          26 :   std::vector<int> all(mpi_nw,0);
     114          26 :   if(action.comm.Get_rank()==0) {
     115          20 :     action.multi_sim_comm.Allgather(int(choice),all);
     116             :   }
     117          26 :   action.comm.Bcast(all,0);
     118          26 :   const unsigned nchoice=std::accumulate(all.begin(),all.end(),0);
     119          26 :   if(nchoice!=0 && nchoice!=mpi_nw) {
     120           0 :     action.error("in this WALKERS_MPI run some replicas have restarted from "+what+" while others have not");
     121             :   }
     122             : }
     123             : 
     124         471 : void MetaDWalkers::openOutput(Action& action, OFile& file, const std::string& name) const {
     125         471 :   file.link(action);
     126         441 :   if(shareFiles()) {
     127         392 :     file.enforceSuffix("");
     128             :   }
     129         637 :   file.open(writesFiles() ? name : "/dev/null");
     130         470 : }
     131             : 
     132          43 : std::vector<long long> readBiasGrid(Action& action, const MetaDWalkers& walkers, MetaDBiasGrid& grid, const std::string& filename,
     133             :                                     const std::string& funcl, const std::vector<Value*>& args, const std::vector<std::string>& gmin,
     134             :                                     const std::vector<std::string>& gmax, const std::vector<unsigned>& gbin, bool sparse, bool spline,
     135             :                                     bool nobounds) {
     136          43 :   std::string path=filename;
     137          43 :   if(walkers.mpi && path.at(0)!='/') {
     138           1 :     path=walkers.firstReplicaDirectory(action)+"/"+path;
     139             :   }
     140          43 :   IFile gridfile;
     141          43 :   gridfile.link(action);
     142          35 :   if(walkers.shareFiles()) {
     143             :     // multiple walkers write their files without the suffix of the replica: with WALKERS_MPI all the walkers
     144             :     // read the same file
     145          16 :     gridfile.enforceSuffix("");
     146             :   }
     147          43 :   if(gridfile.FileExist(path)) {
     148          43 :     gridfile.open(path);
     149             :   } else {
     150           0 :     action.error("The GRID file you want to read: " + path + ", cannot be found!");
     151             :   }
     152          43 :   grid.read(gridfile,path,funcl,args,gmin,gmax,gbin,sparse,spline,nobounds);
     153          42 :   std::vector<long long> walkerhills;
     154          42 :   if(walkers.n>1) {
     155             :     // the hills of the other walkers in the grid
     156          24 :     walkerhills=MetaDHillsFiles::readGridComment(action.comm,gridfile.getPath(),walkers.n);
     157             :   }
     158          42 :   action.log.printf("  Restarting from %s\n",path.c_str());
     159          42 :   return walkerhills;
     160          43 : }
     161             : 
     162         333 : void readHillsAtStart(Action& action, MetaDWalkers& walkers, const std::vector<MetaDHillsInput>& inputs, bool restartedFromGrid,
     163             :                       MetaDHillSchedule& schedule) {
     164         333 :   Log& log=action.log;
     165         333 :   Communicator& comm=action.comm;
     166             :   const bool restart=action.getRestart();
     167             :   // with WALKERS_MPI the walkers must all read a grid, or none
     168         333 :   walkers.checkSameChoice(action,restartedFromGrid,"GRID");
     169             : 
     170         333 :   if(walkers.mpi && walkers.dir=="") {
     171             :     // if possible the first replica shares its current directory, so that all the walkers read the same files
     172             :     bool relative=true;
     173         264 :     for(const auto & input : inputs) {
     174         158 :       relative=relative && input.name.at(0)!='/';
     175             :     }
     176         106 :     if(relative) {
     177         212 :       walkers.dir=walkers.firstReplicaDirectory(action);
     178             :     }
     179             :   }
     180             : 
     181             :   // open all the files
     182         765 :   for(const auto & input : inputs) {
     183         900 :     for(int j=0; j<walkers.n; ++j) {
     184             :       // with multiple walkers the files are written without the suffix of the replica
     185         870 :       const unsigned k=input.files->addInput(walkers.getFileName(input.name,j),walkers.shareFiles());
     186         468 :       plumed_assert(k==static_cast<unsigned>(j));
     187             :     }
     188             :   }
     189             : 
     190             :   // When the bias is read from grids, the own hills files are read first: the time of their last hill tells
     191             :   // which hills of the other walkers (WALKERS_N) are already in the grids (for grids without their numbers)
     192         333 :   std::vector<int> order;
     193         333 :   if(restart && restartedFromGrid) {
     194          37 :     order.push_back(walkers.id);
     195             :   }
     196         694 :   for(int j=0; j<walkers.n; ++j) {
     197         361 :     if(order.empty() || order[0]!=j) {
     198         324 :       order.push_back(j);
     199             :     }
     200             :   }
     201             :   bool restartedFromHills=false;
     202             :   // the time of the last hill in the files of this walker (the hills of all the files are written at the same time:
     203             :   // the last one is used)
     204         333 :   bool lastHillTimeKnown=false;
     205         333 :   double lastHillTime=0.0;
     206             :   // (lasttime is set by readHills, and is valid only if at least one hill was read)
     207          74 :   auto setLastHill=[&](unsigned nhills, double lasttime) {
     208          74 :     if(nhills>0 && !(lastHillTimeKnown && lasttime<=lastHillTime)) {
     209          62 :       lastHillTimeKnown=true;
     210          62 :       lastHillTime=lasttime;
     211             :     }
     212         333 :   };
     213             :   // the actual paths of the hills files of this walker, if they were read
     214         333 :   std::vector<std::string> ownpath(inputs.size());
     215         694 :   for(const int j : order) {
     216         829 :     for(unsigned s=0; s<inputs.size(); ++s) {
     217         468 :       MetaDHillsFiles& files=*inputs[s].files;
     218         468 :       const unsigned k=j;
     219         468 :       if(!files.isOpen(k)) {
     220             :         // in case a file does not exist and we are restarting, complain that the file was not found
     221         354 :         if(restart && !restartedFromGrid) {
     222           0 :           action.error("restart file "+files.getInputName(k)+" not found");
     223             :         }
     224         354 :         continue;
     225             :       }
     226         114 :       if(restart && !restartedFromGrid) {
     227          64 :         log.printf("  Restarting from %s:",files.getInputName(k).c_str());
     228          64 :         if(j==walkers.id) {
     229          56 :           double lasttime=0.0;
     230          56 :           const unsigned nhills=files.readHills(k,&lasttime);
     231          56 :           setLastHill(nhills,lasttime);
     232         112 :           ownpath[s]=files.getPath(k);
     233             :         } else {
     234           8 :           files.readHills(k);
     235             :         }
     236             :         restartedFromHills=true;
     237             :       }
     238         114 :       if(restart && restartedFromGrid && j==walkers.id) {
     239             :         // new hills will be appended to this file also when the bias is read from a grid
     240          20 :         ownpath[s]=files.getPath(k);
     241             :         // the bias is read from the grid, but the hills are still needed for the time of the last hill (to decide
     242             :         // if a hill is added at the first step, and which hills of the other walkers are in the grid) and for the
     243             :         // statistics of ADAPTIVE=DIFF
     244          20 :         log.printf("  Reading hills from %s:",files.getInputName(k).c_str());
     245          20 :         double lasttime=0.0;
     246             :         try {
     247          20 :           const unsigned nhills=files.readHills(k,&lasttime,false);
     248          18 :           setLastHill(nhills,lasttime);
     249           2 :         } catch(const std::exception& e) {
     250             :           // the bias comes from the grid: if the hills file cannot be used, its information is not used
     251           2 :           log.printf("\n  WARNING: the hills file cannot be used for the time of the last hill and the statistics of ADAPTIVE=DIFF"
     252           2 :                      " (new hills are appended to it):\n%s\n",e.what());
     253           2 :           if(files.getBias().diffrestart) {
     254           2 :             files.getBias().diffrestart->clear();
     255             :           }
     256           2 :         }
     257             :       }
     258         114 :       if(restart && restartedFromGrid && j!=walkers.id) {
     259             :         // the hills of the other walkers that are not in the grid are added
     260             :         const std::vector<long long>& ingridcount=inputs[s].gridWalkerHills;
     261           8 :         const double ingrid=(lastHillTimeKnown ? lastHillTime : std::numeric_limits<double>::infinity());
     262           8 :         if(ingridcount.size()==static_cast<unsigned>(walkers.n) && ingridcount[j]>=0) {
     263             :           // the grid file contains the number of hills read from each walker
     264           6 :           log.printf("  Reading hills from %s (the first %lld are in the grid):",files.getInputName(k).c_str(),ingridcount[j]);
     265           6 :           files.readHills(k,nullptr,true,-std::numeric_limits<double>::infinity(),ingridcount[j]);
     266           2 :         } else if(lastHillTimeKnown) {
     267             :           // grid written by an older version: those up to the last hill of this walker are assumed to be in the grid
     268           2 :           log.printf("  Reading hills from %s (those up to time %f are assumed to be in the grid):",files.getInputName(k).c_str(),ingrid);
     269           2 :           files.readHills(k,nullptr,true,ingrid);
     270             :         } else {
     271           0 :           log.printf("  Reading hills from %s (they are assumed to be in the grid):",files.getInputName(k).c_str());
     272           0 :           files.readHills(k,nullptr,true,ingrid);
     273             :         }
     274             :       }
     275         114 :       files.reset(k);
     276             :       // close only the walker own hills file for later writing
     277         114 :       if(j==walkers.id) {
     278          88 :         files.close(k);
     279             :       }
     280             :     }
     281             :   }
     282             : 
     283             :   // with WALKERS_MPI the walkers must all read the hills files, or none
     284         333 :   walkers.checkSameChoice(action,restartedFromHills,"FILE");
     285             : 
     286         333 :   if(walkers.mpi) {
     287             :     // the decision to add a hill at the first step must be the same for all the walkers (they share the hills)
     288         199 :     std::vector<double> last {lastHillTimeKnown ? 1.0 : 0.0, lastHillTime};
     289         106 :     if(comm.Get_rank()==0) {
     290          61 :       action.multi_sim_comm.Bcast(last,0);
     291             :     }
     292         106 :     comm.Bcast(last,0);
     293         106 :     lastHillTimeKnown=(last[0]!=0.0);
     294         106 :     lastHillTime=last[1];
     295             :   }
     296         333 :   if(lastHillTimeKnown) {
     297          62 :     schedule.setLastHillTime(lastHillTime);
     298             :   }
     299             : 
     300         333 :   comm.Barrier();
     301             :   // this barrier is needed when using walkers_mpi
     302             :   // to be sure that all files have been read before
     303             :   // backing them up
     304             :   // it should not be used when walkers_mpi is false otherwise
     305             :   // it would introduce troubles when using replicas without METAD
     306             :   // (e.g. in bias exchange with a neutral replica)
     307             :   // see issue #168 on github
     308         333 :   if(comm.Get_rank()==0 && walkers.mpi) {
     309          61 :     action.multi_sim_comm.Barrier();
     310             :   }
     311             : 
     312             :   // all walkers have read the hills: the one that writes the files can now remove an incomplete
     313             :   // last line (left by a run that was interrupted while writing), before new hills are appended to them
     314         765 :   for(const auto & path : ownpath) {
     315             :     std::string message;
     316         432 :     if(path.length()>0 && comm.Get_rank()==0 && walkers.writesFiles()) {
     317         126 :       message=removeIncompleteLastLine(path);
     318             :     }
     319             :     // the log must be written by all the processes
     320         432 :     if(comm.Get_size()>1) {
     321         194 :       bcastString(comm,message);
     322             :     }
     323         432 :     if(message.length()>0) {
     324           6 :       log<<message;
     325             :     }
     326             :   }
     327         666 : }
     328             : 
     329        3112 : void readOtherWalkersHills(Action& action, const MetaDWalkers& walkers, const std::vector<MetaDHillsFiles*>& files) {
     330       12348 :   for(int j=0; j<walkers.n; ++j) {
     331             :     // don't read your own Gaussians
     332        9236 :     if(j==walkers.id) {
     333        3112 :       continue;
     334             :     }
     335       12348 :     for(auto f : files) {
     336        6224 :       if(!f->isOpen(j)) {
     337             :         // check if it exists now and open it!
     338          33 :         if(f->openIfExists(j)) {
     339          10 :           f->reset(j);
     340             :         }
     341             :       } else {
     342             :         // otherwise read the new Gaussians
     343        6191 :         action.log.printf("  Reading hills from %s:",f->getInputName(j).c_str());
     344        6191 :         f->readHills(j);
     345        6191 :         f->reset(j);
     346             :       }
     347             :     }
     348             :   }
     349        3112 : }
     350             : 
     351             : }
     352             : }

Generated by: LCOV version 1.16