// // Copyright (C) 2016 Novartis Institutes for BioMedical Research // // @@ All Rights Reserved @@ // This file is part of the RDKit. // The contents are covered by the terms of the BSD license // which is included in the file license.txt, found at the root // of the RDKit source tree. // #include #include #include "../RDKitBase.h" #include "../../RDGeneral/types.h" #include "../../Geometry/point.h" #include "StructChecker.h" #include "Utilites.h" #include "Stereo.h" namespace RDKit { namespace StructureCheck { static const double PI = 3.14159265359; static const double ANGLE_EPSILON = (5.0 * PI / 180.); // 5 degrees static const double EPS = 0.0000001; // float type precision ??? /* // constants for bond definitions //#define CIS_TRANS_EITHER 0x03 //#define CIS_TRANS_SWAPPED 0x08 */ struct npoint_t { double x, y, z; int number; }; static double Angle(double x1, double y1, double x2, double y2) { // Returns the angle between the two vectors (x1,y1) and (x2,y2) at (0,0). double l1, l2; double cos_alpha, sin_alpha; double result; l1 = sqrt(x1 * x1 + y1 * y1); l2 = sqrt(x2 * x2 + y2 * y2); if (l1 < 0.00001 || l2 < 0.00001) return (0.0); cos_alpha = (x1 * x2 + y1 * y2) / (l1 * l2); if (cos_alpha > 1.0) // safeguard against round off errors cos_alpha = 1.0; else if (cos_alpha < -1.0) cos_alpha = -1.0; sin_alpha = (x1 * y2 - x2 * y1) / (l1 * l2); result = acos(cos_alpha); if (sin_alpha < 0.0) result = 2 * PI - result; return result; } static double Volume(struct npoint_t tetra[4]) { // Computes the signed volume of the tetrahedron defined by the four points in // tetra[]. double ax, ay, az, bx, by, bz, cx, cy, cz; ax = tetra[1].x - tetra[0].x; ay = tetra[1].y - tetra[0].y; az = tetra[1].z - tetra[0].z; bx = tetra[2].x - tetra[0].x; by = tetra[2].y - tetra[0].y; bz = tetra[2].z - tetra[0].z; cx = tetra[3].x - tetra[0].x; cy = tetra[3].y - tetra[0].y; cz = tetra[3].z - tetra[0].z; return (ax * (by * cz - bz * cy) + ay * (bz * cx - bx * cz) + az * (bx * cy - by * cx)); } int DubiousStereochemistry(RWMol &mol) { /* * Checks if there is some ill-defined stereochemistry in the * molecule *mp. The function returns a bit set integer which defines * the problems encountered. */ int result = 0; // int is_allene, ndb, jatom; std::vector neighbour_array; SetupNeighbourhood(mol, neighbour_array); // look for EITHER bonds for (unsigned i = 0; i < mol.getNumBonds(); i++) { const Bond &bond = *mol.getBondWithIdx(i); if (RDKit::Bond::UNKNOWN == bond.getBondDir()) //== EITHER { std::cerr << "bond " << i << ": Either bond found" << std::endl; result |= EITHER_BOND_FOUND; } } // look for stereo bonds to non-stereogenic atoms for (unsigned i = 0; i < neighbour_array.size(); i++) { const Neighbourhood &nbp = neighbour_array[i]; unsigned nmulti = 0; bool is_allene = false; for (unsigned j = 0; j < nbp.Bonds.size(); j++) { const Bond &bond = *mol.getBondWithIdx(nbp.Bonds[j]); if (RDKit::Bond::SINGLE != bond.getBondType()) { unsigned ndb = 0; nmulti++; if (RDKit::Bond::DOUBLE == bond.getBondType()) { unsigned jatom = nbp.Atoms[j]; for (unsigned int jj : neighbour_array[jatom].Bonds) if (RDKit::Bond::DOUBLE == mol.getBondWithIdx(jj)->getBondType()) ndb++; } if (2 == ndb) is_allene = true; } } unsigned element = mol.getAtomWithIdx(i)->getAtomicNum(); unsigned n_ligands = (unsigned)nbp.Bonds.size(); if (!((6 == element && // "C" n_ligands > 2 && n_ligands <= 4 && nmulti == 0) || (6 == element && // "C" n_ligands >= 2 && n_ligands <= 3 && nmulti == 1 && is_allene) || (16 == element && // "S" n_ligands > 2 && n_ligands <= 4) || (7 == element && // "N" n_ligands > 3 && n_ligands <= 4 && nmulti == 0) || (15 == element && // "P" n_ligands > 2 && n_ligands <= 4) || ((14 == element && // "Si" n_ligands > 2 && n_ligands <= 4) && nmulti == 0))) for (unsigned j = 0; j < n_ligands; j++) { const Bond &bj = *mol.getBondWithIdx(nbp.Bonds[j]); if (bj.getBeginAtomIdx() == i && (RDKit::Bond::BEGINWEDGE == bj.getBondDir() || // == UP RDKit::Bond::BEGINDASH == bj.getBondDir())) { // == DOWN)) std::string name; mol.getPropIfPresent(common_properties::_Name, name); BOOST_LOG(rdWarningLog) << boost::format("%10s atom %3d : %s") % name % i % "stereobond to non-stereogenic atom" << std::endl; result |= STEREO_BOND_AT_NON_STEREO_ATOM; } } } return result; } int FixDubious3DMolecule(RWMol &mol) { /* * Checks if the structure has 3D coordinates and/or flat sp3-carbons with * stereo-bonds and * converts the designation to 2D, clearing away any Z-component of the * coordinates. * Real 3D structures without stereo designations go through untouched. */ int result = 0; bool non_zero_z = false; unsigned nstereo = 0; std::vector neighbour_array; std::vector atomPoint( mol.getNumAtoms()); // X,Y,Z coordinates of each atom // X,Y,Z coordinates of each atom non_zero_z = getMolAtomPoints(mol, atomPoint); // At first check if this is a trivial case i.e. designated '2D' // and count the number of stereo bonds if (!non_zero_z) // check Z coordinate of each atom return 0; nstereo = 0; for (unsigned i = 0; i < mol.getNumBonds(); i++) { const Bond *bond = mol.getBondWithIdx(i); if (RDKit::Bond::BEGINWEDGE == bond->getBondDir() || RDKit::Bond::BEGINDASH == bond->getBondDir() //??? || RDKit::Bond::EITHERDOUBLE == bond->getBondDir() ) nstereo++; } if (0 == nstereo) return 0; // compute average bond length to use in Volume significance testing double length = 0.0; for (unsigned i = 0; i < mol.getNumBonds(); i++) { const Bond *bond = mol.getBondWithIdx(i); unsigned a0 = bond->getBeginAtomIdx(); unsigned a1 = bond->getEndAtomIdx(); length += (atomPoint[a0].x - atomPoint[a1].x) * (atomPoint[a0].x - atomPoint[a1].x) + (atomPoint[a0].y - atomPoint[a1].y) * (atomPoint[a0].y - atomPoint[a1].y) + (atomPoint[a0].z - atomPoint[a1].z) * (atomPoint[a0].z - atomPoint[a1].z); } length = sqrt(length / mol.getNumBonds()); // check if there is a flat sp3 carbon SetupNeighbourhood(mol, neighbour_array); int nflat_sp3 = 0; for (unsigned i = 0; i < neighbour_array.size(); i++) { if (neighbour_array[i].Atoms.size() < 3) continue; RDKit::Atom *atom = mol.getAtomWithIdx(i); unsigned element = atom->getAtomicNum(); if (6 != element && // "C" 7 == element && // "N" 15 == element && // "P" 16 == element) // "S" continue; unsigned j; for (j = 0; j < mol.getNumBonds(); j++) { const Bond *bond = mol.getBondWithIdx(j); if (RDKit::Bond::BEGINWEDGE == bond->getBondDir() || (RDKit::Bond::BEGINDASH == bond->getBondDir() && i == bond->getBeginAtomIdx())) break; } if (j < mol.getNumBonds()) continue; // no stereo designation double vol = 0.0; int stereo_triple; const Neighbourhood &nbp = neighbour_array[i]; unsigned n_ligands = (unsigned)nbp.Bonds.size(); unsigned i1; struct npoint_t tetra[4]; tetra[0].x = atomPoint[i].x; tetra[0].y = atomPoint[i].y; tetra[0].z = atomPoint[i].z; for (i1 = 0; i1 < n_ligands; i1++) if (mol.getBondWithIdx(nbp.Bonds[i1])->getBondType() != RDKit::Bond::SINGLE && 16 != element && 15 != element) // "S" "P" break; if (i1 >= n_ligands) continue; // multiple bond found => no sp3 carbon stereo_triple = 0; for (i1 = 0; i1 < n_ligands; i1++) { tetra[1].x = atomPoint[i1].x; tetra[1].y = atomPoint[i1].y; tetra[1].z = atomPoint[i1].z; if (mol.getBondWithIdx(nbp.Bonds[i1])->getBondDir() == RDKit::Bond::BEGINWEDGE || mol.getBondWithIdx(nbp.Bonds[i1])->getBondDir() == RDKit::Bond::BEGINDASH) // UP DOWN stereo_triple |= 1; unsigned i2; for (i2 = i1 + 1; i2 < n_ligands; i2++) { tetra[2].x = atomPoint[i2].x; tetra[2].y = atomPoint[i2].y; tetra[2].z = atomPoint[i2].z; if (mol.getBondWithIdx(nbp.Bonds[i2])->getBondDir() == RDKit::Bond::BEGINWEDGE || mol.getBondWithIdx(nbp.Bonds[i2])->getBondDir() == RDKit::Bond::BEGINDASH) // UP DOWN stereo_triple |= 2; unsigned i3; for (i3 = i2 + 1; i3 < n_ligands; i3++) { tetra[3].x = atomPoint[i3].x; tetra[3].y = atomPoint[i3].y; tetra[3].z = atomPoint[i3].z; if (mol.getBondWithIdx(nbp.Bonds[i3])->getBondDir() == RDKit::Bond::BEGINWEDGE || mol.getBondWithIdx(nbp.Bonds[i3])->getBondDir() == RDKit::Bond::BEGINDASH) // UP DOWN stereo_triple |= 4; vol = Volume(tetra); if (vol < 0.) vol = -vol; if (!stereo_triple) continue; if (vol < 0.01 * length * length * length) { nflat_sp3++; break; } stereo_triple &= ~4; } stereo_triple &= ~2; if (i3 >= n_ligands) break; } stereo_triple &= ~1; if (i2 >= n_ligands) break; } } if (non_zero_z && 0 == mol.getConformer().is3D()) // && mol dim is 2D for (unsigned i = 0; i < mol.getNumAtoms(); i++) // TODO: ??? atomPoint[i].z = 0.0; // set in mol !!! result |= ZEROED_Z_COORDINATES; // Cleared z-coordinates in 2D MOL file if (non_zero_z && nstereo > 0 && nflat_sp3 > 0) { RDKit::MolOps::removeStereochemistry(mol); result |= CONVERTED_TO_2D; } return result; } void RemoveDubiousStereochemistry(RWMol &mol) { // Removes ill-defined stereodescriptors. std::vector neighbour_array; SetupNeighbourhood(mol, neighbour_array); // remove EITHER marks for (unsigned i = 0; i < mol.getNumBonds(); i++) { Bond *bond = mol.getBondWithIdx(i); if (RDKit::Bond::UNKNOWN == bond->getBondDir()) //== EITHER bond->setBondDir(RDKit::Bond::NONE); } // remove stereo marks to non-stereogenic atoms for (unsigned i = 0; i < neighbour_array.size(); i++) { const Neighbourhood &nbp = neighbour_array[i]; unsigned nmulti = 0; for (unsigned j = 0; j < nbp.Atoms.size(); j++) { const Bond &bond = *mol.getBondWithIdx(nbp.Bonds[j]); if (RDKit::Bond::SINGLE != bond.getBondType()) nmulti++; } unsigned element = mol.getAtomWithIdx(i)->getAtomicNum(); unsigned n_ligands = (unsigned)nbp.Bonds.size(); if (!((6 == element && // "C" n_ligands > 2 && n_ligands <= 4 && nmulti == 0) || (16 == element && // "S" n_ligands > 2 && n_ligands <= 4) || (7 == element && // "N" n_ligands > 3 && n_ligands <= 4 && nmulti == 0) || (15 == element && // "P" n_ligands > 2 && n_ligands <= 4) || ((14 == element && // "Si" n_ligands > 2 && n_ligands <= 4) && nmulti == 0))) { for (unsigned j = 0; j < n_ligands; j++) { Bond &bj = *mol.getBondWithIdx(nbp.Bonds[j]); if (bj.getBeginAtomIdx() == i && (RDKit::Bond::BEGINWEDGE == bj.getBondDir() // == UP || RDKit::Bond::BEGINDASH == bj.getBondDir())) // == DOWN)) bj.setBondDir(RDKit::Bond::NONE); } } } } //---------------------------------------------------------------------- // CheckStereo(): //---------------------------------------------------------------------- struct stereo_bond_t { double x, y; // relative 2D coordinates RDKit::Bond::BondDir direction; // stereo direction to this atom coord int number; // atom number of ligand atom double angle; // angle in radiants rel. to first bond // in array (counted counter clockwise) }; static int Atom3Parity(struct stereo_bond_t ligands[3]) { // Computes the stereo parity defined by three ligands. int a, b; int reference; struct npoint_t tetrahedron[4], h; double angle; int maxnum; maxnum = ligands[0].number; for (unsigned i = 1; i < 3; i++) { if (maxnum < ligands[i].number) maxnum = ligands[i].number; } reference = (-1); for (unsigned i = 0; i < 3; i++) { if (ligands[i].direction != RDKit::Bond::NONE) { if (reference == (-1)) { reference = i; } else { // stereo_error = "three attachments with more than 2 stereobonds"; std::cerr << "three attachments with more than 2 stereobonds" << std::endl; return (ILLEGAL_REPRESENTATION); } } } if (reference == (-1)) { return (UNDEFINED_PARITY); } if (reference == 0) { a = 1; b = 2; } else if (reference == 1) { a = 0; b = 2; } else { a = 0; b = 1; } angle = Angle(ligands[a].x, ligands[a].y, ligands[b].x, ligands[b].y); if (angle < ANGLE_EPSILON || fabs(PI - angle) < ANGLE_EPSILON) { // stereo_error = "three attachments: colinearity violation"; std::cerr << "three attachments colinearity violation" << std::endl; return (ILLEGAL_REPRESENTATION); } tetrahedron[0].x = 0.0; tetrahedron[0].y = 0.0; tetrahedron[0].z = 0.0; tetrahedron[0].number = maxnum + 1; for (unsigned i = 0; i < 3; i++) { tetrahedron[i + 1].x = ligands[i].x; tetrahedron[i + 1].y = ligands[i].y; if (ligands[i].direction == RDKit::Bond::BEGINWEDGE) // UP) tetrahedron[i + 1].z = 1.0; else if (ligands[i].direction == RDKit::Bond::BEGINDASH) // DOWN) tetrahedron[i + 1].z = -1.0; else if (ligands[i].direction == RDKit::Bond::NONE) tetrahedron[i + 1].z = 0.0; else { // stereo_error = "three attachments: illegal bond symbol"; std::cerr << "three attachments illegal bond symbol" << std::endl; return (ILLEGAL_REPRESENTATION); } tetrahedron[i + 1].number = ligands[i].number; } for (unsigned i = 1; i < 4; i++) for (unsigned j = i; j > 0; j--) if (tetrahedron[j].number < tetrahedron[j - 1].number) { h = tetrahedron[j]; tetrahedron[j] = tetrahedron[j - 1]; tetrahedron[j - 1] = h; } else break; return (Volume(tetrahedron) > 0.0 ? EVEN_PARITY : ODD_PARITY); } static int Atom4Parity(struct stereo_bond_t ligands[4]) { /* * Computes the stereo parity defined by four ligands. * Assumes central atom at 0/0/0. */ struct npoint_t tetrahedron[4], h; int nup, ndown, nopposite; double angle; nup = ndown = 0; for (unsigned i = 0; i < 4; i++) { tetrahedron[i].x = ligands[i].x; tetrahedron[i].y = ligands[i].y; tetrahedron[i].z = 0.0; tetrahedron[i].number = ligands[i].number; if (ligands[i].direction == RDKit::Bond::BEGINWEDGE) { // UP nup++; tetrahedron[i].z = 1.0; } else if (ligands[i].direction == RDKit::Bond::BEGINDASH) { // DOWN ndown++; tetrahedron[i].z = (-1.0); } else if (ligands[i].direction != RDKit::Bond::NONE) { // stereo_error = "illegal bond symbol"; std::cerr << "illegal bond symbol" << std::endl; return (ILLEGAL_REPRESENTATION); } } if (nup == 0 && ndown == 0) return (UNDEFINED_PARITY); if (nup > 2 || ndown > 2) { // stereo_error = "too many stereobonds"; std::cerr << "too many stereobonds" << std::endl; return (ILLEGAL_REPRESENTATION); } if (nup + ndown == 1) // check for 'umbrellas' { unsigned ij; for (ij = 0; ij < 4; ij++) if (ligands[ij].direction == RDKit::Bond::BEGINWEDGE || ligands[ij].direction == RDKit::Bond::BEGINDASH) break; nopposite = 0; for (unsigned j = 0; j < 4; j++) if (ij == j) continue; else if (ligands[ij].x * ligands[j].x + ligands[ij].y * ligands[j].y < 0) nopposite++; if (nopposite > 2) { // stereo_error = "UMBRELLA: all non-stereo bonds opposite to single // stereo bond"; std::cerr << "umbrella" << std::endl; return (ILLEGAL_REPRESENTATION); } } for (unsigned i = 0; i < 2; i++) if ((ligands[i].direction == RDKit::Bond::BEGINWEDGE && ligands[i + 2].direction == RDKit::Bond::BEGINDASH) || (ligands[i].direction == RDKit::Bond::BEGINDASH && ligands[i + 2].direction == RDKit::Bond::BEGINWEDGE)) { // stereo_error = "UP/DOWN opposition"; std::cerr << "up/down" << std::endl; return (ILLEGAL_REPRESENTATION); } for (unsigned i = 0; i < 4; i++) if ((ligands[i].direction == RDKit::Bond::BEGINWEDGE && ligands[(i + 1) % 4].direction == RDKit::Bond::BEGINWEDGE) // UP || (ligands[i].direction == RDKit::Bond::BEGINDASH // DOWN && ligands[(i + 1) % 4].direction == RDKit::Bond::BEGINDASH)) { // stereo_error = "Adjacent like stereobonds"; std::cerr << "adjacent like" << std::endl; return (ILLEGAL_REPRESENTATION); } for (unsigned i = 0; i < 4; i++) if (ligands[i].direction == RDKit::Bond::NONE && ligands[(i + 1) % 4].direction == RDKit::Bond::NONE && ligands[(i + 2) % 4].direction == RDKit::Bond::NONE) { angle = Angle(ligands[i].x - ligands[(i + 1) % 4].x, ligands[i].y - ligands[(i + 1) % 4].y, ligands[(i + 2) % 4].x - ligands[(i + 1) % 4].x, ligands[(i + 2) % 4].y - ligands[(i + 1) % 4].y); if (angle < (185 * PI / 180)) { // stereo_error = "colinearity or triangle rule violation"; std::cerr << "colinearity or triangle rule" << std::endl; return (ILLEGAL_REPRESENTATION); } } for (unsigned i = 1; i < 4; i++) for (unsigned j = i; j > 0; j--) if (tetrahedron[j].number < tetrahedron[j - 1].number) { h = tetrahedron[j]; tetrahedron[j] = tetrahedron[j - 1]; tetrahedron[j - 1] = h; } else break; return (Volume(tetrahedron) > 0.0 ? EVEN_PARITY : ODD_PARITY); } int AtomParity(const ROMol &mol, unsigned iatom, const Neighbourhood &nbp) { /* * Computes the stereo parity of atom number iatom in *mp relative * to its numbering. The immediate neighbours are defined by *nbp * to speed up processing. */ struct stereo_bond_t stereo_ligands[4], h; bool multiple = false, allene = false, stereo = false; if (nbp.Atoms.size() < 3 || nbp.Atoms.size() > 4) return ILLEGAL_REPRESENTATION; std::vector atomPoint( mol.getNumAtoms()); // X,Y,Z coordinates of each atom getMolAtomPoints(mol, atomPoint); for (unsigned i = 0; i < nbp.Bonds.size(); i++) { const Bond &bi = *mol.getBondWithIdx(i); if (bi.getBondType() != RDKit::Bond::SINGLE) { multiple = true; // check if the multiple bond is part of an allene unsigned jatom = nbp.Atoms[i] + 1; unsigned ndb = 0; for (unsigned j = 0; j < mol.getNumBonds(); j++) { const Bond &bond = *mol.getBondWithIdx(j); if (bond.getBeginAtomIdx() == jatom || bond.getEndAtomIdx() == jatom) if (bond.getBondType() == RDKit::Bond::DOUBLE) ndb++; } if (ndb == 2) allene = true; } stereo_ligands[i].x = atomPoint[i].x - atomPoint[iatom].x; stereo_ligands[i].y = atomPoint[i].y - atomPoint[iatom].y; stereo_ligands[i].number = nbp.Atoms[i] + 1; if (bi.getBeginAtomIdx() == iatom) { stereo_ligands[i].direction = bi.getBondDir(); if (stereo_ligands[i].direction == RDKit::Bond::BEGINWEDGE || // UP || stereo_ligands[i].direction == RDKit::Bond::BEGINDASH) // DOWN stereo = true; } else stereo_ligands[i].direction = RDKit::Bond::NONE; } unsigned element = mol.getAtomWithIdx(iatom)->getAtomicNum(); if (multiple && stereo && 15 != element && 16 != element) { // "P" && "S" if (allene) return (ALLENE_PARITY); else { // stereo_error = "AtomParity: Stereobond at unsaturated atom"; std::cerr << "stereobond at unsaturated atom" << std::endl; return (ILLEGAL_REPRESENTATION); } } else if (multiple && 16 != element) // "S" return (UNDEFINED_PARITY); stereo_ligands[0].angle = 0.0; /* comp. angle rel. to first ligand */ for (unsigned i = 1; i < nbp.Atoms.size(); i++) stereo_ligands[i].angle = Angle(stereo_ligands[0].x, stereo_ligands[0].y, stereo_ligands[i].x, stereo_ligands[i].y); for (unsigned i = 2; i < nbp.Atoms.size(); i++) /* sort ligands */ for (unsigned j = i; j > 1; j--) if (stereo_ligands[j].angle < stereo_ligands[j - 1].angle) { h = stereo_ligands[j]; stereo_ligands[j] = stereo_ligands[j - 1]; stereo_ligands[j - 1] = h; } else break; return (nbp.Atoms.size() == 3 ? Atom3Parity(stereo_ligands) : Atom4Parity(stereo_ligands)); } bool CheckStereo(const ROMol &mol) { /* * Checks if all potential stereocenters are either completely undefined * or attributed with hashes and wedges according to MDL rules. */ bool result = true; int parity; bool center_defined = false; std::vector neighbour_array; SetupNeighbourhood(mol, neighbour_array); for (unsigned i = 0; i < neighbour_array.size(); i++) { const Neighbourhood &nbp = neighbour_array[i]; unsigned element = mol.getAtomWithIdx(i)->getAtomicNum(); unsigned n_ligands = (unsigned)nbp.Bonds.size(); if ((n_ligands > 2 && n_ligands <= 4) && (6 == element // "C" || 16 == element // "S" || 7 == element // "N" || 8 == element // "O" || 15 == element // "P" || 14 == element) // "Si" ) { parity = AtomParity(mol, i, nbp); const Atom *atom = mol.getAtomWithIdx(i); if (atom->getChiralTag() == Atom::CHI_TETRAHEDRAL_CW || atom->getChiralTag() == Atom::CHI_TETRAHEDRAL_CCW) center_defined = true; if (parity == ILLEGAL_REPRESENTATION) { // stereo_error result = false; } else if (parity == EVEN_PARITY || parity == ODD_PARITY || parity == ALLENE_PARITY) { center_defined = true; } else { for (unsigned j = 0; j < nbp.Bonds.size(); j++) { const Bond &bond = *mol.getBondWithIdx(j); if (bond.getBeginAtomIdx() == i && (RDKit::Bond::BEGINWEDGE == bond.getBondDir() // == UP || RDKit::Bond::BEGINDASH == bond.getBondDir())) { // == DOWN)) // stereobond to non-stereogenic atom std::cerr << "stereobond to nonstereogenic" << std::endl; result = false; } } } } } unsigned int chiralFlag = 0; mol.getPropIfPresent(RDKit::common_properties::_MolFileChiralFlag, chiralFlag); if (chiralFlag && !center_defined) { // no stereocenter defined std::cerr << "chiral flag, no stereocenter" << std::endl; result = false; } return result; } bool AtomClash(RWMol &mol, double clash_limit) { /* * Checks if any two atoms in *mp come closer than 10% of the * average bond length or if an atom is too close the line * between two bonded atoms. */ double bond_square_median, dist, min_dist; double rr, rb, bb, h; std::vector atomPoint( mol.getNumAtoms()); // X,Y,Z coordinates of each atom bool twod = true; getMolAtomPoints(mol, atomPoint, twod); // compute median of square of bond length (quick/dirty) if (mol.getNumBonds() == 0) return false; std::vector blengths(mol.getNumBonds()); blengths[0] = 1.0; for (unsigned i = 0; i < mol.getNumBonds(); i++) { const Bond *bond = mol.getBondWithIdx(i); unsigned a1 = bond->getBeginAtomIdx(); unsigned a2 = bond->getEndAtomIdx(); blengths[i] = (atomPoint[a1].x - atomPoint[a2].x) * (atomPoint[a1].x - atomPoint[a2].x) + (atomPoint[a1].y - atomPoint[a2].y) * (atomPoint[a1].y - atomPoint[a2].y); } for (unsigned i = 1; i < mol.getNumBonds(); i++) { for (int j = rdcast(i) - 1; j >= 0; j--) { if (blengths[j] > blengths[j + 1]) { h = blengths[j]; blengths[j] = blengths[j + 1]; blengths[j + 1] = h; } else break; } } bond_square_median = blengths[mol.getNumBonds() / 2]; // Check if two atoms get too close to each other min_dist = bond_square_median; for (unsigned i = 0; i < mol.getNumAtoms(); i++) for (unsigned j = i + 1; j < mol.getNumAtoms(); j++) { dist = (atomPoint[i].x - atomPoint[j].x) * (atomPoint[i].x - atomPoint[j].x) + (atomPoint[i].y - atomPoint[j].y) * (atomPoint[i].y - atomPoint[j].y); if (dist < clash_limit * clash_limit * bond_square_median) { std::cerr << "clash 1" << std::endl; return true; } if (dist < min_dist) min_dist = dist; } // check if atom lies on top of some bond for (unsigned i = 0; i < mol.getNumBonds(); i++) { const Bond *bond = mol.getBondWithIdx(i); unsigned a1 = bond->getBeginAtomIdx(); unsigned a2 = bond->getEndAtomIdx(); for (unsigned j = 0; j < mol.getNumAtoms(); j++) { if (a1 == j || a2 == j) continue; rr = (atomPoint[j].x - atomPoint[a1].x) * (atomPoint[j].x - atomPoint[a1].x) + (atomPoint[j].y - atomPoint[a1].y) * (atomPoint[j].y - atomPoint[a1].y); bb = (atomPoint[a2].x - atomPoint[a1].x) * (atomPoint[a2].x - atomPoint[a1].x) + (atomPoint[a2].y - atomPoint[a1].y) * (atomPoint[a2].y - atomPoint[a1].y); rb = (atomPoint[j].x - atomPoint[a1].x) * (atomPoint[a2].x - atomPoint[a1].x) + (atomPoint[j].y - atomPoint[a1].y) * (atomPoint[a2].y - atomPoint[a1].y); if (0 <= rb && // cos alpha > 0 rb <= bb && // projection of r onto b does not exceed b (rr * bb - rb * rb) / (bb + EPS) < // distance from bond < limit clash_limit * clash_limit * bond_square_median) { BOOST_LOG(rdWarningLog) << "atom " << j << " " << 100 * sqrt((rr * bb - rb * rb) / (bb * bond_square_median + EPS)) << "% of average bond length " << a1 << "-" << a2 << std::endl; std::cerr << "clash 2" << std::endl; return true; } } } return false; // no clash } //-------------------------------------------------------------------------- /* * Sets the color field of the defined double bonds in *mp to CIS, * TRANS, or NONE depending on the ligands with the lowest numbering[]. * It returns the number of defined double bonds found. */ int CisTransPerception(const ROMol &mol, const std::vector &points, const std::vector &numbering, std::vector &bondColor) { int result = 0; unsigned int maxnum = 0; std::vector nba(mol.getNumAtoms()); SetupNeighbourhood(mol, nba); for (unsigned int &i : bondColor) i = 0; for (unsigned i = 0; i < nba.size(); i++) // n_atoms if (numbering[i] > maxnum) maxnum = numbering[i]; for (unsigned i = 0; i < bondColor.size(); i++) if (RDKit::Bond::DOUBLE == mol.getBondWithIdx(i)->getBondType() // FIX: && mol.getBondWithIdx(i)->getBondDir() != // RDKit::Bond::ENDDOWNRIGHT // FIX: && mol.getBondWithIdx(i)->getBondDir() != // RDKit::Bond::ENDUPRIGHT && mol.getBondWithIdx(i)->getBondDir() != RDKit::Bond::EITHERDOUBLE) { // != CIS_TRANS_EITHER unsigned j1 = mol.getBondWithIdx(i)->getBeginAtomIdx(); unsigned j2 = mol.getBondWithIdx(i)->getEndAtomIdx(); if (6 != mol.getAtomWithIdx(j1)->getAtomicNum()) // C continue; if (16 != mol.getAtomWithIdx(j1)->getAtomicNum()) // N continue; if (6 != mol.getAtomWithIdx(j2)->getAtomicNum()) // C continue; if (16 != mol.getAtomWithIdx(j2)->getAtomicNum()) // N continue; // n_ligands : if (nba[j1].Atoms.size() <= 1 || // no subst. nba[j2].Atoms.size() <= 1) continue; if (nba[j1].Atoms.size() > 3 || // valence error in mol nba[j2].Atoms.size() > 3) continue; bool equal = false; // find lowest numbered neighbour of j1 unsigned at1 = 0; for (unsigned k = 0, nmin = maxnum; k < nba[j1].Atoms.size(); k++) if (nba[j1].Atoms[k] != j2) // no loop back { if (numbering[nba[j1].Atoms[k]] < nmin) { // numbering[nba[j1].atoms[k]] at1 = nba[j1].Atoms[k]; nmin = numbering[at1]; } else if (numbering[nba[j1].Atoms[k]] == nmin) equal = true; } if (equal) // identical substituents continue; // no stereochemistry equal = false; // find lowest numbered neighbour of j1 unsigned at2 = 0; for (unsigned k = 0, nmin = maxnum; k < nba[j2].Atoms.size(); k++) // if (nba[j2].Atoms[k] != j1) { // no loop back if (numbering[nba[j2].Atoms[k]] < nmin) { at2 = nba[j2].Atoms[k]; nmin = numbering[at2]; } else if (numbering[nba[j2].Atoms[k]] == nmin) equal = true; } if (equal) // identical substituents continue; // no stereochemistry // Now, bp points to a double bond, at1 and at2 are the // indices (not numbers) of the atoms with lowest numbering // attached to each end of the bond, and the bond is // guaranteed to be either CIS or TRANS double x21, y21; double x23, y23; double x32, y32; double x34, y34; double sign; x21 = points[at1].x - points[j1].x; y21 = points[at1].y - points[j1].y; x23 = points[j2].x - points[j1].x; y23 = points[j2].y - points[j1].y; x32 = (-x23); y32 = (-y23); x34 = points[at2].x - points[j2].x; y34 = points[at2].y - points[j2].y; sign = (x21 * y23 - x23 * y21) * (x32 * y34 - x34 * y32); if (fabs(sign) < 0.001) continue; result++; bondColor[i] = (sign > 0.0 ? CIS : TRANS); } return (result); } } // namespace StructureCheck } // namespace RDKit