OpenMD 3.2
Molecular Dynamics in the Open
Loading...
Searching...
No Matches
GB.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 "nonbonded/GB.hpp"
49
50#include <cmath>
51#include <cstdio>
52#include <cstring>
53
54#include "types/GayBerneAdapter.hpp"
55#include "types/LennardJonesAdapter.hpp"
56#include "utils/simError.h"
57
58using namespace std;
59namespace OpenMD {
60
61 /* GB is the Gay-Berne interaction for ellipsoidal particles. The original
62 * paper (for identical uniaxial particles) is:
63 * J. G. Gay and B. J. Berne, J. Chem. Phys., 74, 3316-3319 (1981).
64 * A more-general GB potential for dissimilar uniaxial particles:
65 * D. J. Cleaver, C. M. Care, M. P. Allen and M. P. Neal, Phys. Rev. E,
66 * 54, 559-567 (1996).
67 * Further parameterizations can be found in:
68 * A. P. J. Emerson, G. R. Luckhurst and S. G. Whatling, Mol. Phys.,
69 * 82, 113-124 (1994).
70 * And a nice force expression:
71 * G. R. Luckhurst and R. A. Stephens, Liq. Cryst. 8, 451-464 (1990).
72 * Even clearer force and torque expressions:
73 * P. A. Golubkov and P. Y. Ren, J. Chem. Phys., 125, 64103 (2006).
74 * New expressions for cross interactions of strength parameters:
75 * J. Wu, X. Zhen, H. Shen, G. Li, and P. Ren, J. Chem. Phys.,
76 * 135, 155104 (2011).
77 *
78 * In this version of the GB interaction, each uniaxial ellipsoidal type
79 * is described using a set of 6 parameters:
80 * d: range parameter for side-by-side (S) and cross (X) configurations
81 * l: range parameter for end-to-end (E) configuration
82 * epsilon_X: well-depth parameter for cross (X) configuration
83 * epsilon_S: well-depth parameter for side-by-side (S) configuration
84 * epsilon_E: well depth parameter for end-to-end (E) configuration
85 * dw: "softness" of the potential
86 *
87 * Additionally, there are two "universal" paramters to govern the overall
88 * importance of the purely orientational (nu) and the mixed
89 * orientational / translational (mu) parts of strength of the interactions.
90 * These parameters have default or "canonical" values, but may be changed
91 * as a force field option:
92 * nu_: purely orientational part : defaults to 1
93 * mu_: mixed orientational / translational part : defaults to 2
94 */
95
96 GB::GB() :
97 initialized_(false), name_("GB"), forceField_(NULL), mu_(2.0), nu_(1.0) {}
98
99 void GB::initialize() {
100 GBtypes.clear();
101 GBtids.clear();
102 MixingMap.clear();
103 nGB_ = 0;
104
105 GBtids.resize(forceField_->getNAtomType(), -1);
106
107 ForceFieldOptions& fopts = forceField_->getForceFieldOptions();
108 mu_ = fopts.getGayBerneMu();
109 nu_ = fopts.getGayBerneNu();
110
111 // GB handles all of the GB-GB interactions as well as GB-LJ cross
112 // interactions:
113 AtomTypeSet::iterator at;
114 for (at = simTypes_.begin(); at != simTypes_.end(); ++at) {
115 if ((*at)->isGayBerne()) nGB_++;
116 if ((*at)->isLennardJones()) nGB_++;
117 }
118
119 MixingMap.resize(nGB_);
120 for (at = simTypes_.begin(); at != simTypes_.end(); ++at) {
121 if ((*at)->isGayBerne() || (*at)->isLennardJones()) addType(*at);
122 }
123
124 initialized_ = true;
125 }
126
127 void GB::addType(AtomType* atomType) {
128 // add it to the map:
129 int atid = atomType->getIdent();
130 int gbtid = GBtypes.size();
131
132 pair<set<int>::iterator, bool> ret;
133 ret = GBtypes.insert(atid);
134 if (ret.second == false) {
135 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
136 "GB already had a previous entry with ident %d\n", atid);
137 painCave.severity = OPENMD_INFO;
138 painCave.isFatal = 0;
139 simError();
140 }
141
142 GBtids[atid] = gbtid;
143 MixingMap[gbtid].resize(nGB_);
144
145 RealType d1(0.0), l1(0.0), eX1(0.0), eS1(0.0), eE1(0.0), dw1(0.0);
146
147 LennardJonesAdapter lja1 = LennardJonesAdapter(atomType);
148 GayBerneAdapter gba1 = GayBerneAdapter(atomType);
149 if (gba1.isGayBerne()) {
150 d1 = gba1.getD();
151 l1 = gba1.getL();
152 eX1 = gba1.getEpsX();
153 eS1 = gba1.getEpsS();
154 eE1 = gba1.getEpsE();
155 dw1 = gba1.getDw();
156 } else if (lja1.isLennardJones()) {
157 d1 = lja1.getSigma() / sqrt(2.0);
158 l1 = d1;
159 eX1 = lja1.getEpsilon();
160 eS1 = eX1;
161 eE1 = eX1;
162 dw1 = 1.0;
163 } else {
164 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
165 "GB::addType was passed an atomType (%s) that does not\n"
166 "\tappear to be a Gay-Berne or Lennard-Jones atom.\n",
167 atomType->getName().c_str());
168 painCave.severity = OPENMD_ERROR;
169 painCave.isFatal = 1;
170 simError();
171 }
172
173 // Now, iterate over all known types and add to the mixing map:
174
175 std::set<int>::iterator it;
176 for (it = GBtypes.begin(); it != GBtypes.end(); ++it) {
177 int gbtid2 = GBtids[(*it)];
178 AtomType* atype2 = forceField_->getAtomType((*it));
179
180 LennardJonesAdapter lja2 = LennardJonesAdapter(atype2);
181 GayBerneAdapter gba2 = GayBerneAdapter(atype2);
182 RealType d2(0.0), l2(0.0), eX2(0.0), eS2(0.0), eE2(0.0), dw2(0.0);
183
184 if (gba2.isGayBerne()) {
185 d2 = gba2.getD();
186 l2 = gba2.getL();
187 eX2 = gba2.getEpsX();
188 eS2 = gba2.getEpsS();
189 eE2 = gba2.getEpsE();
190 dw2 = gba2.getDw();
191 } else if (lja2.isLennardJones()) {
192 d2 = lja2.getSigma() / sqrt(2.0);
193 l2 = d2;
194 eX2 = lja2.getEpsilon();
195 eS2 = eX2;
196 eE2 = eX2;
197 dw2 = 1.0;
198 } else {
199 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
200 "GB::addType found an atomType (%s) that does not\n"
201 "\tappear to be a Gay-Berne or Lennard-Jones atom.\n",
202 atype2->getName().c_str());
203 painCave.severity = OPENMD_ERROR;
204 painCave.isFatal = 1;
205 simError();
206 }
207
208 GBInteractionData mixer1, mixer2;
209
210 // Cleaver paper uses sqrt of squares to get sigma0 for
211 // mixed interactions.
212 ForceFieldOptions& fopts = forceField_->getForceFieldOptions();
213 string DistanceMix = fopts.getDistanceMixingRule();
214 toUpper(DistanceMix);
215
216 if (DistanceMix == "ARITHMETIC")
217 mixer1.sigma0 = 0.5 * (d1 + d2);
218 else
219 mixer1.sigma0 = sqrt(d1 * d1 + d2 * d2);
220
221 mixer1.xa2 = (l1 * l1 - d1 * d1) / (l1 * l1 + d2 * d2);
222 mixer1.xai2 = (l2 * l2 - d2 * d2) / (l2 * l2 + d1 * d1);
223 mixer1.x2 = (l1 * l1 - d1 * d1) * (l2 * l2 - d2 * d2) /
224 ((l2 * l2 + d1 * d1) * (l1 * l1 + d2 * d2));
225
226 mixer2.sigma0 = mixer1.sigma0;
227 // xa2 and xai2 for j-i pairs are reversed from the same i-j pairing.
228 // Swapping the particles reverses the anisotropy parameters:
229 mixer2.xa2 = mixer1.xai2;
230 mixer2.xai2 = mixer1.xa2;
231 mixer2.x2 = mixer1.x2;
232
233 // assumed LB mixing rules for now:
234
235 mixer1.dw = 0.5 * (dw1 + dw2);
236 mixer1.eps0 = sqrt(eX1 * eX2);
237
238 mixer2.dw = mixer1.dw;
239 mixer2.eps0 = mixer1.eps0;
240
241 RealType mi = RealType(1.0) / mu_;
242
243 mixer1.xpap2 =
244 (pow(eS1, mi) - pow(eE1, mi)) / (pow(eS1, mi) + pow(eE2, mi));
245 mixer1.xpapi2 =
246 (pow(eS2, mi) - pow(eE2, mi)) / (pow(eS2, mi) + pow(eE1, mi));
247 mixer1.xp2 =
248 (pow(eS1, mi) - pow(eE1, mi)) * (pow(eS2, mi) - pow(eE2, mi)) /
249 (pow(eS2, mi) + pow(eE1, mi)) / (pow(eS1, mi) + pow(eE2, mi));
250
251 // xpap2 and xpapi2 for j-i pairs are reversed from the same i-j pairing.
252 // Swapping the particles reverses the anisotropy parameters:
253 mixer2.xpap2 = mixer1.xpapi2;
254 mixer2.xpapi2 = mixer1.xpap2;
255 mixer2.xp2 = mixer1.xp2;
256 // keep track of who is the LJ atom:
257 mixer1.i_is_LJ = atomType->isLennardJones();
258 mixer1.j_is_LJ = atype2->isLennardJones();
259 mixer2.i_is_LJ = mixer1.j_is_LJ;
260 mixer2.j_is_LJ = mixer1.i_is_LJ;
261
262 // only add this pairing if at least one of the atoms is a Gay-Berne atom
263
264 if (gba1.isGayBerne() || gba2.isGayBerne()) {
265 MixingMap[gbtid2].resize(nGB_);
266 MixingMap[gbtid][gbtid2] = mixer1;
267 if (gbtid2 != gbtid) { MixingMap[gbtid2][gbtid] = mixer2; }
268 }
269 }
270 }
271
272 void GB::calcForce(InteractionData& idat) {
273 if (!initialized_) initialize();
274
275 GBInteractionData& mixer =
276 MixingMap[GBtids[idat.atid1]][GBtids[idat.atid2]];
277
278 RealType sigma0 = mixer.sigma0;
279 RealType dw = mixer.dw;
280 RealType eps0 = mixer.eps0;
281 RealType x2 = mixer.x2;
282 RealType xa2 = mixer.xa2;
283 RealType xai2 = mixer.xai2;
284 RealType xp2 = mixer.xp2;
285 RealType xpap2 = mixer.xpap2;
286 RealType xpapi2 = mixer.xpapi2;
287
288 Vector3d ul1 = idat.A1.getRow(2);
289 Vector3d ul2 = idat.A2.getRow(2);
290
291 RealType a, b, g;
292
293 if (mixer.i_is_LJ) {
294 a = 0.0;
295 ul1 = V3Zero;
296 } else {
297 a = dot(idat.d, ul1);
298 }
299
300 if (mixer.j_is_LJ) {
301 b = 0.0;
302 ul2 = V3Zero;
303 } else {
304 b = dot(idat.d, ul2);
305 }
306
307 if (mixer.i_is_LJ || mixer.j_is_LJ)
308 g = 0.0;
309 else
310 g = dot(ul1, ul2);
311
312 RealType au = a / idat.rij;
313 RealType bu = b / idat.rij;
314
315 RealType au2 = au * au;
316 RealType bu2 = bu * bu;
317 RealType g2 = g * g;
318
319 RealType H =
320 (xa2 * au2 + xai2 * bu2 - 2.0 * x2 * au * bu * g) / (1.0 - x2 * g2);
321 RealType Hp = (xpap2 * au2 + xpapi2 * bu2 - 2.0 * xp2 * au * bu * g) /
322 (1.0 - xp2 * g2);
323
324 RealType sigma = sigma0 / sqrt(1.0 - H);
325 RealType e1 = 1.0 / sqrt(1.0 - x2 * g2);
326 RealType e2 = 1.0 - Hp;
327 RealType eps = eps0 * pow(e1, nu_) * pow(e2, mu_);
328 RealType BigR = dw * sigma0 / (idat.rij - sigma + dw * sigma0);
329
330 RealType R3 = BigR * BigR * BigR;
331 RealType R6 = R3 * R3;
332 RealType R7 = R6 * BigR;
333 RealType R12 = R6 * R6;
334 RealType R13 = R6 * R7;
335
336 RealType U = idat.vdwMult * 4.0 * eps * (R12 - R6);
337
338 RealType s3 = sigma * sigma * sigma;
339 RealType s03 = sigma0 * sigma0 * sigma0;
340
341 RealType pref1 =
342 -idat.vdwMult * 8.0 * eps * mu_ * (R12 - R6) / (e2 * idat.rij);
343
344 RealType pref2 = idat.vdwMult * 8.0 * eps * s3 * (6.0 * R13 - 3.0 * R7) /
345 (dw * idat.rij * s03);
346
347 RealType dUdr =
348 -(pref1 * Hp + pref2 * (sigma0 * sigma0 * idat.rij / s3 + H));
349
350 RealType dUda = pref1 * (xpap2 * au - xp2 * bu * g) / (1.0 - xp2 * g2) +
351 pref2 * (xa2 * au - x2 * bu * g) / (1.0 - x2 * g2);
352
353 RealType dUdb = pref1 * (xpapi2 * bu - xp2 * au * g) / (1.0 - xp2 * g2) +
354 pref2 * (xai2 * bu - x2 * au * g) / (1.0 - x2 * g2);
355
356 RealType dUdg = 4.0 * eps * nu_ * (R12 - R6) * x2 * g / (1.0 - x2 * g2) +
357 8.0 * eps * mu_ * (R12 - R6) *
358 (xp2 * au * bu - Hp * xp2 * g) / (1.0 - xp2 * g2) / e2 +
359 8.0 * eps * s3 * (3.0 * R7 - 6.0 * R13) *
360 (x2 * au * bu - H * x2 * g) / (1.0 - x2 * g2) /
361 (dw * s03);
362
363 Vector3d rhat = idat.d / idat.rij;
364 Vector3d rxu1 = cross(idat.d, ul1);
365 Vector3d rxu2 = cross(idat.d, ul2);
366 Vector3d uxu = cross(ul1, ul2);
367
368 idat.pot[VANDERWAALS_FAMILY] += U * idat.sw;
369 if (idat.isSelected) idat.selePot[VANDERWAALS_FAMILY] += U * idat.sw;
370
371 idat.f1 += (dUdr * rhat + dUda * ul1 + dUdb * ul2) * idat.sw;
372 idat.t1 += (dUda * rxu1 - dUdg * uxu) * idat.sw;
373 idat.t2 += (dUdb * rxu2 + dUdg * uxu) * idat.sw;
374 idat.vpair += U;
375 return;
376 }
377
378 RealType GB::getSuggestedCutoffRadius(pair<AtomType*, AtomType*> atypes) {
379 if (!initialized_) initialize();
380
381 RealType cut = 0.0;
382
383 LennardJonesAdapter lja1 = LennardJonesAdapter(atypes.first);
384 GayBerneAdapter gba1 = GayBerneAdapter(atypes.first);
385 LennardJonesAdapter lja2 = LennardJonesAdapter(atypes.second);
386 GayBerneAdapter gba2 = GayBerneAdapter(atypes.second);
387
388 if (gba1.isGayBerne()) {
389 RealType d1 = gba1.getD();
390 RealType l1 = gba1.getL();
391 // sigma is actually sqrt(2)*l for prolate ellipsoids
392 cut = max(cut, RealType(2.5) * sqrt(RealType(2.0)) * max(d1, l1));
393 } else if (lja1.isLennardJones()) {
394 cut = max(cut, RealType(2.5) * lja1.getSigma());
395 }
396
397 if (gba2.isGayBerne()) {
398 RealType d2 = gba2.getD();
399 RealType l2 = gba2.getL();
400 cut = max(cut, RealType(2.5) * sqrt(RealType(2.0)) * max(d2, l2));
401 } else if (lja2.isLennardJones()) {
402 cut = max(cut, RealType(2.5) * lja2.getSigma());
403 }
404
405 return cut;
406 }
407} // namespace OpenMD
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.
@ VANDERWAALS_FAMILY
Long-range dispersion and short-range pauli repulsion.