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 "bias/Bias.h"
20 : #include "core/PlumedMain.h"
21 : #include "core/ActionRegister.h"
22 : #include "core/ActionSet.h"
23 : #include "tools/Communicator.h"
24 : #include "tools/File.h"
25 : #include "tools/OpenMP.h"
26 :
27 : #include "ExpansionCVs.h"
28 :
29 : namespace PLMD {
30 : namespace opes {
31 :
32 : //+PLUMEDOC OPES_BIAS OPES_EXPANDED
33 : /*
34 : On-the-fly probability enhanced sampling with expanded ensembles for the target distribution.
35 :
36 : This method is similar to the OPES method (\ref OPES "OPES") with expanded ensembles target distribution (replica-exchange-like) \cite Invernizzi2020unified.
37 :
38 : An expanded ensemble is obtained by summing a set of ensembles at slightly different termodynamic conditions, or with slightly different Hamiltonians.
39 : Such ensembles can be sampled via methods like replica exchange, or this \ref OPES_EXPANDED bias action.
40 : A typical example is a multicanonical simulation, in which a whole range of temperatures is sampled instead of a single one.
41 :
42 : In oreder to define an expanded target ensemble we use \ref EXPANSION_CV "expansion collective variables" (ECVs), \f$\Delta u_\lambda(\mathbf{x})\f$.
43 : The bias at step \f$n\f$ is
44 : \f[
45 : V_n(\mathbf{x})=-\frac{1}{\beta}\log \left(\frac{1}{N_{\{\lambda\}}}\sum_\lambda e^{-\Delta u_\lambda(\mathbf{x})+\beta\Delta F_n(\lambda)}\right)\, .
46 : \f]
47 : See Ref.\cite Invernizzi2020unified for more details on the method.
48 :
49 : Notice that the estimates in the DELTAFS file are expressed in energy units, and should be multiplied by \f$\beta\f$ to be dimensionless as in Ref.\cite Invernizzi2020unified.
50 : The DELTAFS file also contains an estimate of \f$c(t)=\frac{1}{\beta} \log \langle e^{\beta V}\rangle\f$.
51 : Similarly to \ref OPES_METAD, it is printed only for reference, since \f$c(t)\f$ reaches a fixed value as the bias converges, and should NOT be used for reweighting.
52 : Its value is also needed for restarting a simulation.
53 :
54 : You can store the instantaneous \f$\Delta F_n(\lambda)\f$ estimates also in a more readable format using STATE_WFILE and STATE_WSTRIDE.
55 : Restart can be done either from a DELTAFS file or from a STATE_RFILE, it is equivalent.
56 :
57 : Contrary to \ref OPES_METAD, \ref OPES_EXPANDED does not use kernel density estimation.
58 :
59 : \par Examples
60 :
61 : \plumedfile
62 : # simulate multiple temperatures, as in parallel tempering
63 : ene: ENERGY
64 : ecv: ECV_MULTITHERMAL ARG=ene TEMP_MAX=1000
65 : opes: OPES_EXPANDED ARG=ecv.* PACE=500
66 : PRINT FILE=COLVAR STRIDE=500 ARG=ene,opes.bias
67 : \endplumedfile
68 :
69 : You can easily combine multiple ECVs.
70 : The \ref OPES_EXPANDED bias will create a multidimensional target grid to sample all the combinations.
71 :
72 : \plumedfile
73 : # simulate multiple temperatures while biasing a CV
74 : ene: ENERGY
75 : dst: DISTANCE ATOMS=1,2
76 :
77 : ecv1: ECV_MULTITHERMAL ARG=ene TEMP_SET_ALL=200,300,500,1000
78 : ecv2: ECV_UMBRELLAS_LINE ARG=dst CV_MIN=1.2 CV_MAX=4.3 SIGMA=0.5
79 : opes: OPES_EXPANDED ARG=ecv1.*,ecv2.* PACE=500 OBSERVATION_STEPS=1
80 :
81 : PRINT FILE=COLVAR STRIDE=500 ARG=ene,dst,opes.bias
82 : \endplumedfile
83 :
84 : If an ECV is based on more than one CV you must provide all the output component, in the proper order.
85 : You can use \ref Regex for that, like in the following example.
86 :
87 : \plumedfile
88 : # simulate multiple temperatures and pressures while biasing a two-CVs linear path
89 : ene: ENERGY
90 : vol: VOLUME
91 : ecv_mtp: ECV_MULTITHERMAL_MULTIBARIC ...
92 : ARG=ene,vol
93 : TEMP=300
94 : TEMP_MIN=200
95 : TEMP_MAX=800
96 : PRESSURE=0.06022140857*1000 #1 kbar
97 : PRESSURE_MIN=0
98 : PRESSURE_MAX=0.06022140857*2000 #2 kbar
99 : ...
100 :
101 : cv1: DISTANCE ATOMS=1,2
102 : cv2: DISTANCE ATOMS=3,4
103 : ecv_umb: ECV_UMBRELLAS_LINE ARG=cv1,cv2 TEMP=300 CV_MIN=0.1,0.1 CV_MAX=1.5,1.5 SIGMA=0.2 BARRIER=70
104 :
105 : opes: OPES_EXPANDED ARG=(ecv_.*) PACE=500 WALKERS_MPI PRINT_STRIDE=1000
106 :
107 : PRINT FILE=COLVAR STRIDE=500 ARG=ene,vol,cv1,cv2,opes.bias
108 : \endplumedfile
109 :
110 :
111 : */
112 : //+ENDPLUMEDOC
113 :
114 : class OPESexpanded : public bias::Bias {
115 :
116 : private:
117 : bool isFirstStep_;
118 : unsigned NumOMP_;
119 : unsigned NumParallel_;
120 : unsigned rank_;
121 : unsigned NumWalkers_;
122 : unsigned walker_rank_;
123 : unsigned long long counter_;
124 : std::size_t ncv_;
125 :
126 : std::vector<const double *> ECVs_;
127 : std::vector<const double *> derECVs_;
128 : std::vector<opes::ExpansionCVs*> pntrToECVsClass_;
129 : std::vector< std::vector<unsigned> > index_k_;
130 : // A note on indexes usage:
131 : // j -> underlying CVs
132 : // i -> DeltaFs
133 : // k -> single ECVs, which might not be trivially numbered
134 : // l -> groups of ECVs, pntrToECVsClass
135 : // h -> subgroups of ECVs, arguments in ECVsClass
136 : // w -> walkers
137 :
138 : double kbt_;
139 : unsigned stride_;
140 : unsigned deltaF_size_; //different from deltaF_.size() if NumParallel_>1
141 : std::vector<double> deltaF_;
142 : std::vector<double> diff_;
143 : double rct_;
144 :
145 : std::vector<double> all_deltaF_;
146 : std::vector<int> all_size_;
147 : std::vector<int> disp_;
148 :
149 : unsigned obs_steps_;
150 : std::vector<double> obs_cvs_;
151 :
152 : bool calc_work_;
153 : double work_;
154 :
155 : unsigned print_stride_;
156 : OFile deltaFsOfile_;
157 : std::vector<std::string> deltaF_name_;
158 :
159 : OFile stateOfile_;
160 : int wStateStride_;
161 : bool storeOldStates_;
162 :
163 : void init_pntrToECVsClass();
164 : void init_linkECVs();
165 : void init_fromObs();
166 :
167 : void printDeltaF();
168 : void dumpStateToFile();
169 : void updateDeltaF(double);
170 : double getExpansion(const unsigned) const;
171 :
172 : public:
173 : explicit OPESexpanded(const ActionOptions&);
174 : void calculate() override;
175 : void update() override;
176 : static void registerKeywords(Keywords& keys);
177 : };
178 :
179 : PLUMED_REGISTER_ACTION(OPESexpanded,"OPES_EXPANDED")
180 :
181 32 : void OPESexpanded::registerKeywords(Keywords& keys) {
182 32 : Bias::registerKeywords(keys);
183 32 : keys.remove("ARG");
184 64 : keys.add("compulsory","ARG","the label of the ECVs that define the expansion. You can use an * to make sure all the output components of the ECVs are used, as in the examples above");
185 64 : keys.add("compulsory","PACE","how often the bias is updated");
186 64 : keys.add("compulsory","OBSERVATION_STEPS","100","number of unbiased initial PACE steps to collect statistics for initialization");
187 : //DeltaFs and state files
188 64 : keys.add("compulsory","FILE","DELTAFS","a file with the estimate of the relative Delta F for each component of the target and of the global c(t)");
189 64 : keys.add("compulsory","PRINT_STRIDE","100","stride for printing to DELTAFS file, in units of PACE");
190 64 : keys.add("optional","FMT","specify format for DELTAFS file");
191 64 : keys.add("optional","STATE_RFILE","read from this file the Delta F estimates and all the info needed to RESTART the simulation");
192 64 : keys.add("optional","STATE_WFILE","write to this file the Delta F estimates and all the info needed to RESTART the simulation");
193 64 : keys.add("optional","STATE_WSTRIDE","number of MD steps between writing the STATE_WFILE. Default is only on CPT events (but not all MD codes set them)");
194 64 : keys.addFlag("STORE_STATES",false,"append to STATE_WFILE instead of ovewriting it each time");
195 : //miscellaneous
196 64 : keys.addFlag("CALC_WORK",false,"calculate the total accumulated work done by the bias since last restart");
197 64 : keys.addFlag("WALKERS_MPI",false,"switch on MPI version of multiple walkers");
198 64 : keys.addFlag("SERIAL",false,"perform calculations in serial");
199 32 : keys.use("RESTART");
200 32 : keys.use("UPDATE_FROM");
201 32 : keys.use("UPDATE_UNTIL");
202 :
203 : //output components
204 64 : keys.addOutputComponent("work","CALC_WORK","total accumulated work done by the bias");
205 32 : }
206 :
207 30 : OPESexpanded::OPESexpanded(const ActionOptions&ao)
208 : : PLUMED_BIAS_INIT(ao)
209 30 : , isFirstStep_(true)
210 30 : , counter_(0)
211 30 : , ncv_(getNumberOfArguments())
212 30 : , deltaF_size_(0)
213 30 : , rct_(0)
214 30 : , work_(0) {
215 : //set pace
216 30 : parse("PACE",stride_);
217 30 : parse("OBSERVATION_STEPS",obs_steps_);
218 30 : plumed_massert(obs_steps_!=0,"minimum is OBSERVATION_STEPS=1");
219 30 : obs_cvs_.resize(obs_steps_*ncv_);
220 :
221 : //deltaFs file
222 : std::string deltaFsFileName;
223 30 : parse("FILE",deltaFsFileName);
224 60 : parse("PRINT_STRIDE",print_stride_);
225 : std::string fmt;
226 60 : parse("FMT",fmt);
227 : //output checkpoint of current state
228 : std::string restartFileName;
229 60 : parse("STATE_RFILE",restartFileName);
230 : std::string stateFileName;
231 30 : parse("STATE_WFILE",stateFileName);
232 30 : wStateStride_=0;
233 30 : parse("STATE_WSTRIDE",wStateStride_);
234 30 : storeOldStates_=false;
235 30 : parseFlag("STORE_STATES",storeOldStates_);
236 30 : if(wStateStride_!=0 || storeOldStates_) {
237 5 : plumed_massert(stateFileName.length()>0,"filename for storing simulation status not specified, use STATE_WFILE");
238 : }
239 30 : if(wStateStride_>0) {
240 5 : plumed_massert(wStateStride_>=(int)stride_,"STATE_WSTRIDE is in units of MD steps, thus should be a multiple of PACE");
241 : }
242 30 : if(stateFileName.length()>0 && wStateStride_==0) {
243 1 : wStateStride_=-1; //will print only on CPT events (checkpoints set by some MD engines, like gromacs)
244 : }
245 :
246 : //work flag
247 30 : parseFlag("CALC_WORK",calc_work_);
248 :
249 : //multiple walkers //external MW for cp2k not supported, but anyway cp2k cannot put bias on energy!
250 30 : bool walkers_mpi=false;
251 30 : parseFlag("WALKERS_MPI",walkers_mpi);
252 30 : if(walkers_mpi) {
253 : //If this Action is not compiled with MPI the user is informed and we exit gracefully
254 4 : plumed_massert(Communicator::plumedHasMPI(),"Invalid walkers configuration: WALKERS_MPI flag requires MPI compilation");
255 4 : plumed_massert(Communicator::initialized(),"Invalid walkers configuration: WALKERS_MPI needs the communicator correctly initialized.");
256 :
257 4 : if(comm.Get_rank()==0) { //multi_sim_comm works on first rank only
258 4 : NumWalkers_=multi_sim_comm.Get_size();
259 4 : walker_rank_=multi_sim_comm.Get_rank();
260 : }
261 4 : comm.Bcast(NumWalkers_,0); //if each walker has more than one processor update them all
262 4 : comm.Bcast(walker_rank_,0);
263 : } else {
264 26 : NumWalkers_=1;
265 26 : walker_rank_=0;
266 : }
267 :
268 : //parallelization stuff
269 30 : NumOMP_=OpenMP::getNumThreads();
270 30 : NumParallel_=comm.Get_size();
271 30 : rank_=comm.Get_rank();
272 30 : bool serial=false;
273 30 : parseFlag("SERIAL",serial);
274 30 : if(serial) {
275 5 : NumOMP_=1;
276 5 : NumParallel_=1;
277 5 : rank_=0;
278 : }
279 :
280 30 : checkRead();
281 :
282 : //check ECVs and link them
283 30 : init_pntrToECVsClass();
284 : //set kbt_
285 30 : kbt_=pntrToECVsClass_[0]->getKbT();
286 67 : for(unsigned l=0; l<pntrToECVsClass_.size(); l++) {
287 37 : plumed_massert(std::abs(kbt_-pntrToECVsClass_[l]->getKbT())<1e-4,"must set same TEMP for each ECV");
288 : }
289 :
290 : //restart if needed
291 30 : if(getRestart()) {
292 : bool stateRestart=true;
293 10 : if(restartFileName.length()==0) {
294 : stateRestart=false;
295 : restartFileName=deltaFsFileName;
296 : }
297 10 : IFile ifile;
298 10 : ifile.link(*this);
299 10 : if(ifile.FileExist(restartFileName)) {
300 10 : log.printf(" RESTART - make sure all ECVs used are the same as before\n");
301 10 : log.printf(" restarting from: %s\n",restartFileName.c_str());
302 10 : ifile.open(restartFileName);
303 10 : if(stateRestart) { //get all info
304 2 : log.printf(" it should be a STATE file (not a DELTAFS file)\n");
305 : double time; //not used
306 2 : ifile.scanField("time",time);
307 2 : ifile.scanField("counter",counter_);
308 4 : ifile.scanField("rct",rct_);
309 : std::string tmp_lambda;
310 66 : while(ifile.scanField(getPntrToArgument(0)->getName(),tmp_lambda)) {
311 64 : std::string subs="DeltaF_"+tmp_lambda;
312 128 : for(unsigned jj=1; jj<ncv_; jj++) {
313 : tmp_lambda.clear();
314 64 : ifile.scanField(getPntrToArgument(jj)->getName(),tmp_lambda);
315 128 : subs+="_"+tmp_lambda;
316 : }
317 64 : deltaF_name_.push_back(subs);
318 : double tmp_deltaF;
319 64 : ifile.scanField("DeltaF",tmp_deltaF);
320 64 : deltaF_.push_back(tmp_deltaF);
321 64 : ifile.scanField();
322 : tmp_lambda.clear();
323 : }
324 2 : log.printf(" successfully read %lu DeltaF values\n",deltaF_name_.size());
325 2 : if(NumParallel_>1) {
326 2 : all_deltaF_=deltaF_;
327 : }
328 : } else { //get just deltaFs names
329 8 : ifile.scanFieldList(deltaF_name_);
330 8 : plumed_massert(deltaF_name_.size()>=4,"RESTART - fewer than expected FIELDS found in '"+deltaFsFileName+"' file");
331 8 : plumed_massert(deltaF_name_[deltaF_name_.size()-1]=="print_stride","RESTART - coult not find expected FIELDS in '"+deltaFsFileName+"' file");
332 8 : plumed_massert(deltaF_name_[0]=="time","RESTART - coult not find expected FIELDS in '"+deltaFsFileName+"' file");
333 8 : plumed_massert(deltaF_name_[1]=="rct","RESTART - coult not find expected FIELDS in '"+deltaFsFileName+"' file");
334 : deltaF_name_.pop_back();
335 : deltaF_name_.erase(deltaF_name_.begin(),deltaF_name_.begin()+2);
336 : std::size_t pos=5; //each name starts with "DeltaF"
337 22 : for(unsigned j=0; j<ncv_; j++) {
338 14 : pos=deltaF_name_[0].find("_",pos+1); //checking only first one, hopefully is enough
339 : }
340 8 : plumed_massert(pos<deltaF_name_[0].length(),"RESTART - fewer '_' than expected in DeltaF fields: did you remove any CV?");
341 8 : pos=deltaF_name_[0].find("_",pos+1);
342 8 : plumed_massert(pos>deltaF_name_[0].length(),"RESTART - more '_' than expected in DeltaF fields: did you add new CV?");
343 : }
344 : //get lambdas, init ECVs and Link them
345 10 : deltaF_size_=deltaF_name_.size();
346 525 : auto getLambdaName=[](const std::string& name,const unsigned start,const unsigned dim) {
347 : std::size_t pos_start=5; //each name starts with "DeltaF"
348 1068 : for(unsigned j=0; j<=start; j++) {
349 543 : pos_start=name.find("_",pos_start+1);
350 : }
351 : std::size_t pos_end=pos_start;
352 1527 : for(unsigned j=0; j<dim; j++) {
353 1002 : pos_end=name.find("_",pos_end+1);
354 : }
355 525 : pos_start++; //do not include heading "_"
356 525 : return name.substr(pos_start,pos_end-pos_start);
357 : };
358 10 : unsigned index_j=ncv_;
359 : unsigned sizeSkip=1;
360 22 : for(int l=pntrToECVsClass_.size()-1; l>=0; l--) {
361 12 : const unsigned dim_l=pntrToECVsClass_[l]->getNumberOfArguments();
362 12 : index_j-=dim_l;
363 12 : std::vector<std::string> lambdas_l(1);
364 12 : lambdas_l[0]=getLambdaName(deltaF_name_[0],index_j,dim_l);
365 523 : for(unsigned i=sizeSkip; i<deltaF_size_; i+=sizeSkip) {
366 513 : std::string tmp_lambda=getLambdaName(deltaF_name_[i],index_j,dim_l);
367 513 : if(tmp_lambda==lambdas_l[0]) {
368 : break;
369 : }
370 511 : lambdas_l.push_back(tmp_lambda);
371 : }
372 12 : pntrToECVsClass_[l]->initECVs_restart(lambdas_l);
373 12 : sizeSkip*=lambdas_l.size();
374 12 : }
375 10 : plumed_massert(sizeSkip==deltaF_size_,"RESTART - this should not happen");
376 10 : init_linkECVs(); //link ECVs and initializes index_k_
377 10 : log.printf(" ->%4u DeltaFs in total\n",deltaF_size_);
378 10 : obs_steps_=0; //avoid initializing again
379 10 : if(stateRestart) {
380 2 : if(NumParallel_>1) {
381 2 : const unsigned start=(deltaF_size_/NumParallel_)*rank_+std::min(rank_,deltaF_size_%NumParallel_);
382 : unsigned iter=0;
383 34 : for(unsigned i=start; i<start+deltaF_.size(); i++) {
384 32 : deltaF_[iter++]=all_deltaF_[i];
385 : }
386 : }
387 : } else { //read each step
388 8 : counter_=1;
389 : unsigned count_lines=0;
390 8 : ifile.allowIgnoredFields(); //this allows for multiple restart, but without checking for consistency between them!
391 : double time;
392 48 : while(ifile.scanField("time",time)) { //only number of lines and last line is important
393 : unsigned restart_stride;
394 16 : ifile.scanField("print_stride",restart_stride);
395 16 : ifile.scanField("rct",rct_);
396 16 : if(NumParallel_==1) {
397 1014 : for(unsigned i=0; i<deltaF_size_; i++) {
398 998 : ifile.scanField(deltaF_name_[i],deltaF_[i]);
399 : }
400 : } else {
401 0 : const unsigned start=(deltaF_size_/NumParallel_)*rank_+std::min(rank_,deltaF_size_%NumParallel_);
402 : unsigned iter=0;
403 0 : for(unsigned i=start; i<start+deltaF_.size(); i++) {
404 0 : ifile.scanField(deltaF_name_[i],deltaF_[iter++]);
405 : }
406 : }
407 16 : ifile.scanField();
408 16 : if(count_lines>0) {
409 8 : counter_+=restart_stride;
410 : }
411 16 : count_lines++;
412 : }
413 8 : counter_*=NumWalkers_;
414 8 : log.printf(" successfully read %u lines, up to t=%g\n",count_lines,time);
415 : }
416 10 : ifile.reset(false);
417 10 : ifile.close();
418 : } else { //same behaviour as METAD
419 0 : plumed_merror("RESTART requested, but file '"+restartFileName+"' was not found!\n Set RESTART=NO or provide a restart file");
420 : }
421 10 : if(NumWalkers_>1) { //make sure that all walkers are doing the same thing
422 2 : std::vector<unsigned long long> all_counter(NumWalkers_);
423 2 : if(comm.Get_rank()==0) {
424 2 : multi_sim_comm.Allgather(counter_,all_counter);
425 : }
426 2 : comm.Bcast(all_counter,0);
427 : bool same_number_of_steps=true;
428 4 : for(unsigned w=1; w<NumWalkers_; w++)
429 2 : if(all_counter[0]!=all_counter[w]) {
430 : same_number_of_steps=false;
431 : }
432 2 : plumed_massert(same_number_of_steps,"RESTART - not all walkers are reading the same file!");
433 : }
434 30 : } else if(restartFileName.length()>0) {
435 0 : log.printf(" +++ WARNING +++ the provided STATE_RFILE will be ignored, since RESTART was not requested\n");
436 : }
437 :
438 : //sync all walkers to avoid opening files before reding is over (see also METAD)
439 30 : comm.Barrier();
440 30 : if(comm.Get_rank()==0 && walkers_mpi) {
441 4 : multi_sim_comm.Barrier();
442 : }
443 :
444 : //setup DeltaFs file
445 30 : deltaFsOfile_.link(*this);
446 30 : if(NumWalkers_>1) {
447 4 : if(walker_rank_>0) {
448 : deltaFsFileName="/dev/null"; //only first walker writes on file
449 : }
450 8 : deltaFsOfile_.enforceSuffix("");
451 : }
452 30 : deltaFsOfile_.open(deltaFsFileName);
453 30 : if(fmt.length()>0) {
454 60 : deltaFsOfile_.fmtField(" "+fmt);
455 : }
456 : deltaFsOfile_.setHeavyFlush(); //do I need it?
457 30 : deltaFsOfile_.addConstantField("print_stride");
458 30 : deltaFsOfile_.printField("print_stride",print_stride_);
459 :
460 : //open file for storing state
461 30 : if(wStateStride_!=0) {
462 6 : stateOfile_.link(*this);
463 6 : if(NumWalkers_>1) {
464 0 : if(walker_rank_>0) {
465 : stateFileName="/dev/null"; //only first walker writes on file
466 : }
467 0 : stateOfile_.enforceSuffix("");
468 : }
469 6 : stateOfile_.open(stateFileName);
470 6 : if(fmt.length()>0) {
471 12 : stateOfile_.fmtField(" "+fmt);
472 : }
473 : }
474 :
475 : //add output components
476 30 : if(calc_work_) {
477 12 : addComponent("work");
478 12 : componentIsNotPeriodic("work");
479 : }
480 :
481 : //printing some info
482 30 : log.printf(" updating the bias with PACE = %u\n",stride_);
483 30 : log.printf(" initial unbiased OBSERVATION_STEPS = %u (in units of PACE)\n",obs_steps_);
484 30 : if(wStateStride_>0) {
485 5 : log.printf(" state checkpoints are written on file %s every %d MD steps\n",stateFileName.c_str(),wStateStride_);
486 : }
487 30 : if(wStateStride_==-1) {
488 1 : log.printf(" state checkpoints are written on file '%s' only on CPT events (or never if MD code does define them!)\n",stateFileName.c_str());
489 : }
490 30 : if(walkers_mpi) {
491 4 : log.printf(" -- WALKERS_MPI: if multiple replicas are present, they will share the same bias via MPI\n");
492 : }
493 30 : if(NumWalkers_>1) {
494 4 : log.printf(" using multiple walkers\n");
495 4 : log.printf(" number of walkers: %u\n",NumWalkers_);
496 4 : log.printf(" walker rank: %u\n",walker_rank_);
497 : }
498 30 : int mw_warning=0;
499 30 : if(!walkers_mpi && comm.Get_rank()==0 && multi_sim_comm.Get_size()>(int)NumWalkers_) {
500 0 : mw_warning=1;
501 : }
502 30 : comm.Bcast(mw_warning,0);
503 30 : if(mw_warning) { //log.printf messes up with comm, so never use it without Bcast!
504 0 : log.printf(" +++ WARNING +++ multiple replicas will NOT communicate unless the flag WALKERS_MPI is used\n");
505 : }
506 30 : if(NumParallel_>1) {
507 2 : log.printf(" using multiple MPI processes per simulation: %u\n",NumParallel_);
508 : }
509 30 : if(NumOMP_>1) {
510 25 : log.printf(" using multiple OpenMP threads per simulation: %u\n",NumOMP_);
511 : }
512 30 : if(serial) {
513 5 : log.printf(" -- SERIAL: no loop parallelization, despite %d MPI processes and %u OpenMP threads available\n",comm.Get_size(),OpenMP::getNumThreads());
514 : }
515 30 : log.printf(" Bibliography: ");
516 60 : log<<plumed.cite("M. Invernizzi, P.M. Piaggi, and M. Parrinello, Phys. Rev. X 10, 041034 (2020)");
517 30 : log.printf("\n");
518 30 : }
519 :
520 1490 : void OPESexpanded::calculate() {
521 1490 : if(deltaF_size_==0) { //no bias before initialization
522 325 : return;
523 : }
524 :
525 : //get diffMax, to avoid over/underflow
526 1165 : double diffMax=-std::numeric_limits<double>::max();
527 1165 : #pragma omp parallel num_threads(NumOMP_)
528 : {
529 : #pragma omp for reduction(max:diffMax)
530 : for(unsigned i=0; i<deltaF_.size(); i++) {
531 : diff_[i]=(-getExpansion(i)+deltaF_[i]/kbt_);
532 : if(diff_[i]>diffMax) {
533 : diffMax=diff_[i];
534 : }
535 : }
536 : }
537 1165 : if(NumParallel_>1) {
538 102 : comm.Max(diffMax);
539 : }
540 :
541 : //calculate the bias and the forces
542 1165 : double sum=0;
543 1165 : std::vector<double> der_sum_cv(ncv_,0);
544 1165 : if(NumOMP_==1) {
545 2730 : for(unsigned i=0; i<deltaF_.size(); i++) {
546 2520 : double add_i=std::exp(diff_[i]-diffMax);
547 2520 : sum+=add_i;
548 : //set derivatives
549 6960 : for(unsigned j=0; j<ncv_; j++) {
550 4440 : der_sum_cv[j]-=derECVs_[j][index_k_[i][j]]*add_i;
551 : }
552 : }
553 : } else {
554 955 : #pragma omp parallel num_threads(NumOMP_)
555 : {
556 : std::vector<double> omp_der_sum_cv(ncv_,0);
557 : #pragma omp for reduction(+:sum) nowait
558 : for(unsigned i=0; i<deltaF_.size(); i++) {
559 : double add_i=std::exp(diff_[i]-diffMax);
560 : sum+=add_i;
561 : //set derivatives
562 : for(unsigned j=0; j<ncv_; j++) {
563 : omp_der_sum_cv[j]-=derECVs_[j][index_k_[i][j]]*add_i;
564 : }
565 : }
566 : #pragma omp critical
567 : for(unsigned j=0; j<ncv_; j++) {
568 : der_sum_cv[j]+=omp_der_sum_cv[j];
569 : }
570 : }
571 : }
572 1165 : if(NumParallel_>1) {
573 : //each MPI process has part of the full deltaF_ vector, so must Sum
574 102 : comm.Sum(sum);
575 102 : comm.Sum(der_sum_cv);
576 : }
577 :
578 : //set bias and forces
579 1165 : const double bias=-kbt_*(diffMax+std::log(sum/deltaF_size_));
580 1165 : setBias(bias);
581 3163 : for(unsigned j=0; j<ncv_; j++) {
582 1998 : setOutputForce(j,kbt_*der_sum_cv[j]/sum);
583 : }
584 : }
585 :
586 1490 : void OPESexpanded::update() {
587 1490 : if(isFirstStep_) { //skip very first step, as in METAD
588 30 : isFirstStep_=false;
589 30 : if(obs_steps_!=1) { //if obs_steps_==1 go on with initialization
590 : return;
591 : }
592 : }
593 1464 : if(getStep()%stride_==0) {
594 739 : if(obs_steps_>0) {
595 463 : for(unsigned j=0; j<ncv_; j++) {
596 304 : obs_cvs_[counter_*ncv_+j]=getArgument(j);
597 : }
598 159 : counter_++;
599 159 : if(counter_==obs_steps_) {
600 20 : log.printf("\nAction %s\n",getName().c_str());
601 20 : init_fromObs();
602 20 : log.printf("Finished initialization\n\n");
603 20 : counter_=NumWalkers_; //all preliminary observations count 1
604 20 : obs_steps_=0; //no more observation
605 : }
606 159 : return;
607 : }
608 :
609 : //update averages
610 580 : const double current_bias=getOutputQuantity(0); //the first value is always the bias
611 580 : if(NumWalkers_==1) {
612 500 : updateDeltaF(current_bias);
613 : } else {
614 80 : std::vector<double> cvs(ncv_);
615 240 : for(unsigned j=0; j<ncv_; j++) {
616 160 : cvs[j]=getArgument(j);
617 : }
618 80 : std::vector<double> all_bias(NumWalkers_);
619 80 : std::vector<double> all_cvs(NumWalkers_*ncv_);
620 80 : if(comm.Get_rank()==0) {
621 80 : multi_sim_comm.Allgather(current_bias,all_bias);
622 80 : multi_sim_comm.Allgather(cvs,all_cvs);
623 : }
624 80 : comm.Bcast(all_bias,0);
625 80 : comm.Bcast(all_cvs,0);
626 240 : for(unsigned w=0; w<NumWalkers_; w++) {
627 : //calculate ECVs
628 160 : unsigned index_wj=w*ncv_;
629 380 : for(unsigned k=0; k<pntrToECVsClass_.size(); k++) {
630 220 : pntrToECVsClass_[k]->calculateECVs(&all_cvs[index_wj]);
631 220 : index_wj+=pntrToECVsClass_[k]->getNumberOfArguments();
632 : }
633 160 : updateDeltaF(all_bias[w]);
634 : }
635 : }
636 :
637 : //write DeltaFs to file
638 580 : if((counter_/NumWalkers_-1)%print_stride_==0) {
639 44 : printDeltaF();
640 : }
641 :
642 : //calculate work if requested
643 580 : if(calc_work_) {
644 : //some copy and paste from calculate()
645 : //get diffMax, to avoid over/underflow
646 110 : double diffMax=-std::numeric_limits<double>::max();
647 110 : #pragma omp parallel num_threads(NumOMP_)
648 : {
649 : #pragma omp for reduction(max:diffMax)
650 : for(unsigned i=0; i<deltaF_.size(); i++) {
651 : diff_[i]=(-getExpansion(i)+deltaF_[i]/kbt_);
652 : if(diff_[i]>diffMax) {
653 : diffMax=diff_[i];
654 : }
655 : }
656 : }
657 110 : if(NumParallel_>1) {
658 50 : comm.Max(diffMax);
659 : }
660 : //calculate the bias
661 110 : double sum=0;
662 110 : #pragma omp parallel num_threads(NumOMP_)
663 : {
664 : #pragma omp for reduction(+:sum) nowait
665 : for(unsigned i=0; i<deltaF_.size(); i++) {
666 : sum+=std::exp(diff_[i]-diffMax);
667 : }
668 : }
669 110 : if(NumParallel_>1) {
670 50 : comm.Sum(sum);
671 : }
672 110 : const double new_bias=-kbt_*(diffMax+std::log(sum/deltaF_size_));
673 : //accumulate work
674 110 : work_+=new_bias-current_bias;
675 220 : getPntrToComponent("work")->set(work_);
676 : }
677 : }
678 :
679 : //dump state if requested
680 1305 : if( (wStateStride_>0 && getStep()%wStateStride_==0) || (wStateStride_==-1 && getCPT()) ) {
681 11 : dumpStateToFile();
682 : }
683 : }
684 :
685 30 : void OPESexpanded::init_pntrToECVsClass() {
686 30 : std::vector<opes::ExpansionCVs*> all_pntrToECVsClass=plumed.getActionSet().select<opes::ExpansionCVs*>();
687 30 : plumed_massert(all_pntrToECVsClass.size()>0,"no Expansion CVs found");
688 67 : for(unsigned j=0; j<ncv_; j++) {
689 74 : std::string error_notECV("all the ARGs of "+getName()+" must be Expansion Collective Variables (ECV)");
690 37 : const unsigned dot_pos=getPntrToArgument(j)->getName().find(".");
691 37 : plumed_massert(dot_pos<getPntrToArgument(j)->getName().size(),error_notECV+", thus contain a dot in the name");
692 37 : unsigned foundECV_l=all_pntrToECVsClass.size();
693 44 : for(unsigned l=0; l<all_pntrToECVsClass.size(); l++) {
694 44 : if(getPntrToArgument(j)->getName().substr(0,dot_pos)==all_pntrToECVsClass[l]->getLabel()) {
695 : foundECV_l=l;
696 37 : pntrToECVsClass_.push_back(all_pntrToECVsClass[l]);
697 37 : std::string missing_arg="some ECV component is missing from ARG";
698 37 : plumed_massert(j+all_pntrToECVsClass[l]->getNumberOfArguments()<=getNumberOfArguments(),missing_arg);
699 90 : for(unsigned h=0; h<all_pntrToECVsClass[l]->getNumberOfArguments(); h++) {
700 53 : std::string argName=getPntrToArgument(j+h)->getName();
701 53 : std::string expectedECVname=all_pntrToECVsClass[l]->getComponentsVector()[h];
702 53 : plumed_massert(argName==expectedECVname,missing_arg+", or is in wrong order: given ARG="+argName+" expected ARG="+expectedECVname);
703 : }
704 37 : j+=all_pntrToECVsClass[l]->getNumberOfArguments()-1;
705 : break;
706 : }
707 : }
708 37 : plumed_massert(foundECV_l<all_pntrToECVsClass.size(),error_notECV);
709 : }
710 67 : for(unsigned l=0; l<pntrToECVsClass_.size(); l++)
711 44 : for(unsigned ll=l+1; ll<pntrToECVsClass_.size(); ll++) {
712 7 : plumed_massert(pntrToECVsClass_[l]->getLabel()!=pntrToECVsClass_[ll]->getLabel(),"cannot use same ECV twice");
713 : }
714 30 : }
715 :
716 30 : void OPESexpanded::init_linkECVs() {
717 : //TODO It should be possible to make all of this more straightforward (and probably also faster):
718 : // - get rid of index_k_, making it trivial for each ECV
719 : // - store the ECVs_ and derECVs_ vectors here as a contiguous vector, and use pointers in the ECV classes
720 : // Some caveats:
721 : // - ECVmultiThermalBaric has a nontrivial index_k_ to avoid duplicates. use duplicates instead
722 : // - can the ECVs be MPI parallel or it's too complicated?
723 30 : plumed_massert(deltaF_size_>0,"must set deltaF_size_ before calling init_linkECVs()");
724 30 : if(NumParallel_==1) {
725 28 : deltaF_.resize(deltaF_size_);
726 : } else {
727 2 : const unsigned extra=(rank_<(deltaF_size_%NumParallel_)?1:0);
728 2 : deltaF_.resize(deltaF_size_/NumParallel_+extra);
729 : //these are used when printing deltaF_ to file
730 2 : all_deltaF_.resize(deltaF_size_);
731 2 : all_size_.resize(NumParallel_,deltaF_size_/NumParallel_);
732 2 : disp_.resize(NumParallel_);
733 4 : for(unsigned r=0; r<NumParallel_-1; r++) {
734 2 : if(r<deltaF_size_%NumParallel_) {
735 0 : all_size_[r]++;
736 : }
737 2 : disp_[r+1]=disp_[r]+all_size_[r];
738 : }
739 : }
740 30 : diff_.resize(deltaF_.size());
741 30 : ECVs_.resize(ncv_);
742 30 : derECVs_.resize(ncv_);
743 30 : index_k_.resize(deltaF_.size(),std::vector<unsigned>(ncv_));
744 : unsigned index_j=0;
745 30 : unsigned sizeSkip=deltaF_size_;
746 67 : for(unsigned l=0; l<pntrToECVsClass_.size(); l++) {
747 37 : std::vector< std::vector<unsigned> > l_index_k(pntrToECVsClass_[l]->getIndex_k());
748 37 : plumed_massert(deltaF_size_%l_index_k.size()==0,"buggy ECV: mismatch between getTotNumECVs() and getIndex_k().size()");
749 37 : plumed_massert(l_index_k[0].size()==pntrToECVsClass_[l]->getNumberOfArguments(),"buggy ECV: mismatch between number of ARG and underlying CVs");
750 37 : sizeSkip/=l_index_k.size();
751 90 : for(unsigned h=0; h<pntrToECVsClass_[l]->getNumberOfArguments(); h++) {
752 53 : ECVs_[index_j+h]=pntrToECVsClass_[l]->getPntrToECVs(h);
753 53 : derECVs_[index_j+h]=pntrToECVsClass_[l]->getPntrToDerECVs(h);
754 53 : if(NumParallel_==1) {
755 45589 : for(unsigned i=0; i<deltaF_size_; i++) {
756 45540 : index_k_[i][index_j+h]=l_index_k[(i/sizeSkip)%l_index_k.size()][h];
757 : }
758 : } else {
759 4 : const unsigned start=(deltaF_size_/NumParallel_)*rank_+std::min(rank_,deltaF_size_%NumParallel_);
760 : unsigned iter=0;
761 68 : for(unsigned i=start; i<start+deltaF_.size(); i++) {
762 64 : index_k_[iter++][index_j+h]=l_index_k[(i/sizeSkip)%l_index_k.size()][h];
763 : }
764 : }
765 : }
766 37 : index_j+=pntrToECVsClass_[l]->getNumberOfArguments();
767 37 : }
768 30 : plumed_massert(sizeSkip==1,"this should not happen!");
769 30 : }
770 :
771 20 : void OPESexpanded::init_fromObs() { //This could probably be faster and/or require less memory...
772 : //in case of multiple walkers gather all the statistics
773 20 : if(NumWalkers_>1) {
774 2 : std::vector<double> all_obs_cv(ncv_*obs_steps_*NumWalkers_);
775 2 : if(comm.Get_rank()==0) {
776 2 : multi_sim_comm.Allgather(obs_cvs_,all_obs_cv);
777 : }
778 2 : comm.Bcast(all_obs_cv,0);
779 2 : obs_cvs_=all_obs_cv; //could this lead to memory issues?
780 2 : obs_steps_*=NumWalkers_;
781 : }
782 :
783 : //initialize ECVs from observations
784 : unsigned index_j=0;
785 20 : deltaF_size_=1;
786 45 : for(unsigned l=0; l<pntrToECVsClass_.size(); l++) {
787 25 : pntrToECVsClass_[l]->initECVs_observ(obs_cvs_,ncv_,index_j);
788 25 : deltaF_size_*=pntrToECVsClass_[l]->getTotNumECVs(); //ECVs from different exansions will be combined
789 25 : index_j+=pntrToECVsClass_[l]->getNumberOfArguments();
790 : }
791 20 : plumed_massert(index_j==getNumberOfArguments(),"mismatch between number of linked CVs and number of ARG");
792 : //link ECVs and initialize index_k_, mapping each deltaF to a single ECVs set
793 20 : init_linkECVs();
794 :
795 : //initialize deltaF_ from obs
796 : //for the first point, t=0, the ECVs are calculated by initECVs_observ, setting also any initial guess
797 : index_j=0;
798 12379 : for(unsigned i=0; i<deltaF_.size(); i++)
799 56923 : for(unsigned j=0; j<ncv_; j++) {
800 44564 : deltaF_[i]+=kbt_*ECVs_[j][index_k_[i][j]];
801 : }
802 179 : for(unsigned t=1; t<obs_steps_; t++) { //starts from t=1
803 : unsigned index_j=0;
804 383 : for(unsigned l=0; l<pntrToECVsClass_.size(); l++) {
805 224 : pntrToECVsClass_[l]->calculateECVs(&obs_cvs_[t*ncv_+index_j]);
806 224 : index_j+=pntrToECVsClass_[l]->getNumberOfArguments();
807 : }
808 102677 : for(unsigned i=0; i<deltaF_.size(); i++) {
809 102518 : const double diff_i=(-getExpansion(i)+deltaF_[i]/kbt_-std::log(t));
810 102518 : if(diff_i>0) { //save exp from overflow
811 16071 : deltaF_[i]-=kbt_*(diff_i+std::log1p(std::exp(-diff_i))+std::log1p(-1./(1.+t)));
812 : } else {
813 86447 : deltaF_[i]-=kbt_*(std::log1p(std::exp(diff_i))+std::log1p(-1./(1.+t)));
814 : }
815 : }
816 : }
817 : obs_cvs_.clear();
818 :
819 : //set deltaF_name_
820 20 : deltaF_name_.resize(deltaF_size_,"DeltaF");
821 20 : unsigned sizeSkip=deltaF_size_;
822 45 : for(unsigned l=0; l<pntrToECVsClass_.size(); l++) {
823 25 : std::vector<std::string> lambdas_l=pntrToECVsClass_[l]->getLambdas();
824 25 : plumed_massert(lambdas_l.size()==pntrToECVsClass_[l]->getTotNumECVs(),"buggy ECV: mismatch between getTotNumECVs() and getLambdas().size()");
825 25 : sizeSkip/=lambdas_l.size();
826 22457 : for(unsigned i=0; i<deltaF_size_; i++) {
827 44864 : deltaF_name_[i]+="_"+lambdas_l[(i/sizeSkip)%lambdas_l.size()];
828 : }
829 25 : }
830 :
831 : //print initialization to file
832 20 : log.printf(" ->%4u DeltaFs in total\n",deltaF_size_);
833 20 : printDeltaF();
834 20 : }
835 :
836 64 : void OPESexpanded::printDeltaF() {
837 64 : deltaFsOfile_.printField("time",getTime());
838 64 : deltaFsOfile_.printField("rct",rct_);
839 64 : if(NumParallel_==1) {
840 23988 : for(unsigned i=0; i<deltaF_.size(); i++) {
841 23926 : deltaFsOfile_.printField(deltaF_name_[i],deltaF_[i]);
842 : }
843 : } else {
844 2 : comm.Allgatherv(deltaF_,all_deltaF_,&all_size_[0],&disp_[0]); //can we avoid using this big vector?
845 66 : for(unsigned i=0; i<deltaF_size_; i++) {
846 64 : deltaFsOfile_.printField(deltaF_name_[i],all_deltaF_[i]);
847 : }
848 : }
849 64 : deltaFsOfile_.printField();
850 64 : }
851 :
852 11 : void OPESexpanded::dumpStateToFile() {
853 : //rewrite header or rewind file
854 11 : if(storeOldStates_) {
855 3 : stateOfile_.clearFields();
856 8 : } else if(walker_rank_==0) {
857 8 : stateOfile_.rewind();
858 : }
859 : //define fields
860 11 : stateOfile_.addConstantField("time");
861 11 : stateOfile_.addConstantField("counter");
862 11 : stateOfile_.addConstantField("rct");
863 : //print
864 11 : stateOfile_.printField("time",getTime());
865 11 : stateOfile_.printField("counter",counter_);
866 11 : stateOfile_.printField("rct",rct_);
867 11 : if(NumParallel_>1) {
868 0 : comm.Allgatherv(deltaF_,all_deltaF_,&all_size_[0],&disp_[0]); //can we avoid using this big vector?
869 : }
870 240 : for(unsigned i=0; i<deltaF_size_; i++) {
871 : std::size_t pos_start=7; //skip "DeltaF_"
872 687 : for(unsigned j=0; j<ncv_; j++) {
873 : plumed_dbg_massert(pos_start>6,"not enought _ in deltaF_name_"+std::to_string(i-1)+" string?");
874 458 : const std::size_t pos_end=deltaF_name_[i].find("_",pos_start);
875 916 : stateOfile_.printField(getPntrToArgument(j)->getName()," "+deltaF_name_[i].substr(pos_start,pos_end-pos_start));
876 458 : pos_start=pos_end+1;
877 : }
878 229 : if(NumParallel_==1) {
879 458 : stateOfile_.printField("DeltaF",deltaF_[i]);
880 : } else {
881 0 : stateOfile_.printField("DeltaF",all_deltaF_[i]);
882 : }
883 229 : stateOfile_.printField();
884 : }
885 : //make sure file is written even if small
886 11 : if(!storeOldStates_) {
887 8 : stateOfile_.flush();
888 : }
889 11 : }
890 :
891 660 : void OPESexpanded::updateDeltaF(double bias) {
892 : plumed_dbg_massert(counter_>0,"deltaF_ must be initialized");
893 660 : counter_++;
894 660 : const double arg=(bias-rct_)/kbt_-std::log(counter_-1.);
895 : double increment;
896 660 : if(arg>0) { //save exp from overflow
897 28 : increment=kbt_*(arg+std::log1p(std::exp(-arg)));
898 : } else {
899 632 : increment=kbt_*(std::log1p(std::exp(arg)));
900 : }
901 660 : #pragma omp parallel num_threads(NumOMP_)
902 : {
903 : #pragma omp for
904 : for(unsigned i=0; i<deltaF_.size(); i++) {
905 : const double diff_i=(-getExpansion(i)+(bias-rct_+deltaF_[i])/kbt_-std::log(counter_-1.));
906 : if(diff_i>0) { //save exp from overflow
907 : deltaF_[i]+=increment-kbt_*(diff_i+std::log1p(std::exp(-diff_i)));
908 : } else {
909 : deltaF_[i]+=increment-kbt_*std::log1p(std::exp(diff_i));
910 : }
911 : }
912 : }
913 660 : rct_+=increment+kbt_*std::log1p(-1./counter_);
914 660 : }
915 :
916 644469 : double OPESexpanded::getExpansion(unsigned i) const {
917 : double expansion=0;
918 3003062 : for(unsigned j=0; j<ncv_; j++) {
919 2358593 : expansion+=ECVs_[j][index_k_[i][j]]; //the index_k could be trivially guessed for most ECVs, but unfourtunately not all
920 : }
921 644469 : return expansion;
922 : }
923 :
924 : }
925 : }
|