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 : }
|