48#include "rnemd/SPF.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"
74namespace OpenMD::RNEMD {
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";
82 selectedMoleculeStr_ =
"select none";
83 selectedMoleculeEvaluator_.loadScriptString(selectedMoleculeStr_);
84 selectedMoleculeMan_.setSelectionSet(selectedMoleculeEvaluator_.evaluate());
86 if (SPFForceManager* spfForceManager =
87 dynamic_cast<SPFForceManager*
>(forceMan)) {
88 forceManager_ = spfForceManager;
89 forceManager_->spfRNEMD_ =
this;
91 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
92 "SPF-RNEMD cannot be used with the default ForceManager.\n");
94 painCave.severity = OPENMD_ERROR;
98 RNEMDParameters* rnemdParams = info->getSimParams()->getRNEMDParameters();
101 if (useChargedSPF_) {
102 SimInfo::MoleculeIterator i;
104 std::vector<RealType> q_tot(objectTypes_.size());
105 std::vector<int> molCount(objectTypes_.size());
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();
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);
124 for (std::size_t i {}; i < objectTypes_.size(); ++i) {
125 SelectionEvaluator ionEvaluator {info};
126 SelectionManager ionManager {info};
128 std::string ionStr =
"select " + objectTypes_[i]->getName();
129 ionEvaluator.loadScriptString(ionStr);
130 ionManager.setSelectionSet(ionEvaluator.evaluate());
132 if (molCount[i] > 0) {
133 q_tot[i] /= molCount[i];
135 if (q_tot[i] > 0.0) {
136 cationMan_ |= ionManager;
137 }
else if (q_tot[i] < 0.0) {
138 anionMan_ |= ionManager;
146 bool hasParticleFlux = rnemdParams->haveParticleFlux();
147 bool hasCurrentDensity = rnemdParams->haveCurrentDensity();
148 bool hasKineticFlux = rnemdParams->haveKineticFlux();
150 bool methodFluxMismatch =
false;
151 bool hasCorrectFlux =
false;
153 switch (rnemdFluxType_) {
155 hasCorrectFlux = hasParticleFlux;
157 case rnemdParticleKE:
158 hasCorrectFlux = hasParticleFlux && hasKineticFlux;
160 case rnemdCurrentDensity:
161 hasCorrectFlux = hasCurrentDensity;
164 methodFluxMismatch =
true;
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;
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;
190 if (hasParticleFlux) {
191 setParticleFlux(rnemdParams->getParticleFlux());
192 }
else if (hasCurrentDensity) {
193 setParticleFlux(rnemdParams->getCurrentDensity());
195 setParticleFlux(0.0);
198 if (hasKineticFlux) {
199 setKineticFlux(rnemdParams->getKineticFlux());
204 uniformKineticScaling_ = rnemdParams->getSPFUniformKineticScaling();
207 SPFMethod::SlabThermodynamics SPFMethod::calculateSlabTherodynamicQuantities(
208 SelectionManager& sman) {
209 SlabThermodynamics slab {};
211 int selei {}, selej {};
215 for (sd = sman.beginSelected(selei); sd != NULL;
216 sd = sman.nextSelected(selei)) {
217 RealType mass = sd->getMass();
218 Vector3d vel = sd->getVel();
220 slab.P += mass * vel;
224 if (sd->isDirectional()) {
225 Vector3d angMom = sd->getJ();
226 Mat3x3d I = sd->getI();
227 if (sd->isLinear()) {
228 int i = sd->linearAxis();
232 angMom[j] * angMom[j] / I(j, j) + angMom[k] * angMom[k] / I(k, k);
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);
244 MPI_Allreduce(MPI_IN_PLACE, &(slab.P[0]), 3, MPI_REALTYPE, MPI_SUM,
246 MPI_Allreduce(MPI_IN_PLACE, &(slab.M), 1, MPI_REALTYPE, MPI_SUM,
248 MPI_Allreduce(MPI_IN_PLACE, &(slab.K), 1, MPI_REALTYPE, MPI_SUM,
255 void SPFMethod::isValidExchange(Vector3d& v_a, Vector3d& v_b, RealType& a,
257 const auto& [P_a, M_a, K_a] = calculateSlabTherodynamicQuantities(smanA_);
258 const auto& [P_b, M_b, K_b] = calculateSlabTherodynamicQuantities(smanB_);
261 Constants::energyConvert * forceManager_->getScaledDeltaU();
263 if ((M_a > 0.0) && (M_b > 0.0)) {
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();
273 RealType a2 = (numerator / denominator) + 1.0;
280 RealType aNumerator = deltaU - kineticTarget_;
281 RealType aDenominator = 2.0 * K_a;
282 aDenominator -= M_a * v_a.lengthSquare();
284 RealType bNumerator = deltaU + kineticTarget_;
285 RealType bDenominator = 2.0 * K_b;
286 bDenominator -= M_b * v_b.lengthSquare();
288 RealType a2 = (aNumerator / aDenominator) + 1.0;
289 RealType b2 = (bNumerator / bDenominator) + 1.0;
291 if (a2 > 0.0 && b2 > 0.0) {
299 void SPFMethod::doRNEMDImpl(SelectionManager& smanA,
300 SelectionManager& smanB) {
301 if (!doRNEMD_)
return;
303 if (!forceManager_->getHasSelectedMolecule()) { selectMolecule(); }
306 if (spfTarget_ > 0.0) {
307 smanA -= selectedMoleculeMan_;
309 smanB -= selectedMoleculeMan_;
315 if (!failedLastTrial_) {
321 isValidExchange(v_a, v_b, a, b);
325 int selei {}, selej {};
328 for (sd = smanA.beginSelected(selei); sd != NULL;
329 sd = smanA.nextSelected(selei)) {
330 vel = (sd->getVel() - v_a) * a + v_a;
333 if (sd->isDirectional()) {
334 Vector3d angMom = sd->getJ() * a;
339 for (sd = smanB.beginSelected(selej); sd != NULL;
340 sd = smanB.nextSelected(selej)) {
341 vel = (sd->getVel() - v_b) * b + v_b;
344 if (sd->isDirectional()) {
345 Vector3d angMom = sd->getJ() * b;
350 if (useChargedSPF_) {
351 RealType fixedChargeOnIon {};
353 if (Molecule* selectedMolecule = forceManager_->getSelectedMolecule();
355 fixedChargeOnIon = selectedMolecule->getFixedCharge();
359 MPI_Bcast(&fixedChargeOnIon, 1, MPI_REALTYPE,
360 info_->getMolToProc(currentSnap_->getSPFData()->globalID),
364 particleExchange_ += spfTarget_ * fixedChargeOnIon;
366 particleExchange_ += spfTarget_;
369 kineticExchange_ += kineticTarget_;
374 currentSnap_->hasTranslationalKineticEnergy =
false;
375 currentSnap_->hasRotationalKineticEnergy =
false;
376 currentSnap_->hasKineticEnergy =
false;
379 void SPFMethod::selectMolecule() {
380 std::shared_ptr<SPFData> spfData = currentSnap_->getSPFData();
383 spfTarget_ = particleTarget_;
385 bool hasSelectedMolecule = (spfData->globalID == -1) ?
386 setSelectedMolecule(spfData) :
387 getSelectedMolecule(spfData);
389 if (hasSelectedMolecule) {
390 selectedMoleculeStr_ =
"select " + std::to_string(spfData->globalID);
392 selectedMoleculeEvaluator_.loadScriptString(selectedMoleculeStr_);
393 selectedMoleculeMan_.setSelectionSet(
394 selectedMoleculeEvaluator_.evaluate());
397 MPI_Bcast(&spfTarget_, 1, MPI_REALTYPE,
398 info_->getMolToProc(spfData->globalID), MPI_COMM_WORLD);
401 failedLastTrial_ =
true;
404 forceManager_->setHasSelectedMolecule(hasSelectedMolecule);
407 bool SPFMethod::getSelectedMolecule(std::shared_ptr<SPFData> spfData) {
408 Molecule* selectedMolecule;
410 selectedMolecule = info_->getMoleculeByGlobalIndex(spfData->globalID);
412 if (selectedMolecule) {
413 if (useChargedSPF_) {
414 if (selectedMolecule->getFixedCharge() < 0.0) { spfTarget_ *= -1; }
417 convertParticlesToElectrons(selectedMolecule);
420 forceManager_->setSelectedMolecule(selectedMolecule);
426 bool SPFMethod::setSelectedMolecule(std::shared_ptr<SPFData> spfData) {
427 SelectionManager sourceSman {info_}, oppositeIonSman {info_};
428 RealType targetSlabCenter {};
430 Utils::RandNumGenPtr randNumGen = info_->getRandomNumberGenerator();
435 int worldRank {}, size {};
437 MPI_Comm_rank(MPI_COMM_WORLD, &worldRank);
438 MPI_Comm_size(MPI_COMM_WORLD, &size);
440 if (worldRank == 0) {
442 if (useChargedSPF_) {
443 std::uniform_int_distribution<> slabDistribution {0, 1};
444 ion = slabDistribution(*randNumGen);
449 if (useChargedSPF_) { MPI_Bcast(&ion, 1, MPI_INT, 0, MPI_COMM_WORLD); }
452 SelectedIon selectedIon =
static_cast<SelectedIon
>(ion);
454 if (selectedIon == ANION) {
456 oppositeIonSman = cationMan_;
457 }
else if (selectedIon == CATION) {
458 oppositeIonSman = anionMan_;
463 if (spfTarget_ > 0.0) {
464 sourceSman = commonA_;
465 targetSlabCenter = slabBCenter_;
467 sourceSman = commonB_;
468 targetSlabCenter = slabACenter_;
471 if (useChargedSPF_) { sourceSman -= oppositeIonSman; }
473 if (sourceSman.getMoleculeSelectionCount() == 0) {
474 forceManager_->setSelectedMolecule(
nullptr);
479 int whichSelectedID {-1};
480 Molecule* selectedMolecule;
483 if (worldRank == 0) {
485 std::uniform_int_distribution<> selectedMoleculeDistribution {
486 0, sourceSman.getMoleculeSelectionCount() - 1};
488 whichSelectedID = selectedMoleculeDistribution(*randNumGen);
492 MPI_Bcast(&whichSelectedID, 1, MPI_INT, 0, MPI_COMM_WORLD);
495 selectedMolecule = sourceSman.nthSelectedMolecule(whichSelectedID);
497 int globalSelectedID {-1};
499 if (selectedMolecule) {
500 globalSelectedID = selectedMolecule->getGlobalIndex();
503 if (useChargedSPF_) { convertParticlesToElectrons(selectedMolecule); }
507 MPI_Allreduce(MPI_IN_PLACE, &globalSelectedID, 1, MPI_INT, MPI_MAX,
511 int axis0 = (rnemdPrivilegedAxis_ + 1) % 3;
512 int axis1 = (rnemdPrivilegedAxis_ + 2) % 3;
513 int axis2 = rnemdPrivilegedAxis_;
515 spfData->globalID = globalSelectedID;
518 if (info_->getMolToProc(globalSelectedID) == worldRank) {
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,
525 spfData->pos[axis0] = distr0(*randNumGen);
526 spfData->pos[axis1] = distr1(*randNumGen);
527 spfData->pos[axis2] = distr2(*randNumGen);
529 forceManager_->setSelectedMolecule(selectedMolecule);
533 MPI_Bcast(&spfData->pos[0], 3, MPI_REALTYPE,
534 info_->getMolToProc(globalSelectedID), MPI_COMM_WORLD);
Real lengthSquare() const
Returns the squared length of this vector.