/* * sketcherMinimizer.cpp * 2d_sketcher * * Created by Nicola Zonta on 13/04/2010. * Copyright Schrodinger, LLC. All rights reserved * */ #include "sketcherMinimizer.h" #include #include #include #include #include "CoordgenTemplates.h" #ifdef USE_MAEPARSER #include "sketcherMaeReading.h" #endif #include "CoordgenFragmenter.h" #include "CoordgenMacrocycleBuilder.h" #include "sketcherMinimizerBendInteraction.h" #include "sketcherMinimizerClashInteraction.h" #include "sketcherMinimizerMaths.h" #include "sketcherMinimizerStretchInteraction.h" #include "sketcherMinimizerRing.h" using namespace std; using namespace schrodinger; #define RESIDUE_CLASH_DISTANCE_SQUARED 2.0 * 2.0 #ifndef M_PI #define M_PI 3.1415926535897931 #endif const int bondLength = BONDLENGTH; static const unsigned int MINIMUM_LIGAND_ATOMS = 8; static const float SCORE_MULTIPLIER_FOR_DOUBLE_BONDS = 0.82f; static const float SCORE_MULTIPLIER_FOR_SINGLE_BONDED_HETEROATOMS = 0.9f; static const float SCORE_MULTIPLIER_FOR_FRAGMENTS = 0.1f; const int MAX_NUMBER_OF_RINGS = 40; sketcherMinimizer::sketcherMinimizer(float precision) { m_fragmentBuilder.m_evenAngles = false; m_minimizer.m_evenAngles = false; m_minimizer.setPrecision(precision); m_fragmentBuilder.setPrecision(precision); } sketcherMinimizer::~sketcherMinimizer() { clear(); } void sketcherMinimizer::setScoreResidueInteractions(bool b) { m_minimizer.m_scoreResidueInteractions = b; } void sketcherMinimizer::canonicalOrdering(sketcherMinimizerMolecule* minMol) { vector scores; for (unsigned int i = 0; i < minMol->_atoms.size(); i++) { minMol->_atoms[i]->_generalUseN = i; } sketcherMinimizer::morganScores(minMol->_atoms, minMol->_bonds, scores); if (scores.size() == minMol->_atoms.size()) { for (unsigned int i = 0; i < scores.size(); i++) { scores[i] *= 100; scores[i] += minMol->_atoms[i]->atomicNumber; } for (sketcherMinimizerAtom* at : minMol->_atoms) { at->neighbors.clear(); at->bonds.clear(); } for (sketcherMinimizerBond* bo : minMol->_bonds) { bo->startAtom->neighbors.push_back(bo->endAtom); bo->endAtom->neighbors.push_back(bo->startAtom); bo->startAtom->bonds.push_back(bo); bo->endAtom->bonds.push_back(bo); } vector newAtoms; vector newBonds; for (unsigned int i = 0; i < minMol->_atoms.size(); i++) { minMol->_atoms[i]->_generalUseN = i; minMol->_atoms[i]->_generalUseVisited = false; } for (auto& _bond : minMol->_bonds) { _bond->_SSSRVisited = false; } bool found = true; do { int scoreMaxI = -1; for (unsigned int i = 0; i < scores.size(); i++) { if (minMol->_atoms[i]->_generalUseVisited) { continue; } if (scoreMaxI == -1) { scoreMaxI = i; } else if (scores[i] > scores[scoreMaxI]) { scoreMaxI = i; } } if (scoreMaxI > -1) { queue q; q.push(minMol->_atoms[scoreMaxI]); minMol->_atoms[scoreMaxI]->_generalUseVisited = true; while (q.size()) { sketcherMinimizerAtom* at = q.front(); newAtoms.push_back(at); q.pop(); int neighI = -1; do { neighI = -1; for (unsigned int i = 0; i < at->neighbors.size(); i++) { if (at->bonds[i]->_SSSRVisited) { continue; } else { if (neighI == -1) { neighI = i; } else { if (scores[at->neighbors[neighI] ->_generalUseN] < scores[at->neighbors[i] ->_generalUseN]) { neighI = i; } } } } if (neighI > -1) { if (!at->neighbors[neighI]->_generalUseVisited) { at->neighbors[neighI]->_generalUseVisited = true; q.push(at->neighbors[neighI]); } at->bonds[neighI]->_SSSRVisited = true; newBonds.push_back(at->bonds[neighI]); } } while (neighI > -1); } } else { found = false; } } while (found); minMol->_atoms = newAtoms; minMol->_bonds = newBonds; } } void sketcherMinimizer::initialize( sketcherMinimizerMolecule* minMol) // min mol is split into molecules if // needed and then added to the minimizer { clear(); _referenceAtoms = minMol->_atoms; _referenceBonds = minMol->_bonds; std::map bondsToAtom; for (auto& _bond : minMol->_bonds) { bondsToAtom[_bond->startAtom]++; bondsToAtom[_bond->endAtom]++; } for (auto& _bond : minMol->_bonds) { if (_bond->skip) { continue; } if (getTreatNonterminalBondsToMetalAsZOBs()) { if (_bond->bondOrder == 1 || _bond->bondOrder == 2) { bool terminalBond = bondsToAtom[_bond->startAtom] == 1 || bondsToAtom[_bond->endAtom] == 1; if (!terminalBond && (sketcherMinimizerAtom::isMetal( _bond->startAtom->atomicNumber) || sketcherMinimizerAtom::isMetal(_bond->endAtom->atomicNumber))) { _bond->bondOrder = 0; } } } } for (auto& _bond : minMol->_bonds) { if (_bond->skip) { continue; } if (_bond->bondOrder == 0) { m_proximityRelations.push_back(_bond); } else if (_bond->isResidueInteraction()) { if (!_bond->startAtom->isResidue() && !_bond->endAtom->isResidue()) { m_proximityRelations.push_back(_bond); } } } for (auto& m_extraBond : m_extraBonds) { if (m_extraBond->skip) { continue; } if (m_extraBond->bondOrder == 0) { m_proximityRelations.push_back(m_extraBond); } else if (m_extraBond->isResidueInteraction()) { if (!m_extraBond->startAtom->isResidue() && !m_extraBond->endAtom->isResidue()) { m_proximityRelations.push_back(m_extraBond); } } } { // remove skipped and hidden bonds auto new_end = std::remove_if( minMol->_bonds.begin(), minMol->_bonds.end(), [](sketcherMinimizerBond* b) { return (b->skip || b->bondOrder == 0 || b->startAtom->hidden || b->endAtom->hidden); }); minMol->_bonds.erase(new_end, minMol->_bonds.end()); } { // remove hidden atoms atoms auto new_end = std::remove_if( minMol->_atoms.begin(), minMol->_atoms.end(), [](sketcherMinimizerAtom* a) { return (a->hidden); }); minMol->_atoms.erase(new_end, minMol->_atoms.end()); } // order atoms and bonds using morgan indices to make the result input // order independent canonicalOrdering(minMol); for (auto a : minMol->_atoms) { if (!a->hidden) { _atoms.push_back(a); } if (a->isResidue()) { _residues.push_back(static_cast(a)); } } for (auto b : minMol->_bonds) { if (!b->startAtom->hidden && !b->endAtom->hidden) { _bonds.push_back(b); } if (b->isResidueInteraction()) { _residueInteractions.push_back( static_cast(b)); } } minMol->forceUpdateStruct(minMol->_atoms, minMol->_bonds, minMol->_rings); splitIntoMolecules(minMol, _molecules); for (auto& b : m_proximityRelations) { b->startAtom->molecule->m_proximityRelations.push_back(b); if (b->endAtom != b->startAtom) { b->endAtom->molecule->m_proximityRelations.push_back(b); } } flagCrossAtoms(); m_minimizer._atoms = _atoms; m_minimizer._bonds = _bonds; m_minimizer._molecules = _molecules; m_minimizer._residues = _residues; m_minimizer._residueInteractions = _residueInteractions; } bool sketcherMinimizer::structurePassSanityCheck() const { if (!_atoms.size()) { return false; } for (auto molecule : _molecules) { if (molecule->_rings.size() > MAX_NUMBER_OF_RINGS) { return false; } } return true; } bool sketcherMinimizer::runGenerateCoordinates() { bool cleanPose = true; if (structurePassSanityCheck()) { findFragments(); m_minimizer.buildFromFragments(true); cleanPose = m_minimizer.avoidClashes(); bestRotation(); maybeFlip(); arrangeMultipleMolecules(); writeStereoChemistry(); } return cleanPose; } void sketcherMinimizer::flagCrossAtoms() { for (auto at : _atoms) { if (at->atomicNumber == 16 || at->atomicNumber == 15) { at->crossLayout = true; } } for (auto at : _atoms) { if (at->crossLayout) { continue; } int cross = 0; for (auto n : at->neighbors) { if (n->neighbors.size() > 3) { cross++; } } if (cross > 2) { at->crossLayout = true; } } } void sketcherMinimizer::clear() { for (auto& _referenceAtom : _referenceAtoms) { delete _referenceAtom; } _referenceAtoms.clear(); _residues.clear(); for (auto& _referenceBond : _referenceBonds) { delete _referenceBond; } _referenceBonds.clear(); for (auto& m_extraBond : m_extraBonds) { delete m_extraBond; } m_extraBonds.clear(); for (auto& _fragment : _fragments) { delete _fragment; } _fragments.clear(); for (auto& _molecule : _molecules) { delete _molecule; } _molecules.clear(); } void sketcherMinimizer::splitIntoMolecules( sketcherMinimizerMolecule* mol, vector& mols) { if (!mol->_atoms.size()) { mols.push_back(mol); return; } for (sketcherMinimizerAtom* a : mol->_atoms) { a->_generalUseVisited = false; } queue q; q.push(mol->_atoms[0]); for (sketcherMinimizerAtom* a : mol->_atoms) { if (!a->hidden) { q.push(a); break; } } while (q.size()) { sketcherMinimizerAtom* a = q.front(); q.pop(); a->_generalUseVisited = true; for (sketcherMinimizerAtom* n : a->neighbors) { if (!n->_generalUseVisited && !n->hidden) { q.push(n); } } } vector newAtoms; for (sketcherMinimizerAtom* a : mol->_atoms) { if (!a->_generalUseVisited && !a->hidden) { newAtoms.push_back(a); } } if (!newAtoms.size()) { mols.push_back(mol); for (sketcherMinimizerMolecule* m : mols) { for (sketcherMinimizerAtom* a : m->_atoms) { a->_generalUseVisited = false; } } } else { auto* newMol = new sketcherMinimizerMolecule; for (unsigned int i = 0; i < mol->_rings.size(); i++) { if (!mol->_rings[i]->_atoms[0]->_generalUseVisited) { newMol->_rings.push_back(mol->_rings[i]); mol->_rings.erase(mol->_rings.begin() + i); i--; } } for (unsigned int i = 0; i < mol->_bonds.size(); i++) { if (!mol->_bonds[i]->startAtom->_generalUseVisited) { newMol->_bonds.push_back(mol->_bonds[i]); mol->_bonds.erase(mol->_bonds.begin() + i); i--; } } for (unsigned int i = 0; i < mol->_atoms.size(); i++) { if (!mol->_atoms[i]->_generalUseVisited) { mol->_atoms[i]->molecule = newMol; newMol->_atoms.push_back(mol->_atoms[i]); mol->_atoms.erase(mol->_atoms.begin() + i); i--; } } mols.push_back(mol); splitIntoMolecules(newMol, mols); } } sketcherMinimizerRing* sketcherMinimizer::sameRing(const sketcherMinimizerAtom* at1, const sketcherMinimizerAtom* at2) { return sketcherMinimizerAtom::shareARing(at1, at2); } sketcherMinimizerRing* sketcherMinimizer::sameRing(const sketcherMinimizerAtom* at1, const sketcherMinimizerAtom* at2, const sketcherMinimizerAtom* at3) { if (!at1->rings.size()) { return nullptr; } if (!at2->rings.size()) { return nullptr; } if (!at3->rings.size()) { return nullptr; } sketcherMinimizerRing* r = nullptr; for (sketcherMinimizerRing* ring : at1->rings) { if (ring->isMacrocycle()) { continue; } for (sketcherMinimizerRing* ring2 : at2->rings) { if (ring != ring2) { continue; } for (sketcherMinimizerRing* ring3 : at3->rings) { if (ring3 == ring2) { if (!r) { r = ring2; } else if (ring2->_atoms.size() < r->_atoms.size()) { r = ring2; } } } } } for (sketcherMinimizerRing* ring : at1->rings) { for (sketcherMinimizerRing* ring2 : at2->rings) { if (ring != ring2) { continue; } for (sketcherMinimizerRing* ring3 : at3->rings) { if (ring3 == ring2) { if (!r) { r = ring2; } else if (ring2->_atoms.size() < r->_atoms.size()) { r = ring2; } } } } } return r; } void sketcherMinimizer::writeStereoChemistry() { for (sketcherMinimizerAtom* a : _atoms) { if (a->hasStereochemistrySet) { a->writeStereoChemistry(); } } assignPseudoZ(); } void sketcherMinimizer::assignPseudoZ() { for (sketcherMinimizerMolecule* mol : _molecules) { for (sketcherMinimizerAtom* a : mol->_atoms) { a->_generalUseVisited = false; } sketcherMinimizerAtom* lastAtom = nullptr; bool finished = false; while (!finished) { lastAtom = nullptr; for (sketcherMinimizerAtom* a : mol->_atoms) { if (!a->_generalUseVisited) { lastAtom = a; break; } } if (lastAtom) { queue q; q.push(lastAtom); while (q.size()) { lastAtom = q.front(); q.pop(); lastAtom->_generalUseVisited = true; for (unsigned int i = 0; i < lastAtom->neighbors.size(); i++) { if (lastAtom->neighbors[i]->_generalUseVisited) { continue; } float Z = lastAtom->m_pseudoZ; sketcherMinimizerBond* b = lastAtom->bonds[i]; if (b->hasStereochemistryDisplay) { if (b->isWedge) { if ((b->startAtom == lastAtom && !b->isReversed) || (b->endAtom == lastAtom && b->isReversed)) { Z += 1.f; } else if ((b->startAtom == lastAtom && b->isReversed) || (b->endAtom == lastAtom && !b->isReversed)) { Z -= 1.f; } } else { if ((b->startAtom == lastAtom && !b->isReversed) || (b->endAtom == lastAtom && b->isReversed)) { Z -= 1.f; } else if ((b->startAtom == lastAtom && b->isReversed) || (b->endAtom == lastAtom && !b->isReversed)) { Z += 1.f; } } } lastAtom->neighbors[i]->m_pseudoZ = Z; q.push(lastAtom->neighbors[i]); } } } else { finished = true; } } } } void sketcherMinimizer::maybeFlipPeptides( const std::vector& atoms, float& scoreX) { auto chetoCs = m_minimizer.getChetoCs(atoms); auto aminoNs = m_minimizer.getAminoNs(atoms); auto alphaCs = m_minimizer.getAlphaCs(atoms, chetoCs, aminoNs); for (auto alphaC : alphaCs) { sketcherMinimizerAtom* aminoN = nullptr; sketcherMinimizerAtom* chetoC = nullptr; for (auto neighbor : alphaC->neighbors) { if (aminoNs.find(neighbor) != aminoNs.end()) { aminoN = neighbor; } else if (chetoCs.find(neighbor) != chetoCs.end()) { chetoC = neighbor; } } if (aminoN && chetoC) { auto direction = aminoN->coordinates - chetoC->coordinates; const float PEPTIDE_SCORE = 100.f; if (direction.x() > 0) { scoreX -= PEPTIDE_SCORE; } else { scoreX += PEPTIDE_SCORE; } } } } void sketcherMinimizer::maybeFlip() { for (sketcherMinimizerMolecule* mol : _molecules) { if (mol->hasFixedFragments) { continue; } if (mol->hasConstrainedFragments) { continue; } if (mol->_atoms.size() < 2) { continue; } float scoreY = 0.f, scoreX = 0.f; maybeFlipPeptides(mol->getAtoms(), scoreX); sketcherMinimizerPointF cent(0.f, 0.f); for (sketcherMinimizerAtom* a : mol->_atoms) cent += a->coordinates; if (mol->_atoms.size()) { cent /= mol->_atoms.size(); } for (sketcherMinimizerFragment* f : mol->_fragments) { vector rings = f->getRings(); if (rings.size() == 2) { sketcherMinimizerRing* r1 = rings[0]; sketcherMinimizerRing* r2 = rings[1]; sketcherMinimizerPointF c1 = r1->findCenter(); sketcherMinimizerPointF c2 = r2->findCenter(); if (c1.x() - c2.x() > SKETCHER_EPSILON) { sketcherMinimizerPointF swapP = c2; c2 = c1; c1 = swapP; sketcherMinimizerRing* swapR; swapR = r2; r2 = r1; r1 = swapR; } if (r2->isBenzene() && !r1->isBenzene()) { scoreX -= 20; } else if (r1->isBenzene() && !r2->isBenzene()) { scoreX += 20; } } if (rings.size() > 3) { sketcherMinimizerPointF center(0.f, 0.f); sketcherMinimizerPointF weightedCenter(0.f, 0.f); size_t totalN = 0; int totalRings = 0; for (sketcherMinimizerRing* r : rings) { if (r->_atoms.size() < 4) { continue; } sketcherMinimizerPointF c = r->findCenter(); center += c; weightedCenter += c * r->_atoms.size(); totalN += r->_atoms.size(); totalRings++; } if (totalRings && totalN) { center /= totalRings; weightedCenter /= totalN; } if (weightedCenter.y() - center.y() < -SKETCHER_EPSILON) { scoreY += 50.f; } else if (weightedCenter.y() - center.y() > SKETCHER_EPSILON) { scoreY -= 50.f; } if (weightedCenter.x() - center.x() < -SKETCHER_EPSILON) { scoreX += 50.f; } else if (weightedCenter.x() - center.x() > SKETCHER_EPSILON) { scoreX -= 50.f; } } } float minx = 9999.f, miny = 9999.f, maxx = -9999.f, maxy = -9999.f; for (sketcherMinimizerAtom* a : mol->_atoms) { float x = a->coordinates.x(); float y = a->coordinates.y(); if (x < minx) { minx = x; } else if (x > maxx) { maxx = x; } if (y < miny) { miny = y; } else if (y > maxy) { maxy = y; } } float meanx = (maxx + minx) * 0.5f; float meany = (maxy + miny) * 0.5f; if (meanx - cent.x() > SKETCHER_EPSILON) { scoreX -= 0.5f; } else if (meanx - cent.x() < -SKETCHER_EPSILON) { scoreX += 0.5f; } if (meany - cent.y() > SKETCHER_EPSILON) { scoreY += 0.5f; } else if (meany - cent.y() < -SKETCHER_EPSILON) { scoreY -= 0.5f; } for (sketcherMinimizerBond* b : mol->_bonds) { if (b->bondOrder == 2) { if (b->startAtom->neighbors.size() == 1 && b->endAtom->neighbors.size() > 1) { float diff = b->startAtom->coordinates.y() - b->endAtom->coordinates.y(); if (diff > SKETCHER_EPSILON) { scoreY += 1; } else if (diff < -SKETCHER_EPSILON) { scoreY -= 1; } } else if (b->endAtom->neighbors.size() == 1 && b->startAtom->neighbors.size() > 1) { float diff = b->endAtom->coordinates.y() - b->startAtom->coordinates.y(); if (diff > SKETCHER_EPSILON) { scoreY += 1; } else if (diff < -SKETCHER_EPSILON) { scoreY -= 1; } } } } if (0.f > scoreY) { for (sketcherMinimizerAtom* a : mol->_atoms) { a->coordinates.setY(-a->coordinates.y()); } } if (0.f > scoreX) { for (sketcherMinimizerAtom* a : mol->_atoms) { a->coordinates.setX(-a->coordinates.x()); } } } } void sketcherMinimizer::addToVector(float weight, float angle, vector>& angles) { angle = roundToTwoDecimalDigits(angle); while (angle <= 0) { angle += static_cast(M_PI); } for (unsigned int i = 0; i < angles.size(); i++) { if (angles[i].second < angle - SKETCHER_EPSILON) { if (i == angles.size() - 1) { angles.emplace_back(weight, angle); break; } } else if (angles[i].second - angle < SKETCHER_EPSILON && angles[i].second - angle > -SKETCHER_EPSILON) { angles[i].first += weight; break; } else { angles.insert(angles.begin() + i, pair(weight, angle)); break; } } if (!angles.size()) { angles.emplace_back(weight, angle); } } /* if a peptide chain is present rotate the molecule so it's horizontal */ void sketcherMinimizer::addBestRotationInfoForPeptides( vector>& angles, const std::vector& atoms) { auto chetoCs = m_minimizer.getChetoCs(atoms); auto aminoNs = m_minimizer.getAminoNs(atoms); auto alphaCs = m_minimizer.getAlphaCs(atoms, chetoCs, aminoNs); for (auto alphaC : alphaCs) { sketcherMinimizerAtom* aminoN = nullptr; sketcherMinimizerAtom* chetoC = nullptr; for (auto neighbor : alphaC->neighbors) { if (aminoNs.find(neighbor) != aminoNs.end()) { aminoN = neighbor; } else if (chetoCs.find(neighbor) != chetoCs.end()) { chetoC = neighbor; } } if (aminoN && chetoC) { auto direction = aminoN->coordinates - chetoC->coordinates; float weight = 1000.f; float angle = atan2(-direction.y(), direction.x()); addToVector(weight, angle, angles); } } } void sketcherMinimizer::bestRotation() { for (sketcherMinimizerMolecule* mol : _molecules) { vector> angles; if (mol->hasFixedFragments || mol->hasConstrainedFragments) { continue; } addBestRotationInfoForPeptides(angles, mol->getAtoms()); float angle = 0.f; float lastAngle; unsigned int i = 0, j = 0; float weight = 1.f; auto increment = static_cast(M_PI / 6); for (sketcherMinimizerAtom* a : mol->_atoms) { if (a->rings.size()) { continue; } if (a->neighbors.size() > 1) { for (i = 0; i < a->neighbors.size() - 1; i++) { for (j = i + 1; j < a->neighbors.size(); j++) { weight = 6; if (a->neighbors[i]->neighbors.size() != 1) { weight += 2; } if (a->neighbors[j]->neighbors.size() != 1) { weight += 2; } if (a->neighbors[j]->atomicNumber == 6) { weight += 1; } if (a->neighbors[j]->atomicNumber == 6) { weight += 1; } if (a->neighbors[i]->charge == 0) { weight += 1; } if (a->neighbors[j]->charge == 0) { weight += 1; } sketcherMinimizerPointF p = a->neighbors[i]->coordinates - a->neighbors[j]->coordinates; angle = atan2(-p.y(), p.x()); addToVector(weight, angle, angles); } } } } for (sketcherMinimizerBond* b : mol->_bonds) { sketcherMinimizerPointF p = b->endAtom->coordinates - b->startAtom->coordinates; weight = 1; angle = atan2(-p.y(), p.x()); angle = roundToTwoDecimalDigits(angle); while (angle <= 0) { angle += static_cast(M_PI); } lastAngle = angle; for (unsigned int i = 0; i < 6; i++) { if (i == 1 || i == 5) { weight = 5.f; } else if (i == 0 || i == 3) { weight = 1.5f; } else { weight = 1.f; } if (b->bondOrder == 2 && i == 3 && (b->startAtom->neighbors.size() == 1 || b->endAtom->neighbors.size() == 1)) { weight += 1.5; } if (b->startAtom->neighbors.size() == 1 && b->endAtom->neighbors.size() == 1 && i == 0) { weight += 10; } addToVector(weight, lastAngle, angles); lastAngle += increment; if (lastAngle > M_PI) { lastAngle -= static_cast(M_PI); } } } for (sketcherMinimizerFragment* f : mol->_fragments) { vector rings = f->getRings(); vector inPlaneRings = rings; size_t ringsN = inPlaneRings.size(); if (ringsN == 2) { sketcherMinimizerRing* r1 = inPlaneRings[0]; sketcherMinimizerRing* r2 = inPlaneRings[1]; sketcherMinimizerPointF p = r2->findCenter() - r1->findCenter(); p.normalize(); angle = atan2(-p.y(), p.x()); weight = 25.f; addToVector(weight, angle, angles); } else if (ringsN == 3) { sketcherMinimizerPointF r1 = inPlaneRings[0]->findCenter(); sketcherMinimizerPointF r2 = inPlaneRings[1]->findCenter(); for (sketcherMinimizerRing* r : inPlaneRings) { vector fusedWith; vector> fusionAtoms; for (unsigned int fw = 0; fw < r->fusedWith.size(); fw++) { fusedWith.push_back(r->fusedWith[fw]); fusionAtoms.push_back(r->fusionAtoms[fw]); } if (fusedWith.size() == 2) { if (fusionAtoms[0].size() == 2 && fusionAtoms[1].size() == 2) { r1 = (fusionAtoms[0][0]->coordinates + fusionAtoms[0][1]->coordinates) * 0.5; r2 = (fusionAtoms[1][0]->coordinates + fusionAtoms[1][1]->coordinates) * 0.5; break; } } } sketcherMinimizerPointF p = r2 - r1; angle = atan2(-p.y(), p.x()); weight = 50.f; addToVector(weight, angle, angles); } else { vector rings; for (sketcherMinimizerRing* r : inPlaneRings) { if (r->_atoms.size() == 6) { rings.push_back(r); } } for (sketcherMinimizerRing* r : rings) { for (auto fusionAts : r->fusionAtoms) { if (fusionAts.size() == 2) { sketcherMinimizerPointF p = fusionAts[0]->coordinates - fusionAts[1]->coordinates; // if (p.x () != p.x () || p.y () != p.y ()) p = // sketcherMinimizerPointF (50.f, 0.f); sketcherMinimizerPointF rotatedP(p.y(), p.x()); angle = static_cast(atan2(-p.y(), p.x()) - M_PI * 0.5); weight = 25.f; addToVector(weight, angle, angles); } } } } } if (angles.size() > 1) { if (angles[angles.size() - 1].second - angles[0].second >= M_PI - 2 * SKETCHER_EPSILON) { angles[0].first += angles[angles.size() - 1].first; angles.erase(angles.begin() + angles.size() - 1); } } if (angles.size()) { int bestI = 0; for (i = 0; i < angles.size(); i++) { if (angles[i].first > angles[bestI].first) { bestI = i; } } float s = -sin(angles[bestI].second); float c = cos(angles[bestI].second); sketcherMinimizerPointF center(0.f, 0.f); for (sketcherMinimizerAtom* at : mol->_atoms) center += at->coordinates; if (mol->_atoms.size()) { center /= mol->_atoms.size(); } for (sketcherMinimizerAtom* at : mol->_atoms) { sketcherMinimizerPointF v = at->coordinates - center; v.rotate(s, c); at->setCoordinates(center + v); } } } } void sketcherMinimizer::findFragments() { assert(_molecules.size()); for (sketcherMinimizerMolecule* mol : _molecules) { CoordgenFragmenter::splitIntoFragments(mol); if (!mol->_fragments.size()) { continue; } vector fragments = mol->_fragments; _fragments.reserve(_fragments.size() + fragments.size()); _fragments.insert(_fragments.end(), fragments.begin(), fragments.end()); _independentFragments.push_back(mol->getMainFragment()); } m_minimizer._fragments = _fragments; initializeFragments(); } void sketcherMinimizer::placeResiduesProteinOnlyModeCircleStyle( const std::map>& chains) { size_t totalResiduesNumber = _residues.size() + chains.size(); auto angle = static_cast(2.f * M_PI / totalResiduesNumber); const float residueRadius = 30.f; const auto circumference = static_cast(totalResiduesNumber * residueRadius * 2); const auto radius = static_cast(circumference * 0.5 / M_PI); int i = 0; for (const auto& chain : chains) { ++i; // gap between chains auto residues = chain.second; sort(residues.begin(), residues.end(), [](const sketcherMinimizerResidue* firstRes, const sketcherMinimizerResidue* secondRes) { int firstN = firstRes->resnum; int secondN = secondRes->resnum; return firstN < secondN; }); for (auto res : residues) { sketcherMinimizerPointF p(radius, 0); // place residues in a circle p.rotate(sin(angle * i), cos(angle * i)); res->coordinates = p; res->coordinatesSet = true; res->molecule->isPlaced = true; ++i; } } } std::map sketcherMinimizer::computeChainsStartingPositionsMetaMol( const std::map>& chains) { map molMap; auto* metaMol = new sketcherMinimizerMolecule; for (const auto& chainPair : chains) { auto* a = new sketcherMinimizerAtom; a->molecule = metaMol; metaMol->_atoms.push_back(a); molMap[chainPair.first] = a; } for (const auto& chainPair : chains) { for (auto residue : chainPair.second) { for (auto interaction : residue->residueInteractions) { if (interaction->startAtom->isResidue() && interaction->endAtom->isResidue()) { auto* r1 = static_cast( interaction->startAtom); auto* r2 = static_cast( interaction->endAtom); if (r1->chain != r2->chain) { // add a bond to the metaMol if it doesn't exist already sketcherMinimizerAtom* at1 = molMap[r1->chain]; sketcherMinimizerAtom* at2 = molMap[r2->chain]; bool found = false; for (sketcherMinimizerBond* b : metaMol->_bonds) { if ((b->startAtom == at1 && b->endAtom == at2) || (b->startAtom == at2 && b->endAtom == at1)) { found = true; break; } } if (!found) { auto* newBond = new sketcherMinimizerBond; newBond->startAtom = at1; newBond->endAtom = at2; metaMol->_bonds.push_back(newBond); } } } } } } sketcherMinimizer min; if (metaMol->_atoms.size()) { min.m_fragmentBuilder.m_evenAngles = true; min.m_minimizer.m_evenAngles = true; min.initialize(metaMol); min.findFragments(); min.m_minimizer.buildFromFragments(true); min.m_minimizer.avoidClashes(); min.bestRotation(); min.maybeFlip(); min.arrangeMultipleMolecules(); } std::map positions; for (const auto& iter : molMap) { positions[iter.first] = iter.second->coordinates * 10.; } return positions; } void sketcherMinimizer::shortenInteractions( const std::map>& chains) { for (const auto& chain : chains) { for (auto res : chain.second) { for (auto interaction : res->residueInteractions) { sketcherMinimizerPointF midPoint = 0.5 * (interaction->startAtom->coordinates + interaction->endAtom->coordinates); res->coordinates += (midPoint - res->coordinates) * 0.1; } } } } std::vector sketcherMinimizer::orderResiduesOfChains( const std::map>& chains) { std::vector vec; for (const auto& chain : chains) { for (auto res : chain.second) { vec.push_back(res); } } sort(vec.begin(), vec.end(), [](const sketcherMinimizerResidue* firstRes, const sketcherMinimizerResidue* secondRes) { return firstRes->residueInteractions.size() > secondRes->residueInteractions.size(); }); std::set visitedResidues; std::queue residueQueue; std::vector finalVec; for (auto residue : vec) { if (visitedResidues.find(residue) != visitedResidues.end()) { continue; } residueQueue.push(residue); visitedResidues.insert(residue); while (residueQueue.size()) { auto topResidue = residueQueue.front(); finalVec.push_back(topResidue); residueQueue.pop(); for (auto partner : topResidue->residueInteractionPartners) { auto* partnerRes = static_cast(partner); if (visitedResidues.find(partnerRes) == visitedResidues.end()) { residueQueue.push(partnerRes); visitedResidues.insert(partnerRes); } } } } return finalVec; } void sketcherMinimizer::placeResiduesProteinOnlyModeLIDStyle( const std::map>& chains) { auto positions = computeChainsStartingPositionsMetaMol(chains); sketcherMinimizerPointF p; for (const auto& chain : chains) { p = positions[chain.first]; for (auto res : chain.second) { res->coordinates = p; } } shortenInteractions(chains); auto residues = orderResiduesOfChains(chains); for (auto res : residues) { int partnersAlreadySet = 0; sketcherMinimizerResidue* firstPartner = nullptr; for (auto partner : res->residueInteractionPartners) { if (partner->coordinatesSet) { ++partnersAlreadySet; auto partnerResidue = static_cast(partner); if (!firstPartner && partnerResidue->chain != res->chain) { firstPartner = partnerResidue; } } } /* when searching for a position for res prefer a direction * perpendicular to the direction of interactions to optimize use of * space */ sketcherMinimizerPointF direction(0, 1); if (firstPartner) { sketcherMinimizerPointF chainCenterDirections = positions[firstPartner->chain] - positions[res->chain]; direction = sketcherMinimizerPointF(-chainCenterDirections.y(), chainCenterDirections.x()); direction.normalize(); direction *= 4.; } res->coordinates = exploreGridAround(res->coordinates, 10, 5, 0, 0, -1.f, false, res, direction); res->coordinatesSet = true; res->molecule->isPlaced = true; } } void sketcherMinimizer::placeResiduesProteinOnlyMode() { std::map> chains; for (auto residue : _residues) { string chainOfResidue = residue->chain; chains[chainOfResidue].push_back(residue); } placeResiduesProteinOnlyModeLIDStyle(chains); m_minimizer.minimizeProteinOnlyLID(chains); } void sketcherMinimizer::placeResiduesInCrowns() { auto SSEs = groupResiduesInSSEs(_residues); /* sort secondary structure elements so that the most importants are placed * first. prefer longer SSEs and ones that make more interactions */ sort(SSEs.begin(), SSEs.end(), [](const vector& firstSSE, const vector& secondSSE) { float interactionsOfFirst = 0, interactionsOfSecond = 0; for (auto res : firstSSE) { interactionsOfFirst += res->residueInteractions.size(); } for (auto res : secondSSE) { interactionsOfSecond += res->residueInteractions.size(); } float interactionScaling = 3.f; float score1 = firstSSE.size() + interactionScaling * interactionsOfFirst / firstSSE.size(); float score2 = secondSSE.size() + interactionScaling * interactionsOfSecond / secondSSE.size(); return score1 > score2; }); bool needOtherShape = true; int shapeCounter = 0; // place residues in a crowns around the ligand. Keep expanding to further // away crowns until all residues are placed while (needOtherShape) { vector shape = shapeAroundLigand(shapeCounter++); needOtherShape = fillShape(SSEs, shape, shapeCounter); } } /* place residues in SSEs in the current shape. Return false if all residues are * place, true otherwise */ bool sketcherMinimizer::fillShape( vector>& SSEs, const vector& shape, int shapeN) { vector penalties(shape.size(), false); std::set outliers; for (const auto& SSE : SSEs) { placeSSE(SSE, shape, shapeN, penalties, outliers); } return !outliers.empty(); } /* assign a penalty for the stretching of bonds between residues in the same * SSE */ float sketcherMinimizer::scoreSSEBondStretch( const sketcherMinimizerPointF& coordinates1, const sketcherMinimizerPointF& coordinates2) { const float stretchPenalty = 400.f; auto squaredLength = (coordinates2 - coordinates1).squareLength(); return squaredLength * stretchPenalty; } float sketcherMinimizer::getResidueDistance( float startF, float increment, sketcherMinimizerResidue* resToConsider, const vector& SSE) { float totalF = startF; sketcherMinimizerResidue* lastRes = nullptr; for (auto res : SSE) { if (lastRes) { auto result = static_cast(res->resnum - lastRes->resnum); /* if the gap is more than 1, make the distance a bit smaller for * aesthetic reasons */ result = static_cast(1 + (result - 1) * 0.8); if (result < 1.f) { result = 1.f; } totalF += increment * result; } if (res == resToConsider) { break; } lastRes = res; } return totalF; } /* return a score for the placing on the SSE starting at startingPosition and * separated by increment */ float sketcherMinimizer::scoreSSEPosition( const vector& SSE, const vector& shape, int shapeN, vector& penalties, float startingPosition, float increment) { float score = 0.f; sketcherMinimizerResidue* lastResidue = nullptr; int lastResiduePosition = 0; sketcherMinimizerPointF lastResidueCoordinates; for (auto res : SSE) { int index = getShapeIndex( shape, getResidueDistance(startingPosition, increment, res, SSE)); auto residueCoordinates = shape.at(index); int residuePosition = 0; if (res->coordinatesSet) { residuePosition = -1; residueCoordinates = res->coordinates; } else { if (!penalties[index]) { residuePosition = 0; } else { residuePosition = 1; } } if (residuePosition != -1) { score += scoreResiduePosition(index, shape, shapeN, penalties, res); } if (lastResidue && (residuePosition != lastResiduePosition)) { score += scoreSSEBondStretch(residueCoordinates, lastResidueCoordinates); } lastResiduePosition = residuePosition; lastResidueCoordinates = residueCoordinates; lastResidue = res; } return score; } void sketcherMinimizer::placeSSE(const vector& SSE, const vector& shape, int shapeN, vector& penalties, set& outliers, bool placeOnlyInteracting) { int residuesToPlace = 0; for (auto res : SSE) { if (!res->coordinatesSet) { residuesToPlace++; } } if (residuesToPlace == 0) { return; } typedef pair Solution; vector> scoredSolutions; /* move around the crown scoring possible solutions, varying the starting * position and the separation between consecutive residues */ for (float f = 0.f; f < 1.f; f += 0.004f) { float distance = 5.f / shape.size(); for (float increment = -1 * distance; increment <= 1 * distance; increment += distance) { if (increment == 0) { continue; } float score = scoreSSEPosition(SSE, shape, shapeN, penalties, f, increment); scoredSolutions.emplace_back(score, Solution(f, increment)); } } auto bestResult = min_element(scoredSolutions.begin(), scoredSolutions.end()); set alreadyPlaced; for (auto residue : SSE) { if (residue->coordinatesSet) { continue; // placed in a previous crown } float f = getResidueDistance(bestResult->second.first, bestResult->second.second, residue, SSE); int index = getShapeIndex(shape, f); bool alreadyAResidueHere = penalties.at(index); sketcherMinimizerPointF position = shape.at(index); if (alreadyAResidueHere || (placeOnlyInteracting && !residue->residueInteractionPartners.size())) { outliers.insert(residue); } else { residue->coordinates = position; alreadyPlaced.insert(residue); } } // mark the current solution to prevent other residues from being placed on // top of these markSolution(bestResult->second, SSE, shape, penalties, outliers); for (auto res : alreadyPlaced) { res->coordinatesSet = true; } for (auto res : SSE) { if (res->m_isWaterMap && res->m_isClashing && res->coordinatesSet && res->m_closestLigandAtom != nullptr) { sketcherMinimizerPointF directionToLigand = res->m_closestLigandAtom->coordinates - res->coordinates; directionToLigand.normalize(); float displacement = BONDLENGTH * 0.3; res->coordinates = res->m_closestLigandAtom->coordinates - directionToLigand * displacement; } } } void sketcherMinimizer::markSolution( const pair& solution, const vector& SSE, const vector& shape, vector& penalties, set& outliers) { float padding = abs(solution.second) * 0.5f; sketcherMinimizerResidue* lastRes = nullptr; float lastF = 0.f; for (auto res : SSE) { if (res->coordinatesSet || (res->m_isWaterMap && res->m_isClashing) || outliers.find(res) != outliers.end()) { lastRes = nullptr; lastF = 0.f; continue; } float f = getResidueDistance(solution.first, solution.second, res, SSE); int startIndex = getShapeIndex(shape, f - padding); int endIndex = getShapeIndex(shape, f + padding); for (int index = startIndex; index != endIndex; index = (index + 1) % shape.size()) { penalties.at(index) = true; } if (lastRes) { if (solution.second < 0) { std::swap(lastF, f); } int startIndex = getShapeIndex(shape, lastF); int endIndex = getShapeIndex(shape, f); for (int index = startIndex; index != endIndex; index = (index + 1) % shape.size()) { penalties.at(index) = true; } } lastRes = res; lastF = f; } } int sketcherMinimizer::getShapeIndex( const vector& shape, float floatPosition) { float normalizedF = floatPosition; while (normalizedF < 0) { normalizedF += 1.f; } while (normalizedF >= 1.f) { normalizedF -= 1.f; } int counter = static_cast(shape.size() * normalizedF); return counter; } vector> sketcherMinimizer::groupResiduesInSSEs( const vector& residues) { // divide residues by chain map> chainsMap; for (auto res : residues) { chainsMap[res->chain].push_back(res); } // order each chain by residue number for (auto& pair : chainsMap) { sort(pair.second.begin(), pair.second.end(), [](const sketcherMinimizerResidue* firstRes, const sketcherMinimizerResidue* secondRes) { return firstRes->resnum < secondRes->resnum; }); } int gap = 3; // split chains in smaller chunks whenever more than gap consecutive // residues are missing vector> returnValue; for (auto& pair : chainsMap) { vector growingChain; for (auto res : pair.second) { if (!growingChain.empty() && (res->resnum - growingChain.back()->resnum > gap || res->chain == " " || res->chain.empty())) { returnValue.push_back(growingChain); growingChain.clear(); } growingChain.push_back(res); } if (!growingChain.empty()) { returnValue.push_back(growingChain); } } return returnValue; } vector sketcherMinimizer::shapeAroundLigand(int crownN) { // return crownN-th crown around ligand float distanceOfFirstCrown = 60; float distanceBetweenCrowns = 60; // find limits auto atoms = _atoms; auto bonds = _bonds; float border = distanceBetweenCrowns * crownN + distanceOfFirstCrown; float minX = atoms[0]->coordinates.x(); float maxX = atoms[0]->coordinates.x(); float minY = atoms[0]->coordinates.y(); float maxY = atoms[0]->coordinates.y(); float maxPocketD = 0; for (auto atom : atoms) { float distance = atom->m_pocketDistance; if (distance > maxPocketD) { maxPocketD = distance; } float newX = atom->coordinates.x(); float newY = atom->coordinates.y(); if (minX > newX) { minX = newX; } if (maxX < newX) { maxX = newX; } if (minY > newY) { minY = newY; } if (maxY < newY) { maxY = newY; } } maxPocketD += 10; // to account for cutoffs at borders minX -= border + maxPocketD; maxX += border + maxPocketD; minY -= border + maxPocketD; maxY += border + maxPocketD; /* run a marching square algorithm on a grid of x_interval spacing */ sketcherMinimizerMarchingSquares ms; float x_interval = 20.f; ms.initialize(minX, maxX, minY, maxY, x_interval); for (unsigned int j = 0; j < ms.getYN(); j++) { for (unsigned int i = 0; i < ms.getXN(); i++) { float pointX = ms.toRealx(static_cast(i)); float pointY = ms.toRealy(static_cast(j)); sketcherMinimizerPointF p(pointX, pointY); float shortestD = -1; for (auto a : atoms) { float dist = a->m_pocketDistance; auto vect = a->coordinates - p; float D = vect.length(); D -= dist + border; if (D < shortestD || shortestD < 0) { shortestD = D; } } for (auto b : bonds) { sketcherMinimizerPointF sp1(b->startAtom->coordinates); sketcherMinimizerPointF sp2(b->endAtom->coordinates); float distancePercentage = 1.f; float D2 = sketcherMinimizerMaths::squaredDistancePointSegment( p, sp1, sp2, &distancePercentage); float D = sqrt(D2); float distance2 = b->startAtom->m_pocketDistance; float distance1 = b->endAtom->m_pocketDistance; float dist = distance1 * distancePercentage + distance2 * (1 - distancePercentage); D -= dist + border; if (D < shortestD) { shortestD = D; } } ms.setValue(shortestD, i, j); } } ms.setThreshold(0); ms.run(); auto result = ms.getOrderedCoordinatesPoints(); sort(result.begin(), result.end(), [](const vector& firstContour, const vector& secondContour) { return firstContour.size() > secondContour.size(); }); vector returnValue; if (result.size() > 0) { for (unsigned int i = 0; i < result.at(0).size(); i += 2) { returnValue.emplace_back(result.at(0).at(i), result.at(0).at(i + 1)); } } return returnValue; } float sketcherMinimizer::scoreResiduePosition( int index, const vector& shape, int shapeN, vector&, sketcherMinimizerResidue* residue) { auto position = shape.at(index); float distancePenalty = 0.01f; float clashingLigandAtomsPenalty = 100.f; vector targets; for (auto interactionPartner : residue->residueInteractionPartners) { if (interactionPartner->coordinatesSet) { targets.push_back(interactionPartner); } } float interactionsF = 1.f; if (targets.empty() && residue->m_closestLigandAtom != nullptr) { interactionsF = 0.2f; targets.push_back(residue->m_closestLigandAtom); } float score = 0.f; for (auto target : targets) { int clashingLigandAtoms = 0; for (auto ligandAtom : _atoms) { if (ligandAtom == target) { continue; } auto ligandAtomPos = ligandAtom->coordinates; float squareDist = sketcherMinimizerMaths::squaredDistancePointSegment( ligandAtomPos, position, target->coordinates); if (squareDist < 40 * 40) { clashingLigandAtoms++; } } auto distance = sketcherMinimizerMaths::squaredDistance( target->coordinates, position) - (shapeN * 50) * (shapeN * 50); score += interactionsF * (distancePenalty * distance + clashingLigandAtoms * clashingLigandAtomsPenalty); } return score; } void sketcherMinimizer::placeResidues( const vector& atoms) { if (!_residues.size()) { return; } if (!atoms.size()) { placeResiduesProteinOnlyMode(); return; } findClosestAtomToResidues(atoms); placeResiduesInCrowns(); m_minimizer.minimizeResidues(); } /* move mol around to avoid clashes with other already placed molecules. Explore a grid of @levels concentric levels, with #gridD resolution. @distanceFromAtoms is the minimum clash distance to reject a position. */ sketcherMinimizerPointF sketcherMinimizer::exploreMolPosition(sketcherMinimizerMolecule* mol, unsigned int levels, float gridD, float distanceFromAtoms) { sketcherMinimizerPointF v(0, 0), centerOfGrid(0, 0); for (unsigned int i = 0; i < levels; i++) { vector pointstoTest; sketcherMinimizerPointF top = centerOfGrid + sketcherMinimizerPointF(0.f, (1 + i) * gridD); sketcherMinimizerPointF bottom = centerOfGrid + sketcherMinimizerPointF(0.f, -((1 + i) * gridD)); sketcherMinimizerPointF right = centerOfGrid + sketcherMinimizerPointF((1 + i) * gridD, 0.f); sketcherMinimizerPointF left = centerOfGrid + sketcherMinimizerPointF(-((1 + i) * gridD), 0.f); pointstoTest.push_back(centerOfGrid); pointstoTest.push_back(right); pointstoTest.push_back(left); pointstoTest.push_back(bottom); pointstoTest.push_back(top); for (unsigned int j = 0; j < i; j++) { pointstoTest.push_back( right + sketcherMinimizerPointF(0.f, gridD * (j + 1))); pointstoTest.push_back( right - sketcherMinimizerPointF(0.f, gridD * (j + 1))); pointstoTest.push_back( left + sketcherMinimizerPointF(0.f, gridD * (j + 1))); pointstoTest.push_back( left - sketcherMinimizerPointF(0.f, gridD * (j + 1))); pointstoTest.push_back( bottom + sketcherMinimizerPointF(gridD * (j + 1), 0.f)); pointstoTest.push_back( bottom - sketcherMinimizerPointF(gridD * (j + 1), 0.f)); pointstoTest.push_back( top + sketcherMinimizerPointF(gridD * (j + 1), 0.f)); pointstoTest.push_back( top - sketcherMinimizerPointF(gridD * (j + 1), 0.f)); } pointstoTest.push_back( centerOfGrid + sketcherMinimizerPointF((1 + i) * gridD, (1 + i) * gridD)); pointstoTest.push_back( centerOfGrid + sketcherMinimizerPointF((1 + i) * gridD, -((1 + i) * gridD))); pointstoTest.push_back( centerOfGrid + sketcherMinimizerPointF(-((1 + i) * gridD), (1 + i) * gridD)); pointstoTest.push_back( centerOfGrid + sketcherMinimizerPointF(-((1 + i) * gridD), -((1 + i) * gridD))); bool noClash = true; if (distanceFromAtoms < 0) { distanceFromAtoms = bondLength * 1.8; } float dist = distanceFromAtoms; for (const auto& pc : pointstoTest) { noClash = true; v = pc; for (sketcherMinimizerAtom* at : mol->_atoms) { sketcherMinimizerPointF placeNextTo = at->coordinates + v; for (sketcherMinimizerMolecule* m : _molecules) { if (!m->isPlaced) { continue; } if (m == mol) { continue; } for (sketcherMinimizerAtom* a : m->_atoms) { dist = distanceFromAtoms; if (((a->coordinates.x() < placeNextTo.x() + dist) && (a->coordinates.x() > placeNextTo.x() - dist)) && ((a->coordinates.y() < placeNextTo.y() + dist) && (a->coordinates.y() > placeNextTo.y() - dist))) { noClash = false; break; } } if (!noClash) { break; } } if (!noClash) { break; } } if (noClash) { break; } } if (noClash) { break; } } return v; } sketcherMinimizerPointF sketcherMinimizer::exploreGridAround( const sketcherMinimizerPointF& centerOfGrid, unsigned int levels, float gridD, float dx, float dy, float distanceFromAtoms, bool watermap, sketcherMinimizerResidue* residueForInteractions, const sketcherMinimizerPointF& direction) { sketcherMinimizerPointF placeNextTo = centerOfGrid; for (unsigned int i = 0; i < levels; i++) { vector pointstoTest; sketcherMinimizerPointF top = centerOfGrid + sketcherMinimizerPointF(0.f, (1 + i) * gridD); sketcherMinimizerPointF bottom = centerOfGrid + sketcherMinimizerPointF(0.f, -((1 + i) * gridD)); sketcherMinimizerPointF right = centerOfGrid + sketcherMinimizerPointF((1 + i) * gridD, 0.f); sketcherMinimizerPointF left = centerOfGrid + sketcherMinimizerPointF(-((1 + i) * gridD), 0.f); pointstoTest.push_back(centerOfGrid); pointstoTest.push_back(right); pointstoTest.push_back(left); pointstoTest.push_back(bottom); pointstoTest.push_back(top); for (unsigned int j = 0; j < i; j++) { pointstoTest.push_back( right + sketcherMinimizerPointF(0.f, gridD * (j + 1))); pointstoTest.push_back( right - sketcherMinimizerPointF(0.f, gridD * (j + 1))); pointstoTest.push_back( left + sketcherMinimizerPointF(0.f, gridD * (j + 1))); pointstoTest.push_back( left - sketcherMinimizerPointF(0.f, gridD * (j + 1))); pointstoTest.push_back( bottom + sketcherMinimizerPointF(gridD * (j + 1), 0.f)); pointstoTest.push_back( bottom - sketcherMinimizerPointF(gridD * (j + 1), 0.f)); pointstoTest.push_back( top + sketcherMinimizerPointF(gridD * (j + 1), 0.f)); pointstoTest.push_back( top - sketcherMinimizerPointF(gridD * (j + 1), 0.f)); } pointstoTest.push_back( centerOfGrid + sketcherMinimizerPointF((1 + i) * gridD, (1 + i) * gridD)); pointstoTest.push_back( centerOfGrid + sketcherMinimizerPointF((1 + i) * gridD, -((1 + i) * gridD))); pointstoTest.push_back( centerOfGrid + sketcherMinimizerPointF(-((1 + i) * gridD), (1 + i) * gridD)); pointstoTest.push_back( centerOfGrid + sketcherMinimizerPointF(-((1 + i) * gridD), -((1 + i) * gridD))); bool noClash = true; if (distanceFromAtoms < 0) { distanceFromAtoms = bondLength * 1.8; } float distanceFromResidues = bondLength * 1.3; float watermapDistance = 10; float dist = distanceFromAtoms; sketcherMinimizerPointF directionNormal(-direction.y(), direction.x()); directionNormal.normalize(); for (const auto& pc : pointstoTest) { noClash = true; sketcherMinimizerPointF point = pc - centerOfGrid; placeNextTo = point.y() * direction + point.x() * directionNormal + centerOfGrid; for (sketcherMinimizerMolecule* m : _molecules) { if (!m->isPlaced) { continue; } for (sketcherMinimizerAtom* a : m->_atoms) { if (a->isResidue()) { dist = distanceFromResidues; } else { dist = distanceFromAtoms; } if (watermap) { if (!a->isResidue()) { continue; } dist = watermapDistance; } if (((a->coordinates.x() < placeNextTo.x() + dist + dx) && (a->coordinates.x() > placeNextTo.x() - dist - dx)) && ((a->coordinates.y() < placeNextTo.y() + dist + dy) && (a->coordinates.y() > placeNextTo.y() - dist - dy))) { noClash = false; break; } if (residueForInteractions) { for (auto partnerOfA : a->residueInteractionPartners) { if (a == residueForInteractions || partnerOfA == residueForInteractions || !a->coordinatesSet || !partnerOfA->coordinatesSet) { continue; } float squareD = sketcherMinimizerMaths:: squaredDistancePointSegment( placeNextTo, a->coordinates, partnerOfA->coordinates); if (squareD < distanceFromResidues * distanceFromResidues) { noClash = false; break; } for (auto partner : residueForInteractions ->residueInteractionPartners) { if (!partner->coordinatesSet) { continue; } if (sketcherMinimizerMaths:: intersectionOfSegments( placeNextTo, partner->coordinates, a->coordinates, partnerOfA->coordinates)) { noClash = false; break; } } } } } if (!noClash) { break; } } if (noClash) { break; } } if (noClash) { break; } } return placeNextTo; } vector sketcherMinimizer::buildProximityDataVector( vector& proximityMols, map& molMap) { vector proximityDataVector; for (sketcherMinimizerMolecule* mol : proximityMols) { proximityData data; sketcherMinimizerAtom* metaAtom = molMap[mol]; vector additionVectors( metaAtom->neighbors.size(), sketcherMinimizerPointF(0.f, 0.f)); vector centers( metaAtom->neighbors.size(), sketcherMinimizerPointF(0.f, 0.f)); vector counters(metaAtom->neighbors.size(), 0); for (sketcherMinimizerBond* pr : mol->m_proximityRelations) { sketcherMinimizerAtom* otherMetaAtom = nullptr; sketcherMinimizerAtom* targetAtom = nullptr; if (pr->startAtom->molecule == mol && !(pr->endAtom->molecule == mol)) { otherMetaAtom = molMap[pr->endAtom->molecule]; targetAtom = pr->startAtom; } else if (pr->endAtom->molecule == mol && !(pr->startAtom->molecule == mol)) { otherMetaAtom = molMap[pr->startAtom->molecule]; targetAtom = pr->endAtom; } if (otherMetaAtom) { for (unsigned int i = 0; i < metaAtom->neighbors.size(); i++) { if (metaAtom->neighbors[i] == otherMetaAtom) { additionVectors[i] += targetAtom->getSingleAdditionVector(); centers[i] += targetAtom->coordinates; counters[i]++; } } } } for (unsigned int i = 0; i < centers.size(); i++) { if (counters[i] > 0) { centers[i] /= counters[i]; } additionVectors[i].normalize(); } data.additionVectors = additionVectors; data.centers = centers; data.counters = counters; proximityDataVector.push_back(data); } return proximityDataVector; } void sketcherMinimizer::rotateMoleculesWithProximityRelations( vector& proximityMols, map& molMap, vector& proximityDataVector) { for (unsigned int m = 0; m < proximityMols.size(); m++) { sketcherMinimizerMolecule* mol = proximityMols[m]; sketcherMinimizerAtom* metaAtom = molMap[mol]; vector additionVectors = proximityDataVector[m].additionVectors; vector centers = proximityDataVector[m].centers; if (mol->_atoms.size() < 2) { continue; } sketcherMinimizerPointF direction(1, 0); if (metaAtom->bonds.size() == 1) { direction = metaAtom->coordinates - metaAtom->neighbors[0]->coordinates; sketcherMinimizerPointF p1 = additionVectors[0]; p1 *= -1; sketcherMinimizerPointF p3 = direction; float rotationAngle = sketcherMinimizerMaths::signedAngle( p1, sketcherMinimizerPointF(0, 0), p3); rotationAngle *= static_cast(-M_PI / 180.f); float s = sin(rotationAngle); float c = cos(rotationAngle); for (sketcherMinimizerAtom* a : mol->_atoms) { sketcherMinimizerPointF coords = a->coordinates - centers[0]; coords.rotate(s, c); a->coordinates = coords + centers[0]; } } else if (metaAtom->bonds.size() > 1) { vector v1, v2; for (sketcherMinimizerAtom* n : metaAtom->neighbors) { v1.push_back(n->coordinates - metaAtom->coordinates); } v2 = additionVectors; float rotMat[4]; alignmentMatrix(v1, v2, rotMat); sketcherMinimizerPointF center = mol->center(); for (sketcherMinimizerAtom* a : mol->_atoms) { sketcherMinimizerPointF coords = a->coordinates - center; float x = coords.x(); float y = coords.y(); sketcherMinimizerPointF newCoords(x * rotMat[0] + y * rotMat[1], x * rotMat[2] + y * rotMat[3]); a->coordinates = center + newCoords; } } } } void sketcherMinimizer::translateMoleculesWithProximityRelations( vector& proximityMols, map& molMap, map& templateCenters, vector&) { // placing int counterN = 1; bool cleverPlacing = false; do { cleverPlacing = !cleverPlacing; // alternatively try to be smart aboout // single atom mols if (!cleverPlacing) { counterN++; } for (auto mol : proximityMols) { bool residue = false; if (mol->_atoms.size() == 1) { if (mol->_atoms[0]->isResidue()) { residue = true; } } if (!residue) { if (mol->hasConstrainedFragments) { mol->isPlaced = true; continue; } } if (mol->hasFixedFragments) { mol->isPlaced = true; continue; } if (mol->m_proximityRelations.size()) { sketcherMinimizerPointF atomsCenter = sketcherMinimizerPointF(0, 0); int atomsN = 0; for (sketcherMinimizerBond* pr : mol->m_proximityRelations) { if (pr->startAtom->molecule == mol && pr->endAtom->molecule != mol) { atomsCenter += pr->startAtom->coordinates; atomsN++; } else if (pr->endAtom->molecule == mol && pr->startAtom->molecule != mol) { atomsCenter += pr->endAtom->coordinates; atomsN++; } } if (atomsN > 0) { atomsCenter /= atomsN; } /* positioning */ sketcherMinimizerPointF placeNextTo = templateCenters[mol]; placeNextTo *= counterN; for (sketcherMinimizerAtom* a : mol->_atoms) { a->coordinates += placeNextTo - atomsCenter; } mol->isPlaced = true; } } if (cleverPlacing) { // replace single terminal atoms for (auto mol : proximityMols) { if (mol->_atoms.size() == 1) { sketcherMinimizerAtom* metaAtom = molMap[mol]; if (metaAtom->neighbors.size() == 1) { int bondsN = 0; sketcherMinimizerPointF coords(0, 0); for (sketcherMinimizerBond* pr : mol->m_proximityRelations) { if (pr->startAtom->molecule == mol && pr->endAtom->molecule != mol) { sketcherMinimizerPointF addV = pr->endAtom->getSingleAdditionVector(); if (addV.length() < SKETCHER_EPSILON) { continue; } addV.normalize(); addV *= bondLength * counterN; coords += pr->endAtom->coordinates + addV; bondsN++; } else if (pr->endAtom->molecule == mol && pr->startAtom->molecule != mol) { sketcherMinimizerPointF addV = pr->startAtom->getSingleAdditionVector(); if (addV.length() < SKETCHER_EPSILON) { continue; } addV.normalize(); addV *= bondLength * counterN; coords += pr->startAtom->coordinates + addV; bondsN++; } } if (bondsN > 0) { coords /= bondsN; mol->_atoms[0]->coordinates = coords; } else { // a suitable addition Vector could not be // found, try positionings a bondlength to the // right of a proximity partner for (sketcherMinimizerBond* pr : mol->m_proximityRelations) { if (pr->startAtom->molecule == mol && pr->endAtom->molecule != mol) { mol->_atoms[0]->coordinates = pr->endAtom->coordinates; break; } else if (pr->endAtom->molecule == mol && pr->startAtom->molecule != mol) { mol->_atoms[0]->coordinates = pr->startAtom->coordinates; break; } } } } } } } } while (m_minimizer.findIntermolecularClashes(proximityMols, bondLength * 0.5) && counterN < 10); } void sketcherMinimizer::placeMoleculesWithProximityRelations( vector proximityMols) { map molMap; auto* metaMol = new sketcherMinimizerMolecule; for (sketcherMinimizerMolecule* mol : _molecules) { if (mol->m_proximityRelations.size()) { auto* a = new sketcherMinimizerAtom; a->molecule = metaMol; metaMol->_atoms.push_back(a); molMap[mol] = a; } } for (sketcherMinimizerBond* b : m_proximityRelations) { if (b->startAtom->molecule == b->endAtom->molecule) { continue; } sketcherMinimizerAtom* at1 = molMap[b->startAtom->molecule]; sketcherMinimizerAtom* at2 = molMap[b->endAtom->molecule]; bool found = false; for (sketcherMinimizerBond* b : metaMol->_bonds) { if ((b->startAtom == at1 && b->endAtom == at2) || (b->startAtom == at2 && b->endAtom == at1)) { found = true; } } if (!found) { auto* newBond = new sketcherMinimizerBond; newBond->startAtom = at1; newBond->endAtom = at2; metaMol->_bonds.push_back(newBond); } } sketcherMinimizer min(m_minimizer.getPrecision()); if (metaMol->_atoms.size()) { min.m_fragmentBuilder.m_evenAngles = true; min.m_minimizer.m_evenAngles = true; min.initialize(metaMol); min.findFragments(); min.m_minimizer.buildFromFragments(true); min.m_minimizer.avoidClashes(); min.bestRotation(); min.maybeFlip(); min.arrangeMultipleMolecules(); } bool ligandResidueStyle = true; // positions of molecules are determined // more by a bigger central molecule than by // a bonding pattern for (auto molecule : min._molecules) { if (molecule->_rings.size() > 0) { // if at least three molecules are connected to each other // (i.e. two residues are connected to each other and both to // the ligand) abort the ligandResidue display style) ligandResidueStyle = false; } } sketcherMinimizerMolecule* centralMol = proximityMols[0]; for (sketcherMinimizerMolecule* mol : proximityMols) { if (mol->m_proximityRelations.size() > centralMol->m_proximityRelations.size()) { centralMol = mol; } else if (mol->m_proximityRelations.size() == centralMol->m_proximityRelations.size() && mol->_atoms.size() > centralMol->_atoms.size()) { centralMol = mol; } } if (centralMol->_atoms.size() < MINIMUM_LIGAND_ATOMS) { ligandResidueStyle = false; } map templateCenters; for (sketcherMinimizerMolecule* mol : proximityMols) { sketcherMinimizerPointF point(0, 0); sketcherMinimizerAtom* at = molMap[mol]; if (at) { point = at->coordinates; } templateCenters[mol] = point; } if (ligandResidueStyle) { queue q; map getParent; q.push(centralMol); while (q.size()) { sketcherMinimizerMolecule* mol = q.front(); q.pop(); if (mol->isPlaced) { continue; } if (mol == centralMol) { mol->isPlaced = true; } else { sketcherMinimizerMolecule* parent = getParent[mol]; if (parent != nullptr) { placeMolResidueLigandStyle(mol, parent); } } for (sketcherMinimizerBond* b : mol->m_proximityRelations) { if (!b->startAtom->molecule ->isPlaced) { // will place a molecule twice if it has // two relations with mol. This is safe // cause of the continue for mol->isPlace // when looping the second time q.push(b->startAtom->molecule); getParent[b->startAtom->molecule] = mol; } if (!b->endAtom->molecule->isPlaced) { q.push(b->endAtom->molecule); getParent[b->endAtom->molecule] = mol; } } } } else { vector proximityDataVector = buildProximityDataVector(proximityMols, molMap); rotateMoleculesWithProximityRelations(proximityMols, molMap, proximityDataVector); translateMoleculesWithProximityRelations( proximityMols, molMap, templateCenters, proximityDataVector); } } void sketcherMinimizer::placeMolResidueLigandStyle( sketcherMinimizerMolecule* mol, sketcherMinimizerMolecule* parent) { int n = 0; sketcherMinimizerPointF parentV(0, 0); sketcherMinimizerPointF parentAdditionV(0, 0); sketcherMinimizerPointF v(0, 0); sketcherMinimizerPointF additionV(0, 0); // actually using line to centroid, to // orient the molecule away from the // ligand sketcherMinimizerPointF cent = mol->center(); for (sketcherMinimizerBond* b : mol->m_proximityRelations) { sketcherMinimizerAtom *at = nullptr, *parentAt = nullptr; if (b->startAtom->molecule == parent) { parentAt = b->startAtom; at = b->endAtom; } else if (b->endAtom->molecule == parent) { at = b->startAtom; parentAt = b->endAtom; } if (at == nullptr || parentAt == nullptr) { continue; } n++; sketcherMinimizerPointF paddV = parentAt->getSingleAdditionVector(); if (b->isResidueInteraction()) { auto* ri = static_cast(b); if (ri->startAtom->molecule == parent && ri->m_otherStartAtoms.size()) { paddV = sketcherMinimizerAtom::getSingleAdditionVector( ri->getAllStartAtoms()); } else if (ri->endAtom->molecule == parent && ri->m_otherEndAtoms.size()) { paddV = sketcherMinimizerAtom::getSingleAdditionVector( ri->getAllEndAtoms()); } } paddV.normalize(); paddV *= bondLength * 3; parentV += parentAt->coordinates; parentAdditionV += paddV; additionV += at->coordinates - cent; v += at->coordinates; } if (n > 0) { v /= n; parentV /= n; parentAdditionV /= n; additionV /= n; sketcherMinimizerPointF startingPos = parentV + parentAdditionV; startingPos = exploreGridAround(startingPos, 15, 10); auto signedAngle = sketcherMinimizerMaths::signedAngle( startingPos - parentV, sketcherMinimizerPointF(0, 0), -additionV); auto angle = static_cast(signedAngle / 180 * M_PI); float s = sin(angle); float c = cos(angle); for (sketcherMinimizerAtom* a : mol->_atoms) { a->coordinates -= v; a->coordinates.rotate(s, c); a->coordinates += startingPos; a->coordinates.round(); } flipIfCrossingInteractions(mol); sketcherMinimizerPointF avoidClashV = exploreMolPosition( mol, 15, bondLength * 0.5); // explore positions on a grid of points to solve clashes for (sketcherMinimizerAtom* a : mol->_atoms) { a->coordinates += avoidClashV; } } mol->isPlaced = true; } void sketcherMinimizer::flipIfCrossingInteractions( sketcherMinimizerMolecule* mol) { for (unsigned int bb = 0; bb < mol->m_proximityRelations.size() - 1; bb++) { bool out = false; sketcherMinimizerBond* pr1 = mol->m_proximityRelations[bb]; if (pr1->startAtom->molecule == pr1->endAtom->molecule) { continue; } if (!(pr1->startAtom->molecule->isPlaced || pr1->startAtom->molecule == mol)) { continue; } if (!(pr1->endAtom->molecule->isPlaced || pr1->endAtom->molecule == mol)) { continue; } for (unsigned int bb2 = bb + 1; bb2 < mol->m_proximityRelations.size(); bb2++) { sketcherMinimizerBond* pr2 = mol->m_proximityRelations[bb2]; if (pr2->startAtom->molecule == pr2->endAtom->molecule) { continue; } if (!(pr2->startAtom->molecule->isPlaced || pr2->startAtom->molecule == mol)) { continue; } if (!(pr2->endAtom->molecule->isPlaced || pr2->endAtom->molecule == mol)) { continue; } if (sketcherMinimizerMaths::intersectionOfSegments( pr1->startAtom->coordinates, pr1->endAtom->coordinates, pr2->startAtom->coordinates, pr2->endAtom->coordinates)) { /* mirror the coordinates */ sketcherMinimizerAtom* p1 = nullptr; sketcherMinimizerAtom* p2 = nullptr; if (pr1->startAtom->molecule == mol) { p1 = pr1->startAtom; } else if (pr1->endAtom->molecule == mol) { p1 = pr1->endAtom; } if (pr2->startAtom->molecule == mol) { p2 = pr2->startAtom; } else if (pr2->endAtom->molecule == mol) { p2 = pr2->endAtom; } if (p1 && p2) { sketcherMinimizerPointF middleP = p1->coordinates + p2->coordinates; middleP *= 0.5; sketcherMinimizerPointF p1p2V = p1->coordinates - p2->coordinates; p1p2V.normalize(); for (sketcherMinimizerAtom* a : mol->_atoms) { sketcherMinimizerPointF v2 = a->coordinates - middleP; float dot = sketcherMinimizerMaths::dotProduct(p1p2V, v2); sketcherMinimizerPointF parallel = p1p2V; parallel *= dot; // parallel component of v2 a->coordinates -= 2 * parallel; a->coordinates.round(); } out = true; break; } } } if (out) { break; } } } void sketcherMinimizer::arrangeMultipleMolecules() { for (auto& _residue : _residues) { // replace residues _residue->coordinatesSet = false; } if (_molecules.size() > 1) { // find centers for molecules bound by proximity relations vector proximityMols; for (sketcherMinimizerMolecule* mol : _molecules) { if (mol->m_proximityRelations.size()) { proximityMols.push_back(mol); } } sketcherMinimizerPointF center(0.f, 0.f); if (proximityMols.size()) { placeMoleculesWithProximityRelations(proximityMols); } else { int maxI = 0; size_t maxSize = _molecules[0]->_atoms.size(); for (unsigned int i = 0; i < _molecules.size(); i++) { sketcherMinimizerMolecule* m = _molecules[i]; size_t size = m->_atoms.size(); if (size > maxSize) { maxI = i; maxSize = size; } } sketcherMinimizerMolecule* centralMol = _molecules[maxI]; centralMol->isPlaced = true; for (sketcherMinimizerAtom* a : _atoms) a->_generalUseVisited = false; // using _generalUseVisited to keep track of charged // atoms that have already been used for counterions center = centralMol->center(); } // placing non counterions bool foundCounterion = true; while (foundCounterion) { foundCounterion = false; for (sketcherMinimizerMolecule* mol : _molecules) { bool residue = false; if (mol->_atoms.size() == 1) { if (mol->_atoms[0]->isResidue()) { residue = true; } } if (!residue) { if (mol->hasConstrainedFragments) { mol->isPlaced = true; } } if (mol->hasFixedFragments) { mol->isPlaced = true; } if (mol->isPlaced) { continue; } if (residue) { continue; } int charge = mol->totalCharge(); if (charge == 0) { sketcherMinimizerPointF counterionMin, counterionMax; sketcherMinimizerPointF placeNextTo = center; mol->boundingBox(counterionMin, counterionMax); float counteriondx = (counterionMax.x() - counterionMin.x()) * .5f; float counteriondy = (counterionMax.y() - counterionMin.y()) * .5f; sketcherMinimizerPointF counterionCenter = (counterionMax + counterionMin) * .5f; foundCounterion = true; // explore a grid around placeNextTo to find a suitable // place sketcherMinimizerPointF centerOfGrid = placeNextTo; float gridD = bondLength; placeNextTo = exploreGridAround(centerOfGrid, 10, gridD, counteriondx, counteriondy); for (sketcherMinimizerAtom* a : mol->_atoms) { a->coordinates += placeNextTo - counterionCenter; } mol->isPlaced = true; } } } // placing counterions foundCounterion = true; while (foundCounterion) { foundCounterion = false; for (sketcherMinimizerMolecule* mol : _molecules) { if (mol->isPlaced) { continue; } int charge = mol->totalCharge(); if (charge != 0) { sketcherMinimizerPointF counterionMin, counterionMax; sketcherMinimizerPointF placeNextTo = center; mol->boundingBox(counterionMin, counterionMax); float counteriondx = (counterionMax.x() - counterionMin.x()) * .5f; float counteriondy = (counterionMax.y() - counterionMin.y()) * .5f; sketcherMinimizerPointF counterionCenter = (counterionMax + counterionMin) * .5f; foundCounterion = true; // find an already placed charged atom to place the // counterion next to for (sketcherMinimizerMolecule* m : _molecules) { bool found = false; if (!m->isPlaced) { continue; } for (sketcherMinimizerAtom* a : m->_atoms) { if (a->charge == 0) { continue; } if (a->_generalUseVisited) { continue; } if (a->charge * charge < 0) { a->_generalUseVisited = true; placeNextTo = a->coordinates; found = true; break; } } if (found) { break; } } // explore a grid around placeNextTo to find a suitable // place sketcherMinimizerPointF centerOfGrid = placeNextTo; float gridD = bondLength; placeNextTo = exploreGridAround(centerOfGrid, 10, gridD, counteriondx, counteriondy, bondLength * 0.8); for (sketcherMinimizerAtom* a : mol->_atoms) { a->coordinates += placeNextTo - counterionCenter; } mol->isPlaced = true; } } } vector ligandAtoms; for (sketcherMinimizerAtom* a : _atoms) if (a->m_isLigand) { ligandAtoms.push_back(a); } placeResidues(ligandAtoms); } } void sketcherMinimizer::initializeFragments() { if (!_fragments.size()) { cerr << "Sketcherlibs warning: no fragments to initialize" << endl; return; } for (sketcherMinimizerFragment* indf : _independentFragments) { // recursively assign it to children assignNumberOfChildrenAtomsFromHere(indf); } for (sketcherMinimizerFragment* f : _fragments) { m_fragmentBuilder.initializeCoordinates(f); } for (sketcherMinimizerFragment* indf : _independentFragments) { // recursively assign it to children assignLongestChainFromHere(indf); } } bool sketcherMinimizer::alignWithParentDirectionConstrained( sketcherMinimizerFragment* fragment, const sketcherMinimizerPointF& position, float angle) { vector templates, plainCoordinates, flippedCoordinates; float sine = sin(angle); float cosine = cos(angle); for (const auto& atom : fragment->_coordinates) { if (atom.first->constrained) { sketcherMinimizerPointF plainCoordinatesAtom = atom.second; sketcherMinimizerPointF flippedCoordinatesAtom( plainCoordinatesAtom.x(), -plainCoordinatesAtom.y()); plainCoordinatesAtom.rotate(sine, cosine); flippedCoordinatesAtom.rotate(sine, cosine); templates.push_back(atom.first->templateCoordinates); plainCoordinates.push_back(plainCoordinatesAtom + position); flippedCoordinates.push_back(flippedCoordinatesAtom + position); } } float scorePlain = roundToTwoDecimalDigits(RMSD(templates, plainCoordinates)); float scoreFlipped = roundToTwoDecimalDigits(RMSD(templates, flippedCoordinates)); return (scoreFlipped < scorePlain); } vector sketcherMinimizer::getAllTerminalBonds(sketcherMinimizerFragment* fragment) { vector bonds; for (auto bond : fragment->getBonds()) { if (bond->isResidueInteraction()) { continue; } if (bond->startAtom->neighbors.size() == 1 || bond->endAtom->neighbors.size() == 1) { bonds.push_back(bond); } } for (auto child : fragment->_children) { bonds.push_back(child->_bondToParent); } if (fragment->getParent()) { bonds.push_back(fragment->_bondToParent); } return bonds; } vector> sketcherMinimizer::findDirectionsToAlignWith( sketcherMinimizerFragment* fragment) { vector> chainDirs; sketcherMinimizerPointF origin = (fragment->_bondToParent->startAtom->coordinates + fragment->_bondToParent->endAtom->coordinates) * 0.5; vector parentEndBonds = getAllTerminalBonds(fragment->getParent()); for (auto bond : parentEndBonds) { if (bond->endAtom->fragment == fragment) { continue; } sketcherMinimizerPointF direction = origin - (bond->startAtom->coordinates + bond->endAtom->coordinates) * 0.5; direction.normalize(); float score = 1.f; if (bond->bondOrder == 2) { score *= SCORE_MULTIPLIER_FOR_DOUBLE_BONDS; } if ((bond->startAtom->neighbors.size() == 1 && bond->startAtom->atomicNumber != 6) || (bond->endAtom->neighbors.size() == 1 && bond->endAtom->atomicNumber != 6)) { score *= SCORE_MULTIPLIER_FOR_SINGLE_BONDED_HETEROATOMS; } if (bond->endAtom->fragment != fragment->getParent() || bond->startAtom->fragment != fragment->getParent()) { score = bond->endAtom->fragment->longestChainFromHere * SCORE_MULTIPLIER_FOR_FRAGMENTS; if (fragment->getParent()->getParent() && bond->startAtom->fragment == fragment->getParent()->getParent()) { score *= 100; } } chainDirs.emplace_back(direction, score); } return chainDirs; } float sketcherMinimizer::testAlignment( const sketcherMinimizerPointF& direction, const std::pair& templat) { float dot = sketcherMinimizerMaths::dotProduct(direction, templat.first); if (dot < 0) { dot = 0; } float score = dot * dot; if (dot > 1 - SKETCHER_EPSILON) { score += 1000; } score *= templat.second; return score; } sketcherMinimizerPointF sketcherMinimizer::scoreDirections( sketcherMinimizerFragment* fragment, float angle, const vector>& directions, bool& invert) { float sine = sin(angle); float cosine = cos(angle); float bestScore = 0.f; sketcherMinimizerPointF bestDirection(1.f, 0.f); vector terminalBonds = getAllTerminalBonds(fragment); for (auto bond : terminalBonds) { if (bond->startAtom->fragment != fragment) { continue; } sketcherMinimizerPointF bondDirectionPlain = (fragment->_coordinates[bond->startAtom] + fragment->_coordinates[bond->endAtom]) * 0.5 - sketcherMinimizerPointF(-bondLength * 0.5, 0); bondDirectionPlain.normalize(); sketcherMinimizerPointF bondDirectionInverted(bondDirectionPlain.x(), -bondDirectionPlain.y()); bondDirectionPlain.rotate(sine, cosine); bondDirectionInverted.rotate(sine, cosine); float scoreModifier = 1.f; if (bond->bondOrder == 2) { scoreModifier *= SCORE_MULTIPLIER_FOR_DOUBLE_BONDS; } if ((bond->startAtom->neighbors.size() == 1 && bond->startAtom->atomicNumber != 6) || (bond->endAtom->neighbors.size() == 1 && bond->endAtom->atomicNumber != 6)) { scoreModifier *= SCORE_MULTIPLIER_FOR_SINGLE_BONDED_HETEROATOMS; } if (bond->endAtom->fragment != fragment) { scoreModifier = bond->endAtom->fragment->longestChainFromHere * SCORE_MULTIPLIER_FOR_FRAGMENTS; } for (const auto& direction : directions) { float scorePlain = testAlignment(bondDirectionPlain, direction) * scoreModifier; if (scorePlain > bestScore) { bestScore = scorePlain; bestDirection = direction.first; invert = false; } float scoreInverted = testAlignment(bondDirectionInverted, direction) * scoreModifier; if (scoreInverted > bestScore) { bestScore = scoreInverted; bestDirection = direction.first; invert = true; } } } return bestDirection; } bool sketcherMinimizer::alignWithParentDirectionUnconstrained( sketcherMinimizerFragment* fragment, float angle) { vector> directions = findDirectionsToAlignWith(fragment); bool invert = false; scoreDirections(fragment, angle, directions, invert); return invert; } void sketcherMinimizer::alignWithParentDirection( sketcherMinimizerFragment* f, const sketcherMinimizerPointF& position, float angle) { // deciding which "side" the fragment will be drawn, rotating 180° around // the axis of its bond to parent if (f->fixed) { return; } bool invert = (f->constrained ? alignWithParentDirectionConstrained(f, position, angle) : alignWithParentDirectionUnconstrained(f, angle)); if (invert) { for (auto& atom : f->_coordinates) { atom.second.setY(-atom.second.y()); } for (auto atom : f->getAtoms()) { if (atom->hasStereochemistrySet) { for (auto bond : atom->bonds) { bond->isWedge = !bond->isWedge; } } } } } void sketcherMinimizer::assignNumberOfChildrenAtomsFromHere( sketcherMinimizerFragment* f) { size_t cumulatedNumberOfAtoms = 0; float cumulatedNumberOfAtomsRanks = 0; size_t childrenAtoms = 0; for (sketcherMinimizerFragment* child : f->_children) { assignNumberOfChildrenAtomsFromHere(child); cumulatedNumberOfAtoms += child->numberOfChildrenAtoms; cumulatedNumberOfAtomsRanks += child->numberOfChildrenAtomsRank; childrenAtoms += child->getAtoms().size(); } f->numberOfChildrenAtoms = cumulatedNumberOfAtoms + childrenAtoms; f->numberOfChildrenAtomsRank = static_cast(0.01f * cumulatedNumberOfAtomsRanks + childrenAtoms); } void sketcherMinimizer::assignLongestChainFromHere(sketcherMinimizerFragment* f) { float longestDist = 0; for (sketcherMinimizerFragment* child : f->_children) { assignLongestChainFromHere(child); if (child->longestChainFromHere > longestDist) { longestDist = child->longestChainFromHere; } } sketcherMinimizerPointF positionFromParent(0.f, 0.f); if (f->getParent()) { positionFromParent = f->getParent()->_coordinates[f->_bondToParent->endAtom]; } f->longestChainFromHere = longestDist + positionFromParent.length(); } sketcherMinimizerBond* sketcherMinimizer::getBond(const sketcherMinimizerAtom* a1, const sketcherMinimizerAtom* a2) { for (unsigned int i = 0; i < a1->neighbors.size(); i++) { if (a1->neighbors[i] == a2) { return a1->bonds[i]; } } return nullptr; } sketcherMinimizerAtom* sketcherMinimizer::pickBestAtom(vector& atoms) { vector candidates, oldCandidates; { size_t biggestSize = atoms[0]->fragment->numberOfChildrenAtoms; for (sketcherMinimizerAtom* a : atoms) { size_t size = a->fragment->numberOfChildrenAtoms; if (size == biggestSize) { candidates.push_back(a); } else if (size > biggestSize) { biggestSize = size; candidates.clear(); candidates.push_back(a); } } if (candidates.size() == 1) { return candidates[0]; } oldCandidates = candidates; candidates.clear(); } { float biggestSize = oldCandidates[0]->fragment->numberOfChildrenAtomsRank; for (sketcherMinimizerAtom* a : oldCandidates) { float size = a->fragment->numberOfChildrenAtomsRank; if (size == biggestSize) { candidates.push_back(a); } else if (size > biggestSize) { biggestSize = size; candidates.clear(); candidates.push_back(a); } } if (candidates.size() == 1) { return candidates[0]; } oldCandidates = candidates; candidates.clear(); } { int biggestSize = oldCandidates[0]->atomicNumber; for (sketcherMinimizerAtom* a : oldCandidates) { int size = a->atomicNumber; if (size == biggestSize) { candidates.push_back(a); } else if (size > biggestSize) { biggestSize = size; candidates.clear(); candidates.push_back(a); } } if (candidates.size() == 1) { return candidates[0]; } oldCandidates = candidates; candidates.clear(); } // give up return oldCandidates[0]; } void sketcherMinimizer::constrainAllAtoms() { // cerr << "sketcherMinimizer::constrainAllAtoms ()"<constrained = true; } void sketcherMinimizer::constrainAtoms(const vector& constrained) { if (constrained.size() == _referenceAtoms.size()) { for (unsigned int i = 0; i < constrained.size(); i++) { if (constrained[i]) { _referenceAtoms[i]->constrained = true; } } } else { cerr << "warning, wrong size of vector for constrained atoms. Ignoring" << endl; } } void sketcherMinimizer::fixAtoms(const vector& fixed) { if (fixed.size() == _referenceAtoms.size()) { for (unsigned int i = 0; i < fixed.size(); i++) { if (fixed[i]) { _referenceAtoms[i]->fixed = true; } } } else { cerr << "warning, wrong size of vector for fixed atoms. Ignoring" << endl; } } void sketcherMinimizer::findClosestAtomToResidues( const vector& catoms) { const vector& atoms = catoms.empty() ? _atoms : catoms; for (sketcherMinimizerAtom* r : _residues) { float squareD = 9999999.f; sketcherMinimizerAtom* closestA = nullptr; for (sketcherMinimizerAtom* a : atoms) { if (!a->isResidue()) { float diffx = a->m_x3D - r->m_x3D; float diffy = a->m_y3D - r->m_y3D; float diffz = a->m_z3D - r->m_z3D; float newSquareD = diffx * diffx + diffy * diffy + diffz * diffz; if (newSquareD < squareD) { squareD = newSquareD; closestA = a; } } } static_cast(r)->m_closestLigandAtom = closestA; if (!r->m_isClashing) { r->m_isClashing = (squareD < RESIDUE_CLASH_DISTANCE_SQUARED); } } for (sketcherMinimizerBond* b : _bonds) { if (b->startAtom->isResidue()) { static_cast(b->startAtom) ->m_closestLigandAtom = b->endAtom; } if (b->endAtom->isResidue()) { static_cast(b->endAtom) ->m_closestLigandAtom = b->startAtom; } } } float sketcherMinimizer::RMSD(const vector& templates, const vector& points) { assert(templates.size() == points.size()); size_t counter = templates.size(); float total = 0.f; for (unsigned int i = 0; i < templates.size(); i++) { // cerr << templates[i].x () << ", "<< templates[i].y () << " // " << points[i].x()<<", "< 0) { total /= counter; } return sqrt(total); } void sketcherMinimizer::alignmentMatrix( const vector& ref, const vector& points, float* m) { float U[4]; float Sig[4]; float V[4]; float a[4]; a[0] = 0.f; a[1] = 0.f; a[2] = 0.f; a[3] = 0.f; assert(ref.size() == points.size()); for (unsigned int i = 0; i < ref.size(); i++) { a[0] += ref[i].x() * points[i].x(); a[1] += ref[i].y() * points[i].x(); a[2] += ref[i].x() * points[i].y(); a[3] += ref[i].y() * points[i].y(); } svd(a, U, Sig, V); m[0] = V[0] * U[0] + V[1] * U[1]; m[1] = V[0] * U[2] + V[1] * U[3]; m[2] = V[2] * U[0] + V[3] * U[1]; m[3] = V[2] * U[2] + V[3] * U[3]; } void sketcherMinimizer::svd(float* a, float* U, float* Sig, float* V) { float a1[4]; a1[0] = a[0]; a1[1] = a[2]; a1[2] = a[1]; a1[3] = a[3]; float Su[4]; Su[0] = a[0] * a1[0] + a[1] * a1[2]; Su[1] = a[0] * a1[1] + a[1] * a1[3]; Su[2] = a[2] * a1[0] + a[3] * a1[2]; Su[3] = a[2] * a1[1] + a[3] * a1[3]; auto phi = static_cast(0.5 * atan2(Su[1] + Su[2], Su[0] - Su[3])); float cphi = cos(phi); cphi = roundToTwoDecimalDigits(cphi); float sphi = sin(phi); sphi = roundToTwoDecimalDigits(sphi); U[0] = cphi * (-1); U[1] = -sphi; U[2] = sphi * (-1); U[3] = cphi; float Sw[4]; Sw[0] = a1[0] * a[0] + a1[1] * a[2]; Sw[1] = a1[0] * a[1] + a1[1] * a[3]; Sw[2] = a1[2] * a[0] + a1[3] * a[2]; Sw[3] = a1[2] * a[1] + a1[3] * a[3]; auto theta = static_cast(0.5 * atan2(Sw[1] + Sw[2], Sw[0] - Sw[3])); float ctheta = cos(theta); float stheta = sin(theta); float W[4]; W[0] = ctheta; W[1] = -stheta; W[2] = stheta; W[3] = ctheta; float SUsum = Su[0] + Su[3]; float SUdif = sqrt((Su[0] - Su[3]) * (Su[0] - Su[3]) + 4.f * Su[1] * Su[2]); Sig[0] = sqrt((SUsum + SUdif) * 0.5f); Sig[1] = 0.f; Sig[2] = 0.f; Sig[3] = sqrt((SUsum - SUdif) * 0.5f); float U1[4]; U1[0] = U[0]; U1[1] = U[2]; U1[2] = U[1]; U1[3] = U[3]; float U1A[4]; U1A[0] = U1[0] * a[0] + U1[1] * a[2]; U1A[1] = U1[0] * a[1] + U1[1] * a[3]; U1A[2] = U1[2] * a[0] + U1[3] * a[2]; U1A[3] = U1[2] * a[1] + U1[3] * a[3]; float S[4]; S[0] = U1A[0] * W[0] + U1A[1] * W[2]; S[1] = U1A[0] * W[1] + U1A[1] * W[3]; S[2] = U1A[2] * W[0] + U1A[3] * W[2]; S[3] = U1A[2] * W[1] + U1A[3] * W[3]; S[0] = roundToTwoDecimalDigits(S[0]); S[1] = roundToTwoDecimalDigits(S[1]); S[2] = roundToTwoDecimalDigits(S[2]); S[3] = roundToTwoDecimalDigits(S[3]); float sign1 = 1.f, sign2 = 1.f; if (S[0] < 0.f) { sign1 = -1.f; } if (S[3] < 0.f) { sign2 = -1.f; } float C[4]; C[0] = sign1; C[1] = 0.f; C[2] = 0.f; C[3] = sign2; V[0] = W[0] * C[0] + W[1] * C[2]; V[1] = W[0] * C[1] + W[1] * C[3]; V[2] = W[2] * C[0] + W[3] * C[2]; V[3] = W[2] * C[1] + W[3] * C[3]; V[0] = roundToTwoDecimalDigits(V[0]); V[1] = roundToTwoDecimalDigits(V[1]); V[2] = roundToTwoDecimalDigits(V[2]); V[3] = roundToTwoDecimalDigits(V[3]); } bool sketcherMinimizer::compare(const vector& atoms, const vector& bonds, sketcherMinimizerMolecule* templ, vector& mapping) { if (atoms.size() != templ->_atoms.size()) { return false; } vector molScores, tempScores; int molIter = morganScores(atoms, bonds, molScores); int templateIter = morganScores(templ->_atoms, templ->_bonds, tempScores); if (molIter != templateIter) { return false; } size_t size = atoms.size(); vector matrix(size * size, false); vector templateCoordinates; vector> molBonds; vector> templateBonds; // for double bond chirality // vector < vector < int > > templateAllowedZChains; vector> molCisTransChains; vector molIsCis; for (unsigned int ma = 0; ma < size; ma++) { vector vec; molBonds.push_back(vec); } for (unsigned int ta = 0; ta < size; ta++) { vector vec; templateBonds.push_back(vec); } for (unsigned int ta = 0; ta < size; ta++) { templateCoordinates.push_back(templ->_atoms[ta]->coordinates); } for (auto bond : bonds) { if (bond->bondOrder == 2) { sketcherMinimizerBond* b = bond; if (b->m_ignoreZE) { continue; } bool ok = false; if (b->rings.size() == 0) { ok = true; } else { ok = true; for (auto& ring : b->rings) { if (ring->_atoms.size() < MACROCYCLE) { ok = false; } } } if (!ok) { continue; } sketcherMinimizerAtom* startA = b->startAtomCIPFirstNeighbor(); sketcherMinimizerAtom* endA = b->endAtomCIPFirstNeighbor(); sketcherMinimizerAtom* startN = nullptr; sketcherMinimizerAtom* endN = nullptr; for (unsigned int i = 0; i < b->startAtom->neighbors.size(); i++) { if (b->startAtom->neighbors[i] == b->endAtom) { continue; } bool found = false; for (auto atom : atoms) { if (atom == b->startAtom->neighbors[i]) { startN = b->startAtom->neighbors[i]; found = true; break; } } if (found) { break; } } for (unsigned int i = 0; i < b->endAtom->neighbors.size(); i++) { if (b->endAtom->neighbors[i] == b->startAtom) { continue; } bool found = false; for (auto atom : atoms) { if (atom == b->endAtom->neighbors[i]) { endN = b->endAtom->neighbors[i]; found = true; break; } } if (found) { break; } } if (startN && endN && startA && endA) { bool isCis = b->isZ; if (startN != startA) { isCis = !isCis; } if (endN != endA) { isCis = !isCis; } vector chain; chain.push_back(startN->_generalUseN); chain.push_back(b->startAtom->_generalUseN); chain.push_back(b->endAtom->_generalUseN); chain.push_back(endN->_generalUseN); molCisTransChains.push_back(chain); molIsCis.push_back(isCis); // cerr << "chiral double bond" <isZ<startAtom->_generalUseN; size_t in2 = bond->endAtom->_generalUseN; if (in1 < in2) { molBonds[in2].push_back(in1); } else { molBonds[in1].push_back(in2); } } for (auto& _bond : templ->_bonds) { size_t in1 = _bond->startAtom->_generalUseN; size_t in2 = _bond->endAtom->_generalUseN; if (in1 < in2) { templateBonds[in2].push_back(in1); } else { templateBonds[in1].push_back(in2); } } for (unsigned int ma = 0; ma < atoms.size(); ma++) { for (unsigned int ta = 0; ta < templ->_atoms.size(); ta++) { if (molScores[ma] == tempScores[ta]) { matrix[ma * size + ta] = true; } } } bool found = false; vector solution; for (unsigned int i = 0; i < size; i++) { if (!matrix[i]) { continue; } checkIdentity(solution, i, matrix, templateCoordinates, molBonds, templateBonds, molCisTransChains, molIsCis, size, found, mapping); if (found) { break; } } return found; } void sketcherMinimizer::checkIdentity( vector solution, int newSol, vector& matrix, vector& templateCoordinates, vector>& molBonds, vector>& templateBonds, vector>& molCisTransChains, vector& molIsCis, size_t size, bool& found, vector& mapping) { solution.push_back(newSol); if (solution.size() == size) { // check double bonds stereo bool doubleBondsStereoOk = true; for (size_t i = 0; i < molCisTransChains.size(); i++) { sketcherMinimizerPointF p1 = templateCoordinates[solution[molCisTransChains[i][0]]]; sketcherMinimizerPointF p2 = templateCoordinates[solution[molCisTransChains[i][1]]]; sketcherMinimizerPointF p3 = templateCoordinates[solution[molCisTransChains[i][2]]]; sketcherMinimizerPointF p4 = templateCoordinates[solution[molCisTransChains[i][3]]]; if (molIsCis[i] != sketcherMinimizerMaths::sameSide(p1, p4, p2, p3)) { doubleBondsStereoOk = false; break; } } if (doubleBondsStereoOk) { found = true; mapping = solution; } } else { for (unsigned int i = 0; i < size; i++) { if (found) { break; } if (!(matrix[solution.size() * size + i])) { continue; } bool check = true; for (unsigned int ss : solution) { if (ss == i) { check = false; break; } } if (check) { for (const auto& molBond : molBonds[solution.size()]) { check = false; unsigned int high = i; unsigned int low = solution[molBond]; if (low > high) { std::swap(low, high); } for (auto ch : templateBonds[high]) { if (ch == low) { check = true; break; } } if (!check) { break; } } } if (check) { checkIdentity(solution, i, matrix, templateCoordinates, molBonds, templateBonds, molCisTransChains, molIsCis, size, found, mapping); } } } } void sketcherMinimizer::setTemplateFileDir(string dir) { sketcherMinimizer::m_templates.setTemplateDir(std::move(dir)); } static void normalizeTemplate(sketcherMinimizerMolecule* mol) { // normalize bond length and set _generalUseN on atoms. vector dds; vector ns; for (auto& _bond : mol->_bonds) { sketcherMinimizerPointF v = _bond->startAtom->coordinates - _bond->endAtom->coordinates; float dd = v.x() * v.x() + v.y() * v.y(); bool found = false; for (unsigned int j = 0; j < dds.size(); ++j) { if (dd * 0.9 < dds[j] && dd * 1.1 > dds[j]) { ++ns[j]; found = true; break; } } if (!found) { dds.push_back(dd); ns.push_back(1); } } if (dds.size()) { int maxI = 0; for (unsigned int i = 0; i < ns.size(); ++i) { if (ns[i] > ns[maxI]) { maxI = i; } } float f = sqrt(dds[maxI]); for (unsigned int i = 0; i < mol->_atoms.size(); ++i) { mol->_atoms[i]->coordinates /= f; mol->_atoms[i]->_generalUseN = i; } } } #ifdef USE_MAEPARSER static string getTempFileProjDir() { return sketcherMinimizer::m_templates.getTemplateDir(); } static string getUserTemplateFileName() { const string suffix = "user_templates.mae"; // whatever you wish return getTempFileProjDir() + suffix; } static void loadTemplate(const string& filename, vector& templates) { auto pFile = fopen(filename.c_str(), "r"); if (pFile == nullptr) { return; } schrodinger::mae::Reader r(pFile); std::shared_ptr b; while ((b = r.next(mae::CT_BLOCK)) != nullptr) { auto mol = mol_from_mae_block(*b); normalizeTemplate(mol); templates.push_back(mol); } fclose(pFile); } #endif void sketcherMinimizer::loadTemplates() { static bool loaded = false; if (loaded || !m_templates.getTemplates().empty()) { return; } m_templates.getTemplates() = coordgen_templates(); for (auto* m : m_templates.getTemplates()) { normalizeTemplate(m); } #ifdef USE_MAEPARSER string filename = getUserTemplateFileName(); loadTemplate(filename, m_templates.getTemplates()); #endif loaded = true; } int sketcherMinimizer::morganScores(const vector& atoms, const vector& bonds, vector& oldScores) { // assumes that atom[i]->_generalUseN = i if (atoms.size() < 2) { return 0; } oldScores = vector(atoms.size(), 1); vector newScores(atoms.size(), 0); vector orderedScores; bool goOn = false; int n = 0; size_t idx1, idx2; size_t oldTies = atoms.size(); unsigned int i = 0, j = 0; do { ++n; for (i = 0; i < bonds.size(); ++i) { idx1 = bonds[i]->startAtom->_generalUseN; idx2 = bonds[i]->endAtom->_generalUseN; newScores[idx1] += oldScores[idx2]; newScores[idx2] += oldScores[idx1]; } orderedScores = newScores; stable_sort(orderedScores.begin(), orderedScores.end()); size_t newTies = 0; for (j = 1; j < orderedScores.size(); j++) { if (orderedScores[j] == orderedScores[j - 1]) { newTies++; } } if (newTies < oldTies) { goOn = true; oldTies = newTies; oldScores = newScores; } else { goOn = false; } } while (goOn); return n; } CoordgenTemplates sketcherMinimizer::m_templates;