// Copyright (C) 2004-2018 Greg Landrum and Rational Discovery LLC // // @@ All Rights Reserved @@ // This file is part of the RDKit. // The contents are covered by the terms of the BSD license // which is included in the file license.txt, found at the root // of the RDKit source tree. // #include #include #include #include #include #include #include #include #include "AtomTyper.h" #include "Builder.h" namespace RDKit { namespace UFF { using namespace ForceFields::UFF; namespace Tools { // ------------------------------------------------------------------------ // // // // ------------------------------------------------------------------------ void addBonds(const ROMol &mol, const AtomicParamVect ¶ms, ForceFields::ForceField *field) { PRECONDITION(mol.getNumAtoms() == params.size(), "bad parameters"); PRECONDITION(field, "bad forcefield"); for (const auto bond : mol.bonds()) { int idx1 = bond->getBeginAtomIdx(); int idx2 = bond->getEndAtomIdx(); // FIX: recognize amide bonds here. if (params[idx1] && params[idx2]) { BondStretchContrib *contrib; contrib = new BondStretchContrib(field, idx1, idx2, bond->getBondTypeAsDouble(), params[idx1], params[idx2]); field->contribs().push_back(ForceFields::ContribPtr(contrib)); } } } unsigned int twoBitCellPos(unsigned int nAtoms, int i, int j) { if (j < i) { std::swap(i, j); } return i * (nAtoms - 1) + i * (1 - i) / 2 + j; } void setTwoBitCell(boost::shared_array &res, unsigned int pos, std::uint8_t value) { unsigned int twoBitPos = pos / 4; unsigned int shift = 2 * (pos % 4); std::uint8_t twoBitMask = 3 << shift; res[twoBitPos] = ((res[twoBitPos] & (~twoBitMask)) | (value << shift)); } std::uint8_t getTwoBitCell(boost::shared_array &res, unsigned int pos) { unsigned int twoBitPos = pos / 4; unsigned int shift = 2 * (pos % 4); std::uint8_t twoBitMask = 3 << shift; return ((res[twoBitPos] & twoBitMask) >> shift); } // ------------------------------------------------------------------------ // // the two-bit matrix returned by this contains: // 0: if atoms i and j are directly connected // 1: if atoms i and j are connected via an atom // 2: if atoms i and j are in a 1,4 relationship // 3: otherwise // // NOTE: the caller is responsible for calling delete [] // on the result // // ------------------------------------------------------------------------ boost::shared_array buildNeighborMatrix(const ROMol &mol) { const std::uint8_t RELATION_1_X_INIT = RELATION_1_X | (RELATION_1_X << 2) | (RELATION_1_X << 4) | (RELATION_1_X << 6); unsigned int nAtoms = mol.getNumAtoms(); unsigned nTwoBitCells = (nAtoms * (nAtoms + 1) - 1) / 8 + 1; boost::shared_array res(new std::uint8_t[nTwoBitCells]); std::memset(res.get(), RELATION_1_X_INIT, nTwoBitCells); for (const auto bondi : mol.bonds()) { setTwoBitCell( res, twoBitCellPos(nAtoms, bondi->getBeginAtomIdx(), bondi->getEndAtomIdx()), RELATION_1_2); unsigned int bondiBeginAtomIdx = bondi->getBeginAtomIdx(); unsigned int bondiEndAtomIdx = bondi->getEndAtomIdx(); for (const auto bondj : mol.bonds()) { if (bondj == bondi) { continue; } int idx1 = -1; int idx3 = -1; unsigned int bondjBeginAtomIdx = bondj->getBeginAtomIdx(); unsigned int bondjEndAtomIdx = bondj->getEndAtomIdx(); if (bondiBeginAtomIdx == bondjBeginAtomIdx) { idx1 = bondiEndAtomIdx; idx3 = bondjEndAtomIdx; } else if (bondiBeginAtomIdx == bondjEndAtomIdx) { idx1 = bondiEndAtomIdx; idx3 = bondjBeginAtomIdx; } else if (bondiEndAtomIdx == bondjBeginAtomIdx) { idx1 = bondiBeginAtomIdx; idx3 = bondjEndAtomIdx; } else if (bondiEndAtomIdx == bondjEndAtomIdx) { idx1 = bondiBeginAtomIdx; idx3 = bondjBeginAtomIdx; } if (idx1 > -1) { setTwoBitCell(res, twoBitCellPos(nAtoms, idx1, idx3), RELATION_1_3); } } } return res; } // ------------------------------------------------------------------------ // // // // ------------------------------------------------------------------------ void addAngles(const ROMol &mol, const AtomicParamVect ¶ms, ForceFields::ForceField *field) { PRECONDITION(mol.getNumAtoms() == params.size(), "bad parameters"); PRECONDITION(field, "bad forcefield"); ROMol::ADJ_ITER nbr1Idx; ROMol::ADJ_ITER end1Nbrs; ROMol::ADJ_ITER nbr2Idx; ROMol::ADJ_ITER end2Nbrs; RingInfo *rings = mol.getRingInfo(); unsigned int nAtoms = mol.getNumAtoms(); for (unsigned int j = 0; j < nAtoms; j++) { if (!params[j]) { continue; } const Atom *atomJ = mol.getAtomWithIdx(j); if (atomJ->getDegree() == 1) { continue; } boost::tie(nbr1Idx, end1Nbrs) = mol.getAtomNeighbors(atomJ); for (; nbr1Idx != end1Nbrs; nbr1Idx++) { const Atom *atomI = mol[*nbr1Idx]; unsigned int i = atomI->getIdx(); if (!params[i]) { continue; } boost::tie(nbr2Idx, end2Nbrs) = mol.getAtomNeighbors(atomJ); for (; nbr2Idx != end2Nbrs; nbr2Idx++) { if (nbr2Idx < (nbr1Idx + 1)) { continue; } const Atom *atomK = mol[*nbr2Idx]; unsigned int k = atomK->getIdx(); if (!params[k]) { continue; } // skip special cases: if (!(atomJ->getHybridization() == Atom::SP3D && atomJ->getDegree() == 5)) { const Bond *b1 = mol.getBondBetweenAtoms(i, j); const Bond *b2 = mol.getBondBetweenAtoms(k, j); // FIX: recognize amide bonds here. AngleBendContrib *contrib; int order = 0; switch (atomJ->getHybridization()) { case Atom::SP: order = 1; break; case Atom::SP2: order = 3; // the following is a hack to get decent geometries // with 3- and 4-membered rings incorporating sp2 atoms // if the central atom is in a ring of size 3 if (rings->isAtomInRingOfSize(j, 3)) { // if the central atom and one of the bonded atoms, but not the // other one are inside a ring, then this angle is between a // ring substituent and a ring edge if (rings->isAtomInRingOfSize(i, 3) != rings->isAtomInRingOfSize(k, 3)) { order = 30; } // if all atoms are inside the ring, then this is one of ring // angles else if (rings->isAtomInRingOfSize(i, 3) && rings->isAtomInRingOfSize(k, 3)) { order = 35; } } // if the central atom is in a ring of size 4 else if (rings->isAtomInRingOfSize(j, 4)) { // if the central atom and one of the bonded atoms, but not the // other one are inside a ring, then this angle is between a // ring substituent and a ring edge if (rings->isAtomInRingOfSize(i, 4) != rings->isAtomInRingOfSize(k, 4)) { order = 40; } // if all atoms are inside the ring, then this is one of ring // angles else if (rings->isAtomInRingOfSize(i, 4) && rings->isAtomInRingOfSize(k, 4)) { order = 45; } } // end of the hack break; case Atom::SP3D2: order = 4; break; default: order = 0; break; } contrib = new AngleBendContrib(field, i, j, k, b1->getBondTypeAsDouble(), b2->getBondTypeAsDouble(), params[i], params[j], params[k], order); field->contribs().push_back(ForceFields::ContribPtr(contrib)); } } } } } // ------------------------------------------------------------------------ // // // // ------------------------------------------------------------------------ void addTrigonalBipyramidAngles(const Atom *atom, const ROMol &mol, int confId, const AtomicParamVect ¶ms, ForceFields::ForceField *field) { PRECONDITION(atom, "bad atom"); PRECONDITION(atom->getHybridization() == Atom::SP3D, "bad hybridization"); PRECONDITION(atom->getDegree() == 5, "bad degree"); PRECONDITION(mol.getNumAtoms() == params.size(), "bad parameters"); PRECONDITION(field, "bad forcefield"); const Bond *ax1 = nullptr, *ax2 = nullptr; const Bond *eq1 = nullptr, *eq2 = nullptr, *eq3 = nullptr; const Conformer &conf = mol.getConformer(confId); //------------------------------------------------------------ // identify the axial and equatorial bonds: double mostNeg = 100.0; ROMol::OEDGE_ITER beg1, end1; boost::tie(beg1, end1) = mol.getAtomBonds(atom); unsigned int aid = atom->getIdx(); while (beg1 != end1) { const Bond *bond1 = mol[*beg1]; unsigned int oaid = bond1->getOtherAtomIdx(aid); RDGeom::Point3D v1 = conf.getAtomPos(aid).directionVector(conf.getAtomPos(oaid)); ROMol::OEDGE_ITER beg2, end2; boost::tie(beg2, end2) = mol.getAtomBonds(atom); while (beg2 != end2) { const Bond *bond2 = mol[*beg2]; if (bond2->getIdx() > bond1->getIdx()) { unsigned int oaid2 = bond2->getOtherAtomIdx(aid); RDGeom::Point3D v2 = conf.getAtomPos(aid).directionVector(conf.getAtomPos(oaid2)); double dot = v1.dotProduct(v2); if (dot < mostNeg) { mostNeg = dot; ax1 = bond1; ax2 = bond2; } } ++beg2; } ++beg1; } CHECK_INVARIANT(ax1, "axial bond not found"); CHECK_INVARIANT(ax2, "axial bond not found"); boost::tie(beg1, end1) = mol.getAtomBonds(atom); while (beg1 != end1) { const Bond *bond = mol[*beg1]; ++beg1; if (bond == ax1 || bond == ax2) { continue; } if (!eq1) { eq1 = bond; } else if (!eq2) { eq2 = bond; } else if (!eq3) { eq3 = bond; } } CHECK_INVARIANT(eq1, "equatorial bond not found"); CHECK_INVARIANT(eq2, "equatorial bond not found"); CHECK_INVARIANT(eq3, "equatorial bond not found"); //------------------------------------------------------------ // alright, add the angles: AngleBendContrib *contrib; int atomIdx = atom->getIdx(); int i, j; // Axial-Axial i = ax1->getOtherAtomIdx(atomIdx); j = ax2->getOtherAtomIdx(atomIdx); if (params[i] && params[j]) { contrib = new AngleBendContrib( field, i, atomIdx, j, ax1->getBondTypeAsDouble(), ax2->getBondTypeAsDouble(), params[i], params[atomIdx], params[j], 2); field->contribs().push_back(ForceFields::ContribPtr(contrib)); } // Equatorial-Equatorial i = eq1->getOtherAtomIdx(atomIdx); j = eq2->getOtherAtomIdx(atomIdx); if (params[i] && params[j]) { contrib = new AngleBendContrib( field, i, atomIdx, j, eq1->getBondTypeAsDouble(), eq2->getBondTypeAsDouble(), params[i], params[atomIdx], params[j], 3); field->contribs().push_back(ForceFields::ContribPtr(contrib)); } i = eq1->getOtherAtomIdx(atomIdx); j = eq3->getOtherAtomIdx(atomIdx); if (params[i] && params[j]) { contrib = new AngleBendContrib( field, i, atomIdx, j, eq1->getBondTypeAsDouble(), eq3->getBondTypeAsDouble(), params[i], params[atomIdx], params[j], 3); field->contribs().push_back(ForceFields::ContribPtr(contrib)); } i = eq2->getOtherAtomIdx(atomIdx); j = eq3->getOtherAtomIdx(atomIdx); if (params[i] && params[j]) { contrib = new AngleBendContrib( field, i, atomIdx, j, eq2->getBondTypeAsDouble(), eq3->getBondTypeAsDouble(), params[i], params[atomIdx], params[j], 3); field->contribs().push_back(ForceFields::ContribPtr(contrib)); } // Axial-Equatorial i = ax1->getOtherAtomIdx(atomIdx); j = eq1->getOtherAtomIdx(atomIdx); if (params[i] && params[j]) { contrib = new AngleBendContrib( field, i, atomIdx, j, ax1->getBondTypeAsDouble(), eq1->getBondTypeAsDouble(), params[i], params[atomIdx], params[j]); field->contribs().push_back(ForceFields::ContribPtr(contrib)); } i = ax1->getOtherAtomIdx(atomIdx); j = eq2->getOtherAtomIdx(atomIdx); if (params[i] && params[j]) { contrib = new AngleBendContrib( field, i, atomIdx, j, ax1->getBondTypeAsDouble(), eq2->getBondTypeAsDouble(), params[i], params[atomIdx], params[j]); field->contribs().push_back(ForceFields::ContribPtr(contrib)); } i = ax1->getOtherAtomIdx(atomIdx); j = eq3->getOtherAtomIdx(atomIdx); if (params[i] && params[j]) { contrib = new AngleBendContrib( field, i, atomIdx, j, ax1->getBondTypeAsDouble(), eq3->getBondTypeAsDouble(), params[i], params[atomIdx], params[j]); field->contribs().push_back(ForceFields::ContribPtr(contrib)); } i = ax2->getOtherAtomIdx(atomIdx); j = eq1->getOtherAtomIdx(atomIdx); if (params[i] && params[j]) { contrib = new AngleBendContrib( field, i, atomIdx, j, ax2->getBondTypeAsDouble(), eq1->getBondTypeAsDouble(), params[i], params[atomIdx], params[j]); field->contribs().push_back(ForceFields::ContribPtr(contrib)); } i = ax2->getOtherAtomIdx(atomIdx); j = eq2->getOtherAtomIdx(atomIdx); if (params[i] && params[j]) { contrib = new AngleBendContrib( field, i, atomIdx, j, ax2->getBondTypeAsDouble(), eq2->getBondTypeAsDouble(), params[i], params[atomIdx], params[j]); field->contribs().push_back(ForceFields::ContribPtr(contrib)); } i = ax2->getOtherAtomIdx(atomIdx); j = eq3->getOtherAtomIdx(atomIdx); if (params[i] && params[j]) { contrib = new AngleBendContrib( field, i, atomIdx, j, ax2->getBondTypeAsDouble(), eq3->getBondTypeAsDouble(), params[i], params[atomIdx], params[j]); field->contribs().push_back(ForceFields::ContribPtr(contrib)); } } // ------------------------------------------------------------------------ // // // // ------------------------------------------------------------------------ void addAngleSpecialCases(const ROMol &mol, int confId, const AtomicParamVect ¶ms, ForceFields::ForceField *field) { PRECONDITION(mol.getNumAtoms() == params.size(), "bad parameters"); PRECONDITION(field, "bad forcefield"); unsigned int nAtoms = mol.getNumAtoms(); for (unsigned int i = 0; i < nAtoms; i++) { const Atom *atom = mol.getAtomWithIdx(i); // trigonal bipyramidal: if ((atom->getHybridization() == Atom::SP3D && atom->getDegree() == 5)) { addTrigonalBipyramidAngles(atom, mol, confId, params, field); } } } // ------------------------------------------------------------------------ // // // // ------------------------------------------------------------------------ void addNonbonded(const ROMol &mol, int confId, const AtomicParamVect ¶ms, ForceFields::ForceField *field, boost::shared_array neighborMatrix, double vdwThresh, bool ignoreInterfragInteractions) { PRECONDITION(mol.getNumAtoms() == params.size(), "bad parameters"); PRECONDITION(field, "bad forcefield"); INT_VECT fragMapping; if (ignoreInterfragInteractions) { std::vector molFrags = MolOps::getMolFrags(mol, true, &fragMapping); } unsigned int nAtoms = mol.getNumAtoms(); const Conformer &conf = mol.getConformer(confId); for (unsigned int i = 0; i < nAtoms; i++) { if (!params[i]) { continue; } for (unsigned int j = i + 1; j < nAtoms; j++) { if (!params[j] || (ignoreInterfragInteractions && fragMapping[i] != fragMapping[j])) { continue; } if (getTwoBitCell(neighborMatrix, twoBitCellPos(nAtoms, i, j)) >= RELATION_1_4) { double dist = (conf.getAtomPos(i) - conf.getAtomPos(j)).length(); if (dist < vdwThresh * UFF::Utils::calcNonbondedMinimum(params[i], params[j])) { vdWContrib *contrib; contrib = new vdWContrib(field, i, j, params[i], params[j]); field->contribs().push_back(ForceFields::ContribPtr(contrib)); } } } } } const std::string DefaultTorsionBondSmarts::ds_string = "[!$(*#*)&!D1]~[!$(*#*)&!D1]"; boost::scoped_ptr DefaultTorsionBondSmarts::ds_instance; #ifdef RDK_BUILD_THREADSAFE_SSS std::once_flag DefaultTorsionBondSmarts::ds_flag; #endif void DefaultTorsionBondSmarts::create() { ds_instance.reset(SmartsToMol(ds_string)); } const ROMol *DefaultTorsionBondSmarts::query() { #ifdef RDK_BUILD_THREADSAFE_SSS std::call_once(ds_flag, create); #else static bool created = false; if (!created) { created = true; create(); } #endif return ds_instance.get(); } // ------------------------------------------------------------------------ // // // // ------------------------------------------------------------------------ void addTorsions(const ROMol &mol, const AtomicParamVect ¶ms, ForceFields::ForceField *field, const std::string &torsionBondSmarts) { PRECONDITION(mol.getNumAtoms() == params.size(), "bad parameters"); PRECONDITION(field, "bad forcefield"); // find all of the torsion bonds: std::vector matchVect; const ROMol *defaultQuery = DefaultTorsionBondSmarts::query(); const ROMol *query = (torsionBondSmarts == DefaultTorsionBondSmarts::string()) ? defaultQuery : SmartsToMol(torsionBondSmarts); TEST_ASSERT(query); unsigned int nHits = SubstructMatch(mol, *query, matchVect); if (query != defaultQuery) { delete query; } for (unsigned int i = 0; i < nHits; i++) { MatchVectType match = matchVect[i]; TEST_ASSERT(match.size() == 2); int idx1 = match[0].second; int idx2 = match[1].second; if (!params[idx1] || !params[idx2]) { continue; } const Bond *bond = mol.getBondBetweenAtoms(idx1, idx2); std::vector contribsHere; TEST_ASSERT(bond); const Atom *atom1 = mol.getAtomWithIdx(idx1); const Atom *atom2 = mol.getAtomWithIdx(idx2); if ((atom1->getHybridization() == Atom::SP2 || atom1->getHybridization() == Atom::SP3) && (atom2->getHybridization() == Atom::SP2 || atom2->getHybridization() == Atom::SP3)) { ROMol::OEDGE_ITER beg1, end1; boost::tie(beg1, end1) = mol.getAtomBonds(atom1); while (beg1 != end1) { const Bond *tBond1 = mol[*beg1]; if (tBond1 != bond) { int bIdx = tBond1->getOtherAtomIdx(idx1); ROMol::OEDGE_ITER beg2, end2; boost::tie(beg2, end2) = mol.getAtomBonds(atom2); while (beg2 != end2) { const Bond *tBond2 = mol[*beg2]; if (tBond2 != bond && tBond2 != tBond1) { int eIdx = tBond2->getOtherAtomIdx(idx2); // make sure this isn't a three-membered ring: if (eIdx != bIdx) { // we now have a torsion involving atoms (bonds): // bIdx - (tBond1) - idx1 - (bond) - idx2 - (tBond2) - eIdx TorsionAngleContrib *contrib; // if either of the end atoms is SP2 hybridized, set a flag // here. bool hasSP2 = false; if (mol.getAtomWithIdx(bIdx)->getHybridization() == Atom::SP2 || mol.getAtomWithIdx(eIdx)->getHybridization() == Atom::SP2) { hasSP2 = true; } // std::cout << "Torsion: " << bIdx << "-" << idx1 << "-" << // idx2 << "-" << eIdx << std::endl; // if(okToIncludeTorsion(mol,bond,bIdx,idx1,idx2,eIdx)){ // std::cout << " INCLUDED" << std::endl; contrib = new TorsionAngleContrib( field, bIdx, idx1, idx2, eIdx, bond->getBondTypeAsDouble(), atom1->getAtomicNum(), atom2->getAtomicNum(), atom1->getHybridization(), atom2->getHybridization(), params[idx1], params[idx2], hasSP2); field->contribs().push_back(ForceFields::ContribPtr(contrib)); contribsHere.push_back(contrib); //} } } beg2++; } } beg1++; } } // now divide the force constant for each contribution to the torsion energy // about this bond by the number of contribs about this bond: for (auto chI = contribsHere.begin(); chI != contribsHere.end(); ++chI) { (*chI)->scaleForceConstant(contribsHere.size()); } } } // ------------------------------------------------------------------------ // // // // ------------------------------------------------------------------------ void addInversions(const ROMol &mol, const AtomicParamVect ¶ms, ForceFields::ForceField *field) { PRECONDITION(mol.getNumAtoms() == params.size(), "bad parameters"); PRECONDITION(field, "bad forcefield"); unsigned int idx[4]; unsigned int n[4]; const Atom *atom[4]; ROMol::ADJ_ITER nbrIdx; ROMol::ADJ_ITER endNbrs; for (idx[1] = 0; idx[1] < mol.getNumAtoms(); ++idx[1]) { atom[1] = mol.getAtomWithIdx(idx[1]); int at2AtomicNum = atom[1]->getAtomicNum(); // if the central atom is not carbon, nitrogen, oxygen, // phosphorous, arsenic, antimonium or bismuth, skip it if (((at2AtomicNum != 6) && (at2AtomicNum != 7) && (at2AtomicNum != 8) && (at2AtomicNum != 15) && (at2AtomicNum != 33) && (at2AtomicNum != 51) && (at2AtomicNum != 83)) || (atom[1]->getDegree() != 3)) { continue; } // if the central atom is carbon, nitrogen or oxygen // but hybridization is not sp2, skip it if (((at2AtomicNum == 6) || (at2AtomicNum == 7) || (at2AtomicNum == 8)) && (atom[1]->getHybridization() != Atom::SP2)) { continue; } boost::tie(nbrIdx, endNbrs) = mol.getAtomNeighbors(atom[1]); unsigned int i = 0; bool isBoundToSP2O = false; for (; nbrIdx != endNbrs; ++nbrIdx) { atom[i] = mol[*nbrIdx]; idx[i] = atom[i]->getIdx(); // if the central atom is sp2 carbon and is // bound to sp2 oxygen, set a flag if (!isBoundToSP2O) { isBoundToSP2O = ((at2AtomicNum == 6) && (atom[i]->getAtomicNum() == 8) && (atom[i]->getHybridization() == Atom::SP2)); } if (!i) { ++i; } ++i; } for (unsigned int i = 0; i < 3; ++i) { n[1] = 1; switch (i) { case 0: n[0] = 0; n[2] = 2; n[3] = 3; break; case 1: n[0] = 0; n[2] = 3; n[3] = 2; break; case 2: n[0] = 2; n[2] = 3; n[3] = 0; break; } InversionContrib *contrib; contrib = new InversionContrib(field, idx[n[0]], idx[n[1]], idx[n[2]], idx[n[3]], at2AtomicNum, isBoundToSP2O); field->contribs().push_back(ForceFields::ContribPtr(contrib)); } } } } // end of namespace Tools // ------------------------------------------------------------------------ // // // // ------------------------------------------------------------------------ ForceFields::ForceField *constructForceField(ROMol &mol, const AtomicParamVect ¶ms, double vdwThresh, int confId, bool ignoreInterfragInteractions) { PRECONDITION(mol.getNumAtoms() == params.size(), "bad parameters"); if (MolOps::needsHs(mol)) { BOOST_LOG(rdWarningLog) << "Molecule does not have explicit Hs. Consider calling AddHs()" << std::endl; } std::unique_ptr res(new ForceFields::ForceField()); // add the atomic positions: Conformer &conf = mol.getConformer(confId); for (unsigned int i = 0; i < mol.getNumAtoms(); i++) { res->positions().push_back(&conf.getAtomPos(i)); } Tools::addBonds(mol, params, res.get()); Tools::addAngles(mol, params, res.get()); Tools::addAngleSpecialCases(mol, confId, params, res.get()); boost::shared_array neighborMat = Tools::buildNeighborMatrix(mol); Tools::addNonbonded(mol, confId, params, res.get(), neighborMat, vdwThresh, ignoreInterfragInteractions); Tools::addTorsions(mol, params, res.get()); Tools::addInversions(mol, params, res.get()); return res.release(); } // ------------------------------------------------------------------------ // // // // ------------------------------------------------------------------------ ForceFields::ForceField *constructForceField(ROMol &mol, double vdwThresh, int confId, bool ignoreInterfragInteractions) { bool foundAll; AtomicParamVect params; boost::tie(params, foundAll) = getAtomTypes(mol); return constructForceField(mol, params, vdwThresh, confId, ignoreInterfragInteractions); } } // namespace UFF } // namespace RDKit