48#include "rnemd/RNEMD.hpp"
66#include "brains/Thermo.hpp"
67#include "io/Globals.hpp"
68#include "math/ConvexHull.hpp"
75#include "rnemd/RNEMDParameters.hpp"
76#include "types/FixedChargeAdapter.hpp"
77#include "types/FluctuatingChargeAdapter.hpp"
78#include "utils/Accumulator.hpp"
79#include "utils/AccumulatorView.hpp"
80#include "utils/Constants.hpp"
82using namespace OpenMD::Utils;
84namespace OpenMD::RNEMD {
86 RNEMD::RNEMD(SimInfo* info, ForceManager*) :
87 info_(info), commonA_(info_), commonB_(info_), evaluator_(info_),
88 seleMan_(info_), evaluatorA_(info_), evaluatorB_(info_), seleManA_(info_),
89 seleManB_(info_), outputEvaluator_(info_), outputSeleMan_(info_) {
94 Globals* simParams = info->getSimParams();
95 RNEMDParameters* rnemdParams = simParams->getRNEMDParameters();
97 usePeriodicBoundaryConditions_ =
98 simParams->getUsePeriodicBoundaryConditions();
100 doRNEMD_ = rnemdParams->getUseRNEMD();
101 if (!doRNEMD_)
return;
104 std::map<std::string, RNEMDFluxType> stringToFluxType;
106 stringToFluxType[
"KE"] = rnemdKE;
107 stringToFluxType[
"Px"] = rnemdPx;
108 stringToFluxType[
"Py"] = rnemdPy;
109 stringToFluxType[
"Pz"] = rnemdPz;
110 stringToFluxType[
"Pvector"] = rnemdPvector;
111 stringToFluxType[
"Lx"] = rnemdLx;
112 stringToFluxType[
"Ly"] = rnemdLy;
113 stringToFluxType[
"Lz"] = rnemdLz;
114 stringToFluxType[
"Lvector"] = rnemdLvector;
115 stringToFluxType[
"Particle"] = rnemdParticle;
116 stringToFluxType[
"Particle+KE"] = rnemdParticleKE;
117 stringToFluxType[
"CurrentDensity"] = rnemdCurrentDensity;
118 stringToFluxType[
"KE+Px"] = rnemdKePx;
119 stringToFluxType[
"KE+Py"] = rnemdKePy;
120 stringToFluxType[
"KE+Pvector"] = rnemdKePvector;
121 stringToFluxType[
"KE+Lx"] = rnemdKeLx;
122 stringToFluxType[
"KE+Ly"] = rnemdKeLy;
123 stringToFluxType[
"KE+Lz"] = rnemdKeLz;
124 stringToFluxType[
"KE+Lvector"] = rnemdKeLvector;
126 if (rnemdParams->haveFluxType()) {
127 rnemdFluxTypeLabel_ = rnemdParams->getFluxType();
128 rnemdFluxType_ = stringToFluxType.find(rnemdFluxTypeLabel_)->second;
130 std::string allowedFluxTypes;
131 int currentLineLength = 0;
133 for (std::map<std::string, RNEMDFluxType>::iterator fluxStrIter =
134 stringToFluxType.begin();
135 fluxStrIter != stringToFluxType.end(); ++fluxStrIter) {
136 allowedFluxTypes += fluxStrIter->first +
", ";
137 currentLineLength += fluxStrIter->first.length() + 2;
139 if (currentLineLength >= 50) {
140 allowedFluxTypes +=
"\n\t\t";
141 currentLineLength = 0;
145 allowedFluxTypes.erase(allowedFluxTypes.length() - 2, 2);
147 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
148 "RNEMD: No fluxType was set in the omd file. This parameter\n"
149 "\tmust be set to use RNEMD, and can take any of these values:\n"
151 allowedFluxTypes.c_str());
152 painCave.isFatal = 1;
153 painCave.severity = OPENMD_ERROR;
158 const std::string privAxis = rnemdParams->getPrivilegedAxis();
160 if (privAxis ==
"x") {
161 rnemdAxisLabel_ =
"x";
162 rnemdPrivilegedAxis_ = rnemdX;
163 }
else if (privAxis ==
"y") {
164 rnemdAxisLabel_ =
"y";
165 rnemdPrivilegedAxis_ = rnemdY;
167 rnemdAxisLabel_ =
"z";
168 rnemdPrivilegedAxis_ = rnemdZ;
171 runTime_ = simParams->getRunTime();
172 statusTime_ = simParams->getStatusTime();
174 rnemdObjectSelection_ = rnemdParams->getObjectSelection();
176 bool hasSlabWidth = rnemdParams->haveSlabWidth();
177 bool hasSlabACenter = rnemdParams->haveSlabACenter();
178 bool hasSlabBCenter = rnemdParams->haveSlabBCenter();
179 bool hasSphereARadius = rnemdParams->haveSphereARadius();
180 bool hasSphereBRadius = rnemdParams->haveSphereBRadius();
182 hasSelectionA_ = rnemdParams->haveSelectionA();
183 hasSelectionB_ = rnemdParams->haveSelectionB();
185 hasDividingArea_ = rnemdParams->haveDividingArea();
186 dividingArea_ = rnemdParams->getDividingArea();
188 bool hasCoordinateOrigin = rnemdParams->haveCoordinateOrigin();
189 bool hasOutputFileName = rnemdParams->haveOutputFileName();
190 bool hasOutputFields = rnemdParams->haveOutputFields();
191 bool hasOutputSelection = rnemdParams->haveOutputSelection();
193 if (hasOutputSelection) {
194 outputSelection_ = rnemdParams->getOutputSelection();
196 outputSelection_ = rnemdObjectSelection_;
199 if (hasCoordinateOrigin) {
200 std::vector<RealType> co = rnemdParams->getCoordinateOrigin();
201 if (co.size() != 3) {
202 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
203 "RNEMD: Incorrect number of parameters specified for "
204 "coordinateOrigin.\n"
205 "\tthere should be 3 parameters, but %zu were specified.\n",
207 painCave.isFatal = 1;
210 coordinateOrigin_.x() = co[0];
211 coordinateOrigin_.y() = co[1];
212 coordinateOrigin_.z() = co[2];
214 coordinateOrigin_ = V3Zero;
217 outputEvaluator_.loadScriptString(outputSelection_);
218 outputSeleMan_.setSelectionSet(outputEvaluator_.evaluate());
221 outputSeleMan_.replaceRigidBodiesWithAtoms();
224 std::copy(osTypes.begin(), osTypes.end(), std::back_inserter(outputTypes_));
226 nBins_ = rnemdParams->getOutputBins();
227 binWidth_ = rnemdParams->getOutputBinWidth();
230 data_.resize(RNEMD::ENDINDEX);
233 z.units =
"Angstroms";
234 z.title = rnemdAxisLabel_;
235 for (
unsigned int i = 0; i < nBins_; i++)
236 z.accumulator.push_back(
238 data_[Z] = std::move(z);
242 r.units =
"Angstroms";
244 for (
unsigned int i = 0; i < nBins_; i++)
245 r.accumulator.push_back(
247 data_[R] = std::move(r);
250 OutputData temperature;
251 temperature.units =
"K";
252 temperature.title =
"Temperature";
253 for (
unsigned int i = 0; i < nBins_; i++)
254 temperature.accumulator.push_back(
256 data_[TEMPERATURE] = std::move(temperature);
257 outputMap_[
"TEMPERATURE"] = TEMPERATURE;
260 velocity.units =
"angstroms/fs";
261 velocity.title =
"Velocity";
262 for (
unsigned int i = 0; i < nBins_; i++)
263 velocity.accumulator.push_back(
265 data_[VELOCITY] = std::move(velocity);
266 outputMap_[
"VELOCITY"] = VELOCITY;
268 OutputData angularVelocity;
269 angularVelocity.units =
"angstroms^2/fs";
270 angularVelocity.title =
"AngularVelocity";
271 for (
unsigned int i = 0; i < nBins_; i++)
272 angularVelocity.accumulator.push_back(
274 data_[ANGULARVELOCITY] = std::move(angularVelocity);
275 outputMap_[
"ANGULARVELOCITY"] = ANGULARVELOCITY;
278 density.units =
"g cm^-3";
279 density.title =
"Density";
280 for (
unsigned int i = 0; i < nBins_; i++)
281 density.accumulator.push_back(
283 data_[DENSITY] = std::move(density);
284 outputMap_[
"DENSITY"] = DENSITY;
287 activity.units =
"unitless";
288 activity.title =
"Activity";
289 for (
unsigned int i = 0; i < nBins_; i++)
290 activity.accumulator.push_back(
292 data_[ACTIVITY] = std::move(activity);
293 outputMap_[
"ACTIVITY"] = ACTIVITY;
296 eField.units =
"kcal/mol/angstroms/e";
297 eField.title =
"Electric Field";
298 for (
unsigned int i = 0; i < nBins_; i++)
299 eField.accumulator.push_back(
301 data_[ELECTRICFIELD] = std::move(eField);
302 outputMap_[
"ELECTRICFIELD"] = ELECTRICFIELD;
305 ePot.units =
"kcal/mol/e";
306 ePot.title =
"Electrostatic Potential";
307 for (
unsigned int i = 0; i < nBins_; i++)
308 ePot.accumulator.push_back(
310 data_[ELECTROSTATICPOTENTIAL] = std::move(ePot);
311 outputMap_[
"ELECTROSTATICPOTENTIAL"] = ELECTROSTATICPOTENTIAL;
313 if (hasOutputFields) {
314 parseOutputFileFormat(rnemdParams->getOutputFields());
316 if (usePeriodicBoundaryConditions_)
320 switch (rnemdFluxType_) {
324 outputMask_.set(TEMPERATURE);
328 outputMask_.set(VELOCITY);
332 outputMask_.set(VELOCITY);
333 outputMask_.set(DENSITY);
339 outputMask_.set(ANGULARVELOCITY);
345 outputMask_.set(TEMPERATURE);
346 outputMask_.set(ANGULARVELOCITY);
350 outputMask_.set(TEMPERATURE);
351 outputMask_.set(VELOCITY);
354 outputMask_.set(TEMPERATURE);
355 outputMask_.set(VELOCITY);
356 outputMask_.set(DENSITY);
363 if (hasOutputFileName) {
364 rnemdFileName_ = rnemdParams->getOutputFileName();
366 rnemdFileName_ =
getPrefix(info->getFinalConfigFileName()) +
".rnemd";
371 exchangeTime_ = rnemdParams->getExchangeTime();
372 RealType dt = simParams->getDt();
373 RealType newET = std::ceil(exchangeTime_ / dt) * dt;
375 if (std::fabs(newET - exchangeTime_) > 1e-6) {
376 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
377 "RNEMD: The exchangeTime was reset to %lf,\n"
378 "\t\twhich is a multiple of dt, %lf.\n",
380 painCave.isFatal = 0;
381 painCave.severity = OPENMD_WARNING;
383 exchangeTime_ = newET;
387 hmat_ = currentSnap_->
getHmat();
390 std::ostringstream selectionAstream;
391 std::ostringstream selectionBstream;
393 if (hasSelectionA_) {
394 selectionA_ = rnemdParams->getSelectionA();
396 if (usePeriodicBoundaryConditions_) {
399 rnemdParams->getSlabWidth() :
400 hmat_(rnemdPrivilegedAxis_, rnemdPrivilegedAxis_) / 10.0;
402 slabACenter_ = hasSlabACenter ? rnemdParams->getSlabACenter() : 0.0;
404 selectionA_ = this->setSelection(slabACenter_);
406 if (hasSphereARadius)
407 sphereARadius_ = rnemdParams->getSphereARadius();
412 RealType hVol = thermo.getHullVolume();
414 0.1 * pow((3.0 * hVol / (4.0 * Constants::PI)), 1.0 / 3.0);
416 selectionAstream <<
"select r < " << sphereARadius_;
417 selectionA_ = selectionAstream.str();
421 if (hasSelectionB_) {
422 selectionB_ = rnemdParams->getSelectionB();
424 if (usePeriodicBoundaryConditions_) {
427 rnemdParams->getSlabWidth() :
428 hmat_(rnemdPrivilegedAxis_, rnemdPrivilegedAxis_) / 10.0;
432 rnemdParams->getSlabBCenter() :
433 hmat_(rnemdPrivilegedAxis_, rnemdPrivilegedAxis_) / 2.0;
435 selectionB_ = this->setSelection(slabBCenter_);
437 if (hasSphereBRadius) {
438 sphereBRadius_ = rnemdParams->getSphereBRadius();
439 selectionBstream <<
"select r > " << sphereBRadius_;
440 selectionB_ = selectionBstream.str();
442 selectionB_ =
"select hull";
443 hasSelectionB_ =
true;
449 evaluator_.loadScriptString(rnemdObjectSelection_);
450 if (!evaluator_.isDynamic())
451 seleMan_.setSelectionSet(evaluator_.evaluate());
453 evaluatorA_.loadScriptString(selectionA_);
454 if (!evaluatorA_.isDynamic())
455 seleManA_.setSelectionSet(evaluatorA_.evaluate());
457 evaluatorB_.loadScriptString(selectionB_);
458 if (!evaluatorB_.isDynamic())
459 seleManB_.setSelectionSet(evaluatorB_.evaluate());
462 if (rnemdFluxType_ == rnemdCurrentDensity) useChargedSPF_ =
true;
464 MoleculeStampSet obTypes = seleMan_.getSelectedMoleculeStamps();
465 std::copy(obTypes.begin(), obTypes.end(), std::back_inserter(objectTypes_));
469 seleMan_.removeAtomsInRigidBodies().getSelectionCount();
472 if (selectionCount > nIntegrable) {
473 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
474 "RNEMD: The current objectSelection,\n"
476 "\thas resulted in %d selected objects. However,\n"
477 "\tthe total number of integrable objects in the system\n"
478 "\tis only %d. This is almost certainly not what you want\n"
480 rnemdObjectSelection_.c_str(), selectionCount, nIntegrable);
481 painCave.isFatal = 0;
482 painCave.severity = OPENMD_WARNING;
488 if (!doRNEMD_)
return;
490 if (worldRank == 0) {
500 void RNEMD::getStarted() {
501 if (!doRNEMD_)
return;
506 void RNEMD::doRNEMD() {
507 if (!doRNEMD_)
return;
509 hmat_ = currentSnap_->getHmat();
512 evaluator_.loadScriptString(rnemdObjectSelection_);
513 if (evaluator_.isDynamic()) seleMan_.setSelectionSet(evaluator_.evaluate());
515 evaluatorA_.loadScriptString(selectionA_);
516 if (evaluatorA_.isDynamic())
517 seleManA_.setSelectionSet(evaluatorA_.evaluate());
519 evaluatorB_.loadScriptString(selectionB_);
520 if (evaluatorB_.isDynamic())
521 seleManB_.setSelectionSet(evaluatorB_.evaluate());
523 commonA_ = seleManA_ & seleMan_;
524 commonB_ = seleManB_ & seleMan_;
526 auto reducedCommonA = commonA_.removeAtomsInRigidBodies();
527 auto reducedCommonB = commonB_.removeAtomsInRigidBodies();
533 RealType area = getDefaultDividingArea();
535 kineticTarget_ = kineticFlux_ * exchangeTime_ * area;
536 momentumTarget_ = momentumFluxVector_ * exchangeTime_ * area;
537 angularMomentumTarget_ = angularMomentumFluxVector_ * exchangeTime_ * area;
538 particleTarget_ = particleFlux_ * exchangeTime_ * area;
540 if (std::fabs(particleTarget_) > 1.0) {
541 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
542 "RNEMD: The current particleFlux,\n"
544 "\thas resulted in a target particle exchange of %f.\n"
545 "\tThis is equivalent to moving more than one particle\n"
546 "\tduring each exchange. Please reduce your particleFlux.\n",
547 particleFlux_, particleTarget_);
548 painCave.isFatal = 1;
549 painCave.severity = OPENMD_ERROR;
553 if (rnemdFluxType_ == rnemdParticle || rnemdFluxType_ == rnemdParticleKE ||
554 rnemdFluxType_ == rnemdCurrentDensity) {
558 auto reducedTempCommonA = tempCommonA.removeAtomsInRigidBodies();
559 auto reducedTempCommonB = tempCommonB.removeAtomsInRigidBodies();
561 this->doRNEMDImpl(reducedTempCommonA, reducedTempCommonB);
563 this->doRNEMDImpl(reducedCommonA, reducedCommonB);
567 void RNEMD::collectData() {
568 if (!doRNEMD_)
return;
569 currentSnap_ = info_->getSnapshotManager()->getCurrentSnapshot();
570 hmat_ = currentSnap_->getHmat();
574 RealType area = getDefaultDividingArea();
575 areaAccumulator_.add(area);
577 Vector3d u = angularMomentumFluxVector_;
581 if (outputEvaluator_.isDynamic()) {
582 outputSeleMan_.setSelectionSet(outputEvaluator_.evaluate());
597 vector<RealType> binMass(nBins_, 0.0);
598 vector<Vector3d> binP(nBins_, V3Zero);
599 vector<RealType> binOmega(nBins_, 0.0);
600 vector<Vector3d> binL(nBins_, V3Zero);
601 vector<Mat3x3d> binI(nBins_);
602 vector<RealType> binKE(nBins_, 0.0);
603 vector<Vector3d> binEField(nBins_, V3Zero);
604 vector<int> binDOF(nBins_, 0);
605 vector<int> binCount(nBins_, 0);
606 vector<int> binEFieldCount(nBins_, 0);
607 vector<vector<int>> binTypeCounts;
609 if (outputMask_[ACTIVITY]) {
610 binTypeCounts.resize(nBins_);
611 for (
unsigned int i = 0; i < nBins_; i++) {
612 binTypeCounts[i].resize(outputTypes_.size(), 0);
616 SimInfo::MoleculeIterator miter;
617 std::vector<StuntDouble*>::iterator iiter;
618 std::vector<AtomType*>::iterator at;
623 Molecule::ConstraintPairIterator cpi;
625 std::shared_ptr<SPFData> spfData = currentSnap_->getSPFData();
627 for (mol = info_->beginMolecule(miter); mol != NULL;
628 mol = info_->nextMolecule(miter)) {
631 for (sd = mol->beginIntegrableObject(iiter); sd != NULL;
632 sd = mol->nextIntegrableObject(iiter)) {
633 if (outputSeleMan_.isSelected(sd)) {
634 Vector3d pos = sd->
getPos();
639 rPos = sd->
getPos() - coordinateOrigin_;
644 Vector3d angMom = sd->
getJ();
645 Mat3x3d Ia = sd->
getI();
650 KE += 0.5 * (angMom[j] * angMom[j] / Ia(j, j) +
651 angMom[k] * angMom[k] / Ia(k, k));
654 KE += 0.5 * (angMom[0] * angMom[0] / Ia(0, 0) +
655 angMom[1] * angMom[1] / Ia(1, 1) +
656 angMom[2] * angMom[2] / Ia(2, 2));
661 L = mass *
cross(rPos, vel);
662 I = outProduct(rPos, rPos) * mass;
664 I(0, 0) += mass * r2;
665 I(1, 1) += mass * r2;
666 I(2, 2) += mass * r2;
668 if (outputMask_[ACTIVITY]) {
672 std::vector<Atom*>::iterator ai;
674 for (atom = rb->beginAtom(ai); atom != NULL;
675 atom = rb->nextAtom(ai)) {
677 atomBinNo = getBin(atom->
getPos());
679 atype =
static_cast<Atom*
>(atom)->getAtomType();
680 at = std::find(outputTypes_.begin(), outputTypes_.end(), atype);
681 if (at != outputTypes_.end()) {
682 typeIndex = std::distance(outputTypes_.begin(), at);
685 if (atomBinNo >= 0 && atomBinNo <
int(nBins_)) {
686 if (typeIndex != -1) binTypeCounts[atomBinNo][typeIndex]++;
689 }
else if (sd->
isAtom()) {
691 atype =
static_cast<Atom*
>(sd)->getAtomType();
692 at = std::find(outputTypes_.begin(), outputTypes_.end(), atype);
693 if (at != outputTypes_.end()) {
694 typeIndex = std::distance(outputTypes_.begin(), at);
697 if (binNo >= 0 && binNo <
int(nBins_)) {
698 if (outputMask_[ACTIVITY] && typeIndex != -1)
699 binTypeCounts[binNo][typeIndex]++;
704 if (binNo >= 0 && binNo <
int(nBins_)) {
706 binMass[binNo] += mass;
707 binP[binNo] += mass * vel;
711 binDOF[binNo] += DOF;
717 if (outputMask_[ELECTRICFIELD]) {
721 std::vector<Atom*>::iterator ai;
723 for (atom = rb->beginAtom(ai); atom != NULL;
724 atom = rb->nextAtom(ai)) {
725 atomBinNo = getBin(atom->
getPos());
728 if (atomBinNo >= 0 && atomBinNo <
int(nBins_)) {
729 binEFieldCount[atomBinNo]++;
730 binEField[atomBinNo] += eField;
735 atomBinNo = getBin(sd->
getPos());
737 if (atomBinNo >= 0 && atomBinNo <
int(nBins_)) {
738 binEFieldCount[atomBinNo]++;
739 binEField[atomBinNo] += eField;
747 if (outputSeleMan_.isSelected(mol)) {
748 for (consPair = mol->beginConstraintPair(cpi); consPair != NULL;
749 consPair = mol->nextConstraintPair(cpi)) {
753 if (usePeriodicBoundaryConditions_) {
754 currentSnap_->wrapVector(posA);
755 currentSnap_->wrapVector(posB);
758 Vector3d coc = 0.5 * (posA + posB);
759 int binCons = getBin(coc);
760 binDOF[binCons] -= 1;
766 for (
unsigned int i = 0; i < nBins_; i++) {
767 MPI_Allreduce(MPI_IN_PLACE, &binCount[i], 1, MPI_INT, MPI_SUM,
769 MPI_Allreduce(MPI_IN_PLACE, &binMass[i], 1, MPI_REALTYPE, MPI_SUM,
771 MPI_Allreduce(MPI_IN_PLACE, binP[i].getArrayPointer(), 3, MPI_REALTYPE,
772 MPI_SUM, MPI_COMM_WORLD);
773 MPI_Allreduce(MPI_IN_PLACE, binL[i].getArrayPointer(), 3, MPI_REALTYPE,
774 MPI_SUM, MPI_COMM_WORLD);
775 MPI_Allreduce(MPI_IN_PLACE, binI[i].getArrayPointer(), 9, MPI_REALTYPE,
776 MPI_SUM, MPI_COMM_WORLD);
777 MPI_Allreduce(MPI_IN_PLACE, &binKE[i], 1, MPI_REALTYPE, MPI_SUM,
779 MPI_Allreduce(MPI_IN_PLACE, &binDOF[i], 1, MPI_INT, MPI_SUM,
782 if (outputMask_[ELECTRICFIELD]) {
783 MPI_Allreduce(MPI_IN_PLACE, &binEFieldCount[i], 1, MPI_INT, MPI_SUM,
785 MPI_Allreduce(MPI_IN_PLACE, binEField[i].getArrayPointer(), 3,
786 MPI_REALTYPE, MPI_SUM, MPI_COMM_WORLD);
788 if (outputMask_[ACTIVITY]) {
789 MPI_Allreduce(MPI_IN_PLACE, &binTypeCounts[i][0], outputTypes_.size(),
790 MPI_INT, MPI_SUM, MPI_COMM_WORLD);
796 RealType z, r, temp, binVolume, den(0.0), dz(0.0);
797 std::vector<RealType> nden(outputTypes_.size(), 0.0);
798 RealType boxVolume = currentSnap_->getVolume();
801 for (
unsigned int i = 0; i < nBins_; i++) {
802 if (usePeriodicBoundaryConditions_) {
803 z = (((RealType)i + 0.5) / (RealType)nBins_) *
804 hmat_(rnemdPrivilegedAxis_, rnemdPrivilegedAxis_);
805 data_[Z].accumulator[i]->add(z);
807 binVolume = boxVolume / nBins_;
808 dz = hmat_(rnemdPrivilegedAxis_, rnemdPrivilegedAxis_) /
811 r = (((RealType)i + 0.5) * binWidth_);
812 data_[R].accumulator[i]->add(r);
814 RealType rinner = (RealType)i * binWidth_;
815 RealType router = (RealType)(i + 1) * binWidth_;
817 (4.0 * Constants::PI * (pow(router, 3) - pow(rinner, 3))) / 3.0;
822 if (outputMask_[ELECTRICFIELD] && binEFieldCount[i] > 0) {
823 eField = binEField[i] / RealType(binEFieldCount[i]);
824 data_[ELECTRICFIELD].accumulator[i]->add(eField);
827 if (outputMask_[ELECTROSTATICPOTENTIAL]) {
828 if (usePeriodicBoundaryConditions_ && binEFieldCount[i] > 0) {
829 ePot += eField[rnemdPrivilegedAxis_] * dz;
830 data_[ELECTROSTATICPOTENTIAL].accumulator[i]->
add(ePot);
836 if (outputMask_[DENSITY]) {
837 den = binMass[i] * Constants::densityConvert / binVolume;
838 data_[DENSITY].accumulator[i]->add(den);
841 if (outputMask_[ACTIVITY]) {
842 for (
unsigned int j = 0; j < outputTypes_.size(); j++) {
843 nden[j] = (binTypeCounts[i][j] / binVolume) *
844 Constants::concentrationConvert;
846 data_[ACTIVITY].accumulator[i]->add(nden);
849 if (binCount[i] > 0) {
852 if (outputMask_[VELOCITY]) {
853 vel = binP[i] / binMass[i];
854 data_[VELOCITY].accumulator[i]->
add(vel);
857 if (outputMask_[ANGULARVELOCITY]) {
858 omega = binI[i].inverse() * binL[i];
859 data_[ANGULARVELOCITY].accumulator[i]->
add(omega);
862 if (outputMask_[TEMPERATURE]) {
864 temp = 2.0 * binKE[i] /
865 (binDOF[i] * Constants::kb * Constants::energyConvert);
866 data_[TEMPERATURE].accumulator[i]->add(temp);
868 std::cerr <<
"No degrees of freedom in this bin?\n";
877 void RNEMD::writeOutputFile() {
878 if (!doRNEMD_)
return;
879 if (!hasData_)
return;
884 MPI_Comm_rank(MPI_COMM_WORLD, &worldRank);
886 if (worldRank == 0) {
888 rnemdFile_.open(rnemdFileName_.c_str(), std::ios::out | std::ios::trunc);
891 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
892 "Could not open \"%s\" for RNEMD output.\n",
893 rnemdFileName_.c_str());
894 painCave.isFatal = 1;
898 RealType time = currentSnap_->getTime();
899 RealType avgArea = areaAccumulator_.getAverage();
902 Vector3d JzP(V3Zero);
903 Vector3d JzL(V3Zero);
906 if (time >= info_->getSimParams()->getDt()) {
907 Jz = kineticExchange_ / (time * avgArea) / Constants::energyConvert;
908 JzP = momentumExchange_ / (time * avgArea);
909 JzL = angularMomentumExchange_ / (time * avgArea);
910 Jpart = particleExchange_ / (time * avgArea);
913 rnemdFile_ <<
"#######################################################\n";
914 rnemdFile_ <<
"# RNEMD {\n";
915 rnemdFile_ <<
"# exchangeMethod = \"" << rnemdMethodLabel_ <<
"\";\n";
916 rnemdFile_ <<
"# fluxType = \"" << rnemdFluxTypeLabel_ <<
"\";\n";
918 if (usePeriodicBoundaryConditions_)
919 rnemdFile_ <<
"# privilegedAxis = " << rnemdAxisLabel_ <<
";\n";
921 rnemdFile_ <<
"# exchangeTime = " << exchangeTime_ <<
";\n";
922 rnemdFile_ <<
"# objectSelection = \"" << rnemdObjectSelection_
924 rnemdFile_ <<
"# selectionA = \"" << selectionA_ <<
"\";\n";
925 rnemdFile_ <<
"# selectionB = \"" << selectionB_ <<
"\";\n";
926 rnemdFile_ <<
"# outputSelection = \"" << outputSelection_ <<
"\";\n";
927 rnemdFile_ <<
"# }\n";
928 rnemdFile_ <<
"#######################################################\n";
929 rnemdFile_ <<
"# RNEMD report:\n";
930 rnemdFile_ <<
"# running time = " << time <<
" fs\n";
931 rnemdFile_ <<
"# Target flux:\n";
932 rnemdFile_ <<
"# kinetic = "
933 << kineticFlux_ / Constants::energyConvert
934 <<
" (kcal/mol/A^2/fs)\n";
935 rnemdFile_ <<
"# momentum = " << momentumFluxVector_
936 <<
" (amu/A/fs^2)\n";
937 rnemdFile_ <<
"# angular momentum = " << angularMomentumFluxVector_
938 <<
" (amu/A^2/fs^2)\n";
939 if (useChargedSPF_) {
940 rnemdFile_ <<
"# current density = " << particleFlux_
941 <<
" (electrons/A^2/fs)\n";
943 rnemdFile_ <<
"# particle = " << particleFlux_
944 <<
" (particles/A^2/fs)\n";
947 rnemdFile_ <<
"# Target one-time exchanges:\n";
948 rnemdFile_ <<
"# kinetic = "
949 << kineticTarget_ / Constants::energyConvert
951 rnemdFile_ <<
"# momentum = " << momentumTarget_
953 rnemdFile_ <<
"# angular momentum = " << angularMomentumTarget_
954 <<
" (amu*A^2/fs)\n";
955 if (useChargedSPF_) {
956 rnemdFile_ <<
"# current density = " << particleTarget_
959 rnemdFile_ <<
"# particle = " << particleTarget_
963 rnemdFile_ <<
"# Actual exchange totals:\n";
964 rnemdFile_ <<
"# kinetic = "
965 << kineticExchange_ / Constants::energyConvert
967 rnemdFile_ <<
"# momentum = " << momentumExchange_
969 rnemdFile_ <<
"# angular momentum = " << angularMomentumExchange_
970 <<
" (amu*A^2/fs)\n";
971 if (useChargedSPF_) {
972 rnemdFile_ <<
"# current density = " << particleExchange_
975 rnemdFile_ <<
"# particle = " << particleExchange_
979 rnemdFile_ <<
"# Actual flux:\n";
980 rnemdFile_ <<
"# kinetic = " << Jz <<
" (kcal/mol/A^2/fs)\n";
981 rnemdFile_ <<
"# momentum = " << JzP <<
" (amu/A/fs^2)\n";
982 rnemdFile_ <<
"# angular momentum = " << JzL <<
" (amu/A^2/fs^2)\n";
983 if (useChargedSPF_) {
984 rnemdFile_ <<
"# current density = " << Jpart
985 <<
" (electrons/A^2/fs)\n";
987 rnemdFile_ <<
"# particle = " << Jpart
988 <<
" (particles/A^2/fs)\n";
991 rnemdFile_ <<
"# Exchange statistics:\n";
992 rnemdFile_ <<
"# attempted = " << trialCount_ <<
"\n";
993 rnemdFile_ <<
"# failed = " << failTrialCount_ <<
"\n";
994 if (rnemdMethodLabel_ ==
"NIVS") {
995 rnemdFile_ <<
"# NIVS root-check errors = " << failRootCount_ <<
"\n";
997 rnemdFile_ <<
"#######################################################\n";
1001 for (
unsigned int i = 0; i < outputMask_.size(); ++i) {
1002 if (outputMask_[i]) {
1003 rnemdFile_ <<
"\t" << data_[i].title <<
"(" << data_[i].units <<
")";
1006 if (data_[i].accumulator[0]->getType() ==
1007 std::type_index(
typeid(Vector3d))) {
1008 rnemdFile_ <<
"\t\t";
1011 if (data_[i].accumulator[0]->getType() ==
1012 std::type_index(
typeid(std::vector<RealType>))) {
1014 for (
unsigned int type = 0; type < outputTypes_.size(); type++) {
1015 rnemdFile_ << outputTypes_[type]->getName() <<
"\t";
1017 rnemdFile_ <<
")\t";
1024 std::vector<int> nonEmptyAccumulators(nBins_);
1025 int numberOfAccumulators {};
1027 for (
unsigned int i = 0; i < outputMask_.size(); ++i) {
1028 if (outputMask_[i]) {
1029 for (
unsigned int bin = 0; bin < nBins_; bin++) {
1030 nonEmptyAccumulators[bin] +=
1031 static_cast<int>(data_[i].accumulator[bin]->getCount() != 0);
1034 numberOfAccumulators++;
1038 rnemdFile_.precision(8);
1040 for (
unsigned int bin = 0; bin < nBins_; bin++) {
1041 if (nonEmptyAccumulators[bin] == numberOfAccumulators) {
1042 for (
unsigned int i = 0; i < outputMask_.size(); ++i) {
1043 if (outputMask_[i]) {
1044 std::string message =
1045 "RNEMD detected a numerical error writing: " +
1046 data_[i].title +
" for bin " + std::to_string(bin);
1048 data_[i].accumulator[bin]->writeData(rnemdFile_, message);
1056 rnemdFile_ <<
"#######################################################\n";
1057 rnemdFile_ <<
"# 95% confidence intervals in those quantities follow:\n";
1058 rnemdFile_ <<
"#######################################################\n";
1060 for (
unsigned int bin = 0; bin < nBins_; bin++) {
1061 if (nonEmptyAccumulators[bin] == numberOfAccumulators) {
1064 for (
unsigned int i = 0; i < outputMask_.size(); ++i) {
1065 if (outputMask_[i]) {
1066 std::string message =
1067 "RNEMD detected a numerical error writing: " +
1068 data_[i].title +
" std. dev. for bin " + std::to_string(bin);
1070 data_[i].accumulator[bin]->writeErrorBars(rnemdFile_, message);
1085 void RNEMD::setKineticFlux(RealType kineticFlux) {
1088 kineticFlux_ = kineticFlux * Constants::energyConvert;
1091 void RNEMD::setParticleFlux(RealType particleFlux) {
1092 RealType area = getDefaultDividingArea();
1094 particleFlux_ = particleFlux;
1095 particleTarget_ = particleFlux_ * exchangeTime_ * area;
1098 void RNEMD::setMomentumFluxVector(
1099 const std::vector<RealType>& momentumFluxVector) {
1100 if (momentumFluxVector.size() != 3) {
1101 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
1102 "RNEMD: Incorrect number of parameters specified for "
1103 "momentumFluxVector.\n"
1104 "\tthere should be 3 parameters, but %zu were specified.\n",
1105 momentumFluxVector.size());
1106 painCave.isFatal = 1;
1110 momentumFluxVector_.x() = momentumFluxVector[0];
1111 momentumFluxVector_.y() = momentumFluxVector[1];
1112 momentumFluxVector_.z() = momentumFluxVector[2];
1115 void RNEMD::setAngularMomentumFluxVector(
1116 const std::vector<RealType>& angularMomentumFluxVector) {
1117 if (angularMomentumFluxVector.size() != 3) {
1118 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
1119 "RNEMD: Incorrect number of parameters specified for "
1120 "angularMomentumFluxVector.\n"
1121 "\tthere should be 3 parameters, but %zu were specified.\n",
1122 angularMomentumFluxVector.size());
1123 painCave.isFatal = 1;
1127 angularMomentumFluxVector_.x() = angularMomentumFluxVector[0];
1128 angularMomentumFluxVector_.y() = angularMomentumFluxVector[1];
1129 angularMomentumFluxVector_.z() = angularMomentumFluxVector[2];
1132 void RNEMD::parseOutputFileFormat(
const std::string& format) {
1133 if (!doRNEMD_)
return;
1136 while (tokenizer.hasMoreTokens()) {
1137 std::string token(tokenizer.nextToken());
1139 OutputMapType::iterator i = outputMap_.find(token);
1140 if (i != outputMap_.end()) {
1141 outputMask_.set(i->second);
1143 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
1144 "RNEMD::parseOutputFileFormat: %s is not a recognized\n"
1145 "\toutputFileFormat keyword.\n",
1147 painCave.isFatal = 0;
1148 painCave.severity = OPENMD_ERROR;
1154 std::string RNEMD::setSelection(RealType& slabCenter) {
1155 bool printSlabCenterWarning {
false};
1157 Vector3d tempSlabCenter {V3Zero};
1158 tempSlabCenter[rnemdPrivilegedAxis_] = slabCenter;
1160 RealType hmat_2 = hmat_(rnemdPrivilegedAxis_, rnemdPrivilegedAxis_) / 2.0;
1162 if (slabCenter > hmat_2) {
1163 currentSnap_->wrapVector(tempSlabCenter);
1164 printSlabCenterWarning =
true;
1165 }
else if (slabCenter < -hmat_2) {
1166 currentSnap_->wrapVector(tempSlabCenter);
1167 printSlabCenterWarning =
true;
1170 if (printSlabCenterWarning) {
1171 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
1172 "The given slab center was set to %0.2f. In the wrapped "
1174 "\t[-Hmat/2, +Hmat/2], this has been remapped to %0.2f.\n",
1175 slabCenter, tempSlabCenter[rnemdPrivilegedAxis_]);
1176 painCave.isFatal = 0;
1177 painCave.severity = OPENMD_WARNING;
1180 slabCenter = tempSlabCenter[rnemdPrivilegedAxis_];
1183 Vector3d leftSlab {V3Zero};
1184 const RealType& leftSlabBoundary = leftSlab[rnemdPrivilegedAxis_];
1185 leftSlab[rnemdPrivilegedAxis_] = slabCenter - 0.5 * slabWidth_;
1186 currentSnap_->wrapVector(leftSlab);
1188 Vector3d rightSlab {V3Zero};
1189 const RealType& rightSlabBoundary = rightSlab[rnemdPrivilegedAxis_];
1190 rightSlab[rnemdPrivilegedAxis_] = slabCenter + 0.5 * slabWidth_;
1191 currentSnap_->wrapVector(rightSlab);
1193 std::ostringstream selectionStream;
1195 selectionStream <<
"select wrapped" << rnemdAxisLabel_
1196 <<
" >= " << leftSlabBoundary;
1198 if (leftSlabBoundary > rightSlabBoundary)
1199 selectionStream <<
" || wrapped" << rnemdAxisLabel_ <<
" < "
1200 << rightSlabBoundary;
1202 selectionStream <<
" && wrapped" << rnemdAxisLabel_ <<
" < "
1203 << rightSlabBoundary;
1205 return selectionStream.str();
1208 RealType RNEMD::getDefaultDividingArea() {
1209 if (hasDividingArea_)
return dividingArea_;
1211 Snapshot* snap = info_->getSnapshotManager()->getCurrentSnapshot();
1213 if (hasSelectionA_) {
1214 if (evaluatorA_.hasSurfaceArea()) {
1215 areaA_ = evaluatorA_.getSurfaceArea();
1216 volumeA_ = evaluatorA_.getVolume();
1220 std::vector<StuntDouble*> aSites;
1221 seleManA_.setSelectionSet(evaluatorA_.evaluate());
1222 for (sd = seleManA_.beginSelected(isd); sd != NULL;
1223 sd = seleManA_.nextSelected(isd)) {
1224 aSites.push_back(sd);
1226#if defined(HAVE_QHULL)
1228 surfaceMeshA->computeHull(aSites);
1229 areaA_ = surfaceMeshA->getArea();
1230 volumeA_ = surfaceMeshA->getVolume();
1231 delete surfaceMeshA;
1233 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
1234 "RNEMD::getDividingArea : Hull calculation is not possible\n"
1235 "\twithout libqhull. Please rebuild OpenMD with qhull "
1237 painCave.severity = OPENMD_ERROR;
1238 painCave.isFatal = 1;
1243 if (usePeriodicBoundaryConditions_) {
1246 switch (rnemdPrivilegedAxis_) {
1248 areaA_ = 2.0 * snap->getYZarea();
1251 areaA_ = 2.0 * snap->getXZarea();
1255 areaA_ = 2.0 * snap->getXYarea();
1258 volumeA_ = areaA_ * slabWidth_;
1263 areaA_ = 4.0 * Constants::PI * std::pow(sphereARadius_, 2);
1264 volumeA_ = 4.0 * Constants::PI * std::pow(sphereARadius_, 3) / 3.0;
1268 if (hasSelectionB_) {
1269 if (evaluatorB_.hasSurfaceArea()) {
1270 areaB_ = evaluatorB_.getSurfaceArea();
1271 volumeB_ = evaluatorB_.getVolume();
1275 std::vector<StuntDouble*> bSites;
1276 seleManB_.setSelectionSet(evaluatorB_.evaluate());
1277 for (sd = seleManB_.beginSelected(isd); sd != NULL;
1278 sd = seleManB_.nextSelected(isd)) {
1279 bSites.push_back(sd);
1282#if defined(HAVE_QHULL)
1284 surfaceMeshB->computeHull(bSites);
1285 areaB_ = surfaceMeshB->getArea();
1286 volumeB_ = surfaceMeshB->getVolume();
1287 delete surfaceMeshB;
1289 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
1290 "RNEMD::getDividingArea : Hull calculation is not possible\n"
1291 "\twithout libqhull. Please rebuild OpenMD with qhull "
1293 painCave.severity = OPENMD_ERROR;
1294 painCave.isFatal = 1;
1299 if (usePeriodicBoundaryConditions_) {
1302 switch (rnemdPrivilegedAxis_) {
1304 areaB_ = 2.0 * snap->getYZarea();
1307 areaB_ = 2.0 * snap->getXZarea();
1311 areaB_ = 2.0 * snap->getXYarea();
1314 volumeB_ = areaB_ * slabWidth_;
1318 areaB_ = 4.0 * Constants::PI * pow(sphereBRadius_, 2);
1320 RealType hVol = thermo.getHullVolume();
1321 volumeB_ = hVol - 4.0 * Constants::PI * pow(sphereBRadius_, 3) / 3.0;
1325 dividingArea_ = min(areaA_, areaB_);
1326 hasDividingArea_ =
true;
1327 return dividingArea_;
1330 int RNEMD::getBin(Vector3d pos) {
1331 if (usePeriodicBoundaryConditions_) {
1332 currentSnap_->wrapVector(pos);
1335 (pos[rnemdPrivilegedAxis_] /
1336 hmat_(rnemdPrivilegedAxis_, rnemdPrivilegedAxis_) +
1340 Vector3d rPos = pos - coordinateOrigin_;
1341 return int(rPos.
length() / binWidth_);
AtomType is what OpenMD looks to for unchanging data about an atom.
Vector3d getPos()
Returns the current position of this stuntdouble.
ConstraintElem * getConsElem1()
Return the first constraint elemet.
ConstraintElem * getConsElem2()
Retunr the second constraint element.
int getGlobalIndex()
Returns the global index of this molecule.
AtomTypeSet getSelectedAtomTypes()
getSelectedAtomTypes
int getNGlobalIntegrableObjects()
Returns the total number of integrable objects (total number of rigid bodies plus the total number of...
SnapshotManager * getSnapshotManager()
Returns the snapshot manager.
The Snapshot class is a repository storing dynamic data during a Simulation.
Mat3x3d getHmat()
Returns the H-Matrix.
Snapshot * getCurrentSnapshot()
Returns the pointer of current snapshot.
The string tokenizer class allows an application to break a string into tokens The set of delimiters ...
"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.
Vector3d getElectricField()
Returns the current electric field of this stuntDouble.
bool isRigidBody()
Tests if this stuntDouble is a rigid body.
Vector3d getJ()
Returns the current angular momentum of this stuntDouble (body -fixed).
bool isAtom()
Tests if this stuntDouble is an atom.
bool isDirectional()
Tests if this stuntDouble is a directional one.
void normalize()
Normalizes this vector in place.
Real length() const
Returns the length of this vector.
Real lengthSquare() const
Returns the squared length of this vector.
void add(const Vector< Real, Dim > &v1)
Sets the value of this vector to the sum of itself and v1 (*this += v1).
Vector3< Real > cross(const Vector3< Real > &v1, const Vector3< Real > &v2)
Returns the cross product of two Vectors.
std::string getPrefix(const std::string &str)