/* * sketcherMinimizerAtom.h * * Created by Nicola Zonta on 19/10/2010. * Copyright Schrodinger, LLC. All rights reserved. * */ #include #include #include #include "sketcherMinimizerAtom.h" #include "sketcherMinimizer.h" #include "sketcherMinimizerBond.h" #include "sketcherMinimizerMaths.h" #include "sketcherMinimizerRing.h" using namespace std; bool CIPAtom::operator<(const CIPAtom& rhs) const { /* check if this has priority over rhs. An atom is better than another if it has a parent in the chain that gets priority. Parents are evaluated starting from the furthest to the closest. Priority is assigned to the atom with highest atomic number, or has been found to have priority due to its children in previous iterations of the algorithm (scores) or in the present iteration (medals) */ assert(allParents.size() == rhs.allParents.size()); for (size_t i = 0; i < allParents.size(); i++) { if (allParents[i]->atomicNumber > rhs.allParents[i]->atomicNumber) { return true; } if (allParents[i]->atomicNumber < rhs.allParents[i]->atomicNumber) { return false; } if ((*scores)[allParents[i]] < (*rhs.scores)[rhs.allParents[i]]) { return true; } if ((*scores)[allParents[i]] > (*rhs.scores)[rhs.allParents[i]]) { return false; } vector meds = (*medals)[allParents[i]]; vector meds2 = (*rhs.medals)[rhs.allParents[i]]; size_t s = (meds.size() < meds2.size()) ? meds.size() : meds2.size(); for (size_t mm = 0; mm < s; mm++) { if (meds[mm] > meds2[mm]) { return true; } if (meds[mm] < meds2[mm]) { return false; } } if (meds.size() > meds2.size()) { return true; } if (meds2.size() > meds.size()) { return false; } } size_t siz = theseAtoms.size(); if (rhs.theseAtoms.size() < siz) { siz = rhs.theseAtoms.size(); } for (size_t i = 0; i < siz; i++) { if (theseAtoms[i].first > rhs.theseAtoms[i].first) { return true; } if (theseAtoms[i].first < rhs.theseAtoms[i].first) { return false; } } if (theseAtoms.size() > rhs.theseAtoms.size()) { return true; } if (theseAtoms.size() < rhs.theseAtoms.size()) { return false; } return false; } bool CIPAtom::operator==(const CIPAtom& rhs) const { assert(allParents.size() == rhs.allParents.size()); for (size_t i = 0; i < allParents.size(); i++) { if (allParents[i]->atomicNumber != rhs.allParents[i]->atomicNumber) { return false; } if ((*scores)[allParents[i]] != (*rhs.scores)[rhs.allParents[i]]) { return false; } } if (theseAtoms.size() != rhs.theseAtoms.size()) { return false; } for (size_t i = 0; i < theseAtoms.size(); i++) { if (theseAtoms[i].first != rhs.theseAtoms[i].first) { return false; } } return true; } std::ostream& operator<<(std::ostream& os, const CIPAtom& a) { for (size_t i = 0; i < a.allParents.size(); i++) { os << a.allParents[i]->atomicNumber << "(" << (*a.scores)[a.allParents[i]] << ")"; if ((*a.medals)[a.allParents[i]].size()) { cerr << "<"; for (int ii : (*a.medals)[a.allParents[i]]) { cerr << ii << "|"; } cerr << ">"; } cerr << " "; } os << "-"; for (const auto& theseAtom : a.theseAtoms) { os << " " << theseAtom.first; } return os; } bool CIPAtom::isBetter(CIPAtom& rhs, map* m) const { /* Similar to the < operator, but in estabilishing priorities also considers scores stored in m */ assert(allParents.size() == rhs.allParents.size()); for (size_t i = 0; i < allParents.size(); i++) { if ((*m)[allParents[i]] > (*m)[rhs.allParents[i]]) { return true; } if ((*m)[allParents[i]] < (*m)[rhs.allParents[i]]) { return false; } if (allParents[i]->atomicNumber > rhs.allParents[i]->atomicNumber) { return true; } if (allParents[i]->atomicNumber < rhs.allParents[i]->atomicNumber) { return false; } if ((*scores)[allParents[i]] < (*rhs.scores)[rhs.allParents[i]]) { return true; } if ((*scores)[allParents[i]] > (*rhs.scores)[rhs.allParents[i]]) { return false; } vector meds = (*medals)[allParents[i]]; vector meds2 = (*rhs.medals)[rhs.allParents[i]]; size_t s = (meds.size() < meds2.size()) ? meds.size() : meds2.size(); for (size_t mm = 0; mm < s; mm++) { if (meds[mm] > meds2[mm]) { return true; } if (meds[mm] < meds2[mm]) { return false; } } if (meds.size() > meds2.size()) { return true; } if (meds2.size() > meds.size()) { return false; } } size_t siz = theseAtoms.size(); if (rhs.theseAtoms.size() < siz) { siz = rhs.theseAtoms.size(); } for (size_t i = 0; i < siz; i++) { if (theseAtoms[i].first > rhs.theseAtoms[i].first) { return true; } if (theseAtoms[i].first < rhs.theseAtoms[i].first) { return false; } } if (theseAtoms.size() > rhs.theseAtoms.size()) { return true; } if (theseAtoms.size() < rhs.theseAtoms.size()) { return false; } return false; } sketcherMinimizerAtom::~sketcherMinimizerAtom() = default; sketcherMinimizerAtom::sketcherMinimizerAtom() : crossLayout(false), fixed(false), constrained(false), rigid(false), isSharedAndInner(false), atomicNumber(6), charge(0), _valence(-10), _generalUseN(-1), _generalUseN2(-1), m_chmN(-1), _generalUseVisited(false), _generalUseVisited2(false), fragment(nullptr), needsCheckForClashes(false), visited(false), coordinatesSet(false), isR(true), hasStereochemistrySet(false), _hasRingChirality(false) { hidden = false; m_pseudoZ = 0.f; m_pocketDistance = 0.f; m_x3D = INVALID_COORDINATES; m_y3D = INVALID_COORDINATES; m_z3D = INVALID_COORDINATES; m_isClashing = false; m_isLigand = false; m_isWaterMap = false; m_clockwiseInvert = false; m_isStereogenic = false; m_ignoreRingChirality = false; } sketcherMinimizerRing* sketcherMinimizerAtom::shareARing(const sketcherMinimizerAtom* atom1, const sketcherMinimizerAtom* atom2) { /* return a ring shared by the two atoms. return a non-macrocycle if * possible */ if (!atom1->rings.size()) { return nullptr; } if (!atom2->rings.size()) { return nullptr; } for (sketcherMinimizerRing* ring : atom1->rings) { if (ring->isMacrocycle()) { continue; } for (sketcherMinimizerRing* ring2 : atom2->rings) { if (ring == ring2) { return ring; } } } for (sketcherMinimizerRing* ring : atom1->rings) { for (sketcherMinimizerRing* ring2 : atom2->rings) { if (ring == ring2) { return ring; } } } return nullptr; } int sketcherMinimizerAtom::findHsNumber() const { int valence = _valence; if (valence == -10) { valence = expectedValence(atomicNumber); // valence is not yet set } int nBondOrders = 0; for (auto bond : bonds) { nBondOrders += bond->bondOrder; } if (atomicNumber == 16) { // sulfite & sulfate int nOs = 0; for (size_t i = 0; i < neighbors.size(); ++i) { if (neighbors[i]->atomicNumber == 8 && bonds[i]->bondOrder == 2) { ++nOs; } } if ((nOs) < 3) { valence += nOs * 2; } } if (atomicNumber == 15) { // P int nOs = 0; for (size_t i = 0; i < neighbors.size(); ++i) { if (neighbors[i]->atomicNumber == 8 && bonds[i]->bondOrder == 2) { ++nOs; } } if (nOs < 2) { valence += nOs * 2; } } int out = valence - nBondOrders + charge; if (out < 0) { out = 0; } else if (out > 4) { out = 4; } return out; } unsigned int sketcherMinimizerAtom::expectedValence(unsigned int atomicNumber) const { switch (atomicNumber) { case 1: return 1; case 5: return 3; case 6: return 4; case 7: return 3; case 8: return 2; case 9: return 1; case 14: return 4; case 15: return 3; case 16: return 2; case 17: return 1; case 34: return 2; case 35: return 1; case 53: return 1; default: return 4; } return 4; } vector sketcherMinimizerAtom::clockwiseOrderedNeighbors() const { vector orderedNeighs; vector neighs = neighbors; int lastPoppedIndex = 0; sketcherMinimizerAtom* lastPoppedAtom = neighs[lastPoppedIndex]; orderedNeighs.push_back(lastPoppedAtom); neighs.erase(neighs.begin() + lastPoppedIndex); while (neighs.size()) { float smallestAngle = 361; for (unsigned int i = 0; i < neighs.size(); i++) { float newAngle = sketcherMinimizerMaths::signedAngle( lastPoppedAtom->coordinates, coordinates, neighs[i]->coordinates); if (newAngle < 0) { newAngle += 360; } if (newAngle < smallestAngle) { smallestAngle = newAngle; lastPoppedIndex = i; } } lastPoppedAtom = neighs[lastPoppedIndex]; orderedNeighs.push_back(lastPoppedAtom); neighs.erase(neighs.begin() + lastPoppedIndex); } return orderedNeighs; } void sketcherMinimizerAtom::writeStereoChemistry() // sets stereochemistry for // this atom and from // hasStereochemistrySet and // isR { assert(neighbors.size() == bonds.size()); if (!hasStereochemistrySet) { return; } if (!canBeChiral()) { hasStereochemistrySet = false; return; } else { sketcherMinimizerAtom dummyH; dummyH.atomicNumber = 1; dummyH.molecule = molecule; sketcherMinimizerAtom dummyLP; dummyLP.atomicNumber = 0; dummyLP.molecule = molecule; sketcherMinimizerAtom* dummy = &dummyH; vector neighs = neighbors; vector orderedNeighs; vector bbonds = bonds; vector orderedBonds; vector angles; int lastPoppedIndex = 0; sketcherMinimizerAtom* lastPoppedAtom = neighs[lastPoppedIndex]; orderedNeighs.push_back(lastPoppedAtom); neighs.erase(neighs.begin() + lastPoppedIndex); orderedBonds.push_back(bbonds[lastPoppedIndex]); bbonds.erase(bbonds.begin() + lastPoppedIndex); // TODO: consider using sketcherMinimizerAtom::clockwiseOrderedNeighbors while (neighs.size()) { // order atoms float smallestAngle = 361; for (unsigned int i = 0; i < neighs.size(); i++) { float newAngle = sketcherMinimizerMaths::signedAngle( sketcherMinimizerPointF(lastPoppedAtom->coordinates.x(), lastPoppedAtom->coordinates.y()), sketcherMinimizerPointF(coordinates.x(), coordinates.y()), sketcherMinimizerPointF(neighs[i]->coordinates.x(), neighs[i]->coordinates.y())); if (newAngle < 0) { newAngle += 360; } if (newAngle < smallestAngle) { smallestAngle = newAngle; lastPoppedIndex = i; } } angles.push_back(smallestAngle); lastPoppedAtom = neighs[lastPoppedIndex]; orderedNeighs.push_back(lastPoppedAtom); neighs.erase(neighs.begin() + lastPoppedIndex); orderedBonds.push_back(bbonds[lastPoppedIndex]); bbonds.erase(bbonds.begin() + lastPoppedIndex); } if ((atomicNumber == 7 || atomicNumber == 16) && _implicitHs == 0 && orderedBonds.size() == 3) { dummy = &dummyLP; } bool four = true; float totalAngle = std::accumulate(angles.begin(), angles.end(), 0.f); angles.push_back(360.f - totalAngle); vector atomPriorities, orderedAtomPriorities; for (auto& orderedNeigh : orderedNeighs) { sketcherMinimizerAtomPriority p; p.a = orderedNeigh; atomPriorities.push_back(p); } if (atomPriorities.size() == 3) { four = false; sketcherMinimizerAtomPriority p; p.a = dummy; atomPriorities.push_back(p); } bool isStereocenter = setCIPPriorities(atomPriorities, this); if (!isStereocenter) { if (!m_ignoreRingChirality) { _hasRingChirality = true; isStereocenter = setCIPPriorities(atomPriorities, this); } } if (!isStereocenter) { _hasRingChirality = false; } orderedAtomPriorities = atomPriorities; orderAtomPriorities(orderedAtomPriorities, this); if (isStereocenter) { if (!four) { if (orderedAtomPriorities[0].a == dummy) { orderedAtomPriorities.push_back(orderedAtomPriorities[0]); orderedAtomPriorities.insert(orderedAtomPriorities.begin(), orderedAtomPriorities[3]); orderedAtomPriorities.erase(orderedAtomPriorities.begin() + 1); orderedAtomPriorities.erase(orderedAtomPriorities.begin() + 3); } } unsigned int startIndex = 0; for (unsigned int i = 0; i < atomPriorities.size(); i++) { if (atomPriorities[i].a == orderedAtomPriorities[0].a) { startIndex = i; break; } } for (unsigned int i = 0; i < startIndex; i++) { atomPriorities.push_back(atomPriorities[0]); atomPriorities.erase(atomPriorities.begin()); } if (four) { if (atomPriorities[1].a == orderedAtomPriorities[3].a) { atomPriorities.push_back(atomPriorities[0]); atomPriorities.erase(atomPriorities.begin()); } } sketcherMinimizerAtom* mainAtom = nullptr; if (four) { if (atomPriorities[0].a == orderedAtomPriorities[0].a) { mainAtom = atomPriorities[0].a; } if (atomPriorities[3].a == orderedAtomPriorities[0].a) { mainAtom = atomPriorities[3].a; } } bool invert = false; sketcherMinimizerBond* b1 = bondTo(atomPriorities[0].a); if (b1) { if (!four || mainAtom == atomPriorities[0].a || (!sketcherMinimizer::sameRing(this, atomPriorities[0].a) && !atomPriorities[0].a->hasStereochemistrySet)) { bool reverse = false; if (b1->startAtom != this) { reverse = true; } b1->isWedge = !invert; b1->hasStereochemistryDisplay = true; b1->isReversed = reverse; } else { b1->hasStereochemistryDisplay = false; } } if (four) { sketcherMinimizerBond* b2 = bondTo(atomPriorities[3].a); if (b2) { if (mainAtom == atomPriorities[3].a || (!sketcherMinimizer::sameRing(this, atomPriorities[3].a) && !atomPriorities[3].a->hasStereochemistrySet)) { bool reverse = false; if (b2->startAtom != this) { reverse = true; } b2->isWedge = invert; b2->hasStereochemistryDisplay = true; b2->isReversed = reverse; } else { b2->hasStereochemistryDisplay = false; } } } sketcherMinimizerBond* b3 = bondTo(atomPriorities[1].a); if (b3) { b3->hasStereochemistryDisplay = false; } sketcherMinimizerBond* b4 = bondTo(atomPriorities[2].a); if (b4) { b4->hasStereochemistryDisplay = false; } int readS = readStereochemistry(true); readS = -readS; // inverting stereochemistry cause isR is in // sketcher coords and readStereo in coordgen coords // (flipped y) if ((readS == -1 && isR) || (readS == 1 && !isR)) { // inverting invert = true; if (b1) { if (!four || mainAtom == atomPriorities[0].a || (!sketcherMinimizer::sameRing(this, atomPriorities[0].a) && !atomPriorities[0].a->hasStereochemistrySet)) { // cerr << "and setting it "<startAtom != this) { reverse = true; } b1->isWedge = !invert; b1->hasStereochemistryDisplay = true; b1->isReversed = reverse; } else { b1->hasStereochemistryDisplay = false; } } if (four) { sketcherMinimizerBond* b2 = bondTo(atomPriorities[3].a); if (b2) { if (mainAtom == atomPriorities[3].a || (!sketcherMinimizer::sameRing( this, atomPriorities[3].a) && !atomPriorities[3].a->hasStereochemistrySet)) { bool reverse = false; if (b2->startAtom != this) { reverse = true; } b2->isWedge = invert; b2->hasStereochemistryDisplay = true; b2->isReversed = reverse; } else { b2->hasStereochemistryDisplay = false; } } } } } else { for (auto b : bonds) { b->hasStereochemistryDisplay = false; } } } } sketcherMinimizerAtomChiralityInfo::sketcherMinimizerChirality sketcherMinimizerAtom::getRelativeStereo(sketcherMinimizerAtom* lookingFrom, sketcherMinimizerAtom* atom1, sketcherMinimizerAtom* atom2) { readStereochemistry(); // to set m_RSPriorities auto RSpriorities = m_RSPriorities; if (RSpriorities.size() < 3) { return sketcherMinimizerAtomChiralityInfo::unspecified; } vector priorities(4, 3); /* order the CIP priority of the atoms in the following order atom1 - atom2 - atom3 - atomLookingFrom */ for (unsigned int nn = 0; nn < neighbors.size(); nn++) { sketcherMinimizerAtom* n = neighbors[nn]; if (n == atom1) { priorities[0] = RSpriorities[nn]; } else if (n == atom2) { priorities[1] = RSpriorities[nn]; } else if (n == lookingFrom) { priorities[3] = RSpriorities[nn]; } else { priorities[2] = RSpriorities[nn]; } } vector can(4); for (unsigned int i = 0; i < 4; i++) { can[i] = i; } /* this represents a molecule with atom1 (priority 0 - highest) atom2 (priority 1) atom3 (priority 2) atomLookingFrom (priority 3 -lowest) which is the opposite of the CIP rules, where the the lowest priority atom is AWAY from the observer. This is the reason why we return CCW for R and CW for S. */ bool match = sketcherMinimizerAtom::matchCIPSequence(priorities, can); bool isClockWise = (match ? !isR : isR); if (isClockWise) { return sketcherMinimizerAtomChiralityInfo::clockwise; } return sketcherMinimizerAtomChiralityInfo::counterClockwise; } bool sketcherMinimizerAtom::setAbsoluteStereoFromChiralityInfo() { auto info = m_chiralityInfo; if (info.direction == sketcherMinimizerAtomChiralityInfo::unspecified) { return true; } readStereochemistry(); // to set m_RSPriorities auto RSpriorities = m_RSPriorities; if (RSpriorities.size() < 3) { cerr << "CHMMol-> sketcher stereo error: wrong number for RSpriorities" << endl; return false; } vector priorities(4, 5); bool at3 = false; for (unsigned int nn = 0; nn < neighbors.size(); nn++) { sketcherMinimizerAtom* n = neighbors[nn]; if (n == info.atom1) { priorities[0] = RSpriorities[nn]; } else if (n == info.atom2) { priorities[1] = RSpriorities[nn]; } else if (n == info.lookingFrom) { priorities[3] = RSpriorities[nn]; } else { if (at3) { cerr << "CHMMol-> sketcher stereo error: more than 1 atom not " "matching" << endl; return false; } else { at3 = true; priorities[2] = RSpriorities[nn]; } } } int addingHN = 0; if (priorities[0] == 5) { priorities[0] = 3; addingHN++; } if (priorities[1] == 5) { priorities[1] = 3; addingHN++; } if (priorities[2] == 5) { priorities[2] = 3; addingHN++; } if (priorities[3] == 5) { priorities[3] = 3; addingHN++; } if (addingHN > 1) { cerr << "CHMMol-> sketcher stereo error: more than 1 H on chiral center" << endl; return false; } bool invert = false; vector can(4); for (unsigned int i = 0; i < 4; i++) { can[i] = i; } if (!sketcherMinimizerAtom::matchCIPSequence(priorities, can)) { invert = !invert; } bool isRBool = true; if (info.direction == sketcherMinimizerAtomChiralityInfo::clockwise) { isRBool = false; } if (invert) { isRBool = !isRBool; } isR = isRBool; hasStereochemistrySet = true; return true; } bool sketcherMinimizerAtom::matchCIPSequence(vector& v1, vector& v2) { if (v1.size() < v2.size()) { v1.push_back(3); } else if (v2.size() < v1.size()) { v2.push_back(3); } int outofPlaceNs = 0; for (unsigned int i = 0; i < v1.size(); i++) { if (v1[i] != v2[i]) { outofPlaceNs++; } } if (outofPlaceNs == 2) { return false; } else if (outofPlaceNs == 4) { int n1 = v1[0]; int index2 = 0; for (unsigned int j = 0; j < v2.size(); j++) { if (v2[j] == n1) { index2 = j; break; } } if (v1[index2] != v2[0]) { return false; } } return true; } void sketcherMinimizerAtom::setCoordinates(sketcherMinimizerPointF coords) { coordinates = std::move(coords); coordinates.round(); coordinatesSet = true; } bool sketcherMinimizerAtom::hasNoStereoActiveBonds() const { for (auto bond : bonds) { if (bond->isStereo()) { return false; } } return true; } void sketcherMinimizerAtom::orderAtomPriorities( vector& atomPriorities, sketcherMinimizerAtom* center) // orders trying to keep long chains in // position 2 and 3 and side substituents in // 1 and 4 { assert(atomPriorities.size() == 4); vector weights(4); for (unsigned int i = 0; i < 4; i++) { queue q; for (auto& _atom : center->molecule->_atoms) { _atom->_generalUseVisited = false; } q.push(atomPriorities[i].a); center->_generalUseVisited = true; atomPriorities[i].a->_generalUseVisited = true; int counter = 0; while (q.size()) { counter++; sketcherMinimizerAtom* at = q.front(); q.pop(); for (auto n : at->neighbors) { if (!n->_generalUseVisited) { q.push(n); n->_generalUseVisited = true; } } } weights[i] = static_cast(counter); sketcherMinimizerBond* b = center->bondTo(atomPriorities[i].a); if (b) { if (b->bondOrder == 2) { weights[i] -= 0.25; // so that =O get lower priority than -OH in phosphate } if (center->atomicNumber == 16 && b->bondOrder == 2) { weights[i] += 2000; // forcing the wedge away from double bond } // in sulphoxide if (sketcherMinimizer::sameRing(b->startAtom, b->endAtom)) { weights[i] += 500; // force same ring atoms to be in position 3 and 4 } } if (atomPriorities[i].a->atomicNumber == 6) { weights[i] += 0.5; // carbons get priority over other heavy atoms } if (atomPriorities[i].a->atomicNumber == 1) { weights[i] -= 0.5; } if (atomPriorities[i].a->isSharedAndInner && !center->isSharedAndInner) { weights[i] -= 2000; // forced bond to shared and inner } if (center->crossLayout) { if (atomPriorities[i].a->neighbors.size() > 1) { weights[i] += 200; } } if (/* atomPriorities[i].a->isStereogenic && */ atomPriorities[i] .a->hasStereochemistrySet) { weights[i] += 10000; // to avoid problems with wedges when 2 } // stereocenters are near for (unsigned int j = 0; j < atomPriorities[i].a->bonds.size(); j++) { if (atomPriorities[i].a->bonds[j]->bondOrder == 2) { weights[i] += 100; break; } } } float lowestWeight = weights[0]; int index = 0; sketcherMinimizerAtomPriority firstAtom = atomPriorities[0]; for (unsigned int i = 1; i < 4; i++) { if (weights[i] < lowestWeight) { lowestWeight = weights[i]; index = i; } } firstAtom = atomPriorities[index]; atomPriorities.erase(atomPriorities.begin() + index); weights.erase(weights.begin() + index); index = 0; lowestWeight = weights[0]; sketcherMinimizerAtomPriority secondAtom = atomPriorities[0]; for (unsigned int i = 1; i < 3; i++) { if (weights[i] < lowestWeight) { lowestWeight = weights[i]; index = i; } } secondAtom = atomPriorities[index]; atomPriorities.erase(atomPriorities.begin() + index); if ((center->atomicNumber != 16 && center->atomicNumber != 15) || center->neighbors.size() != 4) { atomPriorities.push_back(secondAtom); atomPriorities.insert(atomPriorities.begin(), firstAtom); } else { atomPriorities.insert(atomPriorities.begin() + 1, secondAtom); atomPriorities.insert(atomPriorities.begin(), firstAtom); } } bool sketcherMinimizerAtom::setCIPPriorities( vector& atomPriorities, sketcherMinimizerAtom* center) { for (auto& atomPrioritie : atomPriorities) { atomPrioritie.priority = 3; } if (atomPriorities.size() != 4) { // cerr << "coordgen: stereo error. (wrong number of atom priorities: // "<< atomPriorities.size () << ")"< found(4, false); for (auto& atomPrioritie : atomPriorities) { if (found[atomPrioritie.priority]) { return false; // two atoms have the same priority } found[atomPrioritie.priority] = true; } return true; } sketcherMinimizerAtom* sketcherMinimizerAtom::CIPPriority(sketcherMinimizerAtom* at1, sketcherMinimizerAtom* at2, sketcherMinimizerAtom* center) { assert(center->molecule); assert(at1->molecule == center->molecule); assert(at2->molecule == center->molecule); assert(center->molecule->_atoms.size()); assert(at1); assert(at2); if (at1->atomicNumber > at2->atomicNumber) { return at1; } else if (at2->atomicNumber > at1->atomicNumber) { return at2; } if (center->_hasRingChirality && !center->m_ignoreRingChirality) { if (sketcherMinimizer::sameRing(center, at1, at2)) { if (at1->_generalUseN > at2->_generalUseN) { return at1; } else { return at2; } } } vector AN1, AN2; map score1, score2; // used to keep track if a parent atom has been found to have // priority over another map> medals1, medals2; // marks if an atom is a parent of the atoms being evaluated in // the current iteration map visited1, visited2; // marks at which iteration this atom was evaluated visited1[center] = 1; visited2[center] = 1; visited1[at1] = 2; visited2[at2] = 2; vector> v1, v2; v1.emplace_back(at1->atomicNumber, at1); v2.emplace_back(at2->atomicNumber, at2); vector parents1, parents2; parents1.push_back(center); parents1.push_back(at1); parents2.push_back(center); parents2.push_back(at2); AN1.emplace_back(v1, center, parents1, &score1, &medals1, &visited1); AN2.emplace_back(v2, center, parents2, &score2, &medals2, &visited2); int level = 1; while (AN1.size() || AN2.size()) { level++; stable_sort(AN1.begin(), AN1.end()); stable_sort(AN2.begin(), AN2.end()); sketcherMinimizerAtom::assignMedals(AN1); sketcherMinimizerAtom::assignMedals(AN2); sketcherMinimizerAtom::chooseFirstAndSortAccordingly(AN1); sketcherMinimizerAtom::chooseFirstAndSortAccordingly(AN2); sketcherMinimizerAtom::finalizeScores(AN1); sketcherMinimizerAtom::finalizeScores(AN2); size_t nn = AN1.size(); if (AN2.size() < nn) { nn = AN2.size(); } for (size_t i = 0; i < nn; ++i) { if (AN1[i] < AN2[i]) { return at1; } if (AN2[i] < AN1[i]) { return at2; } } if (AN1.size() > AN2.size()) { return at1; } if (AN2.size() > AN1.size()) { return at2; } AN1 = sketcherMinimizerAtom::expandOneLevel(AN1); AN2 = sketcherMinimizerAtom::expandOneLevel(AN2); } return nullptr; } void sketcherMinimizerAtom::chooseFirstAndSortAccordingly(vector& V) { if (V.size() < 2) { return; } vector copyV = V; V.clear(); map friendsMask; while (copyV.size()) { int bestI = 0; for (unsigned int i = 1; i < copyV.size(); i++) { if (copyV[i].isBetter(copyV[bestI], &friendsMask)) { bestI = i; } } CIPAtom newBest = copyV[bestI]; copyV.erase(copyV.begin() + bestI); V.push_back(newBest); for (auto allParent : newBest.allParents) { friendsMask[allParent] |= (1 << copyV.size()); } } } vector sketcherMinimizerAtom::expandOneLevel(vector& oldV) { // we need to keep the bound atoms together, one by one will not work. map visitedThisRound; vector newV; for (auto& an : oldV) { for (unsigned int aa = 0; aa < an.theseAtoms.size(); aa++) { sketcherMinimizerAtom* a = an.theseAtoms[aa].second; if (a == nullptr) { continue; // dummy atom } // if (visitedThisRound[a]) continue; // a is present twice // because closing a ring and has already been dealt with visitedThisRound[a] = true; map* visited = an.visited; map>* medals = an.medals; map* scores = an.scores; vector allParents = an.allParents; allParents.push_back(a); vector> theseAts; for (unsigned int n = 0; n < a->bonds.size(); n++) { sketcherMinimizerAtom* neigh = a->neighbors[n]; if (neigh != an.parent) { // if n is not the direct parent of a bool ghost = (neigh->atomicNumber == 1 || ((*visited)[neigh] && (*visited)[neigh] != (*visited)[a] + 1) // closing a ring to an atom // already visited in a // previous cycle ); theseAts.emplace_back( neigh->atomicNumber, ghost ? ((sketcherMinimizerAtom*) nullptr) : neigh); // put a ghost for hydrogens and atoms // closing a ring if (!ghost) { (*visited)[neigh] = (*visited)[a] + 1; } } if (a->bonds[n]->bondOrder == 2) { // put ghosts for multiple // order bonds, even to the // parent theseAts.emplace_back(neigh->atomicNumber, (sketcherMinimizerAtom*) nullptr); } else if (a->bonds[n]->bondOrder == 3) { theseAts.emplace_back(neigh->atomicNumber, (sketcherMinimizerAtom*) nullptr); theseAts.emplace_back(neigh->atomicNumber, (sketcherMinimizerAtom*) nullptr); } } for (int counter = 0; counter < a->_implicitHs; counter++) { // put ghosts for implicit Hs theseAts.emplace_back(1, (sketcherMinimizerAtom*) nullptr); } stable_sort(theseAts.begin(), theseAts.end()); reverse(theseAts.begin(), theseAts.end()); newV.emplace_back(theseAts, a, allParents, scores, medals, visited); } } return newV; } void sketcherMinimizerAtom::assignMedals(vector& v) { if (v.size() < 1) { return; } map>* medals = v[0].medals; vector isEqualToPrevious(v.size()); for (unsigned int i = 1; i < v.size(); i++) { // need to be done before assigning the medals because they are // considered in the == operator isEqualToPrevious[i] = (v[i] == v[i - 1]); } unsigned int medalLvl = 0; for (unsigned int i = 0; i < v.size(); i++) { if (i > 0) { if (isEqualToPrevious[i]) { assert(medalLvl > 0); medalLvl--; } } for (auto allParent : v[i].allParents) { vector medalsV = (*medals)[allParent]; while (medalsV.size() < medalLvl) { medalsV.push_back(0); } if (medalsV.size() > medalLvl) { medalsV[medalLvl] = medalsV[medalLvl] + 1; } else { medalsV.push_back(1); } (*medals)[allParent] = medalsV; } medalLvl++; } } void sketcherMinimizerAtom::finalizeScores(vector& v) { if (v.size() < 1) { return; } vector isEqualToPrevious(v.size()); for (unsigned int i = 1; i < v.size(); i++) { // need to be done before assigning the scores because they are // considered in the == operator isEqualToPrevious[i] = (v[i] == v[i - 1]); } map>* medals = v[0].medals; map* scores = v[0].scores; scores->clear(); int score = 1; for (unsigned int i = 0; i < v.size(); i++) { if (i > 0) { if (isEqualToPrevious[i]) { score--; } } /* write the score */ for (auto allParent : v[i].allParents) { if ((*scores)[allParent] == 0) { (*scores)[allParent] = score; } } score++; } medals->clear(); } sketcherMinimizerBond* sketcherMinimizerAtom::bondTo(sketcherMinimizerAtom* at) const { for (unsigned int i = 0; i < neighbors.size(); i++) { if (neighbors[i] == at) { return bonds[i]; } } return nullptr; } bool sketcherMinimizerAtom::isResidue() const { return false; } int sketcherMinimizerAtom::readStereochemistry( bool readOnly) // 0 if not assigned, 1 if R, -1 if S { if (!readOnly) { _hasRingChirality = false; m_isStereogenic = false; } if (!canBeChiral()) { return 0; } // if (neighbors.size () != 4 && neighbors.size () != 3) return 0; sketcherMinimizerAtom dummyH; dummyH.atomicNumber = 1; dummyH.molecule = molecule; sketcherMinimizerAtom dummyLP; dummyLP.atomicNumber = 0; dummyLP.molecule = molecule; vector neighs = neighbors; vector orderedNeighs; vector bnds = bonds; vector orderedBonds; vector angles; int lastPoppedIndex = 0; sketcherMinimizerAtom* lastPoppedAtom = neighs[lastPoppedIndex]; orderedNeighs.push_back(lastPoppedAtom); neighs.erase(neighs.begin() + lastPoppedIndex); orderedBonds.push_back(bnds[lastPoppedIndex]); bnds.erase(bnds.begin() + lastPoppedIndex); // TODO: consider using sketcherMinimizerAtom::clockwiseOrderedNeighbors while (neighs.size()) { // order atoms float smallestAngle = 361; for (unsigned int i = 0; i < neighs.size(); i++) { float newAngle = sketcherMinimizerMaths::signedAngle( lastPoppedAtom->coordinates, coordinates, neighs[i]->coordinates); if (newAngle < 0) { newAngle += 360; } if (newAngle < smallestAngle) { smallestAngle = newAngle; lastPoppedIndex = i; } } angles.push_back(smallestAngle); lastPoppedAtom = neighs[lastPoppedIndex]; orderedNeighs.push_back(lastPoppedAtom); neighs.erase(neighs.begin() + lastPoppedIndex); orderedBonds.push_back(bnds[lastPoppedIndex]); bnds.erase(bnds.begin() + lastPoppedIndex); } float totalAngle = 0; for (float angle : angles) { totalAngle += angle; } angles.push_back(360.f - totalAngle); bool semiplane = false; sketcherMinimizerAtom* centralAtom = nullptr; if (angles.size() == 3) { for (unsigned int i = 0; i < angles.size(); i++) { if (angles[i] > 180.f) { semiplane = true; size_t precI; if (i == 0) { precI = angles.size() - 1; } else { precI = i - 1; } centralAtom = orderedNeighs[precI]; } } } int wedgeN = -1; int dashedN = -1; bool giveUp = false; assert(orderedBonds.size() == 4 || orderedBonds.size() == 3); for (unsigned int i = 0; i < orderedBonds.size(); i++) { if (orderedBonds[i]->hasStereochemistryDisplay) { if (!orderedBonds[i]->isReversed && orderedBonds[i]->startAtom != this) { continue; } if (orderedBonds[i]->isReversed && orderedBonds[i]->endAtom != this) { continue; } bool wedge = orderedBonds[i]->isWedge; if (orderedBonds[i]->isReversed) { wedge = !wedge; } if (orderedBonds[i]->startAtom != this) { wedge = !wedge; } if (wedge) { if (wedgeN == -1) { wedgeN = i; } else { giveUp = true; } } else { if (dashedN == -1) { dashedN = i; } else { giveUp = true; } } } } int startIndex = 0; bool invert = false; if (dashedN == -1 && wedgeN == -1) { giveUp = true; } else if (dashedN == -1) { startIndex = wedgeN; } else if (wedgeN == -1) { startIndex = dashedN; invert = true; } else { if (orderedBonds.size() == 3) { return 0; } if (wedgeN - dashedN == 1 || wedgeN - dashedN == -3) { startIndex = wedgeN; } else if (wedgeN - dashedN == -1 || wedgeN - dashedN == 3) { startIndex = dashedN; invert = true; } else { giveUp = true; } } int nOfHs = _implicitHs; size_t totalSubstituentsN = neighbors.size() + nOfHs; if (orderedNeighs.size() == 3 && nOfHs == 1) { if (semiplane) { if (centralAtom == orderedNeighs[startIndex]) { invert = !invert; } } orderedNeighs.insert(orderedNeighs.begin() + startIndex, &dummyH); invert = !invert; } if (orderedNeighs.size() == 3 && (atomicNumber == 7 || atomicNumber == 16)) { // nitrogen or sulphoxide // should we check for // the =O on the sulphoxide? orderedNeighs.insert(orderedNeighs.begin() + startIndex, &dummyLP); invert = !invert; } vector atomPriorities; for (auto& orderedNeigh : orderedNeighs) { sketcherMinimizerAtomPriority p; p.a = orderedNeigh; atomPriorities.push_back(p); } if (atomPriorities.size() != 4) { return 0; } vector canonical; canonical.push_back(0); canonical.push_back(1); canonical.push_back(2); canonical.push_back(3); m_RSPriorities.clear(); bool isStereocenter = setCIPPriorities(atomPriorities, this); if (!isStereocenter) { if (!readOnly) { if (!m_ignoreRingChirality) { _hasRingChirality = true; isStereocenter = setCIPPriorities(atomPriorities, this); } } } if (!isStereocenter) { if (!readOnly) { _hasRingChirality = false; } } if (!isStereocenter) { giveUp = true; } else { if (!readOnly) { m_isStereogenic = true; } } if (totalSubstituentsN < 4 && (atomicNumber != 7 && atomicNumber != 16)) { if (!readOnly) { m_isStereogenic = false; } } if (!m_isStereogenic) { if (!readOnly) { _hasRingChirality = false; } } for (auto n : neighbors) { for (auto& atomPrioritie : atomPriorities) { if (atomPrioritie.a == n) { m_RSPriorities.push_back(atomPrioritie.priority); break; } } } if (!matchCIPSequence(canonical, m_RSPriorities)) { m_clockwiseInvert = true; } else { m_clockwiseInvert = false; } if (!giveUp) { int outofPlaceAtoms = 0; for (unsigned int i = 0; i < atomPriorities.size(); i++) { // cerr <atomicNumber< 3) { n -= 4; } if (atomPriorities[n].priority != i) { outofPlaceAtoms++; } } if (outofPlaceAtoms == 2) { invert = !invert; } else if (outofPlaceAtoms == 4) { int n2 = atomPriorities[startIndex].priority + startIndex; if (n2 > 3) { n2 -= 4; } if (atomPriorities[n2].priority != 0) { invert = !invert; // if I swapped position 0 with another will } // that settle both atoms? } if (invert) { return -1; } else { return 1; } } return 0; } bool sketcherMinimizerAtom::canBeChiral() const { if (atomicNumber == 16) { if (neighbors.size() == 3) { return true; } } if (atomicNumber == 7) { if (neighbors.size() == 3 || neighbors.size() == 4) { return true; } } if (neighbors.size() != 3 && neighbors.size() != 4) { return false; } if ((neighbors.size() + _implicitHs != 4)) { return false; } return true; } /* get a vector that points out towards the most free region of space around the * atom. Used to determined where an arrow to the atom will point from */ sketcherMinimizerPointF sketcherMinimizerAtom::getSingleAdditionVector( const vector& ats) { sketcherMinimizerPointF out(0.f, 0.f); int n = 0; for (unsigned int i = 0; i < ats.size(); i++) { sketcherMinimizerAtom* at = ats[i]; for (unsigned int j = 0; j < at->neighbors.size(); j++) { sketcherMinimizerAtom* ne = at->neighbors[j]; if (ne->neighbors.size() > 1 && find(ats.begin(), ats.end(), ne) == ats.end()) { out += ne->coordinates - at->coordinates; n++; } } } if (n > 0) { out /= n; } else { return sketcherMinimizerPointF(50, 0); } out *= -1; return out; } sketcherMinimizerPointF sketcherMinimizerAtom::getSingleAdditionVector() const { sketcherMinimizerPointF out(0.f, 0.f); float totalf = 0.f; if (neighbors.size()) { for (auto neighbor : neighbors) { float f = 1.f; if (sketcherMinimizer::sameRing(this, neighbor)) { f = 4.f; } out += f * (neighbor->coordinates - coordinates); totalf += f; } out /= totalf; } out *= -1; return out; } bool sketcherMinimizerAtom::isMetal(const unsigned int atomicNumber) { if (atomicNumber >= 3 && atomicNumber <= 4) { return true; } if (atomicNumber >= 11 && atomicNumber <= 12) { return true; } if (atomicNumber >= 19 && atomicNumber <= 20) { return true; } if (atomicNumber >= 37 && atomicNumber <= 38) { return true; } if (atomicNumber >= 55 && atomicNumber <= 56) { return true; } if (atomicNumber >= 87 && atomicNumber <= 88) { return true; } if (atomicNumber >= 21 && atomicNumber <= 30) { return true; } if (atomicNumber >= 39 && atomicNumber <= 48) { return true; } if (atomicNumber >= 72 && atomicNumber <= 80) { return true; } if (atomicNumber >= 104 && atomicNumber <= 112) { return true; } if (atomicNumber >= 57 && atomicNumber <= 71) { return true; // lanthanoids } if (atomicNumber >= 89 && atomicNumber <= 103) { return true; // actinoids } if (atomicNumber == 13) { return true; } if (atomicNumber == 31) { return true; } if (atomicNumber == 49) { return true; } if (atomicNumber == 81) { return true; } if (atomicNumber == 32) { return true; } if (atomicNumber == 50) { return true; } if (atomicNumber == 82) { return true; } if (atomicNumber == 51) { return true; } if (atomicNumber == 83) { return true; } if (atomicNumber == 84) { return true; } return false; } void sketcherMinimizerAtom::mirrorCoordinates(sketcherMinimizerAtom* at, const sketcherMinimizerBond* bond) { at->setCoordinates(sketcherMinimizerMaths::mirrorPoint( at->getCoordinates(), bond->getStartAtom()->getCoordinates(), bond->getEndAtom()->getCoordinates())); } bool sketcherMinimizerAtom::hasValid3DCoordinates() const { return (m_x3D < INVALID_COORDINATES && m_y3D < INVALID_COORDINATES && m_z3D < INVALID_COORDINATES); } vector sketcherMinimizerAtom::getSubmolecule(sketcherMinimizerAtom* excludedAtom) { vector subMolecule; queue q; map isVisited; isVisited[excludedAtom] = true; q.push(this); isVisited[this] = true; while (q.size()) { sketcherMinimizerAtom* atom = q.front(); subMolecule.push_back(atom); q.pop(); for (sketcherMinimizerAtom* neighbor : atom->neighbors) { if (!isVisited[neighbor]) { q.push(neighbor); isVisited[neighbor] = true; } } } return subMolecule; }