/* Contributors: Nicola Zonta Copyright Schrodinger, LLC. All rights reserved */ #include #include #include "CoordgenMacrocycleBuilder.h" #include "CoordgenMinimizer.h" #include "sketcherMinimizer.h" #include "sketcherMinimizerMaths.h" #include "sketcherMinimizerRing.h" #include "sketcherMinimizerStretchInteraction.h" #define MAX_MACROCYCLES 40 #define PATH_FAILED -1000 #define HETEROATOM_RESTRAINT 1 #define SUBSTITUTED_ATOM_RESTRAINT 10 #define SUBSTITUENTS_TO_SAME_VERTEX_RESTRAINT 200 #define SUBSTITUENT_CLASH_WITH_PATH_RESTRAINT 400 #define SQRT3HALF 0.8660254037844386 using namespace std; std::ostream& operator<<(std::ostream& os, const vertexCoords& v) { os << "(" << v.x << "," << v.y << "," << v.z << ")"; return os; } std::ostream& operator<<(std::ostream& os, const hexCoords& h) { os << "(" << h.x << "," << h.y << ")"; return os; } Polyomino::Polyomino() { resizeGrid(1); } Polyomino::Polyomino(const Polyomino& rhs) { clear(); pentagonVertices = rhs.pentagonVertices; resizeGrid(1); for (auto i : rhs.m_list) { addHex(i->coords()); } reassignHexs(); } Polyomino& Polyomino::operator=(const Polyomino& rhs) { clear(); resizeGrid(1); pentagonVertices = rhs.pentagonVertices; for (auto i : rhs.m_list) { addHex(i->coords()); } reassignHexs(); return *this; } Polyomino::~Polyomino() { clear(); } void Polyomino::clear() { for (auto& i : m_list) { delete i; } m_list.clear(); } size_t Polyomino::size() const { return m_list.size(); } void Polyomino::resizeGrid(int i) const { m_grid.resize((2 * i + 1) * (2 * i + 1)); // grid can hold coordinates from -i to i m_gridSize = i; reassignHexs(); } void Polyomino::reassignHexs() const { for (auto& j : m_grid) { j = nullptr; } for (auto hex : m_list) { m_grid[getIndexInList(hex->coords())] = hex; } } bool Polyomino::isTheSameAs(Polyomino& p) const { // mirrored polyominoes are considered to be different if (size() != p.size()) { return false; } vector targetCoords; for (Hex* hex : p.m_list) { targetCoords.push_back(hex->coords()); } if (!targetCoords.size()) { return true; // both polyominoes are empty } int lowestx = m_list[0]->coords().x; int lowesty = m_list[0]->coords().y; for (Hex* hex : m_list) { int x = hex->coords().x; int y = hex->coords().y; if (x < lowestx) { lowestx = x; } if (y < lowesty) { lowesty = y; } } for (unsigned int i = 0; i < 6; i++) { // explore 6 possible positions rotating the coordinates 30 // degrees int lowestTargetX = 0, lowestTargetY = 0; for (unsigned int j = 0; j < targetCoords.size(); j++) { if (j == 0 || targetCoords[j].x < lowestTargetX) { lowestTargetX = targetCoords[j].x; } if (j == 0 || targetCoords[j].y < lowestTargetY) { lowestTargetY = targetCoords[j].y; } } // translate for (auto& targetCoord : targetCoords) { targetCoord = hexCoords(targetCoord.x + lowestx - lowestTargetX, targetCoord.y + lowesty - lowestTargetY); } // check bool same = true; for (auto targetCoord : targetCoords) { if (!getHex(targetCoord)) { same = false; break; } } if (same) { return true; } // rotate for (auto& targetCoord : targetCoords) { targetCoord = targetCoord.rotate30Degrees(); } } return false; } Hex* Polyomino::getHex(hexCoords coords) const { return m_grid[getIndexInList(coords)]; } void Polyomino::buildSkewedBoxShape(int x, int y, bool pentagon) { clear(); for (int yy = 0; yy < y; yy++) { for (int xx = 0; xx < x; xx++) { addHex(hexCoords(xx, yy)); } } if (pentagon) { markOneVertexAsPentagon(); } } void Polyomino::buildRaggedSmallerBoxShape( int x, int y, bool pentagon) // box alternating rows of length x and x-1 { clear(); int startx = 0; for (int yy = 0; yy < y; yy++) { // bigger row for (int xx = 0; xx < x; xx++) { addHex(hexCoords(startx + xx, yy)); } yy++; if (yy >= y) { break; } for (int xx = 0; xx < x - 1; xx++) { addHex(hexCoords(startx + xx, yy)); } startx--; } if (pentagon) { markOneVertexAsPentagon(); } } void Polyomino::buildRaggedBiggerBoxShape( int x, int y, bool pentagon) // box alternating rows of length x and x+1 { clear(); int startx = 0; for (int yy = 0; yy < y; yy++) { // smaller row for (int xx = 0; xx < x; xx++) { addHex(hexCoords(startx + xx, yy)); } yy++; if (yy >= y) { break; } for (int xx = 0; xx < x + 1; xx++) { addHex(hexCoords(startx + xx - 1, yy)); } startx--; } if (pentagon) { markOneVertexAsPentagon(); } } void Polyomino::buildRaggedBoxShape(int x, int y, bool pentagon) { clear(); int startx = 0; for (int yy = 0; yy < y; yy++) { for (int xx = 0; xx < x; xx++) { addHex(hexCoords(startx + xx, yy)); } yy++; if (yy >= y) { break; } for (int xx = 0; xx < x; xx++) { addHex(hexCoords(startx + xx, yy)); } startx--; } if (pentagon) { markOneVertexAsPentagon(); } } void Polyomino::buildWithVerticesN(int totVertices) { clear(); addHex(hexCoords(0, 0)); addHex(hexCoords(1, 0)); int vertices = 10; while (vertices < totVertices) { // find a hexagon with 2 neighbors to add. std::vector nextCoords = allFreeNeighbors(); int lowestDistance = -1; unsigned int bestI = 0; for (unsigned int i = 0; i < nextCoords.size(); i++) { hexCoords c = nextCoords[i]; if (countNeighbors(c) != 2) { continue; } int distance = c.distanceFrom(hexCoords(0, 0)); if (lowestDistance == -1 || distance < lowestDistance) { lowestDistance = distance; bestI = i; } } assert(lowestDistance != -1); addHex(nextCoords[bestI]); for (unsigned int i = 0; i < nextCoords.size(); i++) { if (i == bestI) { continue; } hexCoords c = nextCoords[i]; if (countNeighbors(c) == 3) { addHex(c); } } vertices += 2; } if ((vertices - totVertices) == 1) { markOneVertexAsPentagon(); } } void Polyomino::markOneVertexAsPentagon() // TO DO: should check if one vertex // is already marked to avoid marking // it again. Not a problem while we // only mark one vertex per polyomino { vector pathV = getPath(); size_t previousMultiplicity = hexagonsAtVertex(pathV[pathV.size() - 1]); size_t thisMultiplicity = hexagonsAtVertex(pathV[0]); size_t nextMultiplicity = 0; for (size_t i = 0; i < pathV.size(); i++) { size_t nextI = (i >= pathV.size() - 1) ? 0 : i + 1; nextMultiplicity = hexagonsAtVertex(pathV[nextI]); if (previousMultiplicity == 2 && thisMultiplicity == 1 && nextMultiplicity == 2) { setPentagon(pathV[i]); return; } previousMultiplicity = thisMultiplicity; thisMultiplicity = nextMultiplicity; } previousMultiplicity = hexagonsAtVertex(pathV[pathV.size() - 1]); thisMultiplicity = hexagonsAtVertex(pathV[0]); nextMultiplicity = 0; for (size_t i = 0; i < pathV.size(); i++) { size_t nextI = (i >= pathV.size() - 1) ? 0 : i + 1; nextMultiplicity = hexagonsAtVertex(pathV[nextI]); if (previousMultiplicity == 1 && thisMultiplicity == 2 && nextMultiplicity == 1) { setPentagon(pathV[i]); return; } previousMultiplicity = thisMultiplicity; thisMultiplicity = nextMultiplicity; } } void Polyomino::setPentagon(vertexCoords c) // the marked vertex and the // following one will count as a // single vertex to turn the hexagon // into a pentagon { pentagonVertices.push_back(c); } size_t Polyomino::hexagonsAtVertex(vertexCoords v) const { return vertexNeighbors(v).size(); } vector Polyomino::freeVertexNeighborPositions(vertexCoords v) const { vector out; int direction = v.x + v.y + v.z; if (direction != 1 && direction != -1) { cerr << "wrong input to free vertex neighbor positions " << v << endl; return out; } Hex* h = getHex(hexCoords(v.x - direction, v.y)); if (!h) { out.emplace_back(v.x - direction, v.y); } h = getHex(hexCoords(v.x, v.y - direction)); if (!h) { out.emplace_back(v.x, v.y - direction); } h = getHex(hexCoords(v.x, v.y)); // z - direction if (!h) { out.emplace_back(v.x, v.y); } return out; } vector Polyomino::vertexNeighbors(vertexCoords v) const { vector out; int direction = v.x + v.y + v.z; if (direction != 1 && direction != -1) { cerr << "wrong input to vertex Neighbors " << v << endl; return out; } Hex* h = getHex(hexCoords(v.x - direction, v.y)); if (h) { out.push_back(h); } h = getHex(hexCoords(v.x, v.y - direction)); if (h) { out.push_back(h); } h = getHex(hexCoords(v.x, v.y)); // z - direction if (h) { out.push_back(h); } return out; } vertexCoords Polyomino::findOuterVertex() const // find an outer vertex. Used to start path around polyomino. { // find a hexagon with a free vertex in direction (1, 0, 0). Such a hexagon // is guarateed to exist in a polyomino. for (auto h : m_list) { vertexCoords vert(h->x() + 1, h->y(), h->z()); if (hexagonsAtVertex(vert) == 1) { return vert; } } cerr << "something went wrong in finding the outer vertex" << endl; return {0, 0, 0}; } int Polyomino::countNeighbors(hexCoords h) const { int out = 0; vector neighs = Hex::neighboringPositions(h); for (auto neigh : neighs) { if (getHex(neigh) != nullptr) { out++; } } return out; } std::vector Polyomino::allFreeNeighbors() const { for (auto i : m_list) { // make sure that the grid is big enough to store // every neighbor getIndexInList(hexCoords(i->x() + 1, i->y() + 1)); getIndexInList(hexCoords(i->x() - 1, i->y() - 1)); } std::vector out; std::vector visited( m_grid.size(), false); // keep track if a neighbors has already been checked or not for (auto i : m_list) { std::vector neighborsCoords = i->neighbors(); for (auto neighborsCoord : neighborsCoords) { bool isPresent = (getHex(neighborsCoord) != nullptr); if (isPresent) { continue; } int index = getIndexInList(neighborsCoord); if (visited[index]) { continue; } visited[index] = true; out.push_back(neighborsCoord); } } return out; } int Polyomino::getIndexInList(hexCoords coords) const { int x = coords.x; int y = coords.y; if (abs(x) > m_gridSize) { resizeGrid(abs(x)); } if (abs(y) > m_gridSize) { resizeGrid(abs(y)); } int a = m_gridSize + x; int b = m_gridSize + y; int i = (2 * m_gridSize + 1) * a + b; return i; } void Polyomino::addHex(hexCoords coords) { int index = getIndexInList(coords); assert(m_grid[index] == NULL); Hex* h = new Hex(coords); m_list.push_back(h); m_grid[index] = h; } void Polyomino::removeHex(hexCoords coords) { int index = getIndexInList(coords); Hex* hex = m_grid[getIndexInList(coords)]; assert(hex != NULL); for (unsigned int i = 0; i < m_list.size(); i++) { if (m_list[i] == hex) { m_list.erase(m_list.begin() + i); break; } } delete hex; m_grid[index] = nullptr; } bool Polyomino::isEquivalentWithout(hexCoords c) const { /* does removing this hexagon yield another polyomino with the same number of vertices? true if hte hexagon has 3 neighbors all next to each other */ if (countNeighbors(c) != 3) { return false; } vector neighs = Hex::neighboringPositions(c); for (unsigned int i = 0; i < neighs.size(); i++) { int i2 = (i - 1 + 6) % 6; int i3 = (i - 2 + 6) % 6; if (getHex(neighs[i]) != nullptr && getHex(neighs[i2]) != nullptr && getHex(neighs[i3]) != nullptr) { return true; } } return false; } vertexCoords Polyomino::coordinatesOfSubstituent(const vertexCoords pos) const { vector neighbors = vertexNeighbors(pos); assert(neighbors.size() != 0); assert(neighbors.size() < 3); vertexCoords out = pos; if (neighbors.size() == 1) { /* the vertex coordinates differ from its parent's by a single +1 or -1 (d). The substituent will be along the same direction, so the other two coordinates will be incremented by -d. e.g. (1, 0, 0)-> (1, -1, -1) or (0, -1, 0)-> (1, -1, 1) */ vertexCoords parentCoords = neighbors[0]->coords().toVertexCoords(); vertexCoords v = pos - parentCoords; int d = -1; if (v.x + v.y + v.z > 0) { d = 1; } if (v.x == 0) { v.x -= d; } if (v.y == 0) { v.y -= d; } if (v.z == 0) { v.z -= d; } out = parentCoords + v; } else if (neighbors.size() == 2) { /* the two parents share two vertices. One is pos, the other one is the neighbor we are looking for */ vertexCoords parent1Coords = neighbors[0]->coords().toVertexCoords(); vertexCoords v = pos - parent1Coords; vertexCoords parent2Coords = neighbors[1]->coords().toVertexCoords(); out = parent2Coords - v; } return out; } vector Polyomino::getPath() const { vector out; vertexCoords firstVertex = findOuterVertex(); vertexCoords currentVertex = firstVertex; vector neighbors = vertexNeighbors(firstVertex); assert(neighbors.size() == 1); Hex* currentHex = neighbors[0]; vertexCoords nextVertex = currentHex->followingVertex(currentVertex); do { bool skip = false; if (pentagonVertices.size()) { for (auto pentagonVertice : pentagonVertices) { if (pentagonVertice == currentVertex) { skip = true; break; } } } if (!skip) { out.push_back(currentVertex); } currentVertex = nextVertex; neighbors = vertexNeighbors(currentVertex); assert(neighbors.size() <= 2); if (neighbors.size() == 2) { currentHex = (neighbors[0] != currentHex ? neighbors[0] : neighbors[1]); } nextVertex = currentHex->followingVertex(currentVertex); } while (currentVertex != firstVertex); return out; } vector Hex::neighboringPositions(hexCoords h) { int xx = h.x; int yy = h.y; vector out; out.emplace_back(xx + 1, yy); // z-1 out.emplace_back(xx + 1, yy - 1); out.emplace_back(xx, yy - 1); // z+1 out.emplace_back(xx - 1, yy); // z+1 out.emplace_back(xx - 1, yy + 1); out.emplace_back(xx, yy + 1); // z-1 return out; } vector Hex::neighbors() const { return neighboringPositions(m_coords); } vertexCoords Hex::followingVertex(vertexCoords v) const { int dx = v.x - x(); int dy = v.y - y(); int dz = v.z - z(); if (dx + dy + dz != 1 && dx + dy + dz != -1) { cerr << "wrong input to transform to following vertex" << endl; } if (dx == 0 && dy == 0) { dx = -dz; dy = 0; dz = 0; } else if (dx == 0 && dz == 0) { dz = -dy; dx = 0; dy = 0; } else if (dy == 0 && dz == 0) { dy = -dx; dx = 0; dz = 0; } else { cerr << "wrong input to transform to following vertex" << endl; } return {x() + dx, y() + dy, z() + dz}; } float CoordgenMacrocycleBuilder::getPrecision() const { return m_precision; } void CoordgenMacrocycleBuilder::setPrecision(float f) { m_precision = f; } vector CoordgenMacrocycleBuilder::newMacrocycle( sketcherMinimizerRing* ring, vector atoms) const { // TODO the coordinates should be built on the returned vector, never saved // to the atoms in atoms. int natoms = static_cast(atoms.size()); Polyomino p; p.buildWithVerticesN(natoms); vector pols; pols.push_back(p); vector squarePols = buildSquaredShapes(natoms); for (unsigned int i = 0; i < squarePols.size(); i++) { assert(squarePols[i].getPath().size() == atoms.size()); } pols.reserve(pols.size() + squarePols.size()); pols.insert(pols.end(), squarePols.begin(), squarePols.end()); pols = removeDuplicates(pols); bool found = false; pathRestraints pr = getPathRestraints(atoms); pathConstraints pc = getPathConstraints(atoms); int scoreOfChosen = PATH_FAILED; int startOfChosen = 0; int bestStart = 0; int bestScore = PATH_FAILED; Polyomino chosenP = pols[0]; int acceptableScore = acceptableShapeScore(natoms); int checkedMacrocycles = 0; if (!m_forceOpenMacrocycles) { do { int bestP = 0; found = matchPolyominoes(pols, pc, pr, bestP, bestScore, bestStart, checkedMacrocycles); if (bestScore > scoreOfChosen) { startOfChosen = bestStart; scoreOfChosen = bestScore; chosenP = pols[bestP]; if (bestScore > acceptableScore) { break; } } if (checkedMacrocycles > MAX_MACROCYCLES) { break; } pols = listOfEquivalents(pols); pols = removeDuplicates(pols); } while (pols.size()); } if (found) { vector path = chosenP.getPath(); writePolyominoCoordinates(path, atoms, startOfChosen); if (chosenP.pentagonVertices.size()) { atoms.at(0)->molecule->requireMinimization(); } } else { // could not find a shape. fallback methods if (!openCycleAndGenerateCoords(ring)) { vector coords = CoordgenFragmentBuilder::listOfCoordinatesFromListofRingAtoms( atoms); int i = 0; for (sketcherMinimizerAtom* atom : atoms) { atom->setCoordinates(coords[i]); ++i; } } atoms.at(0)->molecule->requireMinimization(); } vector coordinates; coordinates.reserve(atoms.size()); for (sketcherMinimizerAtom* atom : atoms) { coordinates.push_back(atom->getCoordinates()); } return coordinates; } sketcherMinimizerBond* CoordgenMacrocycleBuilder::findBondToOpen(sketcherMinimizerRing* ring) const { sketcherMinimizerBond* bestBond = nullptr; float bestScore = 0.f; for (sketcherMinimizerBond* bond : ring->_bonds) { float score = 0.f; if (ring->isMacrocycle()) { if (bond->getBondOrder() != 1) { continue; } bool nextToMultipleBond = false; for (auto otherBond : bond->getStartAtom()->bonds) { if (otherBond->isStereo()) { nextToMultipleBond = true; break; } } for (auto otherBond : bond->getEndAtom()->bonds) { if (otherBond->isStereo()) { nextToMultipleBond = true; break; } } if (nextToMultipleBond) { continue; } } score += bond->rings.size() * 10; score += bond->getStartAtom()->neighbors.size(); score += bond->getEndAtom()->neighbors.size(); score /= bond->crossingBondPenaltyMultiplier; if (bestBond == nullptr || score < bestScore) { bestScore = score; bestBond = bond; } } return bestBond; } bool CoordgenMacrocycleBuilder::openCycleAndGenerateCoords( sketcherMinimizerRing* ring) const // generate coordinates of whole fragment // by opening one bond of the macrocycle { map atomMap; sketcherMinimizer min(getPrecision()); min.m_minimizer.skipMinimization = true; min.m_fragmentBuilder.setForceOpenMacrocycles(true); auto* minMol = new sketcherMinimizerMolecule; sketcherMinimizerBond* bondToBreak = findBondToOpen(ring); if (!bondToBreak) { return false; } sketcherMinimizerAtom* a = ring->_atoms[0]; // vector atoms = a->getFragment()->getAtoms(); vector atoms = a->molecule->getAtoms(); for (sketcherMinimizerAtom* at : atoms) { if (at->isResidue()) { continue; } auto* new_at = new sketcherMinimizerAtom; atomMap[at] = new_at; new_at->templateCoordinates = at->coordinates; new_at->coordinates = at->coordinates; new_at->atomicNumber = at->atomicNumber; new_at->_generalUseN = at->_generalUseN; new_at->molecule = minMol; new_at->_implicitHs = at->_implicitHs; minMol->_atoms.push_back(new_at); } // vector bonds = a->getFragment()->getBonds(); vector bonds = a->molecule->getBonds(); for (sketcherMinimizerBond* bo : bonds) { if (bo == bondToBreak || bo->isResidueInteraction()) { continue; } auto* new_bo = new sketcherMinimizerBond; new_bo->bondOrder = bo->bondOrder; new_bo->startAtom = atomMap[bo->startAtom]; new_bo->endAtom = atomMap[bo->endAtom]; new_bo->isZ = bo->isZ; new_bo->m_ignoreZE = bo->m_ignoreZE; minMol->_bonds.push_back(new_bo); } min.initialize(minMol); min.findFragments(); min.m_minimizer.buildFromFragments(true); auto* brokenBond = new sketcherMinimizerBond; brokenBond->bondOrder = bondToBreak->bondOrder; brokenBond->startAtom = atomMap[bondToBreak->startAtom]; brokenBond->endAtom = atomMap[bondToBreak->endAtom]; brokenBond->isZ = bondToBreak->isZ; minMol->_bonds.push_back(brokenBond); minMol->forceUpdateStruct(minMol->_atoms, minMol->_bonds, minMol->_rings); std::vector extraInteractions; extraInteractions.push_back(new sketcherMinimizerStretchInteraction( brokenBond->startAtom, brokenBond->endAtom)); min.m_minimizer.avoidClashesOfMolecule(minMol, extraInteractions); for (sketcherMinimizerAtom* atom : atoms) { sketcherMinimizerAtom* otherAtom = atomMap[atom]; if (otherAtom->rigid) { atom->rigid = true; } atom->setCoordinates(otherAtom->getCoordinates()); } for (auto r : ring->getAtoms()[0]->fragment->getRings()) { r->coordinatesGenerated = true; } for (auto bondRing : bondToBreak->rings) { if (!bondRing->isMacrocycle() && bondRing != ring) { bondRing->coordinatesGenerated = false; } } delete brokenBond; return true; } vector CoordgenMacrocycleBuilder::listOfEquivalents(const vector& l) const { vector out; for (const auto& i : l) { vector newV = listOfEquivalent(i); out.reserve(out.size() + newV.size()); out.insert(out.end(), newV.begin(), newV.end()); } return out; } vector CoordgenMacrocycleBuilder::listOfEquivalent(const Polyomino& p) const // build a list of polyominoes with the same number of // vertices by removing hexagons with 3 neighbors { vector out; vector l = p.m_list; size_t pentagonVs = p.pentagonVertices.size(); for (auto& i : l) { hexCoords c = i->coords(); if (p.isEquivalentWithout(c)) { Polyomino newP = p; newP.pentagonVertices.clear(); newP.removeHex(c); for (size_t i = 0; i < pentagonVs; i++) { newP.markOneVertexAsPentagon(); } out.push_back(newP); } } return out; } pathConstraints CoordgenMacrocycleBuilder::getPathConstraints( vector& atoms) const { pathConstraints pc; pc.doubleBonds = getDoubleBondConstraints(atoms); pc.ringConstraints = getRingConstraints(atoms); return pc; } vector CoordgenMacrocycleBuilder::getRingConstraints( vector& atoms) const { vector out; for (int i = 0; i < static_cast(atoms.size()); i++) { sketcherMinimizerAtom* a = atoms[i]; if (a->rings.size() > 1) { for (unsigned int rr = 0; rr < a->rings.size(); rr++) { sketcherMinimizerRing* r = a->rings[rr]; if (r->_atoms.size() < MACROCYCLE) { // this excludes the // current cycle and all // fused macrocycles bool forceOutside = false; for (auto n : a->neighbors) { if (find(atoms.begin(), atoms.end(), n) == atoms.end()) { if (r->containsAtom(n)) { forceOutside = true; } break; } } out.emplace_back(i, r, forceOutside); } } } } return out; } vector CoordgenMacrocycleBuilder::getDoubleBondConstraints( vector& atoms) const { vector out; if (atoms.size() >= MACROCYCLE) { for (unsigned int i = 0; i < atoms.size(); i++) { unsigned int index = i; unsigned int index2 = (i + 1) % atoms.size(); sketcherMinimizerBond* b = sketcherMinimizer::getBond(atoms[i], atoms[index2]); if (!b) { cerr << "bad input to get double bond constraints" << endl; break; } if (b->bondOrder != 2) { continue; } bool smallRingBond = false; if (b->rings.size() > 1) { for (auto& ring : b->rings) { if (ring->_atoms.size() < MACROCYCLE) { smallRingBond = true; break; } } } if (smallRingBond) { continue; } int previousI = static_cast((i + atoms.size() - 1) % atoms.size()); int followingI = (i + 2) % atoms.size(); bool isTrans = !b->isZ; // is the atom trans in the ring? (isZ // stores the absolute chirality) if (b->startAtom != atoms[i]) { // atoms[i] could be the end atom of b int swap = previousI; previousI = followingI; followingI = swap; index = index2; index2 = i; } if (b->startAtomCIPFirstNeighbor() != atoms[previousI]) { isTrans = !isTrans; } if (b->endAtomCIPFirstNeighbor() != atoms[followingI]) { isTrans = !isTrans; } doubleBondConstraint constraint; constraint.trans = isTrans; constraint.atom1 = index; constraint.atom2 = index2; constraint.previousAtom = previousI; constraint.followingAtom = followingI; out.push_back(constraint); } } return out; } int CoordgenMacrocycleBuilder::getNumberOfChildren( sketcherMinimizerAtom* a, sketcherMinimizerAtom* parent) const { int n = 0; map visited; queue q; q.push(a); visited[parent] = true; while (q.size()) { sketcherMinimizerAtom* thisA = q.front(); q.pop(); visited[thisA] = true; n++; for (auto n : thisA->neighbors) { if (visited[n]) { continue; } q.push(n); } } return n; } pathRestraints CoordgenMacrocycleBuilder::getPathRestraints( vector& atoms) const { pathRestraints pr; vector heteroAtoms; vector> substitutedAtoms; for (unsigned int i = 0; i < atoms.size(); i++) { if (atoms[i]->atomicNumber != 6) { heteroAtoms.push_back(i); } if (atoms[i]->neighbors.size() != 2) { int totN = 0; int prevI = static_cast((i + atoms.size() - 1) % atoms.size()); int postI = (i + 1) % atoms.size(); for (unsigned int j = 0; j < atoms[i]->neighbors.size(); j++) { sketcherMinimizerAtom* n = atoms[i]->neighbors[j]; if (n == atoms[prevI]) { continue; } if (n == atoms[postI]) { continue; } totN += getNumberOfChildren(n, atoms[i]); } substitutedAtoms.emplace_back(i, totN); } } pr.heteroAtoms = heteroAtoms; pr.substitutedAtoms = substitutedAtoms; return pr; } bool CoordgenMacrocycleBuilder::checkDoubleBoundConstraints( vector& dbConstraints, vector& vertices, int& startI) const { for (auto& dbConstraint : dbConstraints) { size_t counter = (startI + dbConstraint.previousAtom) % vertices.size(); sketcherMinimizerPointF p1 = coordsOfVertex(vertices[counter]); counter = (startI + dbConstraint.atom1) % vertices.size(); sketcherMinimizerPointF p2 = coordsOfVertex(vertices[counter]); counter = startI + dbConstraint.atom2; if (counter >= vertices.size()) { counter -= vertices.size(); } sketcherMinimizerPointF p3 = coordsOfVertex(vertices[counter]); counter = startI + dbConstraint.followingAtom; if (counter >= vertices.size()) { counter -= vertices.size(); } sketcherMinimizerPointF p4 = coordsOfVertex(vertices[counter]); if (sketcherMinimizerMaths::sameSide(p1, p4, p2, p3) == dbConstraint.trans) { return false; } } return true; } int CoordgenMacrocycleBuilder::scorePath(Polyomino& p, vector& path, vector& neighborNs, int& startI, pathConstraints& pc, pathRestraints& pr) const { if (!scorePathConstraints(pc, p, path, neighborNs, startI)) { return PATH_FAILED; } return scorePathRestraints(pr, p, path, neighborNs, startI); } int CoordgenMacrocycleBuilder::scorePathRestraints(pathRestraints& pr, Polyomino& p, vector& path, vector& neighborNs, int& startI) const { int score = 0; set usedSubstituentCoordinates; for (int heteroAtom : pr.heteroAtoms) { int counter = (heteroAtom + startI) % neighborNs.size(); if (neighborNs[counter] == 1) { score -= HETEROATOM_RESTRAINT; // heteroatom placed towards outside } } for (unsigned int i = 0; i < pr.substitutedAtoms.size(); i++) { int counter = (pr.substitutedAtoms[i].first + startI) % neighborNs.size(); if (neighborNs[counter] == 2) { score -= SUBSTITUTED_ATOM_RESTRAINT * pr.substitutedAtoms[i] .second; // substituted atoms placed towards inside vertexCoords substituentsCoordinates = p.coordinatesOfSubstituent(path[i]); if (usedSubstituentCoordinates.find(substituentsCoordinates) != usedSubstituentCoordinates.end()) { score -= SUBSTITUENTS_TO_SAME_VERTEX_RESTRAINT; } if (find(path.begin(), path.end(), substituentsCoordinates) != path.end()) { // substituent would clash with path score -= SUBSTITUENT_CLASH_WITH_PATH_RESTRAINT; } usedSubstituentCoordinates.insert(substituentsCoordinates); } } return score; } bool CoordgenMacrocycleBuilder::scorePathConstraints(pathConstraints& pc, Polyomino& p, vector& path, vector& neighborNs, int& startI) const { if (!checkRingConstraints(pc.ringConstraints, p, path, neighborNs, startI)) { return false; } if (!checkDoubleBoundConstraints(pc.doubleBonds, path, startI)) { return false; } return true; } bool CoordgenMacrocycleBuilder::checkRingConstraints( vector& ringConstraints, Polyomino& p, vector& path, vector& neighborNs, int& startI) const // make sure that all shared atoms with a fused small // ring map to the same hexagon { std::map> allowedHexs; for (auto& ringConstraint : ringConstraints) { unsigned int counter = (ringConstraint.atom + startI) % path.size(); if (ringConstraint.forceOutside) { if (neighborNs[counter] != 1) { return false; } } sketcherMinimizerRing* r = ringConstraint.ring; vector newPos = p.freeVertexNeighborPositions(path[counter]); vector oldPos = allowedHexs[r]; vector nextPos; if (!oldPos.size()) { nextPos = newPos; } else { for (auto toCheck : newPos) { if (find(oldPos.begin(), oldPos.end(), toCheck) != oldPos.end()) { // hexCoords was already marked as allowed and is now // confirmed nextPos.push_back(toCheck); } } } if (!nextPos.size()) { return false; } allowedHexs[r] = nextPos; } return true; } vector CoordgenMacrocycleBuilder::getVertexNeighborNs(Polyomino& p, vector& path) const { vector out; out.reserve(path.size()); for (auto i : path) { out.push_back(static_cast(p.hexagonsAtVertex(i))); } return out; } int CoordgenMacrocycleBuilder::getLowestPeriod( std::vector& neighbors) const { for (unsigned int period = 1; period < neighbors.size(); period++) { bool hasDifference = false; for (unsigned int i = 0; i < neighbors.size(); i++) { unsigned int i2 = (i + period) % neighbors.size(); if (neighbors[i] != neighbors[i2]) { hasDifference = true; break; } } if (!hasDifference) { return period; } } return static_cast(neighbors.size()); } bool CoordgenMacrocycleBuilder::matchPolyominoes(vector& pols, pathConstraints& pc, pathRestraints& pr, int& bestP, int& bestScore, int& bestStart, int& checkedMacrocycles) const { bestStart = 0; bestP = 0; bestScore = PATH_FAILED; int bestScoreOfThis = PATH_FAILED; int bestStartOfThis = 0; bool matched = false; for (unsigned int i = 0; i < pols.size(); i++) { if (matchPolyomino(pols[i], pc, pr, bestStartOfThis, bestScoreOfThis)) { matched = true; if (bestScoreOfThis > bestScore) { bestScore = bestScoreOfThis; bestStart = bestStartOfThis; bestP = i; if (bestScore == 0) { return true; } } } if (checkedMacrocycles++ > MAX_MACROCYCLES) { break; } } return matched; } bool CoordgenMacrocycleBuilder::matchPolyomino(Polyomino& p, pathConstraints& pc, pathRestraints& pr, int& bestStart, int& bestScore) const { vector path = p.getPath(); vector neighborNs = getVertexNeighborNs(p, path); bestStart = 0; bestScore = PATH_FAILED; for (int startI = 0; startI < getLowestPeriod(neighborNs); startI++) { // test starting from every vertex. int score = scorePath(p, path, neighborNs, startI, pc, pr); if (score > bestScore) { bestScore = score; bestStart = startI; if (bestScore == 0) { break; } } } return bestScore > PATH_FAILED; } sketcherMinimizerPointF CoordgenMacrocycleBuilder::coordsOfVertex(vertexCoords& v) const { return sketcherMinimizerPointF( static_cast(BONDLENGTH * SQRT3HALF * v.x - BONDLENGTH * SQRT3HALF * v.z), static_cast(-BONDLENGTH * 0.5 * v.x + BONDLENGTH * v.y + -BONDLENGTH * 0.5 * v.z)); } void CoordgenMacrocycleBuilder::writePolyominoCoordinates( vector& path, const vector& atoms, int startI) const { for (unsigned int n = 0; n < atoms.size(); n++) { unsigned int counter = (n + startI) % path.size(); vertexCoords hCoords = path[counter]; if (!atoms[n]->coordinatesSet) { atoms[n]->setCoordinates(coordsOfVertex(hCoords)); } } } vector CoordgenMacrocycleBuilder::removeDuplicates(vector& pols) const { // This algorithm has potential to be n^2 Could be a potential bottleneck // for performance vector out; for (auto& pol : pols) { bool duplicate = false; for (auto& j : out) { if (pol.isTheSameAs(j)) { duplicate = true; break; } } if (!duplicate) { out.push_back(pol); } } return out; } vector CoordgenMacrocycleBuilder::buildSquaredShapes(int totVertices) const { // a square like this has 4x + 4 y - 2 vertices. Only do if number of // vertices (or number of vertices+1) is divisible by 2 but not by 4. x + y // = (N + 2)/ 4 vector out; bool pentagon = false; if ((totVertices % 2) > 0) { pentagon = true; totVertices++; } if (totVertices % 4 == 0) { if (totVertices >= 12) { // we can build one with ragged borders and even number of y. We'll // only build the ones alternating rows with length x, x+1, because // the x, x-1 one is equivant int xandy = totVertices / 4; int mid = xandy / 2; // truncate for (int i = 1; i < mid; ++i) { int i2 = xandy - i; if ((i2 % 2) == 0 && i > 1) { Polyomino p; p.buildRaggedBiggerBoxShape(i, i2, pentagon); out.insert(out.begin(), p); } if ((i % 2) == 0 && i2 > 1) { Polyomino p; p.buildRaggedBiggerBoxShape(i2, i, pentagon); out.insert(out.begin(), p); } } } } else { // we'll build the skewed box, the ragged box, the boxes with odd y // alternating x and x+1, and the boxes with odd y alternating x and x-1 int xandy = (totVertices + 2) / 4; int mid = xandy / 2; // truncate for (int i = 1; i < mid + 1; i++) { // build all possible combinations of shapes Polyomino p; p.buildSkewedBoxShape(i, xandy - i, pentagon); out.insert(out.begin(), p); // add at the beginning because we want rounder // shapes to be evaluated first (they have more space // inside and less risk of substituents clashes int i2 = xandy - i; if (i < 2 || i2 < 2) { continue; } { Polyomino p; p.buildRaggedBoxShape(i, i2, pentagon); out.insert(out.begin(), p); } { Polyomino p; p.buildRaggedBoxShape(i2, i, pentagon); out.insert(out.begin(), p); } if (i2 % 2 > 0) { Polyomino p; p.buildRaggedBiggerBoxShape(i, i2, pentagon); out.insert(out.begin(), p); } if (i % 2 > 0) { Polyomino p; p.buildRaggedBiggerBoxShape(i2, i, pentagon); out.insert(out.begin(), p); } if (i > 2 && i2 % 2 > 0) { Polyomino p; p.buildRaggedSmallerBoxShape(i, i2, pentagon); out.insert(out.begin(), p); } if (i2 > 2 && i % 2 > 0) { Polyomino p; p.buildRaggedSmallerBoxShape(i2, i, pentagon); out.insert(out.begin(), p); } } } return out; } int CoordgenMacrocycleBuilder::acceptableShapeScore(int numberOfAtoms) const { if (numberOfAtoms < 10) { return 0; } else { return numberOfAtoms * SUBSTITUTED_ATOM_RESTRAINT / 2; } }