66#include "antlr4-runtime.h"
71#include "omdParser/OMDLexer.h"
72#include "omdParser/OMDParser.h"
73#include "omdParser/OMDTreeVisitor.hpp"
74#include "omdParser/SimplePreprocessor.hpp"
75#include "types/DirectionalAdapter.hpp"
76#include "types/EAMAdapter.hpp"
77#include "types/FixedChargeAdapter.hpp"
78#include "types/FluctuatingChargeAdapter.hpp"
79#include "types/MultipoleAdapter.hpp"
80#include "types/PolarizableAdapter.hpp"
81#include "types/SuttonChenAdapter.hpp"
82#include "utils/RandNumGen.hpp"
83#include "utils/Revision.hpp"
84#include "utils/Trim.hpp"
85#include "utils/simError.h"
90 class OMDErrorListener :
public antlr4::BaseErrorListener {
93 antlr4::Recognizer *recognizer,
94 antlr4::Token *offendingSymbol,
96 size_t charPositionInLine,
97 const std::string &msg,
100 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
101 "Syntax error at line %zu:%zu %s\n", line, charPositionInLine,
103 painCave.isFatal = 1;
107 std::runtime_error(
"Parse error");
112 Globals* SimCreator::parseFile(std::istream& rawMetaDataStream,
113 const std::string& filename,
int mdFileVersion,
114 int startOfMetaDataBlock) {
115 Globals* simParams = NULL;
118 std::stringstream ppStream;
121 const int primaryNode = 0;
123 if (worldRank == primaryNode) {
124 MPI_Bcast(&mdFileVersion, 1, MPI_INT, primaryNode, MPI_COMM_WORLD);
126 SimplePreprocessor preprocessor;
127 preprocessor.preprocess(rawMetaDataStream, filename,
128 startOfMetaDataBlock, ppStream);
132 streamSize = ppStream.str().size() + 1;
133 MPI_Bcast(&streamSize, 1, MPI_INT, primaryNode, MPI_COMM_WORLD);
134 MPI_Bcast(
static_cast<void*
>(
const_cast<char*
>(ppStream.str().c_str())),
135 streamSize, MPI_CHAR, primaryNode, MPI_COMM_WORLD);
137 MPI_Bcast(&mdFileVersion, 1, MPI_INT, primaryNode, MPI_COMM_WORLD);
140 MPI_Bcast(&streamSize, 1, MPI_INT, primaryNode, MPI_COMM_WORLD);
141 char* buf =
new char[streamSize];
145 MPI_Bcast(buf, streamSize, MPI_CHAR, primaryNode, MPI_COMM_WORLD);
153 antlr4::ANTLRInputStream input(ppStream);
156 OMDLexer lexer(&input);
158 FilenameObserver* observer =
new FilenameObserver();
159 lexer.setObserver(observer);
162 antlr4::CommonTokenStream tokens(&lexer);
165 OMDParser parser(&tokens);
168 OMDErrorListener errorListener;
169 parser.removeErrorListeners();
170 parser.addErrorListener(&errorListener);
173 OMDParser::OmdfileContext* tree = parser.omdfile();
176 if (parser.getNumberOfSyntaxErrors() > 0) {
177 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
178 "Parsing failed with: %lu errors\n",
179 parser.getNumberOfSyntaxErrors());
180 painCave.isFatal = 1;
187 OMDTreeVisitor visitor;
188 Globals* simParams = visitor.walkTree(tree);
189 simParams->setMDfileVersion(mdFileVersion);
192 }
catch (
const std::exception& e) {
193 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
194 "Exception during parsing: %s\n", e.what());
195 painCave.isFatal = 1;
203 bool loadInitCoords) {
204 const int bufferSize = 65535;
205 char buffer[bufferSize];
207 std::string mdRawData;
208 int metaDataBlockStart = -1;
209 int metaDataBlockEnd = -1;
210 streamoff mdOffset {};
211 int mdFileVersion(2);
215 version.assign(
"## Last run using OpenMD version: ");
216 version.append(OPENMD_VERSION_MAJOR);
218 version.append(OPENMD_VERSION_MINOR);
221 std::string rev(revision, strnlen(revision, 40));
222 rev.append(40 - rev.length(),
' ');
224 version.append(
" revision: ");
229 version.append(
"RELEASE");
233 const int primaryNode = 0;
234 if (worldRank == primaryNode) {
237 std::ifstream mdFile_;
238 mdFile_.open(mdFileName.c_str(), ifstream::in | ifstream::binary);
240 if (mdFile_.fail()) {
241 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
242 "SimCreator: Cannot open file: %s\n", mdFileName.c_str());
243 painCave.isFatal = 1;
247 mdFile_.getline(buffer, bufferSize);
249 std::string line = Utils::trimLeftCopy(buffer);
250 std::size_t i = CaseInsensitiveFind(line,
"<OpenMD");
251 if (i == string::npos) {
253 i = CaseInsensitiveFind(line,
"<OOPSE");
256 if (i == string::npos) {
258 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
259 "SimCreator: File: %s is not a valid OpenMD file!\n",
261 painCave.isFatal = 1;
269 std::string fileType = tokenizer.
nextToken();
274 if (fileType ==
"OPENMD") {
276 std::string token(tokenizer.
nextToken());
278 if (token ==
"VERSION") {
285 bool startFound(
false);
286 bool endFound(
false);
288 while (mdFile_.getline(buffer, bufferSize)) {
291 std::string line = Utils::trimLeftCopy(buffer);
292 if (metaDataBlockStart == -1) {
293 std::size_t i = CaseInsensitiveFind(line,
"<MetaData>");
294 if (i != string::npos) {
295 metaDataBlockStart = lineNo;
297 mdOffset = mdFile_.tellg();
300 std::size_t i = CaseInsensitiveFind(line,
"</MetaData>");
301 if (i != string::npos) {
302 metaDataBlockEnd = lineNo;
306 if (startFound && endFound)
break;
309 if (metaDataBlockStart == -1) {
310 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
311 "SimCreator: File: %s did not contain a <MetaData> tag!\n",
313 painCave.isFatal = 1;
316 if (metaDataBlockEnd == -1) {
318 painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
319 "SimCreator: File: %s did not contain a closed MetaData block!\n",
321 painCave.isFatal = 1;
327 mdFile_.seekg(mdOffset);
331 bool foundVersion =
false;
333 for (
int i = 0; i < metaDataBlockEnd - metaDataBlockStart - 1; ++i) {
334 mdFile_.getline(buffer, bufferSize);
335 std::string line = Utils::trimLeftCopy(buffer);
337 CaseInsensitiveFind(line,
"## Last run using OpenMD Version");
338 if (j != string::npos) {
340 mdRawData += version;
347 if (!foundVersion) mdRawData += version +
"\n";
355 std::stringstream rawMetaDataStream(mdRawData);
358 Globals* simParams = parseFile(rawMetaDataStream, mdFileName, mdFileVersion,
359 metaDataBlockStart + 1);
365 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
366 "ForceField Factory can not create %s force field\n",
367 simParams->getForceField().c_str());
368 painCave.isFatal = 1;
372 if (simParams->haveForceFieldFileName()) {
373 ff->setForceFieldFileName(simParams->getForceFieldFileName());
376 std::string forcefieldFileName;
377 forcefieldFileName = ff->getForceFieldFileName();
379 if (simParams->haveForceFieldVariant()) {
383 std::string variant = simParams->getForceFieldVariant();
385 std::string::size_type pos = forcefieldFileName.rfind(
".frc");
386 variant =
"." + variant;
387 if (pos != std::string::npos) {
388 forcefieldFileName.insert(pos, variant);
392 forcefieldFileName.append(variant);
396 ff->parse(forcefieldFileName);
400 info->setRawMetaData(mdRawData);
404 gatherParameters(info, mdFileName);
408 divideMolecules(info);
412 createMolecules(info);
416 computeStorageLayouts(info);
419 int rbsl = info->getRigidBodyStorageLayout();
420 int cgsl = info->getCutoffGroupStorageLayout();
431 setGlobalIndex(info);
439 SimInfo::MoleculeIterator mi;
446 if (loadInitCoords) loadCoordinates(info, mdFileName);
452 void SimCreator::gatherParameters(
SimInfo* info,
const std::string& mdfile) {
458 if (worldRank == 0) {
460 Globals* simParams = info->getSimParams();
461 if (simParams->haveFinalConfig()) {
462 prefix =
getPrefix(simParams->getFinalConfig());
467 info->setFinalConfigFileName(prefix +
".eor");
468 info->setDumpFileName(prefix +
".dump");
469 info->setStatFileName(prefix +
".stat");
470 info->setReportFileName(prefix +
".report");
471 info->setRestFileName(prefix +
".zang");
479 void SimCreator::divideMolecules(
SimInfo* info) {
482 std::vector<int> atomsPerProc;
484 std::vector<int> molToProcMap(nGlobalMols, -1);
487 MPI_Comm_size(MPI_COMM_WORLD, &nProcessors);
489 if (nProcessors > nGlobalMols) {
490 snprintf(painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
491 "nProcessors (%d) > nMol (%d)\n"
492 "\tThe number of processors is larger than\n"
493 "\tthe number of molecules. This will not result in a \n"
494 "\tusable division of atoms for force decomposition.\n"
495 "\tEither try a smaller number of processors, or run the\n"
496 "\tsingle-processor version of OpenMD.\n",
497 nProcessors, nGlobalMols);
499 painCave.isFatal = 1;
506 atomsPerProc.insert(atomsPerProc.end(), nProcessors, 0);
508 if (worldRank == 0) {
509 Utils::RandNumGenPtr myRandom = info->getRandomNumberGenerator();
511 std::uniform_int_distribution<> processorDistribution {0,
513 std::uniform_real_distribution<RealType> yDistribution {0, 1};
516 RealType denominator = nProcessors;
517 RealType precast = numerator / denominator;
518 int nTarget = (int)(precast + 0.5);
521 for (
int i = 0; i < nGlobalMols; i++) {
523 int stampId = info->getMoleculeStampId(i);
525 int add_atoms = moleculeStamp->getNAtoms();
527 if (nProcessors > 1) {
535 which_proc = processorDistribution(*myRandom);
538 int old_atoms = atomsPerProc[which_proc];
539 int new_atoms = old_atoms + add_atoms;
547 painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
548 "There have been 100 attempts to assign molecule %d to an\n"
549 "\tunderworked processor, but there's no good place to\n"
550 "\tleave it. OpenMD is assigning it at random to processor "
554 painCave.isFatal = 0;
555 painCave.severity = OPENMD_INFO;
558 molToProcMap[i] = which_proc;
559 atomsPerProc[which_proc] += add_atoms;
568 if (new_atoms <= nTarget) {
569 molToProcMap[i] = which_proc;
570 atomsPerProc[which_proc] += add_atoms;
584 RealType x = (RealType)(new_atoms - nTarget);
585 RealType y = yDistribution(*myRandom);
587 if (y < exp(-a * x)) {
588 molToProcMap[i] = which_proc;
589 atomsPerProc[which_proc] += add_atoms;
599 molToProcMap[i] = which_proc;
600 atomsPerProc[which_proc] += add_atoms;
605 MPI_Bcast(&molToProcMap[0], nGlobalMols, MPI_INT, 0, MPI_COMM_WORLD);
609 MPI_Bcast(&molToProcMap[0], nGlobalMols, MPI_INT, 0, MPI_COMM_WORLD);
613 snprintf(checkPointMsg, MAX_SIM_ERROR_MSG_LENGTH,
614 "Successfully divided the molecules among the processors.\n");
620 void SimCreator::createMolecules(
SimInfo* info) {
621 MoleculeCreator molCreator;
625 stampId = info->getMoleculeStampId(i);
633 Molecule* mol = molCreator.createMolecule(
645 void SimCreator::computeStorageLayouts(
SimInfo* info) {
646 Globals* simParams = info->getSimParams();
649 AtomTypeSet::iterator i;
650 bool hasDirectionalAtoms =
false;
651 bool hasFixedCharge =
false;
652 bool hasDipoles =
false;
653 bool hasQuadrupoles =
false;
654 bool hasPolarizable =
false;
655 bool hasFluctuatingCharge =
false;
656 bool hasMetallic =
false;
657 int atomStorageLayout = 0;
658 int rigidBodyStorageLayout = 0;
659 int cutoffGroupStorageLayout = 0;
661 atomStorageLayout |= DataStorage::dslPosition;
662 atomStorageLayout |= DataStorage::dslVelocity;
663 atomStorageLayout |= DataStorage::dslForce;
664 cutoffGroupStorageLayout |= DataStorage::dslPosition;
666 for (i = atomTypes.begin(); i != atomTypes.end(); ++i) {
667 DirectionalAdapter da = DirectionalAdapter((*i));
668 MultipoleAdapter ma = MultipoleAdapter((*i));
669 EAMAdapter ea = EAMAdapter((*i));
670 SuttonChenAdapter sca = SuttonChenAdapter((*i));
671 PolarizableAdapter pa = PolarizableAdapter((*i));
672 FixedChargeAdapter fca = FixedChargeAdapter((*i));
673 FluctuatingChargeAdapter fqa = FluctuatingChargeAdapter((*i));
675 if (da.isDirectional()) { hasDirectionalAtoms =
true; }
676 if (ma.isDipole()) { hasDipoles =
true; }
677 if (ma.isQuadrupole()) { hasQuadrupoles =
true; }
678 if (ea.isEAM() || sca.isSuttonChen()) { hasMetallic =
true; }
679 if (fca.isFixedCharge()) { hasFixedCharge =
true; }
680 if (fqa.isFluctuatingCharge()) { hasFluctuatingCharge =
true; }
681 if (pa.isPolarizable()) { hasPolarizable =
true; }
684 if (nRigidBodies > 0) {
685 rigidBodyStorageLayout |= DataStorage::dslPosition;
686 rigidBodyStorageLayout |= DataStorage::dslVelocity;
687 rigidBodyStorageLayout |= DataStorage::dslForce;
688 rigidBodyStorageLayout |= DataStorage::dslAmat;
689 rigidBodyStorageLayout |= DataStorage::dslAngularMomentum;
690 rigidBodyStorageLayout |= DataStorage::dslTorque;
692 if (hasDirectionalAtoms) {
693 atomStorageLayout |= DataStorage::dslAmat;
694 if (atomStorageLayout & DataStorage::dslVelocity) {
695 atomStorageLayout |= DataStorage::dslAngularMomentum;
697 if (atomStorageLayout & DataStorage::dslForce) {
698 atomStorageLayout |= DataStorage::dslTorque;
701 if (hasDipoles) { atomStorageLayout |= DataStorage::dslDipole; }
702 if (hasQuadrupoles) { atomStorageLayout |= DataStorage::dslQuadrupole; }
703 if (hasFixedCharge || hasFluctuatingCharge) {
704 atomStorageLayout |= DataStorage::dslSkippedCharge;
707 atomStorageLayout |= DataStorage::dslDensity;
708 atomStorageLayout |= DataStorage::dslFunctional;
709 atomStorageLayout |= DataStorage::dslFunctionalDerivative;
711 if (hasPolarizable) { atomStorageLayout |= DataStorage::dslElectricField; }
712 if (hasFluctuatingCharge) {
713 atomStorageLayout |= DataStorage::dslFlucQPosition;
714 if (atomStorageLayout & DataStorage::dslVelocity) {
715 atomStorageLayout |= DataStorage::dslFlucQVelocity;
717 if (atomStorageLayout & DataStorage::dslForce) {
718 atomStorageLayout |= DataStorage::dslFlucQForce;
725 if (simParams->getOutputParticlePotential()) {
726 atomStorageLayout |= DataStorage::dslParticlePot;
729 if (simParams->havePrintHeatFlux()) {
730 if (simParams->getPrintHeatFlux()) {
731 atomStorageLayout |= DataStorage::dslParticlePot;
735 if (simParams->getOutputElectricField() | simParams->haveElectricField() |
736 simParams->haveUniformField() |
737 simParams->haveUniformGradientStrength() |
738 simParams->haveUniformGradientDirection1() |
739 simParams->haveUniformGradientDirection2() |
740 simParams->getLightParameters()->getUseLight()) {
741 atomStorageLayout |= DataStorage::dslElectricField;
742 rigidBodyStorageLayout |= DataStorage::dslElectricField;
745 if (simParams->getRNEMDParameters()->haveUseRNEMD()) {
746 if (simParams->getRNEMDParameters()->getUseRNEMD()) {
747 if (simParams->getRNEMDParameters()->requiresElectricField()) {
748 atomStorageLayout |= DataStorage::dslElectricField;
749 rigidBodyStorageLayout |= DataStorage::dslElectricField;
754 if (simParams->getOutputSitePotential()) {
755 atomStorageLayout |= DataStorage::dslSitePotential;
756 rigidBodyStorageLayout |= DataStorage::dslSitePotential;
759 if (simParams->getOutputFluctuatingCharges()) {
760 atomStorageLayout |= DataStorage::dslFlucQPosition;
761 atomStorageLayout |= DataStorage::dslFlucQVelocity;
762 atomStorageLayout |= DataStorage::dslFlucQForce;
766 info->setRigidBodyStorageLayout(rigidBodyStorageLayout);
767 info->setCutoffGroupStorageLayout(cutoffGroupStorageLayout);
772 void SimCreator::setGlobalIndex(
SimInfo* info) {
773 SimInfo::MoleculeIterator mi;
774 Molecule::AtomIterator ai;
775 Molecule::RigidBodyIterator ri;
776 Molecule::CutoffGroupIterator ci;
777 Molecule::BondIterator boi;
778 Molecule::BendIterator bei;
779 Molecule::TorsionIterator ti;
780 Molecule::InversionIterator ii;
781 Molecule::IntegrableObjectIterator ioi;
789 Inversion* inversion;
791 int beginRigidBodyIndex;
792 int beginCutoffGroupIndex;
795 int beginTorsionIndex;
796 int beginInversionIndex;
805 beginCutoffGroupIndex = 0;
808 beginTorsionIndex = 0;
809 beginInversionIndex = 0;
819 for (atom = mol->beginAtom(ai); atom != NULL;
820 atom = mol->nextAtom(ai)) {
824 for (rb = mol->beginRigidBody(ri); rb != NULL;
825 rb = mol->nextRigidBody(ri)) {
831 for (cg = mol->beginCutoffGroup(ci); cg != NULL;
832 cg = mol->nextCutoffGroup(ci)) {
833 cg->setGlobalIndex(beginCutoffGroupIndex++);
835 for (bond = mol->beginBond(boi); bond != NULL;
836 bond = mol->nextBond(boi)) {
839 for (bend = mol->beginBend(bei); bend != NULL;
840 bend = mol->nextBend(bei)) {
843 for (torsion = mol->beginTorsion(ti); torsion != NULL;
844 torsion = mol->nextTorsion(ti)) {
847 for (inversion = mol->beginInversion(ii); inversion != NULL;
848 inversion = mol->nextInversion(ii)) {
856 int stampId = info->getMoleculeStampId(i);
859 beginAtomIndex += stamp->getNAtoms();
860 beginRigidBodyIndex += stamp->getNRigidBodies();
861 beginCutoffGroupIndex +=
862 stamp->getNCutoffGroups() + stamp->getNFreeAtoms();
863 beginBondIndex += stamp->getNBonds();
864 beginBendIndex += stamp->getNBends();
865 beginTorsionIndex += stamp->getNTorsions();
866 beginInversionIndex += stamp->getNInversions();
876 for (cg = mol->beginCutoffGroup(ci); cg != NULL;
877 cg = mol->nextCutoffGroup(ci)) {
878 for (atom = cg->beginAtom(ai); atom != NULL; atom = cg->nextAtom(ai)) {
879 globalGroupMembership[atom->
getGlobalIndex()] = cg->getGlobalIndex();
891 MPI_Allreduce(&globalGroupMembership[0], &tmpGroupMembership[0],
892 nGlobalAtoms, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
900 std::vector<int> globalMolMembership(
905 for (atom = mol->beginAtom(ai); atom != NULL; atom = mol->nextAtom(ai)) {
908 for (rb = mol->beginRigidBody(ri); rb != NULL;
909 rb = mol->nextRigidBody(ri)) {
915 std::vector<int> tmpMolMembership(
917 MPI_Allreduce(&globalMolMembership[0], &tmpMolMembership[0],
918 nGlobalAtoms + nGlobalRigidBodies, MPI_INT, MPI_SUM,
937 MPI_Allreduce(&nIOPerMol[0], &numIntegrableObjectsPerMol[0],
941 std::vector<int> numIntegrableObjectsPerMol = nIOPerMol;
946 int startingIndex = 0;
948 startingIOIndexForMol[i] = startingIndex;
949 startingIndex += numIntegrableObjectsPerMol[i];
952 std::vector<StuntDouble*> IOIndexToIntegrableObject(
957 int globalIO = startingIOIndexForMol[myGlobalIndex];
958 for (StuntDouble* sd = mol->beginIntegrableObject(ioi); sd != NULL;
959 sd = mol->nextIntegrableObject(ioi)) {
960 sd->setGlobalIntegrableObjectIndex(globalIO);
961 IOIndexToIntegrableObject[globalIO] = sd;
966 info->setIOIndexToIntegrableObject(IOIndexToIntegrableObject);
969 void SimCreator::loadCoordinates(
SimInfo* info,
970 const std::string& mdFileName) {
971 DumpReader reader(info, mdFileName);
972 int nframes = reader.getNFrames();
975 reader.readFrame(nframes - 1);
979 painCave.errMsg, MAX_SIM_ERROR_MSG_LENGTH,
980 "Initial configuration file %s should at least contain one frame\n",
982 painCave.isFatal = 1;
size_t getNIntegrableObjects()
Returns the total number of integrable objects in this molecule.
int getGlobalIndex()
Returns the global index of this molecule.
void setGlobalIndex(int index)
Sets the global index of this ShortRangeInteraction.
SimInfo * createSim(const std::string &mdFileName, bool loadInitCoords=true)
Setup Simulation.
One of the heavy-weight classes of OpenMD, SimInfo maintains objects and variables relating to the cu...
Molecule * getMoleculeByGlobalIndex(int index)
Finds a molecule with a specified global index.
int getNGlobalIntegrableObjects()
Returns the total number of integrable objects (total number of rigid bodies plus the total number of...
void setGlobalMolMembership(const std::vector< int > &gmm)
Sets GlobalMolMembership.
void setAtomStorageLayout(int asl)
Sets the storage layouts (computed by SimCreator).
int getMolToProc(int globalIndex)
Finds the processor where a molecule resides.
ForceField * getForceField()
Returns the force field.
Molecule * beginMolecule(MoleculeIterator &i)
Returns the first molecule in this SimInfo and intialize the iterator.
MoleculeStamp * getMoleculeStamp(int id)
Returns the molecule stamp.
int getNGlobalRigidBodies()
Returns the total number of integrable objects (total number of rigid bodies plus the total number of...
int getAtomStorageLayout()
Returns the storage layouts (computed by SimCreator).
int getNGlobalAtoms()
Returns the total number of atoms in the system.
int getNGlobalMolecules()
Returns the total number of molecules in the system.
void setSnapshotManager(SnapshotManager *sman)
Sets the snapshot manager.
void setGlobalGroupMembership(const std::vector< int > &ggm)
Sets GlobalGroupMembership.
bool addMolecule(Molecule *mol)
Adds a molecule.
void addInteractionPairs(Molecule *mol)
add all special interaction pairs (including excluded interactions) in a molecule into the appropriat...
Molecule * nextMolecule(MoleculeIterator &i)
Returns the next avaliable Molecule based on the iterator.
SnapshotManager * getSnapshotManager()
Returns the snapshot manager.
void setMolToProcMap(const std::vector< int > &molToProcMap)
Set MolToProcMap array.
AtomTypeSet getSimulatedAtomTypes()
Returns the set of atom types present in this simulation.
LocalIndexManager * getLocalIndexManager()
Returns the local index manager.
"brains/SimSnapshotManager.hpp"
The string tokenizer class allows an application to break a string into tokens The set of delimiters ...
std::string nextToken()
Returns the next token from this string tokenizer.
int nextTokenAsInt()
Returns the next token from this string tokenizer as an integer.
bool hasMoreTokens()
Tests if there are more tokens available from this tokenizer's string.
void setGlobalIndex(int index)
Sets the global index of this stuntDouble.
int getGlobalIndex()
Returns the global index of this stuntDouble.
This basic Periodic Table class was originally taken from the data.cpp file in OpenBabel.
std::string getPrefix(const std::string &str)