OpenMD 3.2
Molecular Dynamics in the Open
Loading...
Searching...
No Matches
RigidBody.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 */
48
49#include <algorithm>
50#include <cmath>
51
52#include "utils/Constants.hpp"
53#include "utils/simError.h"
54
55namespace OpenMD {
56
57 RigidBody::RigidBody() :
58 StuntDouble(otRigidBody, &Snapshot::rigidbodyData), inertiaTensor_(0.0) {}
59
60 void RigidBody::setPrevA(const RotMat3x3d& a) {
61 ((snapshotMan_->getPrevSnapshot())->*storage_).aMat[localIndex_] = a;
62
63 for (unsigned int i = 0; i < atoms_.size(); ++i) {
64 if (atoms_[i]->isDirectional()) {
65 atoms_[i]->setPrevA(refOrients_[i].transpose() * a);
66 }
67 }
68 }
69
70 void RigidBody::setA(const RotMat3x3d& a) {
71 ((snapshotMan_->getCurrentSnapshot())->*storage_).aMat[localIndex_] = a;
72
73 for (unsigned int i = 0; i < atoms_.size(); ++i) {
74 if (atoms_[i]->isDirectional()) {
75 atoms_[i]->setA(refOrients_[i].transpose() * a);
76 }
77 }
78 }
79
80 void RigidBody::setA(const RotMat3x3d& a, int snapshotNo) {
81 ((snapshotMan_->getSnapshot(snapshotNo))->*storage_).aMat[localIndex_] = a;
82
83 for (unsigned int i = 0; i < atoms_.size(); ++i) {
84 if (atoms_[i]->isDirectional()) {
85 atoms_[i]->setA(refOrients_[i].transpose() * a, snapshotNo);
86 }
87 }
88 }
89
90 Mat3x3d RigidBody::getI() { return inertiaTensor_; }
91
92 std::vector<RealType> RigidBody::getGrad() {
93 std::vector<RealType> grad(6, 0.0);
94 Vector3d force;
95 Vector3d torque;
96 Vector3d myEuler;
97 RealType phi, theta;
98 RealType cphi, sphi, ctheta, stheta;
99 Vector3d ephi;
100 Vector3d etheta;
101 Vector3d epsi;
102
103 force = getFrc();
104 torque = getTrq();
105
106 myEuler = getA().toEulerAngles();
107
108 phi = myEuler[0];
109 theta = myEuler[1];
110
111 cphi = cos(phi);
112 sphi = sin(phi);
113 ctheta = cos(theta);
114 stheta = sin(theta);
115
116 if (fabs(stheta) < 1.0E-9) { stheta = 1.0E-9; }
117
118 ephi[0] = -sphi * ctheta / stheta;
119 ephi[1] = cphi * ctheta / stheta;
120 ephi[2] = 1.0;
121
122 etheta[0] = cphi;
123 etheta[1] = sphi;
124 etheta[2] = 0.0;
125
126 epsi[0] = sphi / stheta;
127 epsi[1] = -cphi / stheta;
128 epsi[2] = 0.0;
129
130 // gradient is equal to -force
131 for (int j = 0; j < 3; j++)
132 grad[j] = -force[j];
133
134 for (int j = 0; j < 3; j++) {
135 grad[3] -= torque[j] * ephi[j];
136 grad[4] -= torque[j] * etheta[j];
137 grad[5] -= torque[j] * epsi[j];
138 }
139
140 return grad;
141 }
142
143 void RigidBody::accept(BaseVisitor* v) { v->visit(this); }
144
145 /**@todo need modification */
147 RealType mtmp;
148 Vector3d refCOM(0.0);
149 mass_ = 0.0;
150 for (std::size_t i = 0; i < atoms_.size(); ++i) {
151 mtmp = atoms_[i]->getMass();
152 mass_ += mtmp;
153 refCOM += refCoords_[i] * mtmp;
154 }
155 refCOM_ = refCOM / mass_;
156
157 // Next, move the origin of the reference coordinate system to the COM:
158 for (std::size_t i = 0; i < atoms_.size(); ++i) {
159 refCoords_[i] -= refCOM_;
160 }
161
162 // Moment of Inertia calculation
163 Mat3x3d Itmp(0.0);
164 for (std::size_t i = 0; i < atoms_.size(); i++) {
165 Mat3x3d IAtom(0.0);
166 mtmp = atoms_[i]->getMass();
167 IAtom -= outProduct(refCoords_[i], refCoords_[i]) * mtmp;
168 RealType r2 = refCoords_[i].lengthSquare();
169 IAtom(0, 0) += mtmp * r2;
170 IAtom(1, 1) += mtmp * r2;
171 IAtom(2, 2) += mtmp * r2;
172 Itmp += IAtom;
173
174 // project the inertial moment of directional atoms into this rigid body
175 if (atoms_[i]->isDirectional()) {
176 Itmp += refOrients_[i].transpose() * atoms_[i]->getI() * refOrients_[i];
177 }
178 }
179
180 // diagonalize
181 Vector3d evals;
182 Mat3x3d::diagonalize(Itmp, evals, sU_);
183
184 // zero out I and then fill the diagonals with the moments of inertia:
185 inertiaTensor_(0, 0) = evals[0];
186 inertiaTensor_(1, 1) = evals[1];
187 inertiaTensor_(2, 2) = evals[2];
188
189 int nLinearAxis = 0;
190 for (int i = 0; i < 3; i++) {
191 if (fabs(evals[i]) < OpenMD::epsilon) {
192 linear_ = true;
193 linearAxis_ = i;
194 ++nLinearAxis;
195 }
196 }
197
198 if (nLinearAxis > 1) {
199 snprintf(
200 painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
201 "RigidBody error.\n"
202 "\tOpenMD found more than one axis in this rigid body with a "
203 "vanishing \n"
204 "\tmoment of inertia. This can happen in one of three ways:\n"
205 "\t 1) Only one atom was specified, or \n"
206 "\t 2) All atoms were specified at the same location, or\n"
207 "\t 3) The programmers did something stupid.\n"
208 "\tIt is silly to use a rigid body to describe this situation. Be "
209 "smarter.\n");
210 painCave.isFatal = 1;
211 simError();
212 }
213 }
214
216 Vector3d afrc;
217 Vector3d atrq;
218 Vector3d apos;
219 Vector3d rpos;
220 Vector3d frc(0.0);
221 Vector3d trq(0.0);
222 Vector3d ef(0.0);
223 Vector3d pos = this->getPos();
224 AtomType* atype;
225 int eCount = 0;
226
227 int sl =
228 ((snapshotMan_->getCurrentSnapshot())->*storage_).getStorageLayout();
229
230 for (unsigned int i = 0; i < atoms_.size(); i++) {
231 atype = atoms_[i]->getAtomType();
232
233 afrc = atoms_[i]->getFrc();
234 apos = atoms_[i]->getPos();
235 rpos = apos - pos;
236
237 frc += afrc;
238
239 trq[0] += rpos[1] * afrc[2] - rpos[2] * afrc[1];
240 trq[1] += rpos[2] * afrc[0] - rpos[0] * afrc[2];
241 trq[2] += rpos[0] * afrc[1] - rpos[1] * afrc[0];
242
243 // If the atom has a torque associated with it, then we also need to
244 // migrate the torques onto the center of mass:
245
246 if (atoms_[i]->isDirectional()) {
247 atrq = atoms_[i]->getTrq();
248 trq += atrq;
249 }
250
251 if ((sl & DataStorage::dslElectricField) && (atype->isElectrostatic())) {
252 ef += atoms_[i]->getElectricField();
253 eCount++;
254 }
255 }
256 addFrc(frc);
257 addTrq(trq);
258
259 if (sl & DataStorage::dslElectricField) {
260 ef /= eCount;
262 }
263 }
264
266 Vector3d afrc;
267 Vector3d atrq;
268 Vector3d apos;
269 Vector3d rpos;
270 Vector3d dfrc;
271 Vector3d frc(0.0);
272 Vector3d trq(0.0);
273 Vector3d ef(0.0);
274 AtomType* atype;
275 int eCount = 0;
276
277 Vector3d pos = this->getPos();
278 Mat3x3d tau_(0.0);
279
280 int sl =
281 ((snapshotMan_->getCurrentSnapshot())->*storage_).getStorageLayout();
282
283 for (unsigned int i = 0; i < atoms_.size(); i++) {
284 atype = atoms_[i]->getAtomType();
285
286 afrc = atoms_[i]->getFrc();
287 apos = atoms_[i]->getPos();
288 rpos = apos - pos;
289
290 frc += afrc;
291
292 trq[0] += rpos[1] * afrc[2] - rpos[2] * afrc[1];
293 trq[1] += rpos[2] * afrc[0] - rpos[0] * afrc[2];
294 trq[2] += rpos[0] * afrc[1] - rpos[1] * afrc[0];
295
296 // If the atom has a torque associated with it, then we also need to
297 // migrate the torques onto the center of mass:
298
299 if (atoms_[i]->isDirectional()) {
300 atrq = atoms_[i]->getTrq();
301 trq += atrq;
302 }
303
304 if ((sl & DataStorage::dslElectricField) && (atype->isElectrostatic())) {
305 ef += atoms_[i]->getElectricField();
306 eCount++;
307 }
308
309 tau_(0, 0) -= rpos[0] * afrc[0];
310 tau_(0, 1) -= rpos[0] * afrc[1];
311 tau_(0, 2) -= rpos[0] * afrc[2];
312 tau_(1, 0) -= rpos[1] * afrc[0];
313 tau_(1, 1) -= rpos[1] * afrc[1];
314 tau_(1, 2) -= rpos[1] * afrc[2];
315 tau_(2, 0) -= rpos[2] * afrc[0];
316 tau_(2, 1) -= rpos[2] * afrc[1];
317 tau_(2, 2) -= rpos[2] * afrc[2];
318 }
319 addFrc(frc);
320 addTrq(trq);
321
322 if (sl & DataStorage::dslElectricField) {
323 ef /= eCount;
325 }
326
327 return tau_;
328 }
329
331 unsigned int i;
332 Vector3d ref;
333 Vector3d apos;
334 DirectionalAtom* dAtom;
335 Vector3d pos = getPos();
336 RotMat3x3d a = getA();
337
338 for (i = 0; i < atoms_.size(); i++) {
339 ref = body2Lab(refCoords_[i]);
340
341 apos = pos + ref;
342
343 atoms_[i]->setPos(apos);
344
345 if (atoms_[i]->isDirectional()) {
346 dAtom = dynamic_cast<DirectionalAtom*>(atoms_[i]);
347 dAtom->setA(refOrients_[i].transpose() * a);
348 }
349 }
350 }
351
352 void RigidBody::updateAtoms(int frame) {
353 unsigned int i;
354 Vector3d ref;
355 Vector3d apos;
356 DirectionalAtom* dAtom;
357 Vector3d pos = getPos(frame);
358 RotMat3x3d a = getA(frame);
359
360 for (i = 0; i < atoms_.size(); i++) {
361 ref = body2Lab(refCoords_[i], frame);
362
363 apos = pos + ref;
364
365 atoms_[i]->setPos(apos, frame);
366
367 if (atoms_[i]->isDirectional()) {
368 dAtom = dynamic_cast<DirectionalAtom*>(atoms_[i]);
369 dAtom->setA(refOrients_[i].transpose() * a, frame);
370 }
371 }
372 }
373
374 void RigidBody::updateAtomVel() {
375 Mat3x3d skewMat;
376
377 Vector3d ji = getJ();
378 Mat3x3d I = getI();
379
380 skewMat(0, 0) = 0;
381 skewMat(0, 1) = ji[2] / I(2, 2);
382 skewMat(0, 2) = -ji[1] / I(1, 1);
383
384 skewMat(1, 0) = -ji[2] / I(2, 2);
385 skewMat(1, 1) = 0;
386 skewMat(1, 2) = ji[0] / I(0, 0);
387
388 skewMat(2, 0) = ji[1] / I(1, 1);
389 skewMat(2, 1) = -ji[0] / I(0, 0);
390 skewMat(2, 2) = 0;
391
392 Mat3x3d mat = (getA() * skewMat).transpose();
393 Vector3d rbVel = getVel();
394
395 Vector3d velRot;
396 for (unsigned int i = 0; i < refCoords_.size(); ++i) {
397 atoms_[i]->setVel(rbVel + mat * refCoords_[i]);
398 }
399 }
400
401 void RigidBody::updateAtomVel(int frame) {
402 Mat3x3d skewMat;
403 ;
404
405 Vector3d ji = getJ(frame);
406 Mat3x3d I = getI();
407
408 skewMat(0, 0) = 0;
409 skewMat(0, 1) = ji[2] / I(2, 2);
410 skewMat(0, 2) = -ji[1] / I(1, 1);
411
412 skewMat(1, 0) = -ji[2] / I(2, 2);
413 skewMat(1, 1) = 0;
414 skewMat(1, 2) = ji[0] / I(0, 0);
415
416 skewMat(2, 0) = ji[1] / I(1, 1);
417 skewMat(2, 1) = -ji[0] / I(0, 0);
418 skewMat(2, 2) = 0;
419
420 Mat3x3d mat = (getA(frame) * skewMat).transpose();
421 Vector3d rbVel = getVel(frame);
422
423 Vector3d velRot;
424 for (unsigned int i = 0; i < refCoords_.size(); ++i) {
425 atoms_[i]->setVel(rbVel + mat * refCoords_[i], frame);
426 }
427 }
428
429 bool RigidBody::getAtomPos(Vector3d& pos, unsigned int index) {
430 if (index < atoms_.size()) {
431 Vector3d ref = body2Lab(refCoords_[index]);
432 pos = getPos() + ref;
433 return true;
434 } else {
435 snprintf(
436 painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
437 "%d is an invalid index. The current rigid body contans %zu atoms.\n",
438 index, atoms_.size());
439 painCave.isFatal = 0;
440 simError();
441 return false;
442 }
443 }
444
445 bool RigidBody::getAtomPos(Vector3d& pos, Atom* atom) {
446 std::vector<Atom*>::iterator i;
447 i = std::find(atoms_.begin(), atoms_.end(), atom);
448 if (i != atoms_.end()) {
449 // RigidBody class makes sure refCoords_ and atoms_ match each other
450 Vector3d ref = body2Lab(refCoords_[i - atoms_.begin()]);
451 pos = getPos() + ref;
452 return true;
453 } else {
454 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
455 "Atom %d does not belong to rigid body %d.\n",
456 atom->getGlobalIndex(), getGlobalIndex());
457 painCave.isFatal = 0;
458 simError();
459 return false;
460 }
461 }
462 bool RigidBody::getAtomVel(Vector3d& vel, unsigned int index) {
463 // velRot = $(A\cdot skew(I^{-1}j))^{T}refCoor$
464
465 if (index < atoms_.size()) {
466 Vector3d velRot;
467 Mat3x3d skewMat;
468 ;
469 Vector3d ref = refCoords_[index];
470 Vector3d ji = getJ();
471 Mat3x3d I = getI();
472
473 skewMat(0, 0) = 0;
474 skewMat(0, 1) = ji[2] / I(2, 2);
475 skewMat(0, 2) = -ji[1] / I(1, 1);
476
477 skewMat(1, 0) = -ji[2] / I(2, 2);
478 skewMat(1, 1) = 0;
479 skewMat(1, 2) = ji[0] / I(0, 0);
480
481 skewMat(2, 0) = ji[1] / I(1, 1);
482 skewMat(2, 1) = -ji[0] / I(0, 0);
483 skewMat(2, 2) = 0;
484
485 velRot = (getA() * skewMat).transpose() * ref;
486
487 vel = getVel() + velRot;
488 return true;
489
490 } else {
491 snprintf(
492 painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
493 "Index %d is an invalid index, the current rigid body contains %zu "
494 "atoms.\n",
495 index, atoms_.size());
496 painCave.isFatal = 0;
497 simError();
498 return false;
499 }
500 }
501
502 bool RigidBody::getAtomVel(Vector3d& vel, Atom* atom) {
503 std::vector<Atom*>::iterator i;
504 i = std::find(atoms_.begin(), atoms_.end(), atom);
505 if (i != atoms_.end()) {
506 return getAtomVel(vel, i - atoms_.begin());
507 } else {
508 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
509 "Atom %d does not belong to rigid body %d.\n",
510 atom->getGlobalIndex(), getGlobalIndex());
511 painCave.isFatal = 0;
512 simError();
513 return false;
514 }
515 }
516
517 bool RigidBody::getAtomRefCoor(Vector3d& coor, unsigned int index) {
518 if (index < atoms_.size()) {
519 coor = refCoords_[index];
520 return true;
521 } else {
522 snprintf(
523 painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
524 "Index %d is an invalid index, the current rigid body contains %zu "
525 "atoms.\n",
526 index, atoms_.size());
527 painCave.isFatal = 0;
528 simError();
529 return false;
530 }
531 }
532
533 bool RigidBody::getAtomRefCoor(Vector3d& coor, Atom* atom) {
534 std::vector<Atom*>::iterator i;
535 i = std::find(atoms_.begin(), atoms_.end(), atom);
536 if (i != atoms_.end()) {
537 // RigidBody class makes sure refCoords_ and atoms_ match each other
538 coor = refCoords_[i - atoms_.begin()];
539 return true;
540 } else {
541 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
542 "Atom %d does not belong to rigid body %d.\n",
543 atom->getGlobalIndex(), getGlobalIndex());
544 painCave.isFatal = 0;
545 simError();
546 return false;
547 }
548 }
549
550 void RigidBody::addAtom(Atom* at, AtomStamp* ats) {
551 Vector3d coords;
552 Vector3d euler;
553
554 atoms_.push_back(at);
555
556 if (!ats->havePosition()) {
557 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
558 "RigidBody error.\n"
559 "\tAtom %s does not have a position specified.\n"
560 "\tThis means RigidBody cannot set up reference coordinates.\n",
561 ats->getType().c_str());
562 painCave.isFatal = 1;
563 simError();
564 }
565
566 coords[0] = ats->getPosX();
567 coords[1] = ats->getPosY();
568 coords[2] = ats->getPosZ();
569
570 refCoords_.push_back(coords);
571
572 RotMat3x3d identMat = RotMat3x3d::identity();
573
574 if (at->isDirectional()) {
575 if (!ats->haveOrientation()) {
576 snprintf(
577 painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
578 "RigidBody error.\n"
579 "\tAtom %s does not have an orientation specified.\n"
580 "\tThis means RigidBody cannot set up reference orientations.\n",
581 ats->getType().c_str());
582 painCave.isFatal = 1;
583 simError();
584 }
585
586 euler[0] = ats->getEulerPhi() * Constants::PI / 180.0;
587 euler[1] = ats->getEulerTheta() * Constants::PI / 180.0;
588 euler[2] = ats->getEulerPsi() * Constants::PI / 180.0;
589
590 RotMat3x3d Atmp(euler);
591 refOrients_.push_back(Atmp);
592
593 } else {
594 refOrients_.push_back(identMat);
595 }
596 }
597
598} // namespace OpenMD
AtomType is what OpenMD looks to for unchanging data about an atom.
Definition AtomType.hpp:69
virtual void setA(const RotMat3x3d &a)
Sets the current rotation matrix of this stuntdouble.
void calcRefCoords()
calculates the reference coordinates
virtual void setPrevA(const RotMat3x3d &a)
Sets the previous rotation matrix of this stuntdouble.
Definition RigidBody.cpp:60
bool getAtomRefCoor(Vector3d &coor, unsigned int index)
Return the reference coordinate of atom which belongs to this rigid body.
virtual void setA(const RotMat3x3d &a)
Sets the current rotation matrix of this stuntdouble.
Definition RigidBody.cpp:70
void updateAtoms()
update the positions of atoms belong to this rigidbody
virtual std::vector< RealType > getGrad()
Returns the gradient of this stuntdouble.
Definition RigidBody.cpp:92
Mat3x3d calcForcesAndTorquesAndVirial()
Converts Atomic forces and torques to total forces and torques and computes the rigid body contributi...
virtual Mat3x3d getI()
Returns the inertia tensor of this stuntdouble.
Definition RigidBody.cpp:90
void calcForcesAndTorques()
Converts Atomic forces and torques to total forces and torques.
virtual void accept(BaseVisitor *v)
bool getAtomPos(Vector3d &pos, unsigned int index)
Return the position of atom which belongs to this rigid body.
bool getAtomVel(Vector3d &vel, unsigned int index)
Return the velocity of atom which belongs to this rigid body.
The Snapshot class is a repository storing dynamic data during a Simulation.
Definition Snapshot.hpp:166
"Don't move, or you're dead! Stand up! Captain, we've got them!"
Vector3d getTrq()
Returns the current torque of this stuntDouble.
RotMat3x3d getA()
Returns the current rotation matrix of this stuntDouble.
Vector3d getVel()
Returns the current velocity of this stuntDouble.
Vector3d getPos()
Returns the current position of this stuntDouble.
Vector3d body2Lab(const Vector3d &v)
Converts a body fixed vector to a lab fixed vector.
void setElectricField(const Vector3d &eField)
Sets the current electric field of this stuntDouble.
void addTrq(const Vector3d &trq)
Adds torque into the current torque of this stuntDouble.
Vector3d getJ()
Returns the current angular momentum of this stuntDouble (body -fixed).
int getGlobalIndex()
Returns the global index of this stuntDouble.
bool isDirectional()
Tests if this stuntDouble is a directional one.
Vector3d getFrc()
Returns the current force of this stuntDouble.
void addFrc(const Vector3d &frc)
Adds force into the current force of this stuntDouble.
This basic Periodic Table class was originally taken from the data.cpp file in OpenBabel.