OpenMD 3.1
Molecular Dynamics in the Open
Loading...
Searching...
No Matches
SPF.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/SPF.hpp"
46
47#include <config.h>
48
49#include <cmath>
50#include <random>
51#include <vector>
52
53#ifdef IS_MPI
54#include <mpi.h>
55#endif
56
58#include "brains/SimInfo.hpp"
59#include "math/Vector3.hpp"
62#include "rnemd/RNEMD.hpp"
63#include "rnemd/RNEMDParameters.hpp"
64#include "rnemd/SPFForceManager.hpp"
65#include "selection/SelectionManager.hpp"
66#include "utils/Constants.hpp"
67#include "utils/RandNumGen.hpp"
68#include "utils/simError.h"
69
70namespace OpenMD::RNEMD {
71
72 SPFMethod::SPFMethod(SimInfo* info, ForceManager* forceMan) :
73 RNEMD {info, forceMan}, selectedMoleculeMan_(info),
74 selectedMoleculeEvaluator_(info) {
75 rnemdMethodLabel_ = "SPF";
76
77 selectedMoleculeStr_ = "select none";
78 selectedMoleculeEvaluator_.loadScriptString(selectedMoleculeStr_);
79 selectedMoleculeMan_.setSelectionSet(selectedMoleculeEvaluator_.evaluate());
80
81 if (SPFForceManager* spfForceManager =
82 dynamic_cast<SPFForceManager*>(forceMan)) {
83 forceManager_ = spfForceManager;
84 } else {
85 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
86 "SPF-RNEMD cannot be used with the default ForceManager.\n");
87 painCave.isFatal = 1;
88 painCave.severity = OPENMD_ERROR;
89 simError();
90 }
91
92 RNEMDParameters* rnemdParams = info->getSimParams()->getRNEMDParameters();
93
94 bool hasParticleFlux = rnemdParams->haveParticleFlux();
95 bool hasKineticFlux = rnemdParams->haveKineticFlux();
96
97 bool methodFluxMismatch = false;
98 bool hasCorrectFlux = false;
99
100 switch (rnemdFluxType_) {
101 case rnemdParticle:
102 hasCorrectFlux = hasParticleFlux;
103 break;
104 case rnemdParticleKE:
105 hasCorrectFlux = hasParticleFlux && hasKineticFlux;
106 break;
107 default:
108 methodFluxMismatch = true;
109 break;
110 }
111
112 if (methodFluxMismatch) {
113 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
114 "RNEMD: The current method, SPF\n"
115 "\tcannot be used with the current flux type, %s\n",
116 rnemdFluxTypeLabel_.c_str());
117 painCave.isFatal = 1;
118 painCave.severity = OPENMD_ERROR;
119 simError();
120 }
121
122 if (!hasCorrectFlux) {
123 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
124 "RNEMD: The current method, SPF, and flux type, %s,\n"
125 "\tdid not have the correct flux type specified. Options\n"
126 "\tinclude: particleFlux and particleFlux + kineticFlux.\n",
127 rnemdFluxTypeLabel_.c_str());
128 painCave.isFatal = 1;
129 painCave.severity = OPENMD_ERROR;
130 simError();
131 }
132
133 if (hasParticleFlux) {
134 setParticleFlux(rnemdParams->getParticleFlux());
135 } else {
136 setParticleFlux(0.0);
137 }
138
139 if (hasKineticFlux) {
140 setKineticFlux(rnemdParams->getKineticFlux());
141 } else {
142 setKineticFlux(0.0);
143 }
144
145 uniformKineticScaling_ = rnemdParams->getSPFUniformKineticScaling();
146 selectMolecule();
147 }
148
149 void SPFMethod::doRNEMDImpl(SelectionManager& smanA,
150 SelectionManager& smanB) {
151 if (!doRNEMD_) return;
152
153 // Remove selected molecule from the source selection manager
154 if (particleTarget_ > 0.0) {
155 smanA -= selectedMoleculeMan_;
156 } else {
157 smanB -= selectedMoleculeMan_;
158 }
159
160 failedLastTrial_ = false;
161
162 int selei {}, selej {};
163
164 StuntDouble* sd;
165
166 Vector3d P_a {V3Zero};
167 Vector3d P_b {V3Zero};
168 RealType M_a {};
169 RealType M_b {};
170 RealType K_a {};
171 RealType K_b {};
172
173 for (sd = smanA.beginSelected(selei); sd != NULL;
174 sd = smanA.nextSelected(selei)) {
175 RealType mass = sd->getMass();
176 Vector3d vel = sd->getVel();
177
178 P_a += mass * vel;
179 M_a += mass;
180 K_a += mass * vel.lengthSquare();
181
182 if (sd->isDirectional()) {
183 Vector3d angMom = sd->getJ();
184 Mat3x3d I = sd->getI();
185 if (sd->isLinear()) {
186 int i = sd->linearAxis();
187 int j = (i + 1) % 3;
188 int k = (i + 2) % 3;
189 K_a +=
190 angMom[j] * angMom[j] / I(j, j) + angMom[k] * angMom[k] / I(k, k);
191 } else {
192 K_a += angMom[0] * angMom[0] / I(0, 0) +
193 angMom[1] * angMom[1] / I(1, 1) +
194 angMom[2] * angMom[2] / I(2, 2);
195 }
196 }
197 }
198
199 for (sd = smanB.beginSelected(selej); sd != NULL;
200 sd = smanB.nextSelected(selej)) {
201 RealType mass = sd->getMass();
202 Vector3d vel = sd->getVel();
203
204 P_b += mass * vel;
205 M_b += mass;
206 K_b += mass * vel.lengthSquare();
207
208 if (sd->isDirectional()) {
209 Vector3d angMom = sd->getJ();
210 Mat3x3d I = sd->getI();
211 if (sd->isLinear()) {
212 int i = sd->linearAxis();
213 int j = (i + 1) % 3;
214 int k = (i + 2) % 3;
215 K_b +=
216 angMom[j] * angMom[j] / I(j, j) + angMom[k] * angMom[k] / I(k, k);
217 } else {
218 K_b += angMom[0] * angMom[0] / I(0, 0) +
219 angMom[1] * angMom[1] / I(1, 1) +
220 angMom[2] * angMom[2] / I(2, 2);
221 }
222 }
223 }
224
225 K_a *= 0.5;
226 K_b *= 0.5;
227
228#ifdef IS_MPI
229 MPI_Allreduce(MPI_IN_PLACE, &P_a[0], 3, MPI_REALTYPE, MPI_SUM,
230 MPI_COMM_WORLD);
231 MPI_Allreduce(MPI_IN_PLACE, &P_b[0], 3, MPI_REALTYPE, MPI_SUM,
232 MPI_COMM_WORLD);
233 MPI_Allreduce(MPI_IN_PLACE, &M_a, 1, MPI_REALTYPE, MPI_SUM, MPI_COMM_WORLD);
234 MPI_Allreduce(MPI_IN_PLACE, &M_b, 1, MPI_REALTYPE, MPI_SUM, MPI_COMM_WORLD);
235 MPI_Allreduce(MPI_IN_PLACE, &K_a, 1, MPI_REALTYPE, MPI_SUM, MPI_COMM_WORLD);
236 MPI_Allreduce(MPI_IN_PLACE, &K_b, 1, MPI_REALTYPE, MPI_SUM, MPI_COMM_WORLD);
237#endif
238
239 bool successfulExchange = false;
240 bool doExchange = false;
241
242 if ((M_a > 0.0) && (M_b > 0.0) &&
243 forceManager_->getHasSelectedMolecule()) { // both slabs are not empty
244 Vector3d v_a = P_a / M_a;
245 Vector3d v_b = P_b / M_b;
246
247 RealType tempParticleTarget =
248 (particleTarget_ != deltaLambda_) ? deltaLambda_ : particleTarget_;
249
250 RealType deltaU = forceManager_->getScaledDeltaU(tempParticleTarget) *
251 Constants::energyConvert;
252 RealType a {};
253 RealType b {};
254
255 if (uniformKineticScaling_) {
256 RealType numerator = deltaU;
257 RealType denominator = K_a + K_b;
258 denominator -= 0.5 * M_a * v_a.lengthSquare();
259 denominator -= 0.5 * M_b * v_b.lengthSquare();
260
261 RealType a2 = (numerator / denominator) + 1.0;
262
263 if (a2 > 0.0) {
264 a = std::sqrt(a2);
265 b = a;
266
267 // restrict scaling coefficients
268 if ((a > 0.999) && (a < 1.001)) { doExchange = true; }
269 }
270 } else {
271 RealType aNumerator = deltaU - kineticTarget_;
272 RealType aDenominator = 2.0 * K_a;
273 aDenominator -= M_a * v_a.lengthSquare();
274
275 RealType bNumerator = deltaU + kineticTarget_;
276 RealType bDenominator = 2.0 * K_b;
277 bDenominator -= M_b * v_b.lengthSquare();
278
279 RealType a2 = (aNumerator / aDenominator) + 1.0;
280 RealType b2 = (bNumerator / bDenominator) + 1.0;
281
282 if (a2 > 0.0 && b2 > 0.0) {
283 a = std::sqrt(a2);
284 b = std::sqrt(b2);
285
286 // restrict scaling coefficients
287 if ((a > 0.999) && (a < 1.001) && (b > 0.999) && (b < 1.001)) {
288 doExchange = true;
289 }
290 }
291 }
292
293 if (doExchange) {
294 Vector3d vel;
295
296 int selei2 {}, selej2 {};
297 StuntDouble* sd2;
298
299 for (sd2 = smanA.beginSelected(selei2); sd2 != NULL;
300 sd2 = smanA.nextSelected(selei2)) {
301 vel = (sd2->getVel() - v_a) * a + v_a;
302
303 sd2->setVel(vel);
304
305 if (sd2->isDirectional()) {
306 Vector3d angMom = sd2->getJ() * a;
307 sd2->setJ(angMom);
308 }
309 }
310
311 for (sd2 = smanB.beginSelected(selei2); sd2 != NULL;
312 sd2 = smanB.nextSelected(selei2)) {
313 vel = (sd2->getVel() - v_b) * b + v_b;
314
315 sd2->setVel(vel);
316
317 if (sd2->isDirectional()) {
318 Vector3d angMom = sd2->getJ() * b;
319 sd2->setJ(angMom);
320 }
321 }
322
323 currentSnap_->hasTranslationalKineticEnergy = false;
324 currentSnap_->hasRotationalKineticEnergy = false;
325 currentSnap_->hasKineticEnergy = false;
326 currentSnap_->hasTotalEnergy = false;
327
328 RealType deltaLambda = particleTarget_;
329
330 bool updateSelectedMolecule =
331 forceManager_->updateLambda(deltaLambda, deltaLambda_);
332
333 if (updateSelectedMolecule) selectMolecule();
334
335 successfulExchange = true;
336 particleExchange_ += deltaLambda;
337 kineticExchange_ += kineticTarget_;
338 }
339 }
340
341 if (!forceManager_->getHasSelectedMolecule()) {
342 selectMolecule();
343 deltaLambda_ = particleTarget_;
344 failTrialCount_++;
345 failedLastTrial_ = true;
346 return;
347 }
348
349 if (!successfulExchange) {
350 // snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
351 // "SPF exchange NOT performed - roots that solve\n"
352 // "\tthe constraint equations may not exist or there may
353 // be\n"
354 // "\tno selected objects in one or both slabs.\n");
355 // painCave.isFatal = 0;
356 // painCave.severity = OPENMD_INFO;
357 // simError();
358 failTrialCount_++;
359 failedLastTrial_ = true;
360 }
361 }
362
363 void SPFMethod::selectMolecule() {
364 bool hasSelectedMolecule = (currentSnap_->getSPFData()->globalID == -1) ?
365 setSelectedMolecule() :
366 getSelectedMolecule();
367
368 if (hasSelectedMolecule) {
369 selectedMoleculeStr_ =
370 "select " + std::to_string(currentSnap_->getSPFData()->globalID);
371
372 selectedMoleculeEvaluator_.loadScriptString(selectedMoleculeStr_);
373 selectedMoleculeMan_.setSelectionSet(
374 selectedMoleculeEvaluator_.evaluate());
375 }
376
377 forceManager_->setHasSelectedMolecule(hasSelectedMolecule);
378 }
379
380 bool SPFMethod::getSelectedMolecule() {
381 std::shared_ptr<SPFData> spfData = currentSnap_->getSPFData();
382
383 Molecule* selectedMolecule;
384
385 selectedMolecule = info_->getMoleculeByGlobalIndex(spfData->globalID);
386
387 if (selectedMolecule) {
388 forceManager_->setSelectedMolecule(selectedMolecule);
389 }
390
391 return true;
392 }
393
394 bool SPFMethod::setSelectedMolecule() {
395 SelectionManager sourceSman {info_};
396 RealType targetSlabCenter {};
397
398 // The sign of our flux determines which slab is the source and which is
399 // the sink
400 if (particleTarget_ > 0.0) {
401 sourceSman = commonA_;
402 targetSlabCenter = slabBCenter_;
403 } else {
404 sourceSman = commonB_;
405 targetSlabCenter = slabACenter_;
406 }
407
408 if (sourceSman.getMoleculeSelectionCount() == 0) {
409 forceManager_->setSelectedMolecule(nullptr);
410 return false;
411 }
412
413 int whichSelectedID {-1};
414 Molecule* selectedMolecule;
415
416 std::shared_ptr<SPFData> spfData = currentSnap_->getSPFData();
417 Utils::RandNumGenPtr randNumGen = info_->getRandomNumberGenerator();
418
419#ifdef IS_MPI
420 int worldRank {}, size {};
421
422 MPI_Comm_rank(MPI_COMM_WORLD, &worldRank);
423 MPI_Comm_size(MPI_COMM_WORLD, &size);
424 MPI_Status ierr;
425
426 if (worldRank == 0) {
427#endif
428 std::uniform_int_distribution<> selectedMoleculeDistribution {
429 0, sourceSman.getMoleculeSelectionCount() - 1};
430
431 whichSelectedID = selectedMoleculeDistribution(*randNumGen);
432#ifdef IS_MPI
433 }
434
435 MPI_Bcast(&whichSelectedID, 1, MPI_INT, 0, MPI_COMM_WORLD);
436#endif
437
438 selectedMolecule = sourceSman.nthSelectedMolecule(whichSelectedID);
439
440 int globalSelectedID {-1};
441
442 if (selectedMolecule) {
443 globalSelectedID = selectedMolecule->getGlobalIndex();
444
445#ifdef IS_MPI
446 for (int i {}; i < size; ++i) {
447 if (i != worldRank) {
448 MPI_Send(&globalSelectedID, 1, MPI_INT, i, 0, MPI_COMM_WORLD);
449 }
450 }
451 } else {
452 MPI_Recv(&globalSelectedID, 1, MPI_INT, MPI_ANY_SOURCE, 0, MPI_COMM_WORLD,
453 &ierr);
454#endif
455 }
456
457 int axis0 = (rnemdPrivilegedAxis_ + 1) % 3;
458 int axis1 = (rnemdPrivilegedAxis_ + 2) % 3;
459 int axis2 = rnemdPrivilegedAxis_;
460
461 spfData->globalID = globalSelectedID;
462
463#ifdef IS_MPI
464 if (info_->getMolToProc(globalSelectedID) == worldRank) {
465#endif
466 std::uniform_real_distribution<RealType> distr0 {0, hmat_(axis0, axis0)};
467 std::uniform_real_distribution<RealType> distr1 {0, hmat_(axis1, axis1)};
468 std::normal_distribution<RealType> distr2 {targetSlabCenter,
469 0.25 * slabWidth_};
470
471 spfData->pos[axis0] = distr0(*randNumGen);
472 spfData->pos[axis1] = distr1(*randNumGen);
473 spfData->pos[axis2] = distr2(*randNumGen);
474
475 forceManager_->setSelectedMolecule(selectedMolecule);
476
477#ifdef IS_MPI
478 }
479 MPI_Bcast(&spfData->pos[0], 3, MPI_REALTYPE,
480 info_->getMolToProc(globalSelectedID), MPI_COMM_WORLD);
481#endif
482
483 return true;
484 }
485} // namespace OpenMD::RNEMD
ForceManager is responsible for calculating both the short range (bonded) interactions and long range...
int getGlobalIndex()
Returns the global index of this molecule.
Definition Molecule.hpp:107
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.
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 lengthSquare()
Returns the squared length of this vector.
Definition Vector.hpp:399