Line data Source code
1 : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 : Copyright (c) 2017-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 "DomainDecomposition.h"
23 : #include "ActionToPutData.h"
24 : #include "ActionAtomistic.h"
25 : #include "ActionRegister.h"
26 : #include "PlumedMain.h"
27 : #include "ActionSet.h"
28 :
29 : #include "small_vector/small_vector.h"
30 : #include "tools/MergeVectorTools.h"
31 :
32 : //+PLUMEDOC ANALYSIS DOMAIN_DECOMPOSITION
33 : /*
34 : Pass domain decomposed properties of atoms to PLUMED
35 :
36 : Data is passed from the MD code to PLUMED by creating [PUT](PUT.md) actions.
37 : These PUT actions take the data from a void pointer that is passed to PLUMED from the MD code and transfer it into a
38 : PLMD::Value object. Passing a void pointer and using a PUT action to convert the data within it
39 : to a PLMD::Value is also used when the atomic positions, masses and charges are sent to PLUMED. However,
40 : there are some other subtleties for these quantities because MD codes use a domain decomposition and scatter the properties of atoms across
41 : multiple domains. We thus need to use the action DOMAIN_DECOMPOSITION when passing these quantities to make sense of the
42 : information in the void pointers that the MD code passes.
43 :
44 : ## Creating a DOMAIN_DECOMPOSITION action
45 :
46 : A DOMAIN_DECOMPOSITION can be created by using a call to plumed.cmd as follows:
47 :
48 : ```c++
49 : plumed.cmd("readInputLine dd: DOMAIN_DECOMPOSITION NATOMS=20 VALUE1=vv UNIT1=length PERIODIC1=NO CONSTANT1=False");
50 : ```
51 :
52 : The DOMAIN_DECOMPOSTION command above creates a PUT action with the label vv. The pointer to the data that needs to be transferred to the PLMD::Value
53 : object that is created by the PUT action is then set by using the command below:
54 :
55 : ```c++
56 : plumed.cmd("setInputValue vv, &val);
57 : ```
58 :
59 : Meanwhile, the pointer to the forces that should be modified is passed as follows:
60 :
61 : ```c++
62 : plumed.cmd("setValueForces vv", force);
63 : ```
64 :
65 : In other words, pointers to values and forces in the MD code are passed to PUT actions that are created by the DOMAIN_DECOMPOSION in
66 : [the way you pass data to other PUT actions](PUT.md).
67 :
68 : The PLMD::Value objects created by a DOMAIN_DECOMPOSITION action are always vectors with the same number of components as atoms in the system. Furthermore, you can create multiple PUT
69 : actions from a single DOMAIN_DECOMPOSITION action. To see why this is useful, consider the following DOMAIN_DECOMPOSITION command:
70 :
71 : ```plumed
72 : gromacs: DOMAIN_DECOMPOSITION ...
73 : NATOMS=2000
74 : VALUE1=myposx UNIT1=length PERIODIC1=NO CONSTANT1=False ROLE1=x
75 : VALUE2=myposy UNIT2=length PERIODIC2=NO CONSTANT2=False ROLE2=y
76 : VALUE3=myposz UNIT3=length PERIODIC3=NO CONSTANT3=False ROLE3=z
77 : VALUE4=myMasses UNIT4=mass PERIODIC4=NO CONSTANT4=True ROLE4=m
78 : VALUE5=myCharges UNIT5=charge PERIODIC5=NO CONSTANT5=True ROLE5=q
79 : PBCLABEL=mybox
80 : ...
81 : ```
82 :
83 : This action is created when you call `plumed.cmd("setNatoms",&natoms)` from gromacs. It makes 5 PLMD::Value called posx, posy, posz, Masses and Charges.
84 : These PLMD::Value objects then hold the x, y and z positions of the atoms and the masses and charges of the atoms. It is important to note that this command will
85 : also, create a PBC_ACTION to hold the cell.
86 :
87 : The ROLE keywords above are only needed because the five quantities passed by the command above play a unique role within PLUMED. If you pass
88 : some other quantities, this instruction is not required. PLMD::ActionAtomistic searches for atomic positions, masses and charges by looking for PUT Actions
89 : that have these five particular roles and for ActionWithVirtualAtom objects.
90 :
91 : ## Differences from regular PUT actions
92 :
93 : PUT actions created from a DOMAIN_DECOMPOSITION action behave differently from other PUT actions. In particular:
94 :
95 : * If a DOMAIN_DECOMPOSITION action creates a PUT action, then the PUT action depends on the DOMAIN_DECOMPOSITION action. ActionToPutData::apply thus does nothing for these PUT actions.
96 : * Similarly, when DOMAIN_DECOMPOSITION actions create PUT actions, data is transferred from the input pointer to the PLMD::Value object by the DOMAIN_DECOMPOSITION action. When ActionToPutData::wait is called for these PUT Actions `wasset=true`, ActionToPutData::wait does nothing.
97 : * Lastly, if a constant PUT action is created by DOMAIN_DECOMPOSITION, the values in the vector are set during the first step of MD rather than during initialisation.
98 :
99 : These differences are necessary because PUT actions cannot understand the information in the pointers that are passed from the MD code. These pointers are only understandable if you know
100 : which atoms are on each processor. This information is only passed to the DOMAIN_DECOMPOSITION action. DOMAIN_DECOMPOSITION must translate the information passed from the MD code before it is
101 : passed back on through PLMD::Value objects created by the PUT actions. DOMAIN_DECOMPOSITION thus keeps pointers to all the PUT actions that it creates. It sets the data in these action's PLMD::Value objects
102 : within `DomainDecomposition::share(const std::vector<AtomNumber>& unique)` and `DomainDecomposition::wait().` The forces on the PUT actions created by the DOMAIN_DECOMPOSITION action are added in `DomainDecomposition::apply()`.
103 :
104 : */
105 : //+ENDPLUMEDOC
106 :
107 : namespace PLMD {
108 :
109 : namespace {
110 :
111 : enum class Option {automatic, no, yes };
112 :
113 1039 : Option interpretEnvString(const char* env,const char* str) {
114 1039 : if(!str) {
115 : return Option::automatic;
116 : }
117 2 : if(!std::strcmp(str,"yes")) {
118 : return Option::yes;
119 : }
120 1 : if(!std::strcmp(str,"no")) {
121 : return Option::no;
122 : }
123 0 : if(!std::strcmp(str,"auto")) {
124 : return Option::automatic;
125 : }
126 0 : plumed_error()<<"Cannot understand env var "<<env<<"\nPossible values: yes/no/auto\nActual value: "<<str;
127 : }
128 :
129 : /// Use unique list of atoms to manipulate forces and positions.
130 : /// A unique list of atoms is used to manipulate forces and positions in MPI parallel runs.
131 : /// In serial runs, this is done if convenient: with auto, the unique list is used
132 : /// when the atoms needed by all the active actions are less than half of the atoms.
133 : /// An env var can be used to override this decision.
134 : /// export PLUMED_FORCE_UNIQUE=yes # enforce using the unique list in serial runs
135 : /// export PLUMED_FORCE_UNIQUE=no # enforce not using the unique list in serial runs
136 : /// export PLUMED_FORCE_UNIQUE=auto # choose heuristically
137 : /// default: auto
138 162823 : Option getenvForceUnique() {
139 : static const char* name="PLUMED_FORCE_UNIQUE";
140 162823 : static const auto opt = interpretEnvString(name,std::getenv(name));
141 162823 : return opt;
142 : }
143 :
144 : }
145 :
146 : PLUMED_REGISTER_ACTION(DomainDecomposition,"DOMAIN_DECOMPOSITION")
147 :
148 1056 : void DomainDecomposition::DomainComms::enable(Communicator& c) {
149 1056 : on=true;
150 1056 : Set_comm(c.Get_comm());
151 1056 : async=Get_size()<10;
152 1056 : if(std::getenv("PLUMED_ASYNC_SHARE")) {
153 20 : std::string s(std::getenv("PLUMED_ASYNC_SHARE"));
154 20 : if(s=="yes") {
155 8 : async=true;
156 12 : } else if(s=="no") {
157 12 : async=false;
158 : } else {
159 0 : plumed_merror("PLUMED_ASYNC_SHARE variable is set to " + s + "; should be yes or no");
160 : }
161 : }
162 1056 : }
163 :
164 2180 : void DomainDecomposition::registerKeywords(Keywords& keys) {
165 2180 : ActionForInterface::registerKeywords( keys );
166 4360 : keys.remove("ROLE");
167 2180 : keys.add("compulsory","NATOMS","the number of atoms across all the domains");
168 2180 : keys.add("numbered","VALUE","value to create for the domain decomposition");
169 2180 : keys.add("numbered","UNIT","unit of the ith value that is being passed through the domain decomposition");
170 2180 : keys.add("numbered","CONSTANT","is the ith value that is being passed through the domain decomposition constant");
171 2180 : keys.add("numbered","PERIODIC","if the value being passed to plumed is periodic then you should specify the periodicity of the function. If the value "
172 : "is not periodic you must state this using PERIODIC=NO. Positions are passed with PERIODIC=NO even though special methods are used "
173 : "to deal with pbc");
174 2180 : keys.add("numbered","ROLE","Get the role this value plays in the code can be x/y/z/m/q to signify that this is x, y, z positions of atoms or masses or charges of atoms");
175 2180 : keys.add("compulsory","PBCLABEL","Box","the label to use for the PBC action that will be created");
176 2180 : keys.remove("NUMERICAL_DERIVATIVES");
177 4360 : keys.setValueDescription("vector","the domain that each atom is within");
178 2180 : }
179 :
180 2176 : DomainDecomposition::DomainDecomposition(const ActionOptions&ao):
181 : Action(ao),
182 : ActionForInterface(ao),
183 2176 : ddStep(0),
184 2176 : shuffledAtoms(0),
185 2176 : asyncSent(false),
186 2176 : unique_serial(false),
187 2176 : g2lIsValid(false),
188 2176 : uniqueListsValid(false),
189 2176 : allActiveValid(false),
190 2176 : atomsNeeded(false) {
191 : // Read in the number of atoms
192 : int natoms;
193 4352 : parse("NATOMS",natoms);
194 : std::string str_natoms;
195 2176 : Tools::convert( natoms, str_natoms );
196 : // Setup the gat index
197 2176 : gatindex.resize(natoms);
198 51286751 : for(unsigned i=0; i<gatindex.size(); i++) {
199 51284575 : gatindex[i]=i;
200 : }
201 : // Now read in the values we are creating here
202 10880 : for(unsigned i=1;; ++i) {
203 : std::string valname;
204 26112 : if( !parseNumbered("VALUE",i,valname) ) {
205 : break;
206 : }
207 : std::string unit;
208 21760 : parseNumbered("UNIT",i,unit);
209 : std::string constant;
210 21760 : parseNumbered("CONSTANT",i,constant);
211 : std::string period;
212 21760 : parseNumbered("PERIODIC",i,period);
213 : std::string ddrole;
214 10880 : parseNumbered("ROLE",i,ddrole);
215 10880 : if( constant=="True") {
216 21760 : plumed.readInputLine( valname + ": PUT FROM_DOMAINS CONSTANT SHAPE=" + str_natoms + " UNIT=" + unit + " PERIODIC=" + period + " ROLE=" + ddrole, !plumed.hasBeenInitialized() );
217 6528 : } else if( constant=="False") {
218 32640 : plumed.readInputLine( valname + ": PUT FROM_DOMAINS SHAPE=" + str_natoms + " UNIT=" + unit + " PERIODIC=" + period + " ROLE=" + ddrole, !plumed.hasBeenInitialized() );
219 : } else {
220 0 : plumed_merror("missing information on whether value is constant");
221 : }
222 : // And save the list of values that are set from here
223 10880 : ActionToPutData* ap=plumed.getActionSet().selectWithLabel<ActionToPutData*>(valname);
224 10880 : ap->addDependency( this );
225 10880 : inputs.push_back( ap );
226 10880 : }
227 : std::string pbclabel;
228 2176 : parse("PBCLABEL",pbclabel);
229 2176 : plumed.readInputLine(pbclabel + ": PBC",true);
230 : // Turn on the domain decomposition
231 2176 : if( Communicator::initialized() ) {
232 1055 : Set_comm(comm);
233 : }
234 2176 : }
235 :
236 1056 : void DomainDecomposition::Set_comm(Communicator& newcomm) {
237 1056 : dd.enable(newcomm);
238 1056 : setAtomsNlocal(getNumberOfAtoms());
239 1056 : setAtomsContiguous(0);
240 1056 : if( dd.Get_rank()!=0 ) {
241 : // The virial computed by PLUMED is added on the first rank only. The virial of the
242 : // MD code is instead spread over all ranks, so each rank still rescales its share (ENERGY)
243 408 : ActionToPutData* ap=plumed.getActionSet().selectWithLabel<ActionToPutData*>("Box");
244 408 : ap->forces_added_elsewhere=true;
245 : }
246 1056 : }
247 :
248 992925 : unsigned DomainDecomposition::getNumberOfAtoms() const {
249 992925 : if( inputs.size()==0 ) {
250 : return 0;
251 : }
252 992925 : return (inputs[0]->getPntrToValue())->getShape()[0];
253 : }
254 :
255 166448 : void DomainDecomposition::resetForStepStart() {
256 998688 : for(const auto & pp : inputs) {
257 832240 : pp->resetForStepStart();
258 : }
259 166448 : }
260 :
261 754709 : void DomainDecomposition::setStart( const std::string& argName, const unsigned& sss) {
262 2108907 : for(const auto & pp : inputs) {
263 2108907 : if( pp->getLabel()==argName ) {
264 754709 : pp->setStart(argName, sss);
265 754709 : return;
266 : }
267 : }
268 0 : plumed_error();
269 : }
270 :
271 754709 : void DomainDecomposition::setStride( const std::string& argName, const unsigned& sss) {
272 2108907 : for(const auto & pp : inputs) {
273 2108907 : if( pp->getLabel()==argName ) {
274 754709 : pp->setStride(argName, sss);
275 754709 : return;
276 : }
277 : }
278 0 : plumed_error();
279 : }
280 :
281 758839 : bool DomainDecomposition::setValuePointer( const std::string& argName, const TypesafePtr & val ) {
282 758839 : wasset=true; // Once the domain decomposition stuff is transferred moved the setting of this to where the g2l vector is setup
283 2133681 : for(const auto & pp : inputs) {
284 2129554 : if( pp->setValuePointer( argName, val ) ) {
285 : return true;
286 : }
287 : }
288 : return false;
289 : }
290 :
291 499326 : bool DomainDecomposition::setForcePointer( const std::string& argName, const TypesafePtr & val ) {
292 998772 : for(const auto & pp : inputs) {
293 998742 : if( pp->setForcePointer( argName, val ) ) {
294 : return true;
295 : }
296 : }
297 : return false;
298 : }
299 :
300 2504 : void DomainDecomposition::setAtomsNlocal(int n) {
301 2504 : if( static_cast<std::size_t>(n)!=gatindex.size() ) {
302 : // the local atoms will be set again by setAtomsGatindex or setAtomsContiguous
303 1202 : g2lIsValid=false;
304 1202 : invalidateUniqueLists();
305 : }
306 2504 : gatindex.resize(n);
307 2504 : g2l.resize(getNumberOfAtoms(),-1);
308 2504 : if(dd) {
309 : // Since these vectors are sent with MPI by using e.g.
310 : // &dd.positionsToBeSent[0]
311 : // we make sure they are non-zero-sized so as to
312 : // avoid errors when doing boundary check
313 2332 : if(n==0) {
314 14 : n++;
315 : }
316 2332 : std::size_t nvals = inputs.size(), natoms = getNumberOfAtoms();
317 2332 : dd.positionsToBeSent.resize(n*nvals,0.0);
318 2332 : dd.positionsToBeReceived.resize(natoms*nvals,0.0);
319 2332 : dd.indexToBeSent.resize(n,0);
320 2332 : dd.indexToBeReceived.resize(natoms,0);
321 : }
322 2504 : }
323 :
324 1296 : void DomainDecomposition::setAtomsGatindex(const TypesafePtr & g,bool fortran) {
325 1296 : plumed_massert( g || gatindex.size()==0, "NULL gatindex pointer with non-zero local atoms");
326 1296 : auto gg=g.get<const int*>({gatindex.size()});
327 1296 : ddStep=getStep();
328 : // MD codes call this at every step, but the local atoms only change when the
329 : // domains are redistributed: in that case the lists of atoms are not rebuilt
330 1296 : bool localAtomsChanged=!g2lIsValid;
331 1296 : const int shift=fortran ? 1 : 0;
332 32792 : for(unsigned i=0; i<gatindex.size(); i++) {
333 31496 : if( gatindex[i]!=gg[i]-shift ) {
334 21729 : gatindex[i]=gg[i]-shift;
335 : localAtomsChanged=true;
336 : }
337 : }
338 1296 : const bool wasShuffled=shuffledAtoms>0;
339 1296 : if( gatindex.size()==getNumberOfAtoms() ) {
340 142 : shuffledAtoms=0;
341 1474 : for(unsigned i=0; i<gatindex.size(); i++) {
342 1455 : if( gatindex[i]!=static_cast<int>(i) ) {
343 123 : shuffledAtoms=1;
344 123 : break;
345 : }
346 : }
347 : } else {
348 1154 : shuffledAtoms=1;
349 : }
350 1296 : if(dd) {
351 1124 : dd.Sum(shuffledAtoms);
352 : }
353 1296 : if( !localAtomsChanged && wasShuffled==(shuffledAtoms>0) ) {
354 : return;
355 : }
356 62892 : for(unsigned i=0; i<g2l.size(); i++) {
357 61707 : g2l[i]=-1;
358 : }
359 25094 : for(unsigned i=0; i<gatindex.size(); i++) {
360 23909 : g2l[gatindex[i]]=i;
361 : }
362 1185 : g2lIsValid=true;
363 : // keep in unique only those atoms that are local
364 10482 : for(unsigned i=0; i<actions.size(); i++) {
365 9297 : actions[i]->unique_local_needs_update=true;
366 : }
367 1185 : unique.clear();
368 1185 : forced_unique.clear();
369 1185 : invalidateUniqueLists();
370 : }
371 :
372 1208 : void DomainDecomposition::setAtomsContiguous(int start) {
373 1208 : ddStep=plumed.getStep();
374 240431 : for(unsigned i=0; i<gatindex.size(); i++) {
375 239223 : gatindex[i]=start+i;
376 : }
377 252743 : for(unsigned i=0; i<g2l.size(); i++) {
378 251535 : g2l[i]=-1;
379 : }
380 240431 : for(unsigned i=0; i<gatindex.size(); i++) {
381 239223 : g2l[gatindex[i]]=i;
382 : }
383 1208 : g2lIsValid=true;
384 1208 : if(gatindex.size()<getNumberOfAtoms()) {
385 152 : shuffledAtoms=1;
386 : }
387 : // keep in unique only those atoms that are local
388 1420 : for(unsigned i=0; i<actions.size(); i++) {
389 212 : actions[i]->unique_local_needs_update=true;
390 : }
391 1208 : unique.clear();
392 1208 : forced_unique.clear();
393 1208 : invalidateUniqueLists();
394 1208 : }
395 :
396 432435 : bool DomainDecomposition::allAtomsAreLocal() const {
397 432435 : return gatindex.size()==getNumberOfAtoms() && shuffledAtoms==0;
398 : }
399 :
400 5650 : void DomainDecomposition::invalidateUniqueLists() {
401 5650 : uniqueListsValid=false;
402 5650 : allActiveValid=false;
403 5650 : }
404 :
405 5531 : void DomainDecomposition::setIndexesInMDArrays() {
406 5531 : const bool allLocal=allAtomsAreLocal();
407 5531 : uniq_index.resize(unique.size());
408 6661588 : for(unsigned i=0; i<unique.size(); i++) {
409 : // only atoms that the MD code passes to this rank can be in this list
410 : plumed_dbg_assert( allLocal || g2l[unique[i].index()]>=0 );
411 13288467 : uniq_index[i]=allLocal ? unique[i].index() : g2l[unique[i].index()];
412 : }
413 5531 : forced_uniq_index.resize(forced_unique.size());
414 6431849 : for(unsigned i=0; i<forced_unique.size(); i++) {
415 : plumed_dbg_assert( allLocal || g2l[forced_unique[i].index()]>=0 );
416 12830875 : forced_uniq_index[i]=allLocal ? forced_unique[i].index() : g2l[forced_unique[i].index()];
417 : }
418 5531 : }
419 :
420 2055 : void DomainDecomposition::shareAll() {
421 2055 : unique.clear();
422 2055 : const unsigned natoms = getNumberOfAtoms();
423 2055 : if( allAtomsAreLocal() ) {
424 1810 : unique.resize(natoms);
425 6220945 : for(unsigned i=0; i<natoms; i++) {
426 6219135 : unique[i]=AtomNumber::index(i);
427 : }
428 : } else {
429 : // only the atoms that the MD code passes to this rank can be read
430 19498 : for(unsigned i=0; i<natoms; ++i)
431 19253 : if( g2l[i]>=0 ) {
432 9302 : unique.push_back( AtomNumber::index(i) );
433 : }
434 : }
435 2055 : forced_unique=unique;
436 2055 : setIndexesInMDArrays();
437 : // these lists are not the ones requested by the actions, so they are rebuilt at the next share
438 2055 : invalidateUniqueLists();
439 2300 : shareValues( allAtomsAreLocal() && !unique_serial );
440 2036 : }
441 :
442 162816 : bool DomainDecomposition::updateUniqueLists() {
443 : // useunique is false only when the local atoms are a subset of the atoms, scattered across the ranks.
444 : // It can only change in setAtomsGatindex or setAtomsContiguous, which ask the actions to update unique_local
445 162816 : const bool useunique=!(dd && shuffledAtoms>0);
446 162816 : bool changed=!uniqueListsValid;
447 162816 : if( actionState.size()!=actions.size() ) {
448 0 : actionState.assign(actions.size(),0);
449 : changed=true;
450 : }
451 162816 : atomsNeeded=false;
452 873173 : for(unsigned i=0; i<actions.size(); i++) {
453 : // atomsNeeded is computed from the global lists of atoms, so it is the same on all ranks
454 : unsigned char state=0;
455 710357 : if( actions[i]->isActive() && !actions[i]->getUnique().empty() ) {
456 411510 : atomsNeeded=true;
457 411510 : state = actions[i]->actionHasForces() ? 2 : 1;
458 : }
459 710357 : if( state!=actionState[i] ) {
460 7309 : actionState[i]=state;
461 : changed=true;
462 : }
463 : // unique_local_needs_update is set when an action requests new atoms or when the local atoms change
464 710357 : if( state>0 && actions[i]->unique_local_needs_update ) {
465 12048 : actions[i]->updateUniqueLocal( useunique, g2l );
466 : changed=true;
467 : }
468 : }
469 162816 : if( !changed ) {
470 : return false;
471 : }
472 : gch::small_vector<const std::vector<AtomNumber>*,32> forced_vectors;
473 : gch::small_vector<const std::vector<AtomNumber>*,32> nonforced_vectors;
474 : forced_vectors.reserve(actions.size());
475 3476 : nonforced_vectors.reserve(actions.size()+1);
476 33259 : for(unsigned i=0; i<actions.size(); i++) {
477 : // unique_local are the local atoms
478 29783 : if( actionState[i]==2 ) {
479 26138 : forced_vectors.push_back(&actions[i]->getUniqueLocal());
480 16714 : } else if( actionState[i]==1 ) {
481 7300 : nonforced_vectors.push_back(&actions[i]->getUniqueLocal());
482 : }
483 : }
484 : // Merge the atoms from the atoms that have a force on
485 3476 : unique.clear();
486 3476 : forced_unique.clear();
487 3476 : mergeVectorTools::mergeSortedVectors(forced_vectors,forced_unique);
488 : // Merge all the atoms
489 3476 : nonforced_vectors.push_back( &forced_unique );
490 3476 : mergeVectorTools::mergeSortedVectors(nonforced_vectors,unique);
491 3476 : setIndexesInMDArrays();
492 3476 : allActiveValid=false;
493 3476 : uniqueListsValid=true;
494 : return true;
495 : }
496 :
497 36458 : bool DomainDecomposition::activeAtomsChangedSinceShare() const {
498 36458 : if( !uniqueListsValid ) {
499 : return true;
500 : }
501 205603 : for(unsigned i=0; i<actions.size(); i++) {
502 : // actions that were active with no atoms at share time and still have none are not considered
503 169269 : if( actions[i]->isActive() && actions[i]->unique_local_needs_update &&
504 27787 : (actionState[i]>0 || !actions[i]->getUnique().empty()) ) {
505 : return true;
506 : }
507 : }
508 : return false;
509 : }
510 :
511 164752 : void DomainDecomposition::share() {
512 : // We can no longer set the pointers after the share
513 988512 : for(const auto & pp : inputs) {
514 823760 : pp->share();
515 : }
516 : // At first step I scatter all the atoms so as to store their mass and charge
517 : // Notice that this works with the assumption that charges and masses are
518 : // not changing during the simulation!
519 164752 : if( firststep ) {
520 1929 : actions = plumed.getActionSet().select<ActionAtomistic*>();
521 1929 : actionState.assign(actions.size(),0);
522 1929 : shareAll();
523 1910 : return;
524 : }
525 :
526 162823 : const bool allLocal=allAtomsAreLocal();
527 162823 : const Option forceUnique=getenvForceUnique();
528 162823 : if( allLocal && forceUnique==Option::no ) {
529 : // all the atoms are copied at once, so the lists of atoms are not needed
530 7 : unique_serial=false;
531 7 : atomsNeeded=false;
532 7 : for(unsigned i=0; i<actions.size(); i++) {
533 7 : if( actions[i]->isActive() && !actions[i]->getUnique().empty() ) {
534 7 : atomsNeeded=true;
535 7 : break;
536 : }
537 : }
538 7 : if( atomsNeeded ) {
539 7 : shareValues( true );
540 : }
541 7 : return;
542 : }
543 :
544 : // The lists are only rebuilt when the set of active actions, the atoms they request,
545 : // or the atoms that are local to this rank change
546 162816 : updateUniqueLists();
547 :
548 162816 : if( allLocal ) {
549 160429 : if( forceUnique==Option::yes ) {
550 7 : unique_serial=true;
551 : } else {
552 : // Copying only the atoms that are needed is worthwhile when they are a small fraction of
553 : // all the atoms, as copying all the atoms reads contiguous memory and needs no indexes.
554 : // The choice is based on the union of the atoms needed by all the active actions.
555 160422 : unique_serial = unique.size()*2<getNumberOfAtoms();
556 : }
557 : }
558 :
559 : // Now we retrieve the atom numbers we need
560 162816 : if( atomsNeeded ) {
561 198449 : shareValues( allLocal && !unique_serial );
562 : }
563 : }
564 :
565 164167 : void DomainDecomposition::shareValues( const bool& allAtoms ) {
566 : // This retrieves what values we need to get. The list is stored, as wait() must receive the same values
567 164167 : values_to_get.clear();
568 984907 : for(const auto & ip : inputs) {
569 820759 : if( (!ip->fixed || firststep) && ip->wasset ) {
570 495588 : if( allAtoms ) {
571 : // faster version, which retrieves all atoms
572 385573 : (ip->mydata)->share_data( 0, getNumberOfAtoms(), ip->copyOutput(0) );
573 : } else {
574 110015 : (ip->mydata)->share_data( unique, uniq_index, gatindex.size(), ip->copyOutput(0) );
575 : }
576 495569 : values_to_get.push_back(ip->copyOutput(0));
577 : }
578 : }
579 164148 : const int ndata=values_to_get.size();
580 :
581 164148 : if(dd && shuffledAtoms>0) {
582 2460 : if(dd.async) {
583 10280 : for(unsigned i=0; i<dd.mpi_request_positions.size(); i++) {
584 7858 : dd.mpi_request_positions[i].wait();
585 : }
586 10280 : for(unsigned i=0; i<dd.mpi_request_index.size(); i++) {
587 7858 : dd.mpi_request_index[i].wait();
588 : }
589 : }
590 :
591 2460 : int count=0;
592 33839 : for(const auto & p : unique) {
593 31379 : dd.indexToBeSent[count]=p.index();
594 : int dpoint=0;
595 131518 : for(unsigned i=0; i<values_to_get.size(); ++i) {
596 100139 : dd.positionsToBeSent[ndata*count+dpoint]=values_to_get[i]->get(p.index());
597 100139 : dpoint++;
598 : }
599 31379 : count++;
600 : }
601 :
602 2460 : if(dd.async) {
603 2422 : asyncSent=true;
604 2422 : dd.mpi_request_positions.resize(dd.Get_size());
605 2422 : dd.mpi_request_index.resize(dd.Get_size());
606 10564 : for(int i=0; i<dd.Get_size(); i++) {
607 8142 : dd.mpi_request_index[i]=dd.Isend(&dd.indexToBeSent[0],count,i,666);
608 8142 : dd.mpi_request_positions[i]=dd.Isend(&dd.positionsToBeSent[0],ndata*count,i,667);
609 : }
610 : } else {
611 38 : const int n=(dd.Get_size());
612 38 : std::vector<int> counts(n);
613 38 : std::vector<int> displ(n);
614 38 : std::vector<int> counts5(n);
615 38 : std::vector<int> displ5(n);
616 38 : dd.Allgather(count,counts);
617 38 : displ[0]=0;
618 116 : for(int i=1; i<n; ++i) {
619 78 : displ[i]=displ[i-1]+counts[i-1];
620 : }
621 154 : for(int i=0; i<n; ++i) {
622 116 : counts5[i]=counts[i]*ndata;
623 : }
624 154 : for(int i=0; i<n; ++i) {
625 116 : displ5[i]=displ[i]*ndata;
626 : }
627 38 : dd.Allgatherv(&dd.indexToBeSent[0],count,&dd.indexToBeReceived[0],&counts[0],&displ[0]);
628 38 : dd.Allgatherv(&dd.positionsToBeSent[0],ndata*count,&dd.positionsToBeReceived[0],&counts5[0],&displ5[0]);
629 38 : int tot=displ[n-1]+counts[n-1];
630 1674 : for(int i=0; i<tot; i++) {
631 : int dpoint=0;
632 7440 : for(unsigned j=0; j<values_to_get.size(); ++j) {
633 5804 : values_to_get[j]->data[dd.indexToBeReceived[i]] = dd.positionsToBeReceived[ndata*i+dpoint];
634 5804 : dpoint++;
635 : }
636 : }
637 38 : }
638 : }
639 164148 : }
640 :
641 164143 : void DomainDecomposition::wait() {
642 984858 : for(const auto & ip : inputs) {
643 820715 : ip->dataCanBeSet=false;
644 : }
645 :
646 164143 : if(dd && shuffledAtoms>0) {
647 : // the values that were sent in share()
648 : const std::vector<Value*> & values_to_set=values_to_get;
649 : const std::size_t ndata=values_to_set.size();
650 :
651 : // receive toBeReceived
652 2460 : if(asyncSent) {
653 : Communicator::Status status;
654 : std::size_t count=0;
655 10564 : for(int i=0; i<dd.Get_size(); i++) {
656 8142 : plumed_assert(count*ndata<=dd.positionsToBeReceived.size() && count<=dd.indexToBeReceived.size());
657 8142 : const std::size_t remaining=dd.indexToBeReceived.size()-count;
658 8142 : const std::size_t remainingData=dd.positionsToBeReceived.size()-ndata*count;
659 : // Empty ranks still send messages after an earlier rank fills the buffer.
660 : // Avoid indexing one past the end, including for zero-sized storage.
661 8142 : int* indices=remaining ? dd.indexToBeReceived.data()+count : nullptr;
662 8142 : double* positionData=remainingData ? dd.positionsToBeReceived.data()+ndata*count : nullptr;
663 8142 : dd.Recv(indices,remaining,i,666,status);
664 8142 : const int c=status.Get_count<int>();
665 8142 : plumed_assert(c>=0 && static_cast<std::size_t>(c)<=remaining);
666 8142 : dd.Recv(positionData,remainingData,i,667);
667 8142 : count+=c;
668 : }
669 70326 : for(unsigned i=0; i<count; i++) {
670 : int dpoint=0;
671 285572 : for(unsigned j=0; j<values_to_set.size(); ++j) {
672 217668 : values_to_set[j]->set(dd.indexToBeReceived[i], dd.positionsToBeReceived[ndata*i+dpoint] );
673 217668 : dpoint++;
674 : }
675 : }
676 2422 : asyncSent=false;
677 : }
678 : }
679 164143 : }
680 :
681 150 : unsigned DomainDecomposition::getNumberOfForcesToRescale() const {
682 150 : return gatindex.size();
683 : }
684 :
685 163939 : void DomainDecomposition::apply() {
686 : // forced_uniq_index was computed together with forced_unique
687 163939 : const bool allAtoms=!unique_serial && allAtomsAreLocal();
688 983609 : for(const auto & ip : inputs) {
689 819675 : if( !(ip->getPntrToValue())->forcesWereAdded() || ip->noforce ) {
690 401578 : continue;
691 418097 : } else if( ip->wasscaled || allAtoms ) {
692 316991 : if( gatindex.size()>0 ) {
693 316991 : ip->checkForcePointer();
694 : }
695 316988 : (ip->mydata)->add_force( gatindex, ip->getPntrToValue() );
696 : } else {
697 101106 : if( forced_unique.size()>0 ) {
698 100050 : ip->checkForcePointer();
699 : }
700 101106 : (ip->mydata)->add_force( forced_unique, forced_uniq_index, gatindex.size(), ip->getPntrToValue() );
701 : }
702 : }
703 163934 : }
704 :
705 163934 : void DomainDecomposition::reset() {
706 163934 : if( !unique_serial && allAtomsAreLocal() ) {
707 : return;
708 : }
709 : // This is an optimisation to ensure that we don't call std::fill over the whole forces
710 : // array if there are a small number of atoms passed between the MD code and PLUMED.
711 : // The forces were added by the active actions on the atoms they requested
712 36458 : const std::vector<AtomNumber>* toclear=&unique;
713 36458 : if( activeAtomsChangedSinceShare() ) {
714 : // an action requested different atoms after share (e.g. a neighbor list update)
715 : // or unique was set by shareAll, so the global list is rebuilt
716 124 : if( uniqueListsValid || (dd && shuffledAtoms>0) ) {
717 116 : getAllActiveAtoms( activeAtomsBuffer );
718 : toclear=&activeAtomsBuffer;
719 : }
720 36334 : } else if( dd && shuffledAtoms>0 ) {
721 : // unique only holds the local atoms, but forces can be on all the atoms of the actions
722 2226 : if( !allActiveValid ) {
723 1226 : getAllActiveAtoms( allActiveUnique );
724 1226 : allActiveValid=true;
725 : }
726 2226 : toclear=&allActiveUnique;
727 : }
728 218748 : for(const auto & ip : inputs) {
729 182290 : (ip->copyOutput(0))->clearInputForce( *toclear );
730 : }
731 : }
732 :
733 126 : void DomainDecomposition::writeBinary(std::ostream&o) {
734 756 : for(const auto & ip : inputs) {
735 630 : ip->writeBinary(o);
736 : }
737 126 : }
738 :
739 126 : void DomainDecomposition::readBinary(std::istream&i) {
740 756 : for(const auto & ip : inputs) {
741 630 : ip->readBinary(i);
742 : }
743 126 : }
744 :
745 160048 : void DomainDecomposition::broadcastToDomains( Value* val ) {
746 160048 : if( dd ) {
747 31067 : dd.Bcast( val->data, 0 );
748 : }
749 160048 : }
750 :
751 3999 : void DomainDecomposition::sumOverDomains( Value* val ) {
752 3999 : if( dd && shuffledAtoms>0 ) {
753 0 : dd.Sum( val->data );
754 : }
755 3999 : }
756 :
757 0 : const long int& DomainDecomposition::getDdStep() const {
758 0 : return ddStep;
759 : }
760 :
761 485 : const std::vector<int>& DomainDecomposition::getGatindex() const {
762 485 : return gatindex;
763 : }
764 :
765 1472 : void DomainDecomposition::getAllActiveAtoms( std::vector<AtomNumber>& u ) {
766 : gch::small_vector<const std::vector<AtomNumber>*,32> vectors;
767 : vectors.reserve(actions.size());
768 12651 : for(unsigned i=0; i<actions.size(); i++) {
769 11179 : if(actions[i]->isActive()) {
770 7716 : if(!actions[i]->getUnique().empty()) {
771 : // unique are the local atoms
772 13608 : vectors.push_back(&actions[i]->getUnique());
773 : }
774 : }
775 : }
776 1472 : u.clear();
777 1472 : mergeVectorTools::mergeSortedVectors(vectors,u);
778 1472 : }
779 :
780 140 : void DomainDecomposition::createFullList(const TypesafePtr & n) {
781 140 : if( firststep ) {
782 10 : int natoms = getNumberOfAtoms();
783 10 : n.set(natoms);
784 10 : fullList.resize(natoms);
785 1106 : for(int i=0; i<natoms; i++) {
786 1096 : fullList[i]=i;
787 : }
788 : } else {
789 : // The list of the atoms needed by the active actions.
790 : // The MD code (e.g. NAMD) then passes only these atoms.
791 130 : getAllActiveAtoms( activeAtomsBuffer );
792 130 : fullList.clear();
793 130 : fullList.reserve(activeAtomsBuffer.size());
794 5816 : for(const auto & p : activeAtomsBuffer) {
795 5686 : fullList.push_back(p.index());
796 : }
797 130 : n.set(int(fullList.size()));
798 : }
799 140 : }
800 :
801 140 : void DomainDecomposition::getFullList(const TypesafePtr & x) {
802 : auto xx=x.template get<const int**>();
803 140 : if(!fullList.empty()) {
804 134 : *xx=&fullList[0];
805 : } else {
806 6 : *xx=NULL;
807 : }
808 140 : }
809 :
810 116 : void DomainDecomposition::clearFullList() {
811 116 : fullList.resize(0);
812 116 : }
813 :
814 : }
|