OpenMD 3.1
Molecular Dynamics in the Open
Loading...
Searching...
No Matches
Swap.cpp
1/*
2 * Copyright (c) 2004-present, The University of Notre Dame. All rights
3 * reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * 3. Neither the name of the copyright holder nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 *
31 * SUPPORT OPEN SCIENCE! If you use OpenMD or its source code in your
32 * research, please cite the appropriate papers when you publish your
33 * work. Good starting points are:
34 *
35 * [1] Meineke, et al., J. Comp. Chem. 26, 252-271 (2005).
36 * [2] Fennell & Gezelter, J. Chem. Phys. 124, 234104 (2006).
37 * [3] Sun, Lin & Gezelter, J. Chem. Phys. 128, 234107 (2008).
38 * [4] Vardeman, Stocker & Gezelter, J. Chem. Theory Comput. 7, 834 (2011).
39 * [5] Kuang & Gezelter, Mol. Phys., 110, 691-701 (2012).
40 * [6] Lamichhane, Gezelter & Newman, J. Chem. Phys. 141, 134109 (2014).
41 * [7] Lamichhane, Newman & Gezelter, J. Chem. Phys. 141, 134110 (2014).
42 * [8] Bhattarai, Newman & Gezelter, Phys. Rev. B 99, 094106 (2019).
43 */
44
45#include "rnemd/Swap.hpp"
46
47#include <algorithm>
48#include <cmath>
49#include <map>
50#include <set>
51#include <sstream>
52#include <string>
53#include <vector>
54
55#ifdef IS_MPI
56#include <mpi.h>
57#endif
58
60#include "brains/Thermo.hpp"
61#include "io/Globals.hpp"
62#include "math/ConvexHull.hpp"
63#include "math/Polynomial.hpp"
65#include "math/Vector.hpp"
66#include "math/Vector3.hpp"
69#include "rnemd/RNEMD.hpp"
70#include "rnemd/RNEMDParameters.hpp"
71#include "types/FixedChargeAdapter.hpp"
72#include "types/FluctuatingChargeAdapter.hpp"
73#include "utils/Constants.hpp"
74
75#define HONKING_LARGE_VALUE 1.0e10
76
77namespace OpenMD::RNEMD {
78
79 SwapMethod::SwapMethod(SimInfo* info, ForceManager* forceMan) :
80 RNEMD {info, forceMan} {
81 rnemdMethodLabel_ = "Swap";
82
83 RNEMDParameters* rnemdParams = info->getSimParams()->getRNEMDParameters();
84
85 bool hasKineticFlux = rnemdParams->haveKineticFlux();
86 bool hasMomentumFlux = rnemdParams->haveMomentumFlux();
87
88 bool methodFluxMismatch = false;
89 bool hasCorrectFlux = false;
90
91 switch (rnemdFluxType_) {
92 case rnemdKE:
93 hasCorrectFlux = hasKineticFlux;
94 break;
95 case rnemdPx:
96 case rnemdPy:
97 case rnemdPz:
98 hasCorrectFlux = hasMomentumFlux;
99 break;
100 default:
101 methodFluxMismatch = true;
102 break;
103 }
104
105 if (methodFluxMismatch) {
106 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
107 "RNEMD: The current method,\n"
108 "\t\tSwap\n"
109 "\tcannot be used with the current flux type, %s\n",
110 rnemdFluxTypeLabel_.c_str());
111 painCave.isFatal = 1;
112 painCave.severity = OPENMD_ERROR;
113 simError();
114 }
115
116 if (!hasCorrectFlux) {
117 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
118 "RNEMD: The current method, Swap, and flux type, %s,\n"
119 "\tdid not have the correct flux value specified. Options\n"
120 "\tinclude: kineticFlux and momentumFlux.\n",
121 rnemdFluxTypeLabel_.c_str());
122 painCave.isFatal = 1;
123 painCave.severity = OPENMD_ERROR;
124 simError();
125 }
126
127 if (hasKineticFlux) {
128 setKineticFlux(rnemdParams->getKineticFlux());
129 } else {
130 setKineticFlux(0.0);
131 }
132
133 if (hasMomentumFlux) {
134 RealType momentumFlux = rnemdParams->getMomentumFlux();
135 std::vector<RealType> momentumFluxVector(3);
136
137 switch (rnemdFluxType_) {
138 case rnemdPx:
139 momentumFluxVector[0] = momentumFlux;
140 break;
141 case rnemdPy:
142 momentumFluxVector[1] = momentumFlux;
143 break;
144 case rnemdPz:
145 momentumFluxVector[2] = momentumFlux;
146 break;
147 default:
148 break;
149 }
150
151 setMomentumFluxVector(momentumFluxVector);
152 }
153 }
154
155 void SwapMethod::doRNEMDImpl(SelectionManager& smanA,
156 SelectionManager& smanB) {
157 if (!doRNEMD_) return;
158 int selei;
159 int selej;
160
161 StuntDouble* sd;
162
163 RealType min_val(0.0);
164 int min_found = 0;
165 StuntDouble* min_sd = NULL;
166
167 RealType max_val(0.0);
168 int max_found = 0;
169 StuntDouble* max_sd = NULL;
170
171 for (sd = smanA.beginSelected(selei); sd != NULL;
172 sd = smanA.nextSelected(selei)) {
173 Vector3d pos = sd->getPos();
174
175 // wrap the stuntdouble's position back into the box:
176
177 if (usePeriodicBoundaryConditions_) currentSnap_->wrapVector(pos);
178
179 RealType mass = sd->getMass();
180 Vector3d vel = sd->getVel();
181 RealType value(0.0);
182
183 switch (rnemdFluxType_) {
184 case rnemdKE:
185
186 value = mass * vel.lengthSquare();
187
188 if (sd->isDirectional()) {
189 Vector3d angMom = sd->getJ();
190 Mat3x3d I = sd->getI();
191
192 if (sd->isLinear()) {
193 int i = sd->linearAxis();
194 int j = (i + 1) % 3;
195 int k = (i + 2) % 3;
196 value += angMom[j] * angMom[j] / I(j, j) +
197 angMom[k] * angMom[k] / I(k, k);
198 } else {
199 value += angMom[0] * angMom[0] / I(0, 0) +
200 angMom[1] * angMom[1] / I(1, 1) +
201 angMom[2] * angMom[2] / I(2, 2);
202 }
203 } // angular momenta exchange enabled
204 value *= 0.5;
205 break;
206 case rnemdPx:
207 value = mass * vel[0];
208 break;
209 case rnemdPy:
210 value = mass * vel[1];
211 break;
212 case rnemdPz:
213 value = mass * vel[2];
214 break;
215 default:
216 break;
217 }
218 if (!max_found) {
219 max_val = value;
220 max_sd = sd;
221 max_found = 1;
222 } else {
223 if (max_val < value) {
224 max_val = value;
225 max_sd = sd;
226 }
227 }
228 }
229
230 for (sd = smanB.beginSelected(selej); sd != NULL;
231 sd = smanB.nextSelected(selej)) {
232 Vector3d pos = sd->getPos();
233
234 // wrap the stuntdouble's position back into the box:
235
236 if (usePeriodicBoundaryConditions_) currentSnap_->wrapVector(pos);
237
238 RealType mass = sd->getMass();
239 Vector3d vel = sd->getVel();
240 RealType value(0.0);
241
242 switch (rnemdFluxType_) {
243 case rnemdKE:
244
245 value = mass * vel.lengthSquare();
246
247 if (sd->isDirectional()) {
248 Vector3d angMom = sd->getJ();
249 Mat3x3d I = sd->getI();
250
251 if (sd->isLinear()) {
252 int i = sd->linearAxis();
253 int j = (i + 1) % 3;
254 int k = (i + 2) % 3;
255 value += angMom[j] * angMom[j] / I(j, j) +
256 angMom[k] * angMom[k] / I(k, k);
257 } else {
258 value += angMom[0] * angMom[0] / I(0, 0) +
259 angMom[1] * angMom[1] / I(1, 1) +
260 angMom[2] * angMom[2] / I(2, 2);
261 }
262 } // angular momenta exchange enabled
263 value *= 0.5;
264 break;
265 case rnemdPx:
266 value = mass * vel[0];
267 break;
268 case rnemdPy:
269 value = mass * vel[1];
270 break;
271 case rnemdPz:
272 value = mass * vel[2];
273 break;
274 default:
275 break;
276 }
277
278 if (!min_found) {
279 min_val = value;
280 min_sd = sd;
281 min_found = 1;
282 } else {
283 if (min_val > value) {
284 min_val = value;
285 min_sd = sd;
286 }
287 }
288 }
289
290#ifdef IS_MPI
291 int worldRank;
292 MPI_Comm_rank(MPI_COMM_WORLD, &worldRank);
293
294 int my_min_found = min_found;
295 int my_max_found = max_found;
296
297 // Even if we didn't find a minimum, did someone else?
298 MPI_Allreduce(&my_min_found, &min_found, 1, MPI_INT, MPI_LOR,
299 MPI_COMM_WORLD);
300 // Even if we didn't find a maximum, did someone else?
301 MPI_Allreduce(&my_max_found, &max_found, 1, MPI_INT, MPI_LOR,
302 MPI_COMM_WORLD);
303#endif
304
305 if (max_found && min_found) {
306#ifdef IS_MPI
307 struct {
308 RealType val;
309 int rank;
310 } max_vals, min_vals;
311
312 if (my_min_found) {
313 min_vals.val = min_val;
314 } else {
315 min_vals.val = HONKING_LARGE_VALUE;
316 }
317 min_vals.rank = worldRank;
318
319 // Who had the minimum?
320 MPI_Allreduce(&min_vals, &min_vals, 1, MPI_REALTYPE_INT, MPI_MINLOC,
321 MPI_COMM_WORLD);
322 min_val = min_vals.val;
323
324 if (my_max_found) {
325 max_vals.val = max_val;
326 } else {
327 max_vals.val = -HONKING_LARGE_VALUE;
328 }
329 max_vals.rank = worldRank;
330
331 // Who had the maximum?
332 MPI_Allreduce(&max_vals, &max_vals, 1, MPI_REALTYPE_INT, MPI_MAXLOC,
333 MPI_COMM_WORLD);
334 max_val = max_vals.val;
335#endif
336
337 if (min_val < max_val) {
338#ifdef IS_MPI
339 if (max_vals.rank == worldRank && min_vals.rank == worldRank) {
340 // I have both maximum and minimum, so proceed like a single
341 // processor version:
342#endif
343
344 Vector3d min_vel = min_sd->getVel();
345 Vector3d max_vel = max_sd->getVel();
346 RealType temp_vel;
347
348 switch (rnemdFluxType_) {
349 case rnemdKE:
350 min_sd->setVel(max_vel);
351 max_sd->setVel(min_vel);
352 if (min_sd->isDirectional() && max_sd->isDirectional()) {
353 Vector3d min_angMom = min_sd->getJ();
354 Vector3d max_angMom = max_sd->getJ();
355 min_sd->setJ(max_angMom);
356 max_sd->setJ(min_angMom);
357 } // angular momenta exchange enabled
358 // assumes same rigid body identity
359 break;
360 case rnemdPx:
361 temp_vel = min_vel.x();
362 min_vel.x() = max_vel.x();
363 max_vel.x() = temp_vel;
364 min_sd->setVel(min_vel);
365 max_sd->setVel(max_vel);
366 break;
367 case rnemdPy:
368 temp_vel = min_vel.y();
369 min_vel.y() = max_vel.y();
370 max_vel.y() = temp_vel;
371 min_sd->setVel(min_vel);
372 max_sd->setVel(max_vel);
373 break;
374 case rnemdPz:
375 temp_vel = min_vel.z();
376 min_vel.z() = max_vel.z();
377 max_vel.z() = temp_vel;
378 min_sd->setVel(min_vel);
379 max_sd->setVel(max_vel);
380 break;
381 default:
382 break;
383 }
384
385#ifdef IS_MPI
386 // the rest of the cases only apply in parallel simulations:
387 } else if (max_vals.rank == worldRank) {
388 // I had the max, but not the minimum
389
390 Vector3d min_vel;
391 Vector3d max_vel = max_sd->getVel();
392 MPI_Status status;
393
394 // point-to-point swap of the velocity vector
395 MPI_Sendrecv(max_vel.getArrayPointer(), 3, MPI_REALTYPE,
396 min_vals.rank, 0, min_vel.getArrayPointer(), 3,
397 MPI_REALTYPE, min_vals.rank, 0, MPI_COMM_WORLD, &status);
398
399 switch (rnemdFluxType_) {
400 case rnemdKE:
401 max_sd->setVel(min_vel);
402 // angular momenta exchange enabled
403 if (max_sd->isDirectional()) {
404 Vector3d min_angMom;
405 Vector3d max_angMom = max_sd->getJ();
406
407 // point-to-point swap of the angular momentum vector
408 MPI_Sendrecv(max_angMom.getArrayPointer(), 3, MPI_REALTYPE,
409 min_vals.rank, 1, min_angMom.getArrayPointer(), 3,
410 MPI_REALTYPE, min_vals.rank, 1, MPI_COMM_WORLD,
411 &status);
412
413 max_sd->setJ(min_angMom);
414 }
415 break;
416 case rnemdPx:
417 max_vel.x() = min_vel.x();
418 max_sd->setVel(max_vel);
419 break;
420 case rnemdPy:
421 max_vel.y() = min_vel.y();
422 max_sd->setVel(max_vel);
423 break;
424 case rnemdPz:
425 max_vel.z() = min_vel.z();
426 max_sd->setVel(max_vel);
427 break;
428 default:
429 break;
430 }
431 } else if (min_vals.rank == worldRank) {
432 // I had the minimum but not the maximum:
433
434 Vector3d max_vel;
435 Vector3d min_vel = min_sd->getVel();
436 MPI_Status status;
437
438 // point-to-point swap of the velocity vector
439 MPI_Sendrecv(min_vel.getArrayPointer(), 3, MPI_REALTYPE,
440 max_vals.rank, 0, max_vel.getArrayPointer(), 3,
441 MPI_REALTYPE, max_vals.rank, 0, MPI_COMM_WORLD, &status);
442
443 switch (rnemdFluxType_) {
444 case rnemdKE:
445 min_sd->setVel(max_vel);
446 // angular momenta exchange enabled
447 if (min_sd->isDirectional()) {
448 Vector3d min_angMom = min_sd->getJ();
449 Vector3d max_angMom;
450
451 // point-to-point swap of the angular momentum vector
452 MPI_Sendrecv(min_angMom.getArrayPointer(), 3, MPI_REALTYPE,
453 max_vals.rank, 1, max_angMom.getArrayPointer(), 3,
454 MPI_REALTYPE, max_vals.rank, 1, MPI_COMM_WORLD,
455 &status);
456
457 min_sd->setJ(max_angMom);
458 }
459 break;
460 case rnemdPx:
461 min_vel.x() = max_vel.x();
462 min_sd->setVel(min_vel);
463 break;
464 case rnemdPy:
465 min_vel.y() = max_vel.y();
466 min_sd->setVel(min_vel);
467 break;
468 case rnemdPz:
469 min_vel.z() = max_vel.z();
470 min_sd->setVel(min_vel);
471 break;
472 default:
473 break;
474 }
475 }
476#endif
477
478 switch (rnemdFluxType_) {
479 case rnemdKE:
480 kineticExchange_ += max_val - min_val;
481 break;
482 case rnemdPx:
483 momentumExchange_.x() += max_val - min_val;
484 break;
485 case rnemdPy:
486 momentumExchange_.y() += max_val - min_val;
487 break;
488 case rnemdPz:
489 momentumExchange_.z() += max_val - min_val;
490 break;
491 default:
492 break;
493 }
494 } else {
495 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
496 "RNEMD::doSwap exchange NOT performed "
497 "because min_val > max_val\n");
498 painCave.isFatal = 0;
499 painCave.severity = OPENMD_INFO;
500 simError();
501 failTrialCount_++;
502 }
503 } else {
504 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
505 "Swap exchange NOT performed because selected object\n"
506 "\twas not present in at least one of the two slabs.\n");
507 painCave.isFatal = 0;
508 painCave.severity = OPENMD_INFO;
509 simError();
510 failTrialCount_++;
511 }
512 }
513} // namespace OpenMD::RNEMD
ForceManager is responsible for calculating both the short range (bonded) interactions and long range...
StuntDouble * nextSelected(int &i)
Finds the next selected StuntDouble in the selection.
StuntDouble * beginSelected(int &i)
Finds the first selected StuntDouble in the selection.
One of the heavy-weight classes of OpenMD, SimInfo maintains objects and variables relating to the cu...
Definition SimInfo.hpp:93
"Don't move, or you're dead! Stand up! Captain, we've got them!"
Vector3d getVel()
Returns the current velocity of this stuntDouble.
int linearAxis()
Returns the linear axis of the rigidbody, atom and directional atom will always return -1.
RealType getMass()
Returns the mass of this stuntDouble.
virtual Mat3x3d getI()=0
Returns the inertia tensor of this stuntDouble.
bool isLinear()
Tests the if this stuntDouble is a linear rigidbody.
Vector3d getPos()
Returns the current position of this stuntDouble.
void setVel(const Vector3d &vel)
Sets the current velocity of this stuntDouble.
Vector3d getJ()
Returns the current angular momentum of this stuntDouble (body -fixed).
bool isDirectional()
Tests if this stuntDouble is a directional one.
void setJ(const Vector3d &angMom)
Sets the current angular momentum of this stuntDouble (body-fixed).
Real & z()
Returns reference of the third element of Vector3.
Definition Vector3.hpp:120
Real & x()
Returns reference of the first element of Vector3.
Definition Vector3.hpp:96
Real & y()
Returns reference of the second element of Vector3.
Definition Vector3.hpp:108
Real * getArrayPointer()
Returns the pointer of internal array.
Definition Vector.hpp:171
Real lengthSquare()
Returns the squared length of this vector.
Definition Vector.hpp:399