OpenMD 3.2
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 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/SPF.hpp"
49
50#include <config.h>
51
52#include <cmath>
53#include <random>
54#include <vector>
55
56#ifdef IS_MPI
57#include <mpi.h>
58#endif
59
61#include "brains/SimInfo.hpp"
62#include "math/Vector3.hpp"
65#include "rnemd/RNEMD.hpp"
66#include "rnemd/RNEMDParameters.hpp"
67#include "rnemd/SPFForceManager.hpp"
68#include "selection/SelectionManager.hpp"
69#include "types/FixedChargeAdapter.hpp"
70#include "utils/Constants.hpp"
71#include "utils/RandNumGen.hpp"
72#include "utils/simError.h"
73
74namespace OpenMD::RNEMD {
75
76 SPFMethod::SPFMethod(SimInfo* info, ForceManager* forceMan) :
77 RNEMD {info, forceMan}, smanA_ {info}, smanB_ {info}, anionMan_ {info},
78 cationMan_ {info}, selectedMoleculeMan_ {info},
79 selectedMoleculeEvaluator_ {info} {
80 rnemdMethodLabel_ = "SPF";
81
82 selectedMoleculeStr_ = "select none";
83 selectedMoleculeEvaluator_.loadScriptString(selectedMoleculeStr_);
84 selectedMoleculeMan_.setSelectionSet(selectedMoleculeEvaluator_.evaluate());
85
86 if (SPFForceManager* spfForceManager =
87 dynamic_cast<SPFForceManager*>(forceMan)) {
88 forceManager_ = spfForceManager;
89 forceManager_->spfRNEMD_ = this;
90 } else {
91 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
92 "SPF-RNEMD cannot be used with the default ForceManager.\n");
93 painCave.isFatal = 1;
94 painCave.severity = OPENMD_ERROR;
95 simError();
96 }
97
98 RNEMDParameters* rnemdParams = info->getSimParams()->getRNEMDParameters();
99
100 // Calculate ion fixed charges for use in the Charged-SPF method
101 if (useChargedSPF_) {
102 SimInfo::MoleculeIterator i;
103 Molecule* mol;
104 std::vector<RealType> q_tot(objectTypes_.size());
105 std::vector<int> molCount(objectTypes_.size());
106
107 for (mol = info_->beginMolecule(i); mol != NULL;
108 mol = info_->nextMolecule(i)) {
109 for (std::size_t i {}; i < objectTypes_.size(); ++i) {
110 if (objectTypes_[i] == mol->getMolStamp()) {
111 q_tot[i] += mol->getFixedCharge();
112 molCount[i]++;
113 }
114 }
115 }
116
117#ifdef IS_MPI
118 MPI_Allreduce(MPI_IN_PLACE, &q_tot[0], q_tot.size(), MPI_REALTYPE,
119 MPI_SUM, MPI_COMM_WORLD);
120 MPI_Allreduce(MPI_IN_PLACE, &molCount[0], molCount.size(), MPI_INT,
121 MPI_SUM, MPI_COMM_WORLD);
122#endif
123
124 for (std::size_t i {}; i < objectTypes_.size(); ++i) {
125 SelectionEvaluator ionEvaluator {info};
126 SelectionManager ionManager {info};
127
128 std::string ionStr = "select " + objectTypes_[i]->getName();
129 ionEvaluator.loadScriptString(ionStr);
130 ionManager.setSelectionSet(ionEvaluator.evaluate());
131
132 if (molCount[i] > 0) {
133 q_tot[i] /= molCount[i];
134
135 if (q_tot[i] > 0.0) {
136 cationMan_ |= ionManager;
137 } else if (q_tot[i] < 0.0) {
138 anionMan_ |= ionManager;
139 }
140 } else {
141 q_tot[i] = 0.0;
142 }
143 }
144 }
145
146 bool hasParticleFlux = rnemdParams->haveParticleFlux();
147 bool hasCurrentDensity = rnemdParams->haveCurrentDensity();
148 bool hasKineticFlux = rnemdParams->haveKineticFlux();
149
150 bool methodFluxMismatch = false;
151 bool hasCorrectFlux = false;
152
153 switch (rnemdFluxType_) {
154 case rnemdParticle:
155 hasCorrectFlux = hasParticleFlux;
156 break;
157 case rnemdParticleKE:
158 hasCorrectFlux = hasParticleFlux && hasKineticFlux;
159 break;
160 case rnemdCurrentDensity:
161 hasCorrectFlux = hasCurrentDensity;
162 break;
163 default:
164 methodFluxMismatch = true;
165 break;
166 }
167
168 if (methodFluxMismatch) {
169 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
170 "RNEMD: The current method, SPF\n"
171 "\tcannot be used with the current flux type, %s\n",
172 rnemdFluxTypeLabel_.c_str());
173 painCave.isFatal = 1;
174 painCave.severity = OPENMD_ERROR;
175 simError();
176 }
177
178 if (!hasCorrectFlux) {
179 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
180 "RNEMD: The current method, SPF, and flux type, %s,\n"
181 "\tdid not have the correct flux type specified. Options\n"
182 "\tinclude: particleFlux, particleFlux + kineticFlux,\n"
183 "\tand currentDensity.\n",
184 rnemdFluxTypeLabel_.c_str());
185 painCave.isFatal = 1;
186 painCave.severity = OPENMD_ERROR;
187 simError();
188 }
189
190 if (hasParticleFlux) {
191 setParticleFlux(rnemdParams->getParticleFlux());
192 } else if (hasCurrentDensity) {
193 setParticleFlux(rnemdParams->getCurrentDensity());
194 } else {
195 setParticleFlux(0.0);
196 }
197
198 if (hasKineticFlux) {
199 setKineticFlux(rnemdParams->getKineticFlux());
200 } else {
201 setKineticFlux(0.0);
202 }
203
204 uniformKineticScaling_ = rnemdParams->getSPFUniformKineticScaling();
205 }
206
207 SPFMethod::SlabThermodynamics SPFMethod::calculateSlabTherodynamicQuantities(
208 SelectionManager& sman) {
209 SlabThermodynamics slab {};
210
211 int selei {}, selej {};
212
213 StuntDouble* sd;
214
215 for (sd = sman.beginSelected(selei); sd != NULL;
216 sd = sman.nextSelected(selei)) {
217 RealType mass = sd->getMass();
218 Vector3d vel = sd->getVel();
219
220 slab.P += mass * vel;
221 slab.M += mass;
222 slab.K += mass * vel.lengthSquare();
223
224 if (sd->isDirectional()) {
225 Vector3d angMom = sd->getJ();
226 Mat3x3d I = sd->getI();
227 if (sd->isLinear()) {
228 int i = sd->linearAxis();
229 int j = (i + 1) % 3;
230 int k = (i + 2) % 3;
231 slab.K +=
232 angMom[j] * angMom[j] / I(j, j) + angMom[k] * angMom[k] / I(k, k);
233 } else {
234 slab.K += angMom[0] * angMom[0] / I(0, 0) +
235 angMom[1] * angMom[1] / I(1, 1) +
236 angMom[2] * angMom[2] / I(2, 2);
237 }
238 }
239 }
240
241 slab.K *= 0.5;
242
243#ifdef IS_MPI
244 MPI_Allreduce(MPI_IN_PLACE, &(slab.P[0]), 3, MPI_REALTYPE, MPI_SUM,
245 MPI_COMM_WORLD);
246 MPI_Allreduce(MPI_IN_PLACE, &(slab.M), 1, MPI_REALTYPE, MPI_SUM,
247 MPI_COMM_WORLD);
248 MPI_Allreduce(MPI_IN_PLACE, &(slab.K), 1, MPI_REALTYPE, MPI_SUM,
249 MPI_COMM_WORLD);
250#endif
251
252 return slab;
253 }
254
255 void SPFMethod::isValidExchange(Vector3d& v_a, Vector3d& v_b, RealType& a,
256 RealType& b) {
257 const auto& [P_a, M_a, K_a] = calculateSlabTherodynamicQuantities(smanA_);
258 const auto& [P_b, M_b, K_b] = calculateSlabTherodynamicQuantities(smanB_);
259
260 RealType deltaU =
261 Constants::energyConvert * forceManager_->getScaledDeltaU();
262
263 if ((M_a > 0.0) && (M_b > 0.0)) { // both slabs are not empty
264 v_a = P_a / M_a;
265 v_b = P_b / M_b;
266
267 if (uniformKineticScaling_) {
268 RealType numerator = deltaU;
269 RealType denominator = K_a + K_b;
270 denominator -= 0.5 * M_a * v_a.lengthSquare();
271 denominator -= 0.5 * M_b * v_b.lengthSquare();
272
273 RealType a2 = (numerator / denominator) + 1.0;
274
275 if (a2 > 0.0) {
276 a = std::sqrt(a2);
277 b = a;
278 }
279 } else {
280 RealType aNumerator = deltaU - kineticTarget_;
281 RealType aDenominator = 2.0 * K_a;
282 aDenominator -= M_a * v_a.lengthSquare();
283
284 RealType bNumerator = deltaU + kineticTarget_;
285 RealType bDenominator = 2.0 * K_b;
286 bDenominator -= M_b * v_b.lengthSquare();
287
288 RealType a2 = (aNumerator / aDenominator) + 1.0;
289 RealType b2 = (bNumerator / bDenominator) + 1.0;
290
291 if (a2 > 0.0 && b2 > 0.0) {
292 a = std::sqrt(a2);
293 b = std::sqrt(b2);
294 }
295 }
296 }
297 }
298
299 void SPFMethod::doRNEMDImpl(SelectionManager& smanA,
300 SelectionManager& smanB) {
301 if (!doRNEMD_) return;
302
303 if (!forceManager_->getHasSelectedMolecule()) { selectMolecule(); }
304
305 // Remove selected molecule from the source selection manager
306 if (spfTarget_ > 0.0) {
307 smanA -= selectedMoleculeMan_;
308 } else {
309 smanB -= selectedMoleculeMan_;
310 }
311
312 smanA_ = smanA;
313 smanB_ = smanB;
314
315 if (!failedLastTrial_) {
316 Vector3d v_a {};
317 Vector3d v_b {};
318 RealType a {0.0};
319 RealType b {0.0};
320
321 isValidExchange(v_a, v_b, a, b);
322
323 Vector3d vel;
324
325 int selei {}, selej {};
326 StuntDouble* sd;
327
328 for (sd = smanA.beginSelected(selei); sd != NULL;
329 sd = smanA.nextSelected(selei)) {
330 vel = (sd->getVel() - v_a) * a + v_a;
331 sd->setVel(vel);
332
333 if (sd->isDirectional()) {
334 Vector3d angMom = sd->getJ() * a;
335 sd->setJ(angMom);
336 }
337 }
338
339 for (sd = smanB.beginSelected(selej); sd != NULL;
340 sd = smanB.nextSelected(selej)) {
341 vel = (sd->getVel() - v_b) * b + v_b;
342 sd->setVel(vel);
343
344 if (sd->isDirectional()) {
345 Vector3d angMom = sd->getJ() * b;
346 sd->setJ(angMom);
347 }
348 }
349
350 if (useChargedSPF_) {
351 RealType fixedChargeOnIon {};
352
353 if (Molecule* selectedMolecule = forceManager_->getSelectedMolecule();
354 selectedMolecule) {
355 fixedChargeOnIon = selectedMolecule->getFixedCharge();
356 }
357
358#ifdef IS_MPI
359 MPI_Bcast(&fixedChargeOnIon, 1, MPI_REALTYPE,
360 info_->getMolToProc(currentSnap_->getSPFData()->globalID),
361 MPI_COMM_WORLD);
362#endif
363
364 particleExchange_ += spfTarget_ * fixedChargeOnIon;
365 } else {
366 particleExchange_ += spfTarget_;
367 }
368
369 kineticExchange_ += kineticTarget_;
370 } else {
371 failTrialCount_++;
372 }
373
374 currentSnap_->hasTranslationalKineticEnergy = false;
375 currentSnap_->hasRotationalKineticEnergy = false;
376 currentSnap_->hasKineticEnergy = false;
377 }
378
379 void SPFMethod::selectMolecule() {
380 std::shared_ptr<SPFData> spfData = currentSnap_->getSPFData();
381
382 // Always reset spfTarget_ before selecting a new particle
383 spfTarget_ = particleTarget_;
384
385 bool hasSelectedMolecule = (spfData->globalID == -1) ?
386 setSelectedMolecule(spfData) :
387 getSelectedMolecule(spfData);
388
389 if (hasSelectedMolecule) {
390 selectedMoleculeStr_ = "select " + std::to_string(spfData->globalID);
391
392 selectedMoleculeEvaluator_.loadScriptString(selectedMoleculeStr_);
393 selectedMoleculeMan_.setSelectionSet(
394 selectedMoleculeEvaluator_.evaluate());
395
396#ifdef IS_MPI
397 MPI_Bcast(&spfTarget_, 1, MPI_REALTYPE,
398 info_->getMolToProc(spfData->globalID), MPI_COMM_WORLD);
399#endif
400 } else {
401 failedLastTrial_ = true;
402 }
403
404 forceManager_->setHasSelectedMolecule(hasSelectedMolecule);
405 }
406
407 bool SPFMethod::getSelectedMolecule(std::shared_ptr<SPFData> spfData) {
408 Molecule* selectedMolecule;
409
410 selectedMolecule = info_->getMoleculeByGlobalIndex(spfData->globalID);
411
412 if (selectedMolecule) {
413 if (useChargedSPF_) {
414 if (selectedMolecule->getFixedCharge() < 0.0) { spfTarget_ *= -1; }
415
416 // Scale the particle flux by the charge yielding a current density
417 convertParticlesToElectrons(selectedMolecule);
418 }
419
420 forceManager_->setSelectedMolecule(selectedMolecule);
421 }
422
423 return true;
424 }
425
426 bool SPFMethod::setSelectedMolecule(std::shared_ptr<SPFData> spfData) {
427 SelectionManager sourceSman {info_}, oppositeIonSman {info_};
428 RealType targetSlabCenter {};
429
430 Utils::RandNumGenPtr randNumGen = info_->getRandomNumberGenerator();
431
432 int ion {-1};
433
434#ifdef IS_MPI
435 int worldRank {}, size {};
436
437 MPI_Comm_rank(MPI_COMM_WORLD, &worldRank);
438 MPI_Comm_size(MPI_COMM_WORLD, &size);
439
440 if (worldRank == 0) {
441#endif
442 if (useChargedSPF_) {
443 std::uniform_int_distribution<> slabDistribution {0, 1};
444 ion = slabDistribution(*randNumGen);
445 }
446#ifdef IS_MPI
447 }
448
449 if (useChargedSPF_) { MPI_Bcast(&ion, 1, MPI_INT, 0, MPI_COMM_WORLD); }
450#endif
451
452 SelectedIon selectedIon = static_cast<SelectedIon>(ion);
453
454 if (selectedIon == ANION) {
455 spfTarget_ *= -1;
456 oppositeIonSman = cationMan_;
457 } else if (selectedIon == CATION) {
458 oppositeIonSman = anionMan_;
459 }
460
461 // The sign of our flux determines which slab is the source and which is
462 // the sink
463 if (spfTarget_ > 0.0) {
464 sourceSman = commonA_;
465 targetSlabCenter = slabBCenter_;
466 } else {
467 sourceSman = commonB_;
468 targetSlabCenter = slabACenter_;
469 }
470
471 if (useChargedSPF_) { sourceSman -= oppositeIonSman; }
472
473 if (sourceSman.getMoleculeSelectionCount() == 0) {
474 forceManager_->setSelectedMolecule(nullptr);
475 return false;
476 }
477
478 // Choose a molecule to move from the designated source slab
479 int whichSelectedID {-1};
480 Molecule* selectedMolecule;
481
482#ifdef IS_MPI
483 if (worldRank == 0) {
484#endif
485 std::uniform_int_distribution<> selectedMoleculeDistribution {
486 0, sourceSman.getMoleculeSelectionCount() - 1};
487
488 whichSelectedID = selectedMoleculeDistribution(*randNumGen);
489#ifdef IS_MPI
490 }
491
492 MPI_Bcast(&whichSelectedID, 1, MPI_INT, 0, MPI_COMM_WORLD);
493#endif
494
495 selectedMolecule = sourceSman.nthSelectedMolecule(whichSelectedID);
496
497 int globalSelectedID {-1};
498
499 if (selectedMolecule) {
500 globalSelectedID = selectedMolecule->getGlobalIndex();
501
502 // Scale the particle flux by the charge yielding a current density
503 if (useChargedSPF_) { convertParticlesToElectrons(selectedMolecule); }
504 }
505
506#ifdef IS_MPI
507 MPI_Allreduce(MPI_IN_PLACE, &globalSelectedID, 1, MPI_INT, MPI_MAX,
508 MPI_COMM_WORLD);
509#endif
510
511 int axis0 = (rnemdPrivilegedAxis_ + 1) % 3;
512 int axis1 = (rnemdPrivilegedAxis_ + 2) % 3;
513 int axis2 = rnemdPrivilegedAxis_;
514
515 spfData->globalID = globalSelectedID;
516
517#ifdef IS_MPI
518 if (info_->getMolToProc(globalSelectedID) == worldRank) {
519#endif
520 std::uniform_real_distribution<RealType> distr0 {0, hmat_(axis0, axis0)};
521 std::uniform_real_distribution<RealType> distr1 {0, hmat_(axis1, axis1)};
522 std::normal_distribution<RealType> distr2 {targetSlabCenter,
523 0.25 * slabWidth_};
524
525 spfData->pos[axis0] = distr0(*randNumGen);
526 spfData->pos[axis1] = distr1(*randNumGen);
527 spfData->pos[axis2] = distr2(*randNumGen);
528
529 forceManager_->setSelectedMolecule(selectedMolecule);
530
531#ifdef IS_MPI
532 }
533 MPI_Bcast(&spfData->pos[0], 3, MPI_REALTYPE,
534 info_->getMolToProc(globalSelectedID), MPI_COMM_WORLD);
535#endif
536
537 return true;
538 }
539} // namespace OpenMD::RNEMD
Real lengthSquare() const
Returns the squared length of this vector.
Definition Vector.hpp:403