OpenMD 3.2
Molecular Dynamics in the Open
Loading...
Searching...
No Matches
LDForceModifier.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#include "integrators/LDForceModifier.hpp"
48
49#include <cmath>
50#include <fstream>
51#include <iostream>
52#include <memory>
53#include <random>
54
55#include "brains/ForceModifier.hpp"
56#include "hydrodynamics/Ellipsoid.hpp"
57#include "hydrodynamics/HydroIO.hpp"
58#include "hydrodynamics/Sphere.hpp"
59#include "math/CholeskyDecomposition.hpp"
61#include "types/GayBerneAdapter.hpp"
62#include "types/LennardJonesAdapter.hpp"
63#include "utils/Constants.hpp"
65
66using namespace std;
67namespace OpenMD {
68
69 LDForceModifier::LDForceModifier(SimInfo* info) :
70 ForceModifier {info}, maxIterNum_ {6}, forceTolerance_ {1e-6},
71 randNumGen_ {info->getRandomNumberGenerator()},
72 simParams_ {info->getSimParams()} {
73 RealType dt = simParams_->getDt();
74 dt2_ = dt * 0.5;
75
76 veloMunge_ = std::make_unique<Velocitizer>(info_);
77
78 sphericalBoundaryConditions_ = false;
79 if (simParams_->getUseSphericalBoundaryConditions()) {
80 sphericalBoundaryConditions_ = true;
81 if (simParams_->haveLangevinBufferRadius()) {
82 langevinBufferRadius_ = simParams_->getLangevinBufferRadius();
83 } else {
84 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
85 "langevinBufferRadius must be specified "
86 "when useSphericalBoundaryConditions is turned on.\n");
87 painCave.severity = OPENMD_ERROR;
88 painCave.isFatal = 1;
89 simError();
90 }
91
92 if (simParams_->haveFrozenBufferRadius()) {
93 frozenBufferRadius_ = simParams_->getFrozenBufferRadius();
94 } else {
95 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
96 "frozenBufferRadius must be specified "
97 "when useSphericalBoundaryConditions is turned on.\n");
98 painCave.severity = OPENMD_ERROR;
99 painCave.isFatal = 1;
100 simError();
101 }
102
103 if (frozenBufferRadius_ < langevinBufferRadius_) {
104 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
105 "frozenBufferRadius has been set smaller than the "
106 "langevinBufferRadius. This is probably an error.\n");
107 painCave.severity = OPENMD_WARNING;
108 painCave.isFatal = 0;
109 simError();
110 }
111 }
112
113 // Build the hydroProp_ map:
114 Molecule* mol;
115 StuntDouble* sd;
116 SimInfo::MoleculeIterator i;
117 Molecule::IntegrableObjectIterator j;
118 bool needHydroPropFile = false;
119
120 for (mol = info_->beginMolecule(i); mol != NULL;
121 mol = info_->nextMolecule(i)) {
122 for (sd = mol->beginIntegrableObject(j); sd != NULL;
123 sd = mol->nextIntegrableObject(j)) {
124 if (sd->isRigidBody()) {
125 RigidBody* rb = static_cast<RigidBody*>(sd);
126 if (rb->getNumAtoms() > 1) needHydroPropFile = true;
127 }
128 }
129 }
130
131 if (needHydroPropFile) {
132 if (simParams_->haveHydroPropFile()) {
133 HydroIO* hio = new HydroIO();
134 hydroPropMap_ = hio->parseHydroFile(simParams_->getHydroPropFile());
135 } else {
136 snprintf(
137 painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
138 "HydroPropFile must be set to a file name if Langevin Dynamics\n"
139 "\tis specified for rigidBodies which contain more than one atom\n"
140 "\tTo create a HydroPropFile, run the \"Hydro\" program.\n");
141 painCave.severity = OPENMD_ERROR;
142 painCave.isFatal = 1;
143 simError();
144 }
145
146 for (mol = info_->beginMolecule(i); mol != NULL;
147 mol = info_->nextMolecule(i)) {
148 for (sd = mol->beginIntegrableObject(j); sd != NULL;
149 sd = mol->nextIntegrableObject(j)) {
150 map<string, HydroProp*>::iterator iter =
151 hydroPropMap_.find(sd->getType());
152 if (iter != hydroPropMap_.end()) {
153 hydroProps_.push_back(iter->second);
154 moments_.push_back(getMomentData(sd));
155
156 } else {
157 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
158 "Can not find resistance tensor for atom [%s]\n",
159 sd->getType().c_str());
160 painCave.severity = OPENMD_ERROR;
161 painCave.isFatal = 1;
162 simError();
163 }
164 }
165 }
166
167 } else {
168 for (mol = info_->beginMolecule(i); mol != NULL;
169 mol = info_->nextMolecule(i)) {
170 for (sd = mol->beginIntegrableObject(j); sd != NULL;
171 sd = mol->nextIntegrableObject(j)) {
172 Shape* currShape = NULL;
173
174 if (sd->isAtom()) {
175 Atom* atom = static_cast<Atom*>(sd);
176 AtomType* atomType = atom->getAtomType();
177 GayBerneAdapter gba = GayBerneAdapter(atomType);
178 if (gba.isGayBerne()) {
179 currShape = new Ellipsoid(V3Zero, gba.getL() / 2.0,
180 gba.getD() / 2.0, Mat3x3d::identity());
181 } else {
182 LennardJonesAdapter lja = LennardJonesAdapter(atomType);
183 if (lja.isLennardJones()) {
184 currShape = new Sphere(V3Zero, lja.getSigma() / 2.0);
185 } else {
186 int aNum(0);
187 vector<AtomType*> atChain = atomType->allYourBase();
188 vector<AtomType*>::iterator i;
189 for (i = atChain.begin(); i != atChain.end(); ++i) {
190 aNum = etab.GetAtomicNum((*i)->getName().c_str());
191 if (aNum != 0) {
192 currShape = new Sphere(V3Zero, etab.GetVdwRad(aNum));
193 break;
194 }
195 }
196 if (aNum == 0) {
197 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
198 "Could not find atom type in default element.txt\n");
199 painCave.severity = OPENMD_ERROR;
200 painCave.isFatal = 1;
201 simError();
202 }
203 }
204 }
205 }
206
207 if (!simParams_->haveTargetTemp()) {
208 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
209 "You can't use LangevinDynamics without a targetTemp!\n");
210 painCave.isFatal = 1;
211 painCave.severity = OPENMD_ERROR;
212 simError();
213 }
214
215 if (!simParams_->haveViscosity()) {
216 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
217 "You can't use LangevinDynamics without a viscosity!\n");
218 painCave.isFatal = 1;
219 painCave.severity = OPENMD_ERROR;
220 simError();
221 }
222
223 HydroProp* currHydroProp =
224 currShape->getHydroProp(simParams_->getViscosity());
225 map<string, HydroProp*>::iterator iter =
226 hydroPropMap_.find(sd->getType());
227 if (iter != hydroPropMap_.end()) {
228 hydroProps_.push_back(iter->second);
229 moments_.push_back(getMomentData(sd));
230
231 } else {
232 currHydroProp->complete();
233 hydroPropMap_.insert(map<string, HydroProp*>::value_type(
234 sd->getType(), currHydroProp));
235 hydroProps_.push_back(currHydroProp);
236 moments_.push_back(getMomentData(sd));
237 }
238 delete currShape;
239 }
240 }
241 }
242
243 RealType stdDev =
244 std::sqrt(2.0 * Constants::kb * simParams_->getTargetTemp() / dt);
245
246 forceDistribution_ = std::normal_distribution<RealType>(0.0, stdDev);
247 }
248
249 MomentData* LDForceModifier::getMomentData(StuntDouble* sd) {
250 map<string, MomentData*>::iterator j = momentsMap_.find(sd->getType());
251 if (j != momentsMap_.end()) {
252 return j->second;
253 } else {
254 MomentData* moment = new MomentData;
255 map<string, HydroProp*>::iterator i = hydroPropMap_.find(sd->getType());
256
257 if (i != hydroPropMap_.end()) {
258 // Center of Resistance:
259 moment->rcr = i->second->getCenterOfResistance();
260 } else {
261 snprintf(
262 painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
263 "LDForceManager createMomentData: Couldn't find HydroProp for\n"
264 "object type %s!\n",
265 sd->getType().c_str());
266 painCave.isFatal = 1;
267 painCave.severity = OPENMD_ERROR;
268 simError();
269 }
270
271 if (sd->isRigidBody())
272 moment->rcr -= dynamic_cast<RigidBody*>(sd)->getRefCOM();
273
274 // Parallel axis formula to get the moment of inertia around
275 // the center of resistance:
276 RealType mass = sd->getMass();
277 moment->Icr = sd->getI();
278 moment->Icr +=
279 mass * (dot(moment->rcr, moment->rcr) * Mat3x3d::identity() +
280 outProduct(moment->rcr, moment->rcr));
281 moment->IcrInv = moment->Icr.inverse();
282
283 momentsMap_.insert(
284 map<string, MomentData*>::value_type(sd->getType(), moment));
285 return moment;
286 }
287 }
288
289 void LDForceModifier::modifyForces() {
290 SimInfo::MoleculeIterator i;
291 Molecule::IntegrableObjectIterator j;
292 Molecule* mol;
293 StuntDouble* sd;
294 RealType mass;
295 Vector3d pos;
296 Vector3d frc;
297 Mat3x3d A;
298 Mat3x3d Atrans;
299 Vector3d Tb;
300 Vector3d ji;
301 unsigned int index = 0;
302 bool doLangevinForces;
303 bool freezeMolecule;
304 int fdf;
305
306 fdf = 0;
307
308 for (mol = info_->beginMolecule(i); mol != NULL;
309 mol = info_->nextMolecule(i)) {
310 doLangevinForces = true;
311 freezeMolecule = false;
312
313 if (sphericalBoundaryConditions_) {
314 Vector3d molPos = mol->getCom();
315 RealType molRad = molPos.length();
316
317 doLangevinForces = false;
318
319 if (molRad > langevinBufferRadius_) {
320 doLangevinForces = true;
321 freezeMolecule = false;
322 }
323 if (molRad > frozenBufferRadius_) {
324 doLangevinForces = false;
325 freezeMolecule = true;
326 }
327 }
328
329 for (sd = mol->beginIntegrableObject(j); sd != NULL;
330 sd = mol->nextIntegrableObject(j)) {
331 if (freezeMolecule) fdf += sd->freeze();
332
333 if (doLangevinForces) {
334 mass = sd->getMass();
335 if (sd->isDirectional()) {
336 // preliminaries for directional objects:
337
338 A = sd->getA();
339 Atrans = A.transpose();
340
341 Vector3d rcrLab = Atrans * moments_[index]->rcr;
342
343 // apply random force and torque at center of resistance
344
345 Vector3d randomForceBody;
346 Vector3d randomTorqueBody;
347 genRandomForceAndTorque(randomForceBody, randomTorqueBody, index);
348 Vector3d randomForceLab = Atrans * randomForceBody;
349 Vector3d randomTorqueLab = Atrans * randomTorqueBody;
350
351 sd->addFrc(randomForceLab);
352 sd->addTrq(randomTorqueLab + cross(rcrLab, randomForceLab));
353
354 Vector3d omegaBody;
355
356 // What remains contains velocity explicitly, but the
357 // velocity required is at the full step: v(t + h), while
358 // we have initially the velocity at the half step: v(t + h/2).
359 // We need to iterate to converge the friction
360 // force and friction torque vectors.
361
362 // this is the velocity at the half-step:
363
364 Vector3d vel = sd->getVel();
365 Vector3d angMom = sd->getJ();
366
367 // estimate velocity at full-step using everything but
368 // friction forces:
369
370 frc = sd->getFrc();
371
372 Vector3d velStep =
373 vel + (dt2_ / mass * Constants::energyConvert) * frc;
374
375 Tb = sd->lab2Body(sd->getTrq());
376 Vector3d angMomStep =
377 angMom + (dt2_ * Constants::energyConvert) * Tb;
378
379 Vector3d omegaLab;
380 Vector3d vcdLab;
381 Vector3d vcdBody;
382 Vector3d frictionForceBody;
383 Vector3d frictionForceLab(0.0);
384 Vector3d oldFFL; // used to test for convergence
385 Vector3d frictionTorqueBody(0.0);
386 Vector3d oldFTB; // used to test for convergence
387 Vector3d frictionTorqueLab;
388 RealType fdot;
389 RealType tdot;
390
391 // iteration starts here:
392
393 for (int k = 0; k < maxIterNum_; k++) {
394 if (sd->isLinear()) {
395 int linearAxis = sd->linearAxis();
396 int l = (linearAxis + 1) % 3;
397 int m = (linearAxis + 2) % 3;
398 omegaBody[l] = angMomStep[l] / moments_[index]->Icr(l, l);
399 omegaBody[m] = angMomStep[m] / moments_[index]->Icr(m, m);
400
401 } else {
402 omegaBody = moments_[index]->IcrInv * angMomStep;
403 // omegaBody[0] = angMomStep[0] /I(0, 0);
404 // omegaBody[1] = angMomStep[1] /I(1, 1);
405 // omegaBody[2] = angMomStep[2] /I(2, 2);
406 }
407
408 omegaLab = Atrans * omegaBody;
409
410 // apply friction force and torque at center of resistance
411
412 vcdLab = velStep + cross(omegaLab, rcrLab);
413 vcdBody = A * vcdLab;
414 frictionForceBody = -(hydroProps_[index]->getXitt() * vcdBody +
415 hydroProps_[index]->getXirt() * omegaBody);
416 oldFFL = frictionForceLab;
417 frictionForceLab = Atrans * frictionForceBody;
418 oldFTB = frictionTorqueBody;
419 frictionTorqueBody = -(hydroProps_[index]->getXitr() * vcdBody +
420 hydroProps_[index]->getXirr() * omegaBody);
421 frictionTorqueLab = Atrans * frictionTorqueBody;
422
423 // re-estimate velocities at full-step using friction forces:
424
425 velStep = vel + (dt2_ / mass * Constants::energyConvert) *
426 (frc + frictionForceLab);
427 angMomStep = angMom + (dt2_ * Constants::energyConvert) *
428 (Tb + frictionTorqueBody);
429
430 // check for convergence (if the vectors have converged, fdot and
431 // tdot will both be 1.0):
432
433 fdot = dot(frictionForceLab, oldFFL) /
434 frictionForceLab.lengthSquare();
435 tdot = dot(frictionTorqueBody, oldFTB) /
436 frictionTorqueBody.lengthSquare();
437
438 if (fabs(1.0 - fdot) <= forceTolerance_ &&
439 fabs(1.0 - tdot) <= forceTolerance_)
440 break; // iteration ends here
441 }
442
443 sd->addFrc(frictionForceLab);
444 sd->addTrq(frictionTorqueLab + cross(rcrLab, frictionForceLab));
445
446 } else {
447 // spherical atom
448
449 Vector3d systemForce;
450 Vector3d randomForce;
451 Vector3d randomTorque;
452 genRandomForceAndTorque(randomForce, randomTorque, index);
453 systemForce = sd->getFrc();
454 sd->addFrc(randomForce);
455
456 // What remains contains velocity explicitly, but the
457 // velocity required is at the full step: v(t + h), while
458 // we have initially the velocity at the half step: v(t + h/2).
459 // We need to iterate to converge the friction
460 // force vector.
461
462 // this is the velocity at the half-step:
463
464 Vector3d vel = sd->getVel();
465
466 // estimate velocity at full-step using everything but
467 // friction forces:
468
469 frc = sd->getFrc();
470 Vector3d velStep =
471 vel + (dt2_ / mass * Constants::energyConvert) * frc;
472
473 Vector3d frictionForce(0.0);
474 Vector3d oldFF; // used to test for convergence
475 RealType fdot;
476
477 // iteration starts here:
478
479 for (int k = 0; k < maxIterNum_; k++) {
480 oldFF = frictionForce;
481 frictionForce = -hydroProps_[index]->getXitt() * velStep;
482
483 // re-estimate velocities at full-step using friction forces:
484
485 velStep = vel + (dt2_ / mass * Constants::energyConvert) *
486 (frc + frictionForce);
487
488 // check for convergence (if the vector has converged,
489 // fdot will be 1.0):
490
491 fdot = dot(frictionForce, oldFF) / frictionForce.lengthSquare();
492
493 if (fabs(1.0 - fdot) <= forceTolerance_)
494 break; // iteration ends here
495 }
496
497 sd->addFrc(frictionForce);
498 }
499 }
500
501 ++index;
502 }
503 }
504
505 info_->setFdf(fdf);
506 if (simParams_->getConserveLinearMomentum()) veloMunge_->removeComDrift();
507 // Remove angular drift if we are not using periodic boundary conditions.
508 if (!simParams_->getUsePeriodicBoundaryConditions() &&
509 simParams_->getConserveAngularMomentum())
510 veloMunge_->removeAngularDrift();
511 }
512
513 void LDForceModifier::genRandomForceAndTorque(Vector3d& force,
514 Vector3d& torque,
515 unsigned int index) {
516 Vector<RealType, 6> Z;
517 Vector<RealType, 6> generalForce;
518
519 Z[0] = forceDistribution_(*randNumGen_);
520 Z[1] = forceDistribution_(*randNumGen_);
521 Z[2] = forceDistribution_(*randNumGen_);
522 Z[3] = forceDistribution_(*randNumGen_);
523 Z[4] = forceDistribution_(*randNumGen_);
524 Z[5] = forceDistribution_(*randNumGen_);
525
526 generalForce = hydroProps_[index]->getS() * Z;
527
528 force[0] = generalForce[0];
529 force[1] = generalForce[1];
530 force[2] = generalForce[2];
531 torque[0] = generalForce[3];
532 torque[1] = generalForce[4];
533 torque[2] = generalForce[5];
534 }
535} // namespace OpenMD
This basic Periodic Table class was originally taken from the data.h file in OpenBabel.
RealType GetVdwRad(int atomicnum)
int GetAtomicNum(const char *str)
Abstract class for external ForceModifier classes.
One of the heavy-weight classes of OpenMD, SimInfo maintains objects and variables relating to the cu...
Definition SimInfo.hpp:96
static SquareMatrix< Real, Dim > identity()
Returns an identity matrix.
Real length() const
Returns the length of this vector.
Definition Vector.hpp:397
This basic Periodic Table class was originally taken from the data.cpp file in OpenBabel.
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.