OpenMD 3.2
Molecular Dynamics in the Open
Loading...
Searching...
No Matches
VSS.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 following paper when you publish your work:
33 *
34 * [1] Drisko et al., J. Open Source Softw. 9, 7004 (2024).
35 *
36 * Good starting points for code and simulation methodology are:
37 *
38 * [2] Meineke, et al., J. Comp. Chem. 26, 252-271 (2005).
39 * [3] Fennell & Gezelter, J. Chem. Phys. 124, 234104 (2006).
40 * [4] Sun, Lin & Gezelter, J. Chem. Phys. 128, 234107 (2008).
41 * [5] Vardeman, Stocker & Gezelter, J. Chem. Theory Comput. 7, 834 (2011).
42 * [6] Kuang & Gezelter, Mol. Phys., 110, 691-701 (2012).
43 * [7] Lamichhane, Gezelter & Newman, J. Chem. Phys. 141, 134109 (2014).
44 * [8] Bhattarai, Newman & Gezelter, Phys. Rev. B 99, 094106 (2019).
45 * [9] Drisko & Gezelter, J. Chem. Theory Comput. 20, 4986-4997 (2024).
46 */
47
48#include "rnemd/VSS.hpp"
49
50#include <algorithm>
51#include <cmath>
52#include <map>
53#include <set>
54#include <sstream>
55#include <string>
56#include <vector>
57
58#ifdef IS_MPI
59#include <mpi.h>
60#endif
61
63#include "brains/Thermo.hpp"
64#include "io/Globals.hpp"
65#include "math/ConvexHull.hpp"
66#include "math/Polynomial.hpp"
68#include "math/Vector.hpp"
69#include "math/Vector3.hpp"
72#include "rnemd/RNEMD.hpp"
73#include "rnemd/RNEMDParameters.hpp"
74#include "types/FixedChargeAdapter.hpp"
75#include "types/FluctuatingChargeAdapter.hpp"
76#include "utils/Constants.hpp"
77
78namespace OpenMD::RNEMD {
79
80 VSSMethod::VSSMethod(SimInfo* info, ForceManager* forceMan) :
81 RNEMD {info, forceMan} {
82 rnemdMethodLabel_ = "VSS";
83
84 RNEMDParameters* rnemdParams = info->getSimParams()->getRNEMDParameters();
85
86 bool hasKineticFlux = rnemdParams->haveKineticFlux();
87 bool hasMomentumFlux = rnemdParams->haveMomentumFlux();
88 bool hasMomentumFluxVector = rnemdParams->haveMomentumFluxVector();
89 bool hasAngularMomentumFlux = rnemdParams->haveAngularMomentumFlux();
90 bool hasAngularMomentumFluxVector =
91 rnemdParams->haveAngularMomentumFluxVector();
92
93 bool methodFluxMismatch = false;
94 bool hasCorrectFlux = false;
95
96 switch (rnemdFluxType_) {
97 case rnemdKE:
98 case rnemdRotKE:
99 case rnemdFullKE:
100 hasCorrectFlux = hasKineticFlux;
101 break;
102 case rnemdPx:
103 case rnemdPy:
104 case rnemdPz:
105 hasCorrectFlux = hasMomentumFlux;
106 break;
107 case rnemdLx:
108 case rnemdLy:
109 case rnemdLz:
110 hasCorrectFlux = hasAngularMomentumFlux;
111 break;
112 case rnemdPvector:
113 hasCorrectFlux = hasMomentumFluxVector;
114 break;
115 case rnemdLvector:
116 hasCorrectFlux = hasAngularMomentumFluxVector;
117 break;
118 case rnemdKePx:
119 case rnemdKePy:
120 hasCorrectFlux = hasMomentumFlux && hasKineticFlux;
121 break;
122 case rnemdKeLx:
123 case rnemdKeLy:
124 case rnemdKeLz:
125 hasCorrectFlux = hasAngularMomentumFlux && hasKineticFlux;
126 break;
127 case rnemdKePvector:
128 hasCorrectFlux = hasMomentumFluxVector && hasKineticFlux;
129 break;
130 case rnemdKeLvector:
131 hasCorrectFlux = hasAngularMomentumFluxVector && hasKineticFlux;
132 break;
133 default:
134 methodFluxMismatch = true;
135 break;
136 }
137
138 if (methodFluxMismatch) {
139 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
140 "RNEMD: The current method,\n"
141 "\t\tVSS\n"
142 "\tcannot be used with the current flux type, %s\n",
143 rnemdFluxTypeLabel_.c_str());
144 painCave.isFatal = 1;
145 painCave.severity = OPENMD_ERROR;
146 simError();
147 }
148
149 if (!hasCorrectFlux) {
150 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
151 "RNEMD: The current method, VSS, and flux type, %s,\n"
152 "\tdid not have the correct flux value specified. Options\n"
153 "\tinclude: kineticFlux, momentumFlux, angularMomentumFlux,\n"
154 "\tmomentumFluxVector, and angularMomentumFluxVector.\n",
155 rnemdFluxTypeLabel_.c_str());
156 painCave.isFatal = 1;
157 painCave.severity = OPENMD_ERROR;
158 simError();
159 }
160
161 if (hasKineticFlux) {
162 setKineticFlux(rnemdParams->getKineticFlux());
163 } else {
164 setKineticFlux(0.0);
165 }
166
167 if (hasMomentumFluxVector) {
168 setMomentumFluxVector(rnemdParams->getMomentumFluxVector());
169 } else {
170 std::vector<RealType> momentumFluxVector(3);
171
172 if (hasMomentumFlux) {
173 RealType momentumFlux = rnemdParams->getMomentumFlux();
174
175 switch (rnemdFluxType_) {
176 case rnemdPx:
177 case rnemdKePx:
178 momentumFluxVector[0] = momentumFlux;
179 break;
180 case rnemdPy:
181 case rnemdKePy:
182 momentumFluxVector[1] = momentumFlux;
183 break;
184 case rnemdPz:
185 momentumFluxVector[2] = momentumFlux;
186 break;
187 default:
188 break;
189 }
190 }
191
192 setMomentumFluxVector(momentumFluxVector);
193 }
194
195 if (hasAngularMomentumFluxVector) {
196 setAngularMomentumFluxVector(rnemdParams->getAngularMomentumFluxVector());
197 } else {
198 std::vector<RealType> angularMomentumFluxVector(3);
199
200 if (hasAngularMomentumFlux) {
201 RealType angularMomentumFlux = rnemdParams->getAngularMomentumFlux();
202
203 switch (rnemdFluxType_) {
204 case rnemdLx:
205 case rnemdKeLx:
206 angularMomentumFluxVector[0] = angularMomentumFlux;
207 break;
208 case rnemdLy:
209 case rnemdKeLy:
210 angularMomentumFluxVector[1] = angularMomentumFlux;
211 break;
212 case rnemdLz:
213 case rnemdKeLz:
214 angularMomentumFluxVector[2] = angularMomentumFlux;
215 default:
216 break;
217 }
218 }
219
220 setAngularMomentumFluxVector(angularMomentumFluxVector);
221 }
222 }
223
224 void VSSMethod::doRNEMDImpl(SelectionManager& smanA,
225 SelectionManager& smanB) {
226 if (!doRNEMD_) return;
227 int selei;
228 int selej;
229
230 StuntDouble* sd;
231
232 vector<StuntDouble*> hotBin, coldBin;
233
234 Vector3d Ph(V3Zero);
235 Vector3d Lh(V3Zero);
236 RealType Mh = 0.0;
237 Mat3x3d Ih(0.0);
238 RealType Kh = 0.0;
239 Vector3d Pc(V3Zero);
240 Vector3d Lc(V3Zero);
241 RealType Mc = 0.0;
242 Mat3x3d Ic(0.0);
243 RealType Kc = 0.0;
244
245 // Constraints can be on only the linear or angular momentum, but
246 // not both. Usually, the user will specify which they want, but
247 // in case they don't, the use of periodic boundaries should make
248 // the choice for us.
249 bool doLinearPart = false;
250 bool doAngularPart = false;
251
252 switch (rnemdFluxType_) {
253 case rnemdPx:
254 case rnemdPy:
255 case rnemdPz:
256 case rnemdPvector:
257 case rnemdKePx:
258 case rnemdKePy:
259 case rnemdKePvector:
260 doLinearPart = true;
261 break;
262 case rnemdLx:
263 case rnemdLy:
264 case rnemdLz:
265 case rnemdLvector:
266 case rnemdKeLx:
267 case rnemdKeLy:
268 case rnemdKeLz:
269 case rnemdKeLvector:
270 doAngularPart = true;
271 break;
272 case rnemdKE:
273 case rnemdRotKE:
274 case rnemdFullKE:
275 default:
276 if (usePeriodicBoundaryConditions_)
277 doLinearPart = true;
278 else
279 doAngularPart = true;
280 break;
281 }
282
283 for (sd = smanA.beginSelected(selei); sd != NULL;
284 sd = smanA.nextSelected(selei)) {
285 Vector3d pos = sd->getPos();
286
287 // wrap the stuntdouble's position back into the box:
288 if (usePeriodicBoundaryConditions_) currentSnap_->wrapVector(pos);
289
290 RealType mass = sd->getMass();
291 Vector3d vel = sd->getVel();
292 Vector3d rPos = sd->getPos() - coordinateOrigin_;
293 RealType r2;
294
295 hotBin.push_back(sd);
296 Ph += mass * vel;
297 Mh += mass;
298 Kh += mass * vel.lengthSquare();
299 Lh += mass * cross(rPos, vel);
300 Ih -= outProduct(rPos, rPos) * mass;
301 r2 = rPos.lengthSquare();
302 Ih(0, 0) += mass * r2;
303 Ih(1, 1) += mass * r2;
304 Ih(2, 2) += mass * r2;
305
306 if (rnemdFluxType_ == rnemdFullKE) {
307 if (sd->isDirectional()) {
308 Vector3d angMom = sd->getJ();
309 Mat3x3d I = sd->getI();
310 if (sd->isLinear()) {
311 int i = sd->linearAxis();
312 int j = (i + 1) % 3;
313 int k = (i + 2) % 3;
314 Kh += angMom[j] * angMom[j] / I(j, j) +
315 angMom[k] * angMom[k] / I(k, k);
316 } else {
317 Kh += angMom[0] * angMom[0] / I(0, 0) +
318 angMom[1] * angMom[1] / I(1, 1) +
319 angMom[2] * angMom[2] / I(2, 2);
320 }
321 }
322 }
323 }
324
325 for (sd = smanB.beginSelected(selej); sd != NULL;
326 sd = smanB.nextSelected(selej)) {
327 Vector3d pos = sd->getPos();
328
329 // wrap the stuntdouble's position back into the box:
330 if (usePeriodicBoundaryConditions_) currentSnap_->wrapVector(pos);
331
332 RealType mass = sd->getMass();
333 Vector3d vel = sd->getVel();
334 Vector3d rPos = sd->getPos() - coordinateOrigin_;
335 RealType r2;
336
337 coldBin.push_back(sd);
338 Pc += mass * vel;
339 Mc += mass;
340 Kc += mass * vel.lengthSquare();
341 Lc += mass * cross(rPos, vel);
342 Ic -= outProduct(rPos, rPos) * mass;
343 r2 = rPos.lengthSquare();
344 Ic(0, 0) += mass * r2;
345 Ic(1, 1) += mass * r2;
346 Ic(2, 2) += mass * r2;
347
348 if (rnemdFluxType_ == rnemdFullKE) {
349 if (sd->isDirectional()) {
350 Vector3d angMom = sd->getJ();
351 Mat3x3d I = sd->getI();
352 if (sd->isLinear()) {
353 int i = sd->linearAxis();
354 int j = (i + 1) % 3;
355 int k = (i + 2) % 3;
356 Kc += angMom[j] * angMom[j] / I(j, j) +
357 angMom[k] * angMom[k] / I(k, k);
358 } else {
359 Kc += angMom[0] * angMom[0] / I(0, 0) +
360 angMom[1] * angMom[1] / I(1, 1) +
361 angMom[2] * angMom[2] / I(2, 2);
362 }
363 }
364 }
365 }
366
367 Kh *= 0.5;
368 Kc *= 0.5;
369
370#ifdef IS_MPI
371 MPI_Allreduce(MPI_IN_PLACE, &Ph[0], 3, MPI_REALTYPE, MPI_SUM,
372 MPI_COMM_WORLD);
373 MPI_Allreduce(MPI_IN_PLACE, &Pc[0], 3, MPI_REALTYPE, MPI_SUM,
374 MPI_COMM_WORLD);
375 MPI_Allreduce(MPI_IN_PLACE, &Lh[0], 3, MPI_REALTYPE, MPI_SUM,
376 MPI_COMM_WORLD);
377 MPI_Allreduce(MPI_IN_PLACE, &Lc[0], 3, MPI_REALTYPE, MPI_SUM,
378 MPI_COMM_WORLD);
379 MPI_Allreduce(MPI_IN_PLACE, &Mh, 1, MPI_REALTYPE, MPI_SUM, MPI_COMM_WORLD);
380 MPI_Allreduce(MPI_IN_PLACE, &Kh, 1, MPI_REALTYPE, MPI_SUM, MPI_COMM_WORLD);
381 MPI_Allreduce(MPI_IN_PLACE, &Mc, 1, MPI_REALTYPE, MPI_SUM, MPI_COMM_WORLD);
382 MPI_Allreduce(MPI_IN_PLACE, &Kc, 1, MPI_REALTYPE, MPI_SUM, MPI_COMM_WORLD);
383 MPI_Allreduce(MPI_IN_PLACE, Ih.getArrayPointer(), 9, MPI_REALTYPE, MPI_SUM,
384 MPI_COMM_WORLD);
385 MPI_Allreduce(MPI_IN_PLACE, Ic.getArrayPointer(), 9, MPI_REALTYPE, MPI_SUM,
386 MPI_COMM_WORLD);
387#endif
388
389 Vector3d ac, acrec, bc, bcrec;
390 Vector3d ah, ahrec, bh, bhrec;
391
392 bool successfulExchange = false;
393 if ((Mh > 0.0) && (Mc > 0.0)) { // both slabs are not empty
394
395 Vector3d vc = Pc / Mc;
396 ac = -momentumTarget_ / Mc + vc;
397 acrec = -momentumTarget_ / Mc;
398
399 // We now need the inverse of the inertia tensor to calculate the
400 // angular velocity of the cold slab;
401 Mat3x3d Ici = Ic.inverse();
402 Vector3d omegac = Ici * Lc;
403 bc = -(Ici * angularMomentumTarget_) + omegac;
404 bcrec = bc - omegac;
405
406 RealType cNumerator = Kc - kineticTarget_;
407 if (doLinearPart) cNumerator -= 0.5 * Mc * ac.lengthSquare();
408
409 if (doAngularPart) cNumerator -= 0.5 * (dot(bc, Ic * bc));
410
411 RealType cDenominator = Kc;
412
413 if (doLinearPart) cDenominator -= 0.5 * Mc * vc.lengthSquare();
414
415 if (doAngularPart) cDenominator -= 0.5 * (dot(omegac, Ic * omegac));
416
417 if (cNumerator / cDenominator > 0.0) {
418 RealType c = sqrt(cNumerator / cDenominator);
419
420 if ((c > 0.9) && (c < 1.1)) { // restrict scaling coefficients
421
422 Vector3d vh = Ph / Mh;
423 ah = momentumTarget_ / Mh + vh;
424 ahrec = momentumTarget_ / Mh;
425
426 // We now need the inverse of the inertia tensor to
427 // calculate the angular velocity of the hot slab;
428 Mat3x3d Ihi = Ih.inverse();
429 Vector3d omegah = Ihi * Lh;
430 bh = (Ihi * angularMomentumTarget_) + omegah;
431 bhrec = bh - omegah;
432
433 RealType hNumerator = Kh + kineticTarget_;
434 if (doLinearPart) hNumerator -= 0.5 * Mh * ah.lengthSquare();
435
436 if (doAngularPart) hNumerator -= 0.5 * (dot(bh, Ih * bh));
437
438 RealType hDenominator = Kh;
439 if (doLinearPart) hDenominator -= 0.5 * Mh * vh.lengthSquare();
440 if (doAngularPart) hDenominator -= 0.5 * (dot(omegah, Ih * omegah));
441
442 if (hNumerator / hDenominator > 0.0) {
443 RealType h = sqrt(hNumerator / hDenominator);
444
445 if ((h > 0.9) && (h < 1.1)) {
446 vector<StuntDouble*>::iterator sdi;
447 Vector3d vel;
448 Vector3d rPos;
449
450 for (sdi = coldBin.begin(); sdi != coldBin.end(); ++sdi) {
451 if (doLinearPart) vel = ((*sdi)->getVel() - vc) * c + ac;
452 if (doAngularPart) {
453 rPos = (*sdi)->getPos() - coordinateOrigin_;
454 vel = ((*sdi)->getVel() - cross(omegac, rPos)) * c +
455 cross(bc, rPos);
456 }
457
458 (*sdi)->setVel(vel);
459
460 if (rnemdFluxType_ == rnemdFullKE) {
461 if ((*sdi)->isDirectional()) {
462 Vector3d angMom = (*sdi)->getJ() * c;
463 (*sdi)->setJ(angMom);
464 }
465 }
466 }
467
468 for (sdi = hotBin.begin(); sdi != hotBin.end(); ++sdi) {
469 if (doLinearPart) vel = ((*sdi)->getVel() - vh) * h + ah;
470 if (doAngularPart) {
471 rPos = (*sdi)->getPos() - coordinateOrigin_;
472 vel = ((*sdi)->getVel() - cross(omegah, rPos)) * h +
473 cross(bh, rPos);
474 }
475
476 (*sdi)->setVel(vel);
477
478 if (rnemdFluxType_ == rnemdFullKE) {
479 if ((*sdi)->isDirectional()) {
480 Vector3d angMom = (*sdi)->getJ() * h;
481 (*sdi)->setJ(angMom);
482 }
483 }
484 }
485
486 successfulExchange = true;
487 kineticExchange_ += kineticTarget_;
488 momentumExchange_ += momentumTarget_;
489 angularMomentumExchange_ += angularMomentumTarget_;
490 }
491 }
492 }
493 }
494 }
495
496 if (successfulExchange != true) {
497 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
498 "VSS exchange NOT performed - roots that solve\n"
499 "\tthe constraint equations may not exist or there may be\n"
500 "\tno selected objects in one or both slabs.\n");
501 painCave.isFatal = 0;
502 painCave.severity = OPENMD_INFO;
503 simError();
504 failTrialCount_++;
505 }
506 }
507} // namespace OpenMD::RNEMD
SquareMatrix3< Real > inverse() const
Sets the value of this matrix to the inverse of itself.
Real lengthSquare() const
Returns the squared length of this vector.
Definition Vector.hpp:403
Vector3< Real > cross(const Vector3< Real > &v1, const Vector3< Real > &v2)
Returns the cross product of two Vectors.
Definition Vector3.hpp:139
Real dot(const DynamicVector< Real > &v1, const DynamicVector< Real > &v2)
Returns the dot product of two DynamicVectors.