OpenMD 3.2
Molecular Dynamics in the Open
Loading...
Searching...
No Matches
MoleculeStamp.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 "types/MoleculeStamp.hpp"
48
49#include <algorithm>
50#include <functional>
51#include <iostream>
52#include <sstream>
53#include <tuple>
54
55#include "utils/MemoryUtils.hpp"
56
57using namespace std;
58
59namespace OpenMD {
60
61 template<class ContainerType>
62 bool hasDuplicateElement(const ContainerType& cont) {
63 ContainerType tmp = cont;
64 std::sort(tmp.begin(), tmp.end());
65 tmp.erase(std::unique(tmp.begin(), tmp.end()), tmp.end());
66 return tmp.size() != cont.size();
67 }
68
69 MoleculeStamp::MoleculeStamp() : nintegrable_(0), ident_ {-1} {
70 DefineParameter(Name, "name");
71 DefineOptionalParameter(ConstrainTotalCharge, "constrainTotalCharge");
72
73 deprecatedKeywords_.insert("nAtoms");
74 deprecatedKeywords_.insert("nBonds");
75 deprecatedKeywords_.insert("nBends");
76 deprecatedKeywords_.insert("nTorsions");
77 deprecatedKeywords_.insert("nInversions");
78 deprecatedKeywords_.insert("nRigidBodies");
79 deprecatedKeywords_.insert("nCutoffGroups");
80 }
81
82 MoleculeStamp::~MoleculeStamp() {
83 Utils::deletePointers(atomStamps_);
84 Utils::deletePointers(bondStamps_);
85 Utils::deletePointers(bendStamps_);
86 Utils::deletePointers(torsionStamps_);
87 Utils::deletePointers(inversionStamps_);
88 Utils::deletePointers(rigidBodyStamps_);
89 Utils::deletePointers(cutoffGroupStamps_);
90 Utils::deletePointers(fragmentStamps_);
91 Utils::deletePointers(constraintStamps_);
92 }
93
94 bool MoleculeStamp::addAtomStamp(AtomStamp* atom) {
95 bool ret = addIndexSensitiveStamp(atomStamps_, atom);
96 if (!ret) {
97 std::ostringstream oss;
98 oss << "Error in Molecule " << getName()
99 << ": multiple atoms have the same indices" << atom->getIndex()
100 << "\n";
101 throw OpenMDException(oss.str());
102 }
103 return ret;
104 }
105
106 bool MoleculeStamp::addBondStamp(BondStamp* bond) {
107 bondStamps_.push_back(bond);
108 return true;
109 }
110
111 bool MoleculeStamp::addBendStamp(BendStamp* bend) {
112 bendStamps_.push_back(bend);
113 return true;
114 }
115
116 bool MoleculeStamp::addTorsionStamp(TorsionStamp* torsion) {
117 torsionStamps_.push_back(torsion);
118 return true;
119 }
120
121 bool MoleculeStamp::addInversionStamp(InversionStamp* inversion) {
122 inversionStamps_.push_back(inversion);
123 return true;
124 }
125
126 bool MoleculeStamp::addRigidBodyStamp(RigidBodyStamp* rigidbody) {
127 bool ret = addIndexSensitiveStamp(rigidBodyStamps_, rigidbody);
128 if (!ret) {
129 std::ostringstream oss;
130 oss << "Error in Molecule " << getName()
131 << ": multiple rigidbodies have the same indices: "
132 << rigidbody->getIndex() << "\n";
133 throw OpenMDException(oss.str());
134 }
135 return ret;
136 }
137
138 bool MoleculeStamp::addCutoffGroupStamp(CutoffGroupStamp* cutoffgroup) {
139 cutoffGroupStamps_.push_back(cutoffgroup);
140 return true;
141 }
142
143 bool MoleculeStamp::addFragmentStamp(FragmentStamp* fragment) {
144 return addIndexSensitiveStamp(fragmentStamps_, fragment);
145 }
146
147 bool MoleculeStamp::addConstraintStamp(ConstraintStamp* constraint) {
148 constraintStamps_.push_back(constraint);
149 return true;
150 }
151
152 void MoleculeStamp::validate() {
153 DataHolder::validate();
154
155 atom2Rigidbody.resize(getNAtoms());
156
157 // A negative number means the atom is a free atom, and does not
158 // belong to rigidbody. Every element in atom2Rigidbody has unique
159 // negative number at the very beginning
160
161 for (unsigned int i = 0; i < atom2Rigidbody.size(); ++i) {
162 atom2Rigidbody[i] = -1 - int(i);
163 }
164 for (std::size_t i = 0; i < getNRigidBodies(); ++i) {
165 RigidBodyStamp* rbStamp = getRigidBodyStamp(i);
166 std::vector<int> members = rbStamp->getMembers();
167 for (std::vector<int>::iterator j = members.begin(); j != members.end();
168 ++j) {
169 atom2Rigidbody[*j] = i;
170 }
171 }
172
173 checkAtoms();
174 checkBonds();
175 fillBondInfo();
176 checkBends();
177 checkTorsions();
178 checkInversions();
179 checkRigidBodies();
180 checkCutoffGroups();
181 checkFragments();
182 checkConstraints();
183
184 size_t nrigidAtoms = 0;
185 for (std::size_t i = 0; i < getNRigidBodies(); ++i) {
186 RigidBodyStamp* rbStamp = getRigidBodyStamp(i);
187 nrigidAtoms += rbStamp->getNMembers();
188 }
189 nintegrable_ = getNAtoms() + getNRigidBodies() - nrigidAtoms;
190 }
191
192 void MoleculeStamp::checkAtoms() {
193 std::vector<AtomStamp*>::iterator ai = std::find(
194 atomStamps_.begin(), atomStamps_.end(), static_cast<AtomStamp*>(NULL));
195 if (ai != atomStamps_.end()) {
196 std::ostringstream oss;
197 oss << "Error in Molecule " << getName() << ": atom["
198 << ai - atomStamps_.begin() << "] is missing\n";
199 throw OpenMDException(oss.str());
200 }
201 }
202
203 void MoleculeStamp::checkBonds() {
204 std::ostringstream oss;
205 // make sure index is not out of range
206 int natoms = static_cast<int>(getNAtoms());
207 for (std::size_t i = 0; i < getNBonds(); ++i) {
208 BondStamp* bondStamp = getBondStamp(i);
209 if (bondStamp->getA() > natoms - 1 || bondStamp->getA() < 0 ||
210 bondStamp->getB() > natoms - 1 || bondStamp->getB() < 0 ||
211 bondStamp->getA() == bondStamp->getB()) {
212 oss << "Error in Molecule " << getName() << ": bond("
213 << bondStamp->getA() << ", " << bondStamp->getB()
214 << ") is invalid\n";
215 throw OpenMDException(oss.str());
216 }
217 }
218
219 // make sure bonds are unique
220 std::set<std::pair<int, int>> allBonds;
221 for (std::size_t i = 0; i < getNBonds(); ++i) {
222 BondStamp* bondStamp = getBondStamp(i);
223 std::pair<int, int> bondPair(bondStamp->getA(), bondStamp->getB());
224 // make sure bondPair.first is always less than or equal to
225 // bondPair.third
226 if (bondPair.first > bondPair.second) {
227 std::swap(bondPair.first, bondPair.second);
228 }
229
230 std::set<std::pair<int, int>>::iterator iter = allBonds.find(bondPair);
231 if (iter != allBonds.end()) {
232 oss << "Error in Molecule " << getName() << ": "
233 << "bond(" << iter->first << ", " << iter->second
234 << ") appears multiple times\n";
235 throw OpenMDException(oss.str());
236 } else {
237 allBonds.insert(bondPair);
238 }
239 }
240
241 // make sure atoms belong to same rigidbody do not bond to each other
242 for (std::size_t i = 0; i < getNBonds(); ++i) {
243 BondStamp* bondStamp = getBondStamp(i);
244 if (atom2Rigidbody[bondStamp->getA()] ==
245 atom2Rigidbody[bondStamp->getB()]) {
246 oss << "Error in Molecule " << getName() << ": "
247 << "bond(" << bondStamp->getA() << ", " << bondStamp->getB()
248 << ") belong to same rigidbody "
249 << atom2Rigidbody[bondStamp->getA()] << "\n";
250 throw OpenMDException(oss.str());
251 }
252 }
253 }
254
255 void MoleculeStamp::checkBends() {
256 std::ostringstream oss;
257 for (std::size_t i = 0; i < getNBends(); ++i) {
258 BendStamp* bendStamp = getBendStamp(i);
259 std::vector<int> bendAtoms = bendStamp->getMembers();
260 std::vector<int>::iterator j =
261 std::find_if(bendAtoms.begin(), bendAtoms.end(),
262 std::bind(std::greater<int>(), std::placeholders::_1,
263 getNAtoms() - 1));
264 std::vector<int>::iterator k =
265 std::find_if(bendAtoms.begin(), bendAtoms.end(),
266 std::bind(std::less<int>(), std::placeholders::_1, 0));
267
268 if (j != bendAtoms.end() || k != bendAtoms.end()) {
269 oss << "Error in Molecule " << getName() << " : atoms of bend"
270 << containerToString(bendAtoms) << " have invalid indices\n";
271 throw OpenMDException(oss.str());
272 }
273
274 if (hasDuplicateElement(bendAtoms)) {
275 oss << "Error in Molecule " << getName() << " : atoms of bend"
276 << containerToString(bendAtoms) << " have duplicated indices\n";
277 throw OpenMDException(oss.str());
278 }
279
280 if (bendAtoms.size() == 2) {
281 if (!bendStamp->haveGhostVectorSource()) {
282 oss << "Error in Molecule " << getName()
283 << ": ghostVectorSouce is missing\n";
284 throw OpenMDException(oss.str());
285 } else {
286 std::size_t ghostIndex = bendStamp->getGhostVectorSource();
287 if (ghostIndex < getNAtoms()) {
288 if (std::find(bendAtoms.begin(), bendAtoms.end(), ghostIndex) ==
289 bendAtoms.end()) {
290 oss << "Error in Molecule " << getName() << ": ghostVectorSouce "
291 << ghostIndex << "is invalid\n";
292 throw OpenMDException(oss.str());
293 }
294 if (!getAtomStamp(ghostIndex)->haveOrientation()) {
295 oss << "Error in Molecule " << getName()
296 << ": ghost atom must be a directional atom\n";
297 throw OpenMDException(oss.str());
298 }
299 } else {
300 oss << "Error in Molecule " << getName() << ": ghostVectorSource "
301 << ghostIndex << " is invalid\n";
302 throw OpenMDException(oss.str());
303 }
304 }
305 } else if (bendAtoms.size() == 3 && bendStamp->haveGhostVectorSource()) {
306 oss << "Error in Molecule " << getName()
307 << ": normal bend should not have ghostVectorSouce\n";
308 throw OpenMDException(oss.str());
309 }
310 }
311
312 for (std::size_t i = 0; i < getNBends(); ++i) {
313 BendStamp* bendStamp = getBendStamp(i);
314 std::vector<int> bendAtoms = bendStamp->getMembers();
315 std::vector<int> rigidSet(getNRigidBodies(), 0);
316 std::vector<int>::iterator j;
317 for (j = bendAtoms.begin(); j != bendAtoms.end(); ++j) {
318 int rigidbodyIndex = atom2Rigidbody[*j];
319 if (rigidbodyIndex >= 0) {
320 ++rigidSet[rigidbodyIndex];
321 if (rigidSet[rigidbodyIndex] > 2) {
322 oss << "Error in Molecule " << getName() << ": bend"
323 << containerToString(bendAtoms)
324 << "has three atoms on the same rigid body\n";
325 throw OpenMDException(oss.str());
326 }
327 }
328 }
329 }
330
331 std::set<std::tuple<int, int, int>> allBends;
332 std::set<std::tuple<int, int, int>>::iterator iter;
333
334 for (std::size_t i = 0; i < getNBends(); ++i) {
335 BendStamp* bendStamp = getBendStamp(i);
336 std::vector<int> bend = bendStamp->getMembers();
337 if (bend.size() == 2) {
338 // in case we have two ghost bend. For example,
339 // bend {
340 // members (0, 1);
341 // ghostVectorSource = 0;
342 // }
343 // and
344 // bend {
345 // members (0, 1);
346 // ghostVectorSource = 0;
347 // }
348 // In order to distinguish them. we expand them to Tuple3.
349 // the first one is expanded to (0, 0, 1) while the second one
350 // is expaned to (0, 1, 1)
351 int ghostIndex = bendStamp->getGhostVectorSource();
352 std::vector<int>::iterator j =
353 std::find(bend.begin(), bend.end(), ghostIndex);
354 if (j != bend.end()) { bend.insert(j, ghostIndex); }
355 }
356
357 std::tuple<int, int, int> bendTuple {bend[0], bend[1], bend[2]};
358 auto& [first, second, third] = bendTuple;
359
360 // make sure the first element of bendTuple is always less than or equal
361 // to the third element of bendTuple
362 if (first > third) { std::swap(first, third); }
363
364 iter = allBends.find(bendTuple);
365 if (iter != allBends.end()) {
366 oss << "Error in Molecule " << getName() << ": "
367 << "Bend" << containerToString(bend) << " appears multiple times\n";
368 throw OpenMDException(oss.str());
369 } else {
370 allBends.insert(bendTuple);
371 }
372 }
373
374 for (std::size_t i = 0; i < getNBonds(); ++i) {
375 BondStamp* bondStamp = getBondStamp(i);
376 int a = bondStamp->getA();
377 int b = bondStamp->getB();
378
379 AtomStamp* atomA = getAtomStamp(a);
380 AtomStamp* atomB = getAtomStamp(b);
381
382 // find bend c--a--b
383 AtomStamp::AtomIter ai;
384 for (int c = atomA->getFirstBondedAtom(ai); c != -1;
385 c = atomA->getNextBondedAtom(ai)) {
386 if (b == c) continue;
387
388 std::tuple<int, int, int> newBend {c, a, b};
389 auto [first, second, third] = newBend;
390 if (first > third) { std::swap(first, third); }
391
392 if (allBends.find(newBend) == allBends.end()) {
393 allBends.insert(newBend);
394 BendStamp* newBendStamp = new BendStamp();
395 newBendStamp->setMembers(newBend);
396 addBendStamp(newBendStamp);
397 }
398 }
399
400 // find bend a--b--c
401 for (int c = atomB->getFirstBondedAtom(ai); c != -1;
402 c = atomB->getNextBondedAtom(ai)) {
403 if (a == c) continue;
404
405 std::tuple<int, int, int> newBend {a, b, c};
406 auto [first, second, third] = newBend;
407 if (first > third) { std::swap(first, third); }
408
409 if (allBends.find(newBend) == allBends.end()) {
410 allBends.insert(newBend);
411 BendStamp* newBendStamp = new BendStamp();
412 newBendStamp->setMembers(newBend);
413 addBendStamp(newBendStamp);
414 }
415 }
416 }
417 }
418
419 void MoleculeStamp::checkTorsions() {
420 std::ostringstream oss;
421 for (std::size_t i = 0; i < getNTorsions(); ++i) {
422 TorsionStamp* torsionStamp = getTorsionStamp(i);
423 std::vector<int> torsionAtoms = torsionStamp->getMembers();
424 std::vector<int>::iterator j =
425 std::find_if(torsionAtoms.begin(), torsionAtoms.end(),
426 std::bind(std::greater<int>(), std::placeholders::_1,
427 getNAtoms() - 1));
428 std::vector<int>::iterator k =
429 std::find_if(torsionAtoms.begin(), torsionAtoms.end(),
430 std::bind(std::less<int>(), std::placeholders::_1, 0));
431
432 if (j != torsionAtoms.end() || k != torsionAtoms.end()) {
433 oss << "Error in Molecule " << getName() << ": atoms of torsion"
434 << containerToString(torsionAtoms) << " have invalid indices\n";
435 throw OpenMDException(oss.str());
436 }
437 if (hasDuplicateElement(torsionAtoms)) {
438 oss << "Error in Molecule " << getName() << " : atoms of torsion"
439 << containerToString(torsionAtoms) << " have duplicated indices\n";
440 throw OpenMDException(oss.str());
441 }
442 }
443
444 for (std::size_t i = 0; i < getNTorsions(); ++i) {
445 TorsionStamp* torsionStamp = getTorsionStamp(i);
446 std::vector<int> torsionAtoms = torsionStamp->getMembers();
447 std::vector<int> rigidSet(getNRigidBodies(), 0);
448 std::vector<int>::iterator j;
449 for (j = torsionAtoms.begin(); j != torsionAtoms.end(); ++j) {
450 int rigidbodyIndex = atom2Rigidbody[*j];
451 if (rigidbodyIndex >= 0) {
452 ++rigidSet[rigidbodyIndex];
453 if (rigidSet[rigidbodyIndex] > 3) {
454 oss << "Error in Molecule " << getName() << ": torsion"
455 << containerToString(torsionAtoms)
456 << "has four atoms on the same rigid body\n";
457 throw OpenMDException(oss.str());
458 }
459 }
460 }
461 }
462
463 std::set<std::tuple<int, int, int, int>> allTorsions;
464 std::set<std::tuple<int, int, int, int>>::iterator iter;
465
466 for (std::size_t i = 0; i < getNTorsions(); ++i) {
467 TorsionStamp* torsionStamp = getTorsionStamp(i);
468 std::vector<int> torsion = torsionStamp->getMembers();
469 if (torsion.size() == 3) {
470 int ghostIndex = torsionStamp->getGhostVectorSource();
471 std::vector<int>::iterator j =
472 std::find(torsion.begin(), torsion.end(), ghostIndex);
473 if (j != torsion.end()) { torsion.insert(j, ghostIndex); }
474 }
475
476 std::tuple<int, int, int, int> torsionTuple {torsion[0], torsion[1],
477 torsion[2], torsion[3]};
478 auto& [first, second, third, fourth] = torsionTuple;
479
480 if (first > fourth) {
481 std::swap(first, fourth);
482 std::swap(second, third);
483 }
484
485 iter = allTorsions.find(torsionTuple);
486 if (iter == allTorsions.end()) {
487 allTorsions.insert(torsionTuple);
488 } else {
489 oss << "Error in Molecule " << getName() << ": "
490 << "Torsion" << containerToString(torsion)
491 << " appears multiple times\n";
492 throw OpenMDException(oss.str());
493 }
494 }
495
496 for (std::size_t i = 0; i < getNBonds(); ++i) {
497 BondStamp* bondStamp = getBondStamp(i);
498 int b = bondStamp->getA();
499 int c = bondStamp->getB();
500
501 AtomStamp* atomB = getAtomStamp(b);
502 AtomStamp* atomC = getAtomStamp(c);
503
504 AtomStamp::AtomIter ai2;
505 AtomStamp::AtomIter ai3;
506
507 for (int a = atomB->getFirstBondedAtom(ai2); a != -1;
508 a = atomB->getNextBondedAtom(ai2)) {
509 if (a == c) continue;
510
511 for (int d = atomC->getFirstBondedAtom(ai3); d != -1;
512 d = atomC->getNextBondedAtom(ai3)) {
513 if (d == b) continue;
514
515 std::tuple<int, int, int, int> newTorsion {a, b, c, d};
516 auto [first, second, third, fourth] = newTorsion;
517
518 // make sure the first element is always less than or equal
519 // to the fourth element in IntTuple4
520 if (first > fourth) {
521 std::swap(first, fourth);
522 std::swap(second, third);
523 }
524
525 if (allTorsions.find(newTorsion) == allTorsions.end()) {
526 allTorsions.insert(newTorsion);
527 TorsionStamp* newTorsionStamp = new TorsionStamp();
528 newTorsionStamp->setMembers(newTorsion);
529 addTorsionStamp(newTorsionStamp);
530 }
531 }
532 }
533 }
534 }
535
536 void MoleculeStamp::checkInversions() {
537 std::ostringstream oss;
538
539 // first we automatically find the other three atoms that
540 // are satellites of an inversion center:
541
542 for (std::size_t i = 0; i < getNInversions(); ++i) {
543 InversionStamp* inversionStamp = getInversionStamp(i);
544 int center = inversionStamp->getCenter();
545 std::vector<int> satellites;
546
547 // Some inversions come pre-programmed with the satellites. If
548 // so, don't add the satellites as they are already there.
549
550 if (inversionStamp->getNSatellites() != 3) {
551 for (std::size_t j = 0; j < getNBonds(); ++j) {
552 BondStamp* bondStamp = getBondStamp(j);
553 int a = bondStamp->getA();
554 int b = bondStamp->getB();
555
556 if (a == center) { satellites.push_back(b); }
557 if (b == center) { satellites.push_back(a); }
558 }
559
560 if (satellites.size() == 3) {
561 std::sort(satellites.begin(), satellites.end());
562 inversionStamp->setSatellites(satellites);
563 } else {
564 oss << "Error in Molecule " << getName() << ": found wrong number"
565 << " of bonds for inversion center " << center;
566 throw OpenMDException(oss.str());
567 }
568 }
569 }
570
571 // then we do some sanity checking on the inversions:
572
573 for (std::size_t i = 0; i < getNInversions(); ++i) {
574 InversionStamp* inversionStamp = getInversionStamp(i);
575
576 std::vector<int> inversionAtoms = inversionStamp->getSatellites();
577 // add the central atom to the beginning of the list:
578 inversionAtoms.insert(inversionAtoms.begin(),
579 inversionStamp->getCenter());
580
581 std::vector<int>::iterator j =
582 std::find_if(inversionAtoms.begin(), inversionAtoms.end(),
583 std::bind(std::greater<int>(), std::placeholders::_1,
584 getNAtoms() - 1));
585 std::vector<int>::iterator k =
586 std::find_if(inversionAtoms.begin(), inversionAtoms.end(),
587 std::bind(std::less<int>(), std::placeholders::_1, 0));
588
589 if (j != inversionAtoms.end() || k != inversionAtoms.end()) {
590 oss << "Error in Molecule " << getName() << ": atoms of inversion"
591 << containerToString(inversionAtoms) << " have invalid indices\n";
592 throw OpenMDException(oss.str());
593 }
594
595 if (hasDuplicateElement(inversionAtoms)) {
596 oss << "Error in Molecule " << getName() << " : atoms of inversion"
597 << containerToString(inversionAtoms)
598 << " have duplicated indices\n";
599 throw OpenMDException(oss.str());
600 }
601 }
602
603 for (std::size_t i = 0; i < getNInversions(); ++i) {
604 InversionStamp* inversionStamp = getInversionStamp(i);
605 std::vector<int> inversionAtoms = inversionStamp->getSatellites();
606 inversionAtoms.push_back(inversionStamp->getCenter());
607 std::vector<int> rigidSet(getNRigidBodies(), 0);
608 std::vector<int>::iterator j;
609 for (j = inversionAtoms.begin(); j != inversionAtoms.end(); ++j) {
610 int rigidbodyIndex = atom2Rigidbody[*j];
611 if (rigidbodyIndex >= 0) {
612 ++rigidSet[rigidbodyIndex];
613 if (rigidSet[rigidbodyIndex] > 3) {
614 oss << "Error in Molecule " << getName()
615 << ": inversion centered on atom "
616 << inversionStamp->getCenter()
617 << " has four atoms that belong to same rigidbody "
618 << rigidbodyIndex << "\n";
619 throw OpenMDException(oss.str());
620 }
621 }
622 }
623 }
624
625 std::set<std::tuple<int, int, int, int>> allInversions;
626 std::set<std::tuple<int, int, int, int>>::iterator iter;
627 for (std::size_t i = 0; i < getNInversions(); ++i) {
628 InversionStamp* inversionStamp = getInversionStamp(i);
629 int cent = inversionStamp->getCenter();
630 std::vector<int> inversion = inversionStamp->getSatellites();
631
632 std::tuple<int, int, int, int> inversionTuple {
633 cent, inversion[0], inversion[1], inversion[2]};
634 auto& [first, second, third, fourth] = inversionTuple;
635
636 // In OpenMD, the Central atom in an inversion comes first, and
637 // has a special position. The other three atoms can come in
638 // random order, and should be sorted in increasing numerical
639 // order to check for duplicates. This requires three pairwise
640 // swaps:
641 if (third > fourth) std::swap(third, fourth);
642 if (second > third) std::swap(second, third);
643 if (third > fourth) std::swap(third, fourth);
644
645 iter = allInversions.find(inversionTuple);
646 if (iter == allInversions.end()) {
647 allInversions.insert(inversionTuple);
648 } else {
649 oss << "Error in Molecule " << getName() << ": "
650 << "Inversion" << containerToString(inversion)
651 << " appears multiple times\n";
652 throw OpenMDException(oss.str());
653 }
654 }
655
656 // Next we automatically find the inversion centers that weren't
657 // explicitly created. An inversion center is any atom that has
658 // exactly three bonds to it. Not all inversion centers have
659 // potentials associated with them.
660
661 for (std::size_t i = 0; i < getNAtoms(); ++i) {
662 AtomStamp* ai = getAtomStamp(i);
663 if (ai->getCoordination() == 3) {
664 AtomStamp::AtomIter ai2;
665 std::vector<int> satellites;
666 for (int a = ai->getFirstBondedAtom(ai2); a != -1;
667 a = ai->getNextBondedAtom(ai2)) {
668 satellites.push_back(a);
669 }
670 if (satellites.size() == 3) {
671 int cent = ai->getIndex();
672 std::sort(satellites.begin(), satellites.end());
673
674 std::tuple<int, int, int, int> newInversion {
675 cent, satellites[0], satellites[1], satellites[2]};
676 auto [first, second, third, fourth] = newInversion;
677
678 if (third > fourth) std::swap(third, fourth);
679 if (second > third) std::swap(second, third);
680 if (third > fourth) std::swap(third, fourth);
681
682 if (allInversions.find(newInversion) == allInversions.end()) {
683 allInversions.insert(newInversion);
684 InversionStamp* newInversionStamp = new InversionStamp();
685 newInversionStamp->setCenter(cent);
686 newInversionStamp->setSatellites(satellites);
687 addInversionStamp(newInversionStamp);
688 }
689
690 } else {
691 oss << "Error in Molecule " << getName() << ": found bond mismatch"
692 << " when detecting inversion centers.";
693 throw OpenMDException(oss.str());
694 }
695 }
696 }
697 }
698
699 void MoleculeStamp::checkRigidBodies() {
700 std::ostringstream oss;
701 std::vector<RigidBodyStamp*>::iterator ri =
702 std::find(rigidBodyStamps_.begin(), rigidBodyStamps_.end(),
703 static_cast<RigidBodyStamp*>(NULL));
704 if (ri != rigidBodyStamps_.end()) {
705 oss << "Error in Molecule " << getName() << ":rigidBody["
706 << ri - rigidBodyStamps_.begin() << "] is missing\n";
707 throw OpenMDException(oss.str());
708 }
709
710 for (std::size_t i = 0; i < getNRigidBodies(); ++i) {
711 RigidBodyStamp* rbStamp = getRigidBodyStamp(i);
712 std::vector<int> rigidAtoms = rbStamp->getMembers();
713 std::vector<int>::iterator j =
714 std::find_if(rigidAtoms.begin(), rigidAtoms.end(),
715 std::bind(std::greater<int>(), std::placeholders::_1,
716 getNAtoms() - 1));
717 if (j != rigidAtoms.end()) {
718 oss << "Error in Molecule " << getName();
719 throw OpenMDException(oss.str());
720 }
721 }
722 }
723
724 void MoleculeStamp::checkCutoffGroups() {
725 std::vector<AtomStamp*>::iterator ai;
726 std::vector<int>::iterator fai;
727
728 // add all atoms into freeAtoms_ set
729 for (ai = atomStamps_.begin(); ai != atomStamps_.end(); ++ai) {
730 freeAtoms_.push_back((*ai)->getIndex());
731 }
732
733 for (std::size_t i = 0; i < getNCutoffGroups(); ++i) {
734 CutoffGroupStamp* cutoffGroupStamp = getCutoffGroupStamp(i);
735 std::vector<int> cutoffGroupAtoms = cutoffGroupStamp->getMembers();
736 std::vector<int>::iterator j =
737 std::find_if(cutoffGroupAtoms.begin(), cutoffGroupAtoms.end(),
738 std::bind(std::greater<int>(), std::placeholders::_1,
739 getNAtoms() - 1));
740 if (j != cutoffGroupAtoms.end()) {
741 std::ostringstream oss;
742 oss << "Error in Molecule " << getName() << ": cutoffGroup"
743 << " is out of range\n";
744 throw OpenMDException(oss.str());
745 }
746
747 for (fai = cutoffGroupAtoms.begin(); fai != cutoffGroupAtoms.end();
748 ++fai) {
749 // erase the atoms belonging to cutoff groups from freeAtoms_ vector
750 freeAtoms_.erase(
751 std::remove(freeAtoms_.begin(), freeAtoms_.end(), (*fai)),
752 freeAtoms_.end());
753 }
754 }
755 }
756
757 void MoleculeStamp::checkFragments() {
758 std::vector<FragmentStamp*>::iterator fi =
759 std::find(fragmentStamps_.begin(), fragmentStamps_.end(),
760 static_cast<FragmentStamp*>(NULL));
761 if (fi != fragmentStamps_.end()) {
762 std::ostringstream oss;
763 oss << "Error in Molecule " << getName() << ":fragment["
764 << fi - fragmentStamps_.begin() << "] is missing\n";
765 throw OpenMDException(oss.str());
766 }
767 }
768
769 void MoleculeStamp::checkConstraints() {
770 std::ostringstream oss;
771 // make sure index is not out of range
772 int natoms = getNAtoms();
773 for (std::size_t i = 0; i < getNConstraints(); ++i) {
774 ConstraintStamp* constraintStamp = getConstraintStamp(i);
775 if (constraintStamp->getA() > natoms - 1 || constraintStamp->getA() < 0 ||
776 constraintStamp->getB() > natoms - 1 || constraintStamp->getB() < 0 ||
777 constraintStamp->getA() == constraintStamp->getB()) {
778 oss << "Error in Molecule " << getName() << ": constraint("
779 << constraintStamp->getA() << ", " << constraintStamp->getB()
780 << ") is invalid\n";
781 throw OpenMDException(oss.str());
782 }
783 }
784
785 // make sure constraints are unique
786 std::set<std::pair<int, int>> allConstraints;
787 for (std::size_t i = 0; i < getNConstraints(); ++i) {
788 ConstraintStamp* constraintStamp = getConstraintStamp(i);
789 std::pair<int, int> constraintPair(constraintStamp->getA(),
790 constraintStamp->getB());
791 // make sure constraintPair.first is always less than or equal to
792 // constraintPair.third
793 if (constraintPair.first > constraintPair.second) {
794 std::swap(constraintPair.first, constraintPair.second);
795 }
796
797 std::set<std::pair<int, int>>::iterator iter =
798 allConstraints.find(constraintPair);
799 if (iter != allConstraints.end()) {
800 oss << "Error in Molecule " << getName() << ": "
801 << "constraint(" << iter->first << ", " << iter->second
802 << ") appears multiple times\n";
803 throw OpenMDException(oss.str());
804 } else {
805 allConstraints.insert(constraintPair);
806 }
807 }
808
809 // make sure atoms belong to same rigidbody are not constrained to
810 // each other
811 for (std::size_t i = 0; i < getNConstraints(); ++i) {
812 ConstraintStamp* constraintStamp = getConstraintStamp(i);
813 if (atom2Rigidbody[constraintStamp->getA()] ==
814 atom2Rigidbody[constraintStamp->getB()]) {
815 oss << "Error in Molecule " << getName() << ": "
816 << "constraint(" << constraintStamp->getA() << ", "
817 << constraintStamp->getB() << ") belong to same rigidbody "
818 << atom2Rigidbody[constraintStamp->getA()] << "\n";
819 throw OpenMDException(oss.str());
820 }
821 }
822 }
823
824 void MoleculeStamp::fillBondInfo() {
825 for (std::size_t i = 0; i < getNBonds(); ++i) {
826 BondStamp* bondStamp = getBondStamp(i);
827 int a = bondStamp->getA();
828 int b = bondStamp->getB();
829 AtomStamp* atomA = getAtomStamp(a);
830 AtomStamp* atomB = getAtomStamp(b);
831 atomA->addBond(i);
832 atomA->addBondedAtom(b);
833 atomB->addBond(i);
834 atomB->addBondedAtom(a);
835 }
836 }
837
838 // Function Name: isBondInSameRigidBody
839 // Returns true is both atoms of the bond belong to the same rigid
840 // body, otherwise return false
841 bool MoleculeStamp::isBondInSameRigidBody(BondStamp* bond) {
842 int rbA;
843 int rbB;
844 int consAtomA;
845 int consAtomB;
846
847 if (!isAtomInRigidBody(bond->getA(), rbA, consAtomA)) return false;
848
849 if (!isAtomInRigidBody(bond->getB(), rbB, consAtomB)) return false;
850
851 if (rbB == rbA)
852 return true;
853 else
854 return false;
855 }
856
857 // Function Name: isAtomInRigidBody
858 // Returns false if atom does not belong to a rigid body, otherwise
859 // returns true
860 bool MoleculeStamp::isAtomInRigidBody(int atomIndex) {
861 return atom2Rigidbody[atomIndex] >= 0;
862 }
863
864 // Function Name: isAtomInRigidBody
865 // Returns false if atom does not belong to a rigid body otherwise
866 // returns true and sets whichRigidBody and consAtomIndex
867 // atomIndex : the index of atom in component
868 // whichRigidBody: the index of the rigidbody in the component
869 // consAtomIndex: the position the joint atom appears in the rigidbody's
870 // definition
871 bool MoleculeStamp::isAtomInRigidBody(int atomIndex, int& whichRigidBody,
872 int& consAtomIndex) {
873 whichRigidBody = -1;
874 consAtomIndex = -1;
875
876 if (atom2Rigidbody[atomIndex] >= 0) {
877 whichRigidBody = atom2Rigidbody[atomIndex];
878 RigidBodyStamp* rbStamp = getRigidBodyStamp(whichRigidBody);
879 int numAtom = rbStamp->getNMembers();
880 for (int j = 0; j < numAtom; j++) {
881 if (rbStamp->getMemberAt(j) == atomIndex) {
882 consAtomIndex = j;
883 return true;
884 }
885 }
886 }
887
888 return false;
889 }
890
891 // Returns the position of joint atoms apearing in a rigidbody's definition
892 // For the time being, we will use the most inefficient algorithm,
893 // the complexity is O(N^2). We could improve the
894 // complexity to O(NlogN) by sorting the atom index in rigid body
895 // first
896 std::vector<std::pair<int, int>> MoleculeStamp::getJointAtoms(int rb1,
897 int rb2) {
898 RigidBodyStamp* rbStamp1;
899 RigidBodyStamp* rbStamp2;
900 int natomInRb1;
901 int natomInRb2;
902 int atomIndex1;
903 int atomIndex2;
904 std::vector<std::pair<int, int>> jointAtomIndexPair;
905
906 rbStamp1 = this->getRigidBodyStamp(rb1);
907 natomInRb1 = rbStamp1->getNMembers();
908
909 rbStamp2 = this->getRigidBodyStamp(rb2);
910 natomInRb2 = rbStamp2->getNMembers();
911
912 for (int i = 0; i < natomInRb1; i++) {
913 atomIndex1 = rbStamp1->getMemberAt(i);
914
915 for (int j = 0; j < natomInRb2; j++) {
916 atomIndex2 = rbStamp2->getMemberAt(j);
917
918 if (atomIndex1 == atomIndex2) {
919 jointAtomIndexPair.push_back(std::make_pair(i, j));
920 break;
921 }
922 }
923 }
924
925 return jointAtomIndexPair;
926 }
927} // namespace OpenMD
This basic Periodic Table class was originally taken from the data.cpp file in OpenBabel.