OpenMD 3.2
Molecular Dynamics in the Open
Loading...
Searching...
No Matches
LHDForceModifier.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
38
39#include <cmath>
40
42#include "types/GayBerneAdapter.hpp"
43#include "types/LennardJonesAdapter.hpp"
44#include "utils/Constants.hpp"
46
47namespace OpenMD {
48
49 LHDForceModifier::LHDForceModifier(SimInfo* info) :
50 ForceModifier {info}, maxIterNum_ {6}, forceTolerance_ {1e-6},
51 simParams_ {info->getSimParams()},
52 randNumGen_ {info->getRandomNumberGenerator()} {
53 dt_ = simParams_->getDt();
54 dt2_ = 0.5 * dt_;
55
56 if (!simParams_->haveTargetTemp()) {
57 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
58 "LHDForceModifier: a targetTemp is required.\n");
59 painCave.isFatal = 1;
60 simError();
61 }
62 if (!simParams_->haveViscosity()) {
63 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
64 "LHDForceModifier: a viscosity is required.\n");
65 painCave.isFatal = 1;
66 simError();
67 }
68 // Convert the input viscosity (Poise) to OpenMD internal units, exactly as
69 // Sphere::getHydroProp does, so the resistance R = M^{-1} is consistent
70 // with LDForceModifier's Xitt. Folding the factor in here means the drag
71 // (linear in R) and the random force (chol(R), so sqrt of the factor) both
72 // scale correctly and the fluctuation-dissipation balance is preserved.
73 viscosity_ = Constants::viscoConvert * simParams_->getViscosity();
74 kT_ = Constants::kb * simParams_->getTargetTemp();
75
76 velField_ = std::make_unique<VelocityField>(info);
77 veloMunge_ = std::make_unique<Velocitizer>(info_);
78
79 // Build one coupled group per molecule. Bead radii are fixed, so the
80 // RPYMobility is constructed once and only its positions are refreshed.
81 SimInfo::MoleculeIterator mi;
82 Molecule::IntegrableObjectIterator ii;
83 for (Molecule* mol = info_->beginMolecule(mi); mol != NULL;
84 mol = info_->nextMolecule(mi)) {
85 HydroMolecule hm;
86 std::vector<RealType> radii;
87 for (StuntDouble* sd = mol->beginIntegrableObject(ii); sd != NULL;
88 sd = mol->nextIntegrableObject(ii)) {
89 if (!sd->isAtom()) {
90 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
91 "LHDForceModifier: only spherical atoms are supported as\n"
92 "\thydrodynamic beads (found a non-atom integrable object).\n");
93 painCave.isFatal = 1;
94 simError();
95 }
96 hm.beads.push_back(sd);
97 hm.masses.push_back(sd->getMass());
98 radii.push_back(beadRadius(static_cast<Atom*>(sd)));
99 }
100 if (!hm.beads.empty()) {
101 hm.mobility = std::make_unique<RPYMobility>(radii, viscosity_);
102 molecules_.push_back(std::move(hm));
103 }
104 }
105 }
106
107 RealType LHDForceModifier::beadRadius(Atom* atom) const {
108 AtomType* atomType = atom->getAtomType();
109
110 GayBerneAdapter gba = GayBerneAdapter(atomType);
111 if (gba.isGayBerne()) {
112 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
113 "LHDForceModifier: Gay-Berne (non-spherical) atoms are not\n"
114 "\tsupported by the translation-only bead model.\n");
115 painCave.isFatal = 1;
116 simError();
117 }
118
119 LennardJonesAdapter lja = LennardJonesAdapter(atomType);
120 if (lja.isLennardJones()) return lja.getSigma() / 2.0;
121
122 std::vector<AtomType*> atChain = atomType->allYourBase();
123 for (std::vector<AtomType*>::iterator i = atChain.begin();
124 i != atChain.end(); ++i) {
125 int aNum = etab.GetAtomicNum((*i)->getName().c_str());
126 if (aNum != 0) return etab.GetVdwRad(aNum);
127 }
128
129 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
130 "LHDForceModifier: could not determine a hydrodynamic radius for\n"
131 "\tatom type %s.\n",
132 atomType->getName().c_str());
133 painCave.isFatal = 1;
134 simError();
135 return 0.0;
136 }
137
138 void LHDForceModifier::modifyForces() {
139 const RealType eConv = Constants::energyConvert;
140 bool useFlow = velField_->isActive();
141 Mat3x3d E = useFlow ? velField_->getRateOfStrain() : Mat3x3d(0.0);
142
143 std::size_t molIndex = 0;
144 for (HydroMolecule& hm : molecules_) {
145 std::size_t N = hm.beads.size();
146 RPYMobility& mob = *hm.mobility;
147
148 // --- gather state ---
149 std::vector<Vector3d> pos(N), vel(N), ambient(N, V3Zero);
150 for (std::size_t i = 0; i < N; ++i) {
151 pos[i] = hm.beads[i]->getPos();
152 vel[i] = hm.beads[i]->getVel();
153 if (useFlow) ambient[i] = velField_->getVelocity(pos[i]);
154 }
155
156 // --- rebuild mobility / resistance / Cholesky at this configuration ---
157 // RPYC keeps M (and R = M^{-1}) positive definite by construction, so a
158 // failure here is not an overlap artifact to tolerate: it means the
159 // configuration handed in is bad (NaN / runaway coordinates) or the bead
160 // radii / viscosity are unphysical. Fail loudly rather than let clamped,
161 // FDT-violating noise enter the trajectory.
162 if (!mob.update(pos)) {
163 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
164 "LHDForceModifier: the resistance tensor for molecule %lu\n"
165 "\t(%lu beads) is not positive definite. The RPYC mobility is\n"
166 "\tSPD by construction, so this indicates invalid coordinates\n"
167 "\t(NaN or overflow) or unphysical bead radii / viscosity.\n",
168 static_cast<unsigned long>(molIndex),
169 static_cast<unsigned long>(N));
170 painCave.severity = OPENMD_ERROR;
171 painCave.isFatal = 1;
172 simError();
173 }
174 ++molIndex;
175
176 // effective ambient velocity (affine flow + dipolar disturbance)
177 std::vector<Vector3d> vEff =
178 useFlow ? mob.effectiveAmbient(pos, ambient, E) : ambient;
179
180 // --- correlated random force, applied once ---
181 std::vector<RealType> Z(3 * N);
182 for (std::size_t k = 0; k < 3 * N; ++k) Z[k] = normal_(*randNumGen_);
183 std::vector<Vector3d> Frand = mob.randomForce(Z, kT_, dt_);
184 for (std::size_t i = 0; i < N; ++i) hm.beads[i]->addFrc(Frand[i]);
185
186 // --- self-consistent coupled friction solve ---
187 // velocity is known at the half step; the friction needs the full-step
188 // velocity, which depends on the friction. Iterate to convergence.
189 std::vector<Vector3d> frc(N), velStep(N), Ffric(N, V3Zero), oldF(N);
190 for (std::size_t i = 0; i < N; ++i) {
191 frc[i] = hm.beads[i]->getFrc(); // conservative + random
192 velStep[i] = vel[i] + (dt2_ / hm.masses[i] * eConv) * frc[i];
193 }
194
195 for (int k = 0; k < maxIterNum_; ++k) {
196 oldF = Ffric;
197 // f_i = sum_j R_ij ( vEff_j - velStep_j )
198 Ffric = mob.dragForce(vEff, velStep);
199 for (std::size_t i = 0; i < N; ++i)
200 velStep[i] =
201 vel[i] + (dt2_ / hm.masses[i] * eConv) * (frc[i] + Ffric[i]);
202
203 // converged when every bead's friction force has stopped changing
204 // direction/magnitude (fdot -> 1)
205 RealType worst = 0.0;
206 for (std::size_t i = 0; i < N; ++i) {
207 RealType f2 = Ffric[i].lengthSquare();
208 if (f2 < 1.0e-12) continue; // negligible drag on this bead
209 RealType fdot = dot(Ffric[i], oldF[i]) / f2;
210 worst = std::max(worst, std::fabs(1.0 - fdot));
211 }
212 if (worst <= forceTolerance_) break;
213 }
214
215 for (std::size_t i = 0; i < N; ++i) hm.beads[i]->addFrc(Ffric[i]);
216 }
217
218 // Drift removal only makes sense without an imposed flow; with a background
219 // flow it would cancel the net advection the flow imparts.
220 if (!useFlow) {
221 if (simParams_->getConserveLinearMomentum()) veloMunge_->removeComDrift();
222 if (!simParams_->getUsePeriodicBoundaryConditions() &&
223 simParams_->getConserveAngularMomentum())
224 veloMunge_->removeAngularDrift();
225 }
226 }
227} // namespace OpenMD
This basic Periodic Table class was originally taken from the data.h file in OpenBabel.
Langevin force modifier with intramolecular RPY hydrodynamic interactions for flexible bead molecules...
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
This basic Periodic Table class was originally taken from the data.cpp file in OpenBabel.
Real dot(const DynamicVector< Real > &v1, const DynamicVector< Real > &v2)
Returns the dot product of two DynamicVectors.