Line data Source code
1 : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 : Copyright (c) 2018-2023 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 "core/ActionAtomistic.h"
23 : #include "core/ActionWithValue.h"
24 : #include "core/ActionPilot.h"
25 : #include "core/ActionRegister.h"
26 : #include "tools/Tools.h"
27 : #include "tools/PlumedHandle.h"
28 : #include "core/PlumedMain.h"
29 : #include <cstring>
30 : #ifdef __PLUMED_HAS_DLOPEN
31 : #include <dlfcn.h>
32 : #endif
33 :
34 : #include <iostream>
35 :
36 : namespace PLMD {
37 : namespace generic {
38 :
39 : //+PLUMEDOC GENERIC PLUMED
40 : /*
41 : Embed a separate PLUMED instance.
42 :
43 : This command can be used to embed a separate PLUMED instance.
44 : Only required atoms will be passed to that instance, using an interface
45 : that is similar to the one used when calling PLUMED from the NAMD engine.
46 :
47 : Notice that the two instances are running in the same UNIX process, so that they cannot be perfectly isolated.
48 : However, most of the features are expected to work correctly.
49 :
50 : Notes:
51 : - The \ref LOAD action will not work correctly since registers will be shared among the two instances.
52 : In particular, the loaded actions will be visible to both guest and host irrespective of where they are loaded from.
53 : This can be fixed and will probably be fixed in a later version.
54 : - `CHDIR` is not thread safe.
55 : However, in most implementations there will be a single process running PLUMED, with perhaps multiple OpenMP threads
56 : spawn in order to parallelize the calculation of individual variables. So, this is likely not a problem.
57 : - MPI is working correctly. However, notice that the guest PLUMED will always run with a single process.
58 : Multiple replicas should be handled correctly.
59 :
60 : As an advanced feature, one can use the option `KERNEL` to select the version of the guest PLUMED instance.
61 : In particular, an empty `KERNEL` (default) implies that the guest PLUMED instance is the same as the host one
62 : (no library is loaded).
63 : On the other hand, `KERNEL=/path/to/libplumedKernel.so` will allow specifying a library to be loaded for the
64 : guest instance.
65 : In addition to those mentioned above, this feature has limitations mostly related to
66 : clashes in the symbols defined in the different instances of the PLUMED library:
67 : - On OSX, if you load a KERNEL with version >=2.5 there should be no problem thanks to the use
68 : of two-level namespaces.
69 : - On OSX, if you load a KERNEL with version <=2.4 there should be clashes in symbol resolution.
70 : The only possible workarounds are:
71 : - If you are are using PLUMED with an MD code, it should be patched with `--runtime` and you should
72 : `export PLUMED_LOAD_NAMESPACE=LOCAL` before starting the MD engine.
73 : - If you are using PLUMED driver, you should launch the `plumed-runtime` executable (contained in the
74 : `prefix/lib/plumed/` directory), export `PLUMED_KERNEL` equal to the path of the host kernel library
75 : (as usual in runtime loading) and `export PLUMED_LOAD_NAMESPACE=LOCAL` before launching `plumed-runtime driver`.
76 : - On Linux, any `KERNEL` should in principle work correctly. To achieve namespace separation we are loading
77 : the guest kernel with `RTLD_DEEPBIND`. However, this might create difficult to track problems in other linked libraries.
78 : - On Unix systems where `RTLD_DEEPBIND` is not available kernels will not load correctly.
79 : - In general, there might be unexpected crashes. Particularly difficult are situations where different
80 : kernels were compiled with different libraries.
81 :
82 : A possible solution for the symbol clashes (not tested) could be to recompile the alternative PLUMED
83 : versions using separate C++ namespaces (e.g. `./configure CPPFLAGS=-DPLMD=PLMD_2_3`).
84 :
85 : \todo
86 : - Add support for multiple time stepping (`STRIDE` different from 1).
87 : - Add the possibility to import CVs calculated in the host PLUMED instance into the guest PLUMED instance.
88 : Will be possible after \issue{83} will be closed.
89 : - Add the possibility to export CVs calculated in the guest PLUMED instance into the host PLUMED instance.
90 : Could be implemented using the `DataFetchingObject` class.
91 :
92 : \par Examples
93 :
94 : Here an example plumed file:
95 : \plumedfile
96 : # plumed.dat
97 : p: PLUMED FILE=plumed2.dat
98 : PRINT ARG=p.bias FILE=COLVAR
99 : \endplumedfile
100 : `plumed2.dat` can be an arbitrary plumed input file, for instance
101 : \plumedfile
102 : #SETTINGS FILENAME=plumed2.dat
103 : # plumed2.dat
104 : d: DISTANCE ATOMS=1,10
105 : RESTRAINT ARG=d KAPPA=10 AT=2
106 : \endplumedfile
107 :
108 : Now a more useful example.
109 : Imagine that you ran simulations using two different PLUMED input files.
110 : The files are long and complex and there are some clashes in the name of the variables (that is: same names
111 : are used in both files, same files are written, etc). In addition, files might have been written using different units (see \ref UNITS`).
112 : If you want to run a single simulation with a bias potential
113 : that is the sum of the two bias potentials, you can:
114 : - Place the two input files, as well as all the files required by plumed, in separate directories `directory1` and `directory2`.
115 : - Run with the following input file in the parent directory:
116 : \plumedfile
117 : # plumed.dat
118 : PLUMED FILE=plumed.dat CHDIR=directory1
119 : PLUMED FILE=plumed.dat CHDIR=directory2
120 : \endplumedfile
121 :
122 : */
123 : //+ENDPLUMEDOC
124 :
125 : class Plumed:
126 : public ActionAtomistic,
127 : public ActionWithValue,
128 : public ActionPilot {
129 : /// True on root processor
130 : const bool root;
131 : /// Separate directory.
132 : const std::string directory;
133 : /// Interface to underlying plumed object.
134 : PlumedHandle p;
135 : /// API number.
136 : const int API;
137 : /// Self communicator
138 : Communicator comm_self;
139 : /// Intercommunicator
140 : Communicator intercomm;
141 : /// Detect first usage.
142 : bool first=true;
143 : /// Stop flag, used to stop e.g. in committor analysis
144 : int stop=0;
145 : /// Index of requested atoms.
146 : std::vector<int> index;
147 : /// Masses of requested atoms.
148 : std::vector<double> masses;
149 : /// Charges of requested atoms.
150 : std::vector<double> charges;
151 : /// Forces on requested atoms.
152 : std::vector<double> forces;
153 : /// Requested positions.
154 : std::vector<double> positions;
155 : /// Applied virial.
156 : Tensor virial;
157 : public:
158 : /// Constructor.
159 : explicit Plumed(const ActionOptions&);
160 : /// Documentation.
161 : static void registerKeywords( Keywords& keys );
162 : void prepare() override;
163 : void calculate() override;
164 : void apply() override;
165 : void update() override;
166 0 : unsigned getNumberOfDerivatives() override {
167 0 : return 0;
168 : }
169 : };
170 :
171 13809 : PLUMED_REGISTER_ACTION(Plumed,"PLUMED")
172 :
173 16 : void Plumed::registerKeywords( Keywords& keys ) {
174 16 : Action::registerKeywords( keys );
175 16 : ActionPilot::registerKeywords( keys );
176 16 : ActionAtomistic::registerKeywords( keys );
177 32 : keys.add("compulsory","STRIDE","1","stride different from 1 are not supported yet");
178 32 : keys.add("optional","FILE","input file for the guest PLUMED instance");
179 32 : keys.add("optional","KERNEL","kernel to be used for the guest PLUMED instance (USE WITH CAUTION!)");
180 32 : keys.add("optional","LOG","log file for the guest PLUMED instance. By default the host log is used");
181 32 : keys.add("optional","CHDIR","run guest in a separate directory");
182 32 : keys.addFlag("NOREPLICAS",false,"run multiple replicas as isolated ones, without letting them know that the host has multiple replicas");
183 32 : keys.addOutputComponent("bias","default","the instantaneous value of the bias potential");
184 16 : }
185 :
186 12 : Plumed::Plumed(const ActionOptions&ao):
187 : Action(ao),
188 : ActionAtomistic(ao),
189 : ActionWithValue(ao),
190 : ActionPilot(ao),
191 24 : root(comm.Get_rank()==0),
192 24 : directory([&]() {
193 : std::string directory;
194 24 : parse("CHDIR",directory);
195 12 : if(directory.length()>0) {
196 0 : log<<" running on separate directory "<<directory<<"\n";
197 : }
198 12 : return directory;
199 : }()),
200 24 : p([&]() {
201 : std::string kernel;
202 24 : parse("KERNEL",kernel);
203 12 : if(kernel.length()==0) {
204 11 : log<<" using the current kernel\n";
205 11 : return PlumedHandle();
206 : } else {
207 1 : log<<" using the kernel "<<kernel<<"\n";
208 1 : return PlumedHandle::dlopen(kernel.c_str());
209 : }
210 : }()),
211 24 : API([&]() {
212 12 : int api=0;
213 24 : p.cmd("getApiVersion",&api);
214 12 : log<<" reported API version is "<<api<<"\n";
215 : // note: this is required in order to have cmd performCalcNoUpdate and cmd update
216 : // as a matter of fact, any version <2.5 will not even load due to namespace pollution
217 12 : plumed_assert(api>3) << "API>3 is required for the PLUMED action to work correctly\n";
218 12 : return api;
219 24 : }()) {
220 12 : Tools::DirectoryChanger directoryChanger(directory.c_str());
221 :
222 : bool noreplicas;
223 12 : parseFlag("NOREPLICAS",noreplicas);
224 : int nreps;
225 12 : if(root) {
226 6 : nreps=multi_sim_comm.Get_size();
227 : }
228 12 : comm.Bcast(nreps,0);
229 12 : if(nreps>1) {
230 6 : if(noreplicas) {
231 0 : log<<" running replicas as independent (no suffix used)\n";
232 : } else {
233 6 : log<<" running replicas as standard multi replic (with suffix)\n";
234 6 : if(root) {
235 6 : intercomm.Set_comm(&multi_sim_comm.Get_comm());
236 9 : p.cmd("GREX setMPIIntercomm",&intercomm.Get_comm());
237 9 : p.cmd("GREX setMPIIntracomm",&comm_self.Get_comm());
238 6 : p.cmd("GREX init");
239 : }
240 : }
241 : } else {
242 6 : if(noreplicas) {
243 0 : log<<" WARNING: flag NOREPLICAS ignored since we are running without replicas\n";
244 : }
245 : }
246 :
247 12 : int natoms=plumed.getAtoms().getNatoms();
248 :
249 12 : plumed_assert(getStride()==1) << "currently only supports STRIDE=1";
250 :
251 12 : double dt=getTimeStep();
252 :
253 : std::string file;
254 24 : parse("FILE",file);
255 12 : if(file.length()>0) {
256 12 : log<<" with input file "<<file<<"\n";
257 : } else {
258 0 : plumed_error() << "you must provide an input file\n";
259 : }
260 :
261 : bool inherited_logfile=false;
262 : std::string logfile;
263 24 : parse("LOG",logfile);
264 12 : if(logfile.length()>0) {
265 0 : log<<" with log file "<<logfile<<"\n";
266 0 : if(root) {
267 0 : p.cmd("setLogFile",logfile.c_str());
268 : }
269 12 : } else if(log.getFILE()) {
270 12 : log<<" with inherited log file\n";
271 12 : if(root) {
272 12 : p.cmd("setLog",log.getFILE());
273 : }
274 : inherited_logfile=true;
275 : } else {
276 0 : log<<" with log on stdout\n";
277 0 : if(root) {
278 0 : p.cmd("setLog",stdout);
279 : }
280 : }
281 :
282 12 : checkRead();
283 :
284 12 : if(root) {
285 12 : p.cmd("setMDEngine","plumed");
286 : }
287 :
288 12 : double engunits=plumed.getAtoms().getUnits().getEnergy();
289 12 : if(root) {
290 12 : p.cmd("setMDEnergyUnits",&engunits);
291 : }
292 :
293 12 : double lenunits=plumed.getAtoms().getUnits().getLength();
294 12 : if(root) {
295 12 : p.cmd("setMDLengthUnits",&lenunits);
296 : }
297 :
298 12 : double timunits=plumed.getAtoms().getUnits().getTime();
299 12 : if(root) {
300 12 : p.cmd("setMDTimeUnits",&timunits);
301 : }
302 :
303 12 : double chaunits=plumed.getAtoms().getUnits().getCharge();
304 12 : if(root) {
305 12 : p.cmd("setMDChargeUnits",&chaunits);
306 : }
307 12 : double masunits=plumed.getAtoms().getUnits().getMass();
308 12 : if(root) {
309 12 : p.cmd("setMDMassUnits",&masunits);
310 : }
311 :
312 12 : double kbt=plumed.getAtoms().getKbT();
313 12 : if(root) {
314 12 : p.cmd("setKbT",&kbt);
315 : }
316 :
317 12 : int res=0;
318 12 : if(getRestart()) {
319 0 : res=1;
320 : }
321 12 : if(root) {
322 12 : p.cmd("setRestart",&res);
323 : }
324 :
325 12 : if(root) {
326 12 : p.cmd("setNatoms",&natoms);
327 : }
328 12 : if(root) {
329 12 : p.cmd("setTimestep",&dt);
330 : }
331 12 : if(root) {
332 12 : p.cmd("setPlumedDat",file.c_str());
333 : }
334 :
335 12 : addComponentWithDerivatives("bias");
336 12 : componentIsNotPeriodic("bias");
337 :
338 12 : if(inherited_logfile) {
339 12 : log<<"++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n";
340 : }
341 12 : if(root) {
342 12 : p.cmd("init");
343 : }
344 12 : if(inherited_logfile) {
345 12 : log<<"++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n";
346 : }
347 12 : }
348 :
349 164 : void Plumed::prepare() {
350 164 : Tools::DirectoryChanger directoryChanger(directory.c_str());
351 164 : int step=getStep();
352 164 : if(root) {
353 178 : p.cmd("setStep",&step);
354 : }
355 164 : if(root) {
356 178 : p.cmd("prepareDependencies");
357 : }
358 164 : int ene=0;
359 164 : if(root) {
360 178 : p.cmd("isEnergyNeeded",&ene);
361 : }
362 164 : if(ene) {
363 0 : plumed_error()<<"It is not currently possible to use ENERGY in a guest PLUMED";
364 : }
365 164 : int n=0;
366 164 : if(root) {
367 178 : p.cmd("createFullList",&n);
368 : }
369 164 : const int *pointer=nullptr;
370 164 : if(root) {
371 178 : p.cmd("getFullList",&pointer);
372 : }
373 164 : bool redo=(index.size()!=n);
374 164 : if(first) {
375 : redo=true;
376 : }
377 164 : first=false;
378 164 : if(root && !redo)
379 4245 : for(int i=0; i<n; i++)
380 4178 : if(index[i]!=pointer[i]) {
381 : redo=true;
382 : break;
383 : };
384 164 : if(root && redo) {
385 22 : index.resize(n);
386 22 : masses.resize(n);
387 22 : forces.resize(3*n);
388 22 : positions.resize(3*n);
389 22 : charges.resize(n);
390 1043 : for(int i=0; i<n; i++) {
391 1021 : index[i]=pointer[i];
392 : };
393 44 : p.cmd("setAtomsNlocal",&n);
394 44 : p.cmd("setAtomsGatindex",index.data(),index.size());
395 : }
396 164 : if(root) {
397 178 : p.cmd("clearFullList");
398 : }
399 164 : int tmp=0;
400 164 : if(root && redo) {
401 22 : tmp=1;
402 : }
403 164 : comm.Bcast(tmp,0);
404 164 : if(tmp) {
405 34 : int s=index.size();
406 34 : comm.Bcast(s,0);
407 34 : if(!root) {
408 12 : index.resize(s);
409 : }
410 34 : comm.Bcast(index,0);
411 : std::vector<AtomNumber> numbers;
412 34 : numbers.reserve(index.size());
413 2138 : for(auto i : index) {
414 2104 : numbers.emplace_back(AtomNumber::index(i));
415 : }
416 34 : requestAtoms(numbers);
417 : }
418 164 : }
419 :
420 134 : void Plumed::calculate() {
421 134 : Tools::DirectoryChanger directoryChanger(directory.c_str());
422 134 : if(root) {
423 148 : p.cmd("setStopFlag",&stop);
424 : }
425 134 : Tensor box=getPbc().getBox();
426 134 : if(root) {
427 148 : p.cmd("setBox",&box[0][0],9);
428 : }
429 :
430 134 : virial.zero();
431 24875 : for(int i=0; i<forces.size(); i++) {
432 24741 : forces[i]=0.0;
433 : }
434 4238 : for(int i=0; i<masses.size(); i++) {
435 4104 : masses[i]=getMass(i);
436 : }
437 4238 : for(int i=0; i<charges.size(); i++) {
438 4104 : charges[i]=getCharge(i);
439 : }
440 :
441 134 : if(root) {
442 148 : p.cmd("setMasses",masses.data(),masses.size());
443 : }
444 134 : if(root) {
445 148 : p.cmd("setCharges",charges.data(),charges.size());
446 : }
447 134 : if(root) {
448 148 : p.cmd("setPositions",positions.data(),positions.size());
449 : }
450 134 : if(root) {
451 148 : p.cmd("setForces",forces.data(),forces.size());
452 : }
453 134 : if(root) {
454 148 : p.cmd("setVirial",&virial[0][0],9);
455 : }
456 :
457 :
458 134 : if(root)
459 4178 : for(unsigned i=0; i<getNumberOfAtoms(); i++) {
460 4104 : positions[3*i+0]=getPosition(i)[0];
461 4104 : positions[3*i+1]=getPosition(i)[1];
462 4104 : positions[3*i+2]=getPosition(i)[2];
463 : }
464 :
465 134 : if(root) {
466 148 : p.cmd("shareData");
467 : }
468 134 : if(root) {
469 148 : p.cmd("performCalcNoUpdate");
470 : }
471 :
472 134 : int s=forces.size();
473 134 : comm.Bcast(s,0);
474 134 : if(!root) {
475 60 : forces.resize(s);
476 : }
477 134 : comm.Bcast(forces,0);
478 134 : comm.Bcast(virial,0);
479 :
480 134 : double bias=0.0;
481 134 : if(root) {
482 148 : p.cmd("getBias",&bias);
483 : }
484 134 : comm.Bcast(bias,0);
485 134 : getPntrToComponent("bias")->set(bias);
486 134 : }
487 :
488 104 : void Plumed::apply() {
489 104 : Tools::DirectoryChanger directoryChanger(directory.c_str());
490 : auto & f(modifyForces());
491 6596 : for(unsigned i=0; i<getNumberOfAtoms(); i++) {
492 6492 : f[i][0]+=forces[3*i+0];
493 6492 : f[i][1]+=forces[3*i+1];
494 6492 : f[i][2]+=forces[3*i+2];
495 : }
496 : auto & v(modifyVirial());
497 104 : v+=virial;
498 104 : }
499 :
500 104 : void Plumed::update() {
501 104 : Tools::DirectoryChanger directoryChanger(directory.c_str());
502 104 : if(root) {
503 118 : p.cmd("update");
504 : }
505 104 : comm.Bcast(stop,0);
506 104 : if(stop) {
507 0 : log<<" Action " << getLabel()<<" asked to stop\n";
508 0 : plumed.stop();
509 : }
510 104 : }
511 :
512 : }
513 : }
|