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