// Copyright (C) 2002-2025 Greg Landrum and other RDKit contributors // // @@ 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. // #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif using namespace RDKit; using namespace std; RWMol _t; typedef class ROMol Mol; TEST_CASE("test1") { string smi; Mol *m; INT_VECT iv; unsigned int count; std::vector frags; std::vector> otherFrags; smi = "CCCC(=O)O"; m = SmilesToMol(smi); REQUIRE_MSG(m, "smiles parse failed"); count = MolOps::getMolFrags(*m, iv); REQUIRE_MSG(count == 1, "bad frag count"); frags = MolOps::getMolFrags(*m); REQUIRE_MSG(frags.size() == 1, "bad frag count"); REQUIRE(frags[0]->getNumAtoms() == 6); count = MolOps::getMolFrags(*m, otherFrags); REQUIRE_MSG(count == 1, "bad frag count"); REQUIRE_MSG(otherFrags.size() == 1, "bad frag count"); delete m; smi = "CCCC(=O)[O-].[Na+]"; m = SmilesToMol(smi); REQUIRE_MSG(m, "smiles parse failed"); count = MolOps::getMolFrags(*m, iv); REQUIRE_MSG(count == 2, "bad frag count"); frags = MolOps::getMolFrags(*m); REQUIRE_MSG(frags.size() == 2, "bad frag count"); REQUIRE(frags[0]->getNumAtoms() == 6); REQUIRE(frags[1]->getNumAtoms() == 1); frags = MolOps::getMolFrags(*m, true, &iv); REQUIRE_MSG(frags.size() == 2, "bad frag count"); REQUIRE(frags[0]->getNumAtoms() == 6); REQUIRE(frags[1]->getNumAtoms() == 1); REQUIRE(iv.size() == 7); REQUIRE(iv[0] == 0); REQUIRE(iv[6] == 1); count = MolOps::getMolFrags(*m, otherFrags, true, &iv); REQUIRE_MSG(count == 2, "bad frag count"); REQUIRE_MSG(otherFrags.size() == 2, "bad frag count"); REQUIRE(frags[0]->getNumAtoms() == 6); REQUIRE(frags[1]->getNumAtoms() == 1); REQUIRE(iv.size() == 7); REQUIRE(iv[0] == 0); REQUIRE(iv[6] == 1); delete m; smi = "CCCC(=O)[O-].[Na+].[NH4+].[Cl-]"; m = SmilesToMol(smi); REQUIRE_MSG(m, "smiles parse failed"); count = MolOps::getMolFrags(*m, iv); REQUIRE_MSG(count == 4, "bad frag count"); frags = MolOps::getMolFrags(*m); REQUIRE_MSG(frags.size() == 4, "bad frag count"); REQUIRE(frags[0]->getNumAtoms() == 6); REQUIRE(frags[1]->getNumAtoms() == 1); REQUIRE(frags[2]->getNumAtoms() == 1); REQUIRE(frags[3]->getNumAtoms() == 1); count = MolOps::getMolFrags(*m, otherFrags); REQUIRE_MSG(count == 4, "bad frag count"); REQUIRE_MSG(otherFrags.size() == 4, "bad frag count"); REQUIRE(otherFrags[0]->getNumAtoms() == 6); REQUIRE(otherFrags[1]->getNumAtoms() == 1); REQUIRE(otherFrags[2]->getNumAtoms() == 1); REQUIRE(otherFrags[3]->getNumAtoms() == 1); delete m; }; TEST_CASE("test2") { string smi; Mol *m; INT_VECT iv; int count; smi = "CCCC(=O)O"; m = SmilesToMol(smi); REQUIRE_MSG(m, "smiles parse failed"); count = MolOps::getMolFrags(*m, iv); REQUIRE_MSG(count == 1, "bad frag count"); REQUIRE_MSG(iv[0] == 0, "bad frag membership"); REQUIRE_MSG(iv[1] == 0, "bad frag membership"); REQUIRE_MSG(iv[4] == 0, "bad frag membership"); REQUIRE_MSG(iv[5] == 0, "bad frag membership"); delete m; smi = "CCCC(=O)[O-].[Na+]"; m = SmilesToMol(smi); REQUIRE_MSG(m, "smiles parse failed"); count = MolOps::getMolFrags(*m, iv); REQUIRE_MSG(count == 2, "bad frag count"); REQUIRE_MSG(iv[0] == 0, "bad frag membership"); REQUIRE_MSG(iv[1] == 0, "bad frag membership"); REQUIRE_MSG(iv[4] == 0, "bad frag membership"); REQUIRE_MSG(iv[5] == 0, "bad frag membership"); REQUIRE_MSG(iv[6] == 1, "bad frag membership"); delete m; }; TEST_CASE("test3") { string smi; Mol *m; VECT_INT_VECT sssr; INT_VECT rings; int count; smi = "C1CC1"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getRingInfo()->numRings() == 1); count = MolOps::findSSSR(*m, sssr); REQUIRE(count == 1); REQUIRE(sssr[0].size() == 3); REQUIRE(m->getRingInfo()->numRings() == 1); for (unsigned int i = 0; i < m->getNumAtoms(); i++) { REQUIRE(m->getRingInfo()->isAtomInRingOfSize(i, 3)); REQUIRE(!m->getRingInfo()->isAtomInRingOfSize(i, 4)); REQUIRE(m->getRingInfo()->numAtomRings(i) == 1); REQUIRE(m->getRingInfo()->atomRingSizes(i) == (INT_VECT{3})); REQUIRE((m->getRingInfo()->atomMembers(i).size() == 1 && m->getRingInfo()->atomMembers(i).at(0) == 0)); } for (unsigned int i = 0; i < m->getNumBonds(); i++) { REQUIRE(m->getRingInfo()->isBondInRingOfSize(i, 3)); REQUIRE(!m->getRingInfo()->isBondInRingOfSize(i, 4)); REQUIRE(m->getRingInfo()->numBondRings(i) == 1); REQUIRE(m->getRingInfo()->bondRingSizes(i) == (INT_VECT{3})); REQUIRE((m->getRingInfo()->bondMembers(i).size() == 1 && m->getRingInfo()->bondMembers(i).at(0) == 0)); } REQUIRE(m->getRingInfo()->areAtomsInSameRing(0, 1)); REQUIRE(m->getRingInfo()->areAtomsInSameRingOfSize(0, 1, 3)); REQUIRE(!m->getRingInfo()->areAtomsInSameRingOfSize(0, 1, 4)); REQUIRE(m->getRingInfo()->areBondsInSameRing(0, 1)); REQUIRE(m->getRingInfo()->areBondsInSameRingOfSize(0, 1, 3)); REQUIRE(!m->getRingInfo()->areBondsInSameRingOfSize(0, 1, 4)); BOOST_LOG(rdInfoLog) << smi << "\n"; delete m; smi = "C1CCC1"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getRingInfo()->numRings() == 1); count = MolOps::findSSSR(*m, sssr); REQUIRE(count == 1); REQUIRE(sssr[0].size() == 4); REQUIRE(m->getRingInfo()->numRings() == 1); for (unsigned int i = 0; i < m->getNumAtoms(); i++) { REQUIRE(m->getRingInfo()->isAtomInRingOfSize(i, 4)); REQUIRE(!m->getRingInfo()->isAtomInRingOfSize(i, 3)); REQUIRE(m->getRingInfo()->numAtomRings(i) == 1); REQUIRE(m->getRingInfo()->atomRingSizes(i) == (INT_VECT{4})); REQUIRE((m->getRingInfo()->atomMembers(i).size() == 1 && m->getRingInfo()->atomMembers(i).at(0) == 0)); } REQUIRE(m->getRingInfo()->isBondInRingOfSize(0, 4)); REQUIRE(m->getRingInfo()->numBondRings(0) == 1); REQUIRE(m->getRingInfo()->bondRingSizes(0) == (INT_VECT{4})); REQUIRE(m->getRingInfo()->areBondsInSameRing(0, 1)); REQUIRE(m->getRingInfo()->areBondsInSameRingOfSize(0, 1, 4)); REQUIRE(!m->getRingInfo()->areBondsInSameRingOfSize(0, 1, 5)); BOOST_LOG(rdInfoLog) << smi << "\n"; delete m; smi = "C1CCCCCC1"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getRingInfo()->numRings() == 1); count = MolOps::findSSSR(*m, sssr); REQUIRE(count == 1); REQUIRE(sssr[0].size() == 7); REQUIRE(m->getRingInfo()->numRings() == 1); for (unsigned int i = 0; i < m->getNumAtoms(); i++) { REQUIRE(m->getRingInfo()->isAtomInRingOfSize(i, 7)); REQUIRE(m->getRingInfo()->numAtomRings(i) == 1); } REQUIRE(m->getRingInfo()->isBondInRingOfSize(0, 7)); REQUIRE(m->getRingInfo()->numBondRings(0) == 1); BOOST_LOG(rdInfoLog) << smi << "\n"; delete m; smi = "C1C(CCC)CC(C(C)CCC(CC))CCC1"; m = SmilesToMol(smi); REQUIRE(m); count = MolOps::findSSSR(*m, sssr); REQUIRE(count == 1); REQUIRE(sssr[0].size() == 7); REQUIRE(m->getRingInfo()->numAtomRings(0) == 1); REQUIRE( m->getRingInfo()->numBondRings(m->getBondBetweenAtoms(0, 1)->getIdx())); REQUIRE(m->getRingInfo()->atomMembers(0).size() == 1); REQUIRE(m->getRingInfo()->atomMembers(2).empty()); REQUIRE(m->getRingInfo()->atomRingSizes(2).empty()); REQUIRE(m->getRingInfo()->atomRingSizes(5) == (INT_VECT{7})); REQUIRE(m->getRingInfo()->atomRingSizes(1) == (INT_VECT{7})); REQUIRE(m->getRingInfo()->atomRingSizes(99).empty()); REQUIRE(m->getRingInfo()->areAtomsInSameRing(0, 1)); REQUIRE(m->getRingInfo()->areAtomsInSameRingOfSize(0, 1, 7)); REQUIRE(!m->getRingInfo()->areAtomsInSameRingOfSize(0, 1, 5)); REQUIRE(!m->getRingInfo()->areAtomsInSameRing(1, 2)); REQUIRE(!m->getRingInfo()->areAtomsInSameRingOfSize(1, 2, 4)); REQUIRE(m->getRingInfo()->bondRingSizes(1).empty()); REQUIRE(m->getRingInfo()->bondRingSizes(4) == (INT_VECT{7})); REQUIRE(m->getRingInfo()->bondRingSizes(5) == (INT_VECT{7})); REQUIRE(m->getRingInfo()->bondRingSizes(99).empty()); REQUIRE(m->getRingInfo()->bondMembers(0).size() == 1); REQUIRE(m->getRingInfo()->bondMembers(1).empty()); REQUIRE(!m->getRingInfo()->isRingFused(0)); REQUIRE(m->getRingInfo()->numFusedBonds(0) == 0); REQUIRE( !m->getRingInfo()->numBondRings(m->getBondBetweenAtoms(1, 2)->getIdx())); BOOST_LOG(rdInfoLog) << smi << "\n"; delete m; smi = "CC1CCC1"; m = SmilesToMol(smi); REQUIRE(m); count = MolOps::findSSSR(*m, sssr); REQUIRE(count == 1); REQUIRE(sssr[0].size() == 4); REQUIRE(!m->getBondBetweenAtoms(0, 1)->hasProp( common_properties::ringMembership)); REQUIRE( !m->getRingInfo()->numBondRings(m->getBondBetweenAtoms(0, 1)->getIdx())); REQUIRE( m->getRingInfo()->numBondRings(m->getBondBetweenAtoms(1, 2)->getIdx())); BOOST_LOG(rdInfoLog) << smi << "\n"; delete m; smi = "CC1C(C2)CCC2C1"; m = SmilesToMol(smi); REQUIRE(m); count = MolOps::findSSSR(*m, sssr); REQUIRE(count == 2); REQUIRE(sssr[0].size() == 5); REQUIRE(sssr[1].size() == 5); REQUIRE(!m->getRingInfo()->isAtomInRingOfSize(0, 5)); REQUIRE(m->getRingInfo()->numAtomRings(0) == 0); REQUIRE(m->getRingInfo()->isAtomInRingOfSize(1, 5)); REQUIRE(m->getRingInfo()->numAtomRings(1) == 1); REQUIRE(m->getRingInfo()->isAtomInRingOfSize(2, 5)); REQUIRE(m->getRingInfo()->numAtomRings(2) == 2); REQUIRE(m->getRingInfo()->atomRingSizes(0).empty()); REQUIRE(m->getRingInfo()->atomRingSizes(1) == (INT_VECT{5})); REQUIRE(m->getRingInfo()->atomRingSizes(2) == (INT_VECT{5, 5})); REQUIRE(m->getRingInfo()->atomMembers(2).size() == 2); REQUIRE(m->getRingInfo()->atomMembers(2).at(0) == 0); REQUIRE(m->getRingInfo()->atomMembers(2).at(1) == 1); REQUIRE(m->getRingInfo()->isRingFused(0)); REQUIRE(m->getRingInfo()->isRingFused(1)); REQUIRE(m->getRingInfo()->areRingsFused(0, 1)); REQUIRE(m->getRingInfo()->numFusedBonds(0) == 2); REQUIRE(m->getRingInfo()->numFusedBonds(1) == 2); delete m; smi = "C(C1C2C3C41)(C2C35)C45"; // cubane // smi = "C1(C2C3C4C5C6C72)C3C4C5C6C71"; // from Figureras paper // smi = "C17C5C4C3C2C1C6C2C3C4C5C67"; // we cannot use the sanitization code, because that finds *symmetric* // rings, which will break this case: m = SmilesToMol(smi, 0, 0); int bfs = MolOps::findSSSR(*m); REQUIRE(bfs == 5); BOOST_LOG(rdInfoLog) << "BFSR: " << bfs << "\n"; VECT_INT_VECT bfrs; bfrs.resize(0); bfs = MolOps::symmetrizeSSSR(*m, bfrs); REQUIRE(bfs == 6); BOOST_LOG(rdInfoLog) << "BFSR: " << bfs << "\n"; for (auto bring : bfrs) { BOOST_LOG(rdInfoLog) << "( "; for (const auto &mi : bring) { BOOST_LOG(rdInfoLog) << " " << mi; } BOOST_LOG(rdInfoLog) << ")\n"; } BOOST_LOG(rdInfoLog) << smi << "\n"; for (unsigned int i = 0; i < m->getRingInfo()->numRings(); ++i) { REQUIRE(m->getRingInfo()->isRingFused(i)); REQUIRE(m->getRingInfo()->numFusedBonds(i) == 4); } delete m; // Figueras figure 4: // * The third ring is bigger, and shouldn't be accessed in symmetrizeSSSR smi = "C12CC(CC2)CC1"; m = SmilesToMol(smi, 0, 0); bfs = MolOps::findSSSR(*m); REQUIRE(bfs == 2); bfrs.resize(0); bfs = MolOps::symmetrizeSSSR(*m, bfrs); REQUIRE(bfs == 2); delete m; // Counterexamples in ring perception figure 4: // * The native Figueras algorithm cannot work on this molecule, it will // fail after finding one ring. Naive modified Figueras finds a 6 membered // ring, which is wrong. smi = "C123C4C5C6(C3)C7C1C8C2C4C5C6C78"; m = SmilesToMol(smi, 0, 0); bfs = MolOps::findSSSR(*m); REQUIRE(bfs == 7); bfrs.resize(0); bfs = MolOps::symmetrizeSSSR(*m, bfrs); REQUIRE(bfs == 8); for (auto bring : bfrs) { REQUIRE(bring.size() < 6); } delete m; smi = "C1CC2C1CCC2"; m = SmilesToMol(smi); REQUIRE(m); count = MolOps::findSSSR(*m, sssr); REQUIRE(count == 2); REQUIRE(sssr[0].size() == 4); REQUIRE(sssr[1].size() == 5); BOOST_LOG(rdInfoLog) << smi << "\n"; delete m; smi = "C12=C3C=CC=C1C=CC2=CC=C3"; BOOST_LOG(rdInfoLog) << "\n" << smi << "\n"; m = SmilesToMol(smi, 0, 0); REQUIRE(m); count = MolOps::findSSSR(*m, sssr); REQUIRE(count == 3); REQUIRE(sssr[0].size() == 6); REQUIRE(sssr[1].size() == 5); REQUIRE(sssr[2].size() == 6); BOOST_LOG(rdInfoLog) << smi << "\n"; delete m; smi = "C1(O)C(O)C(O)C1O"; m = SmilesToMol(smi, 0, 0); REQUIRE(m); count = MolOps::findSSSR(*m, sssr); REQUIRE(count == 1); REQUIRE(sssr[0].size() == 4); for (unsigned i = 0; i < m->getNumAtoms(); i++) { if (!(i % 2)) { REQUIRE(m->getRingInfo()->numAtomRings(i) == 1); REQUIRE(m->getRingInfo()->isAtomInRingOfSize(i, 4)); } else { REQUIRE(m->getRingInfo()->numAtomRings(i) == 0); } } BOOST_LOG(rdInfoLog) << smi << "\n"; delete m; // this molecule is from issue 134 // it should come up with three rings smi = "SC(C3C1CC(C3)CC(C2S)(O)C1)2S"; m = SmilesToMol(smi, 0, 0); REQUIRE(m); count = MolOps::findSSSR(*m, sssr); REQUIRE(count == 3); REQUIRE(sssr[0].size() == 5); REQUIRE(sssr[1].size() == 6); REQUIRE(sssr[2].size() == 6); delete m; // this yet another painful case smi = "CC1=CC=C(C=C1)S(=O)(=O)O[CH]2[CH]3CO[CH](O3)[CH]4OC(C)(C)O[CH]24"; m = SmilesToMol(smi, 0, 0); REQUIRE(m); count = MolOps::findSSSR(*m, sssr); REQUIRE(count == 4); REQUIRE(sssr[0].size() == 6); REQUIRE(sssr[1].size() == 5); REQUIRE(sssr[2].size() == 5); REQUIRE(sssr[3].size() == 6); delete m; smi = "C1CC2C1C2"; m = SmilesToMol(smi, 0, 0); REQUIRE(m); count = MolOps::findSSSR(*m, sssr); REQUIRE(count == 2); REQUIRE(sssr[0].size() == 4); REQUIRE(sssr[1].size() == 3); REQUIRE(m->getRingInfo()->numAtomRings(0) == 1); REQUIRE(m->getRingInfo()->isAtomInRingOfSize(0, 4)); REQUIRE(m->getRingInfo()->numAtomRings(1) == 1); REQUIRE(m->getRingInfo()->isAtomInRingOfSize(1, 4)); REQUIRE(m->getRingInfo()->numAtomRings(2) == 2); REQUIRE(m->getRingInfo()->isAtomInRingOfSize(2, 3)); REQUIRE(m->getRingInfo()->isAtomInRingOfSize(2, 4)); REQUIRE(m->getRingInfo()->numAtomRings(3) == 2); REQUIRE(m->getRingInfo()->isAtomInRingOfSize(3, 4)); REQUIRE(m->getRingInfo()->isAtomInRingOfSize(3, 3)); REQUIRE(m->getRingInfo()->numAtomRings(4) == 1); REQUIRE(!m->getRingInfo()->isAtomInRingOfSize(4, 4)); REQUIRE(m->getRingInfo()->isAtomInRingOfSize(4, 3)); REQUIRE(m->getRingInfo()->atomMembers(2).size() == 2); REQUIRE(m->getRingInfo()->areAtomsInSameRing(2, 3)); REQUIRE(!m->getRingInfo()->areAtomsInSameRing(1, 4)); REQUIRE(m->getRingInfo()->areAtomsInSameRingOfSize(2, 3, 3)); REQUIRE(m->getRingInfo()->areAtomsInSameRingOfSize(2, 3, 4)); REQUIRE(!m->getRingInfo()->areAtomsInSameRingOfSize(2, 3, 5)); REQUIRE(m->getRingInfo()->bondMembers(2).size() == 2); REQUIRE(m->getRingInfo()->bondMembers(0).size() == 1); REQUIRE(m->getRingInfo()->areBondsInSameRing(1, 2)); REQUIRE(m->getRingInfo()->areBondsInSameRing(2, 5)); REQUIRE(!m->getRingInfo()->areBondsInSameRing(1, 3)); REQUIRE(m->getRingInfo()->areBondsInSameRingOfSize(1, 2, 4)); REQUIRE(m->getRingInfo()->areBondsInSameRingOfSize(2, 5, 3)); REQUIRE(!m->getRingInfo()->areBondsInSameRingOfSize(1, 2, 3)); REQUIRE(!m->getRingInfo()->areBondsInSameRingOfSize(1, 3, 4)); REQUIRE(m->getRingInfo()->isRingFused(0)); REQUIRE(m->getRingInfo()->isRingFused(1)); REQUIRE(m->getRingInfo()->areRingsFused(0, 1)); REQUIRE(m->getRingInfo()->numFusedBonds(0) == 1); REQUIRE(m->getRingInfo()->numFusedBonds(1) == 1); delete m; // This is a test of Issue 217 smi = "C=C1C2CC1C2"; m = SmilesToMol(smi, 0, 0); REQUIRE(m); count = MolOps::findSSSR(*m, sssr); REQUIRE(count == 2); REQUIRE(sssr[0].size() == 4); REQUIRE(sssr[1].size() == 4); count = MolOps::symmetrizeSSSR(*m, sssr); REQUIRE(count == 3); REQUIRE(sssr[0].size() == 4); REQUIRE(sssr[1].size() == 4); REQUIRE(sssr[2].size() == 4); REQUIRE(m->getRingInfo()->numAtomRings(0) == 0); REQUIRE(m->getRingInfo()->numAtomRings(1) == 2); REQUIRE(m->getRingInfo()->isAtomInRingOfSize(1, 4)); REQUIRE(m->getRingInfo()->numAtomRings(2) == 3); REQUIRE(m->getRingInfo()->isAtomInRingOfSize(2, 4)); REQUIRE(m->getRingInfo()->numAtomRings(3) == 2); REQUIRE(m->getRingInfo()->isAtomInRingOfSize(3, 4)); REQUIRE(m->getRingInfo()->numAtomRings(4) == 3); REQUIRE(m->getRingInfo()->isAtomInRingOfSize(4, 4)); delete m; } TEST_CASE("test4") { auto m = "C=C"_smiles; REQUIRE(m); double *adjMat = MolOps::getAdjacencyMatrix(*m); REQUIRE(adjMat); REQUIRE(adjMat[0] == 0); REQUIRE(adjMat[1] == 1); REQUIRE(adjMat[2] == 1); REQUIRE(adjMat[3] == 0); adjMat = MolOps::getAdjacencyMatrix(*m); REQUIRE(adjMat); REQUIRE(adjMat[0] == 0); REQUIRE(adjMat[1] == 1); REQUIRE(adjMat[2] == 1); REQUIRE(adjMat[3] == 0); bool useBO = true; adjMat = MolOps::getAdjacencyMatrix(*m, useBO); REQUIRE(adjMat); REQUIRE(adjMat[0] == 0); REQUIRE(adjMat[1] == 2.0); REQUIRE(adjMat[2] == 2.0); REQUIRE(adjMat[3] == 0); } TEST_CASE("test5") { string smi; Mol *m; VECT_INT_VECT sssr; int count; smi = "C1C4C5C3C(=O)C2C5C1C2C34"; m = SmilesToMol(smi, 0, 0); count = MolOps::findSSSR(*m, sssr); BOOST_LOG(rdInfoLog) << "Count: " << count << "\n"; REQUIRE(count == 5); delete m; smi = "C1C(C2)CCC2C1"; m = SmilesToMol(smi); count = MolOps::findSSSR(*m, sssr); REQUIRE(count == 2); delete m; } TEST_CASE("Testing Hydrogen Ops") { ROMol *m, *m2, *m3; INT_VECT tree; std::string smi = "CCC"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 3); m2 = MolOps::addHs(*m); REQUIRE(m2->getNumAtoms() == 11); // addHs should not set the noImplicit flag. // This was Github Issue #7123 for (auto at : m2->atoms()) { REQUIRE(at->getNoImplicit() == false); } delete m; delete m2; smi = "CC(=O)[OH]"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); m2 = MolOps::addHs(*m, true); REQUIRE(m2->getNumAtoms() == 5); m3 = MolOps::addHs(*m2, false); REQUIRE(m3->getNumAtoms() == 8); delete m2; delete m3; m2 = MolOps::addHs(*m, false); REQUIRE(m2->getNumAtoms() == 8); delete m; // remove all // rdInfoLog) << "5" << std::endl; m3 = MolOps::removeHs(*m2, false); REQUIRE(m3->getNumAtoms() == 4); delete m3; // remove only implicit m3 = MolOps::removeHs(*m2, true); REQUIRE(m3->getNumAtoms() == 5); MolOps::removeHs(static_cast(*m3), false); REQUIRE(m3->getNumAtoms() == 4); delete m2; delete m3; // this test is also done in the same order in the python tests: m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); m2 = MolOps::addHs(*m, true); REQUIRE(m2->getNumAtoms() == 5); m3 = MolOps::removeHs(*m2, true); REQUIRE(m3->getNumAtoms() == 5); delete m3; m3 = MolOps::removeHs(*m2, false); REQUIRE(m3->getNumAtoms() == 4); delete m2; delete m3; m2 = MolOps::addHs(*m, false); REQUIRE(m2->getNumAtoms() == 8); m3 = MolOps::removeHs(*m2, true); REQUIRE(m3->getNumAtoms() == 5); delete m3; m3 = MolOps::removeHs(*m2, false); REQUIRE(m3->getNumAtoms() == 4); delete m3; delete m2; delete m; // related to RDTrack Issues 109 and 110: smi = "C1C=C([C@H](N)C(=O)N[C@@]2([H])[C@]3([H])SC(C)(C)[C@@H](C(=O)O)N3C(=O)2)" "C=CC=1"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 24); m3 = MolOps::removeHs(*m, false); REQUIRE(m3->getNumAtoms() == 24); delete m; delete m3; // RDTrack Issue 130: smi = "[H][N+]([H])([H])[H]"; m = SmilesToMol(smi, false, false); REQUIRE(m); REQUIRE(m->getNumAtoms() == 5); m2 = MolOps::removeHs(*m, 0, false); REQUIRE(m2->getNumAtoms() == 1); delete m; delete m2; smi = "[H][N+]([H])([H])[H]"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 1); delete m; smi = "[H][H]"; m = SmilesToMol(smi, false, false); REQUIRE(m); REQUIRE(m->getNumAtoms() == 2); m2 = MolOps::removeHs(*m, 0, false); REQUIRE(m2->getNumAtoms() == 2); delete m; delete m2; std::string sma; smi = "C-C"; m = SmartsToMol(smi); MolOps::sanitizeMol(*((RWMol *)m)); REQUIRE(m); REQUIRE(m->getNumAtoms() == 2); sma = SmartsWrite::GetAtomSmarts( static_cast(m->getAtomWithIdx(0))); REQUIRE(sma == "C"); m2 = MolOps::addHs(*m); REQUIRE(m2->getNumAtoms() == 8); sma = SmartsWrite::GetAtomSmarts( static_cast(m2->getAtomWithIdx(0))); REQUIRE(sma == "C"); delete m; m = MolOps::mergeQueryHs(*m2); REQUIRE(m->getNumAtoms() == 2); sma = SmartsWrite::GetAtomSmarts( static_cast(m->getAtomWithIdx(0))); // this was sf.net issue 3415204: REQUIRE(sma == "[C&!H0&!H1&!H2]"); delete m; delete m2; // RDTrack Issue 1228: smi = "c1c[nH]cc1"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 5); m2 = MolOps::addHs(*m, false, false); REQUIRE(m2->getNumAtoms() == 10); delete m; m = MolOps::removeHs(*m2); REQUIRE(m); REQUIRE(m->getNumAtoms() == 5); delete m; delete m2; // labelling: smi = "c1cn([H])cc1"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 5); m2 = MolOps::removeHs(*m); REQUIRE(m); REQUIRE(m->getNumAtoms() == 5); delete m; delete m2; smi = "c1cn([2H])cc1"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 6); m2 = MolOps::removeHs(*m); REQUIRE(m); REQUIRE(m->getNumAtoms() == 6); delete m; delete m2; smi = "CC[H]"; m = SmilesToMol(smi, false, false); REQUIRE(m); REQUIRE(m->getNumAtoms() == 3); m2 = MolOps::mergeQueryHs(*m); REQUIRE(m2->getNumAtoms() == 2); REQUIRE(!m->getAtomWithIdx(1)->hasQuery()); REQUIRE(m2->getAtomWithIdx(1)->hasQuery()); delete m; delete m2; // sf.net issue 3415206 smi = "CO[H]"; m = SmilesToMol(smi, false, false); REQUIRE(m); REQUIRE(m->getNumAtoms() == 3); m2 = MolOps::mergeQueryHs(*m); REQUIRE(m2->getNumAtoms() == 2); sma = SmartsWrite::GetAtomSmarts( static_cast(m2->getAtomWithIdx(1))); REQUIRE(sma == "[#8&!H0]"); delete m; delete m2; smi = "CN([H])[H]"; m = SmilesToMol(smi, false, false); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); m2 = MolOps::mergeQueryHs(*m); REQUIRE(m2->getNumAtoms() == 2); sma = SmartsWrite::GetAtomSmarts( static_cast(m2->getAtomWithIdx(1))); REQUIRE(sma == "[#7&!H0&!H1]"); delete m; delete m2; { // test the onlyOnAtoms option (github #758) constexpr const char *smi = "CCC"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 3); UINT_VECT onlyOn; onlyOn.push_back(0); onlyOn.push_back(2); m2 = MolOps::addHs(*m, false, false, &onlyOn); REQUIRE(m2->getNumAtoms() == 9); REQUIRE(m2->getAtomWithIdx(0)->getDegree() == 4); REQUIRE(m2->getAtomWithIdx(1)->getDegree() == 2); REQUIRE(m2->getAtomWithIdx(2)->getDegree() == 4); delete m; delete m2; } } TEST_CASE("Testing Distance Matrix Operations") { auto m = "CC=O"_smiles; REQUIRE(m); REQUIRE(m->getNumAtoms() == 3); bool useBO = false; bool useAtomWts = false; double *dMat; dMat = MolOps::getDistanceMat(*m, useBO, useAtomWts); REQUIRE(dMat); REQUIRE(dMat[0] == 0.0); REQUIRE(dMat[1] == 1.0); REQUIRE(dMat[2] == 2.0); REQUIRE(dMat[3] == 1.0); REQUIRE(dMat[4] == 0.0); REQUIRE(dMat[5] == 1.0); REQUIRE(dMat[6] == 2.0); REQUIRE(dMat[7] == 1.0); REQUIRE(dMat[8] == 0.0); dMat = MolOps::getDistanceMat(*m, useBO, useAtomWts); REQUIRE(dMat); REQUIRE(dMat[0] == 0.0); REQUIRE(dMat[1] == 1.0); REQUIRE(dMat[2] == 2.0); REQUIRE(dMat[3] == 1.0); REQUIRE(dMat[4] == 0.0); REQUIRE(dMat[5] == 1.0); REQUIRE(dMat[6] == 2.0); REQUIRE(dMat[7] == 1.0); REQUIRE(dMat[8] == 0.0); // test Issue328: useBO = true; dMat = MolOps::getDistanceMat(*m, useBO, useAtomWts); REQUIRE(dMat); REQUIRE(dMat[0] == 0.0); REQUIRE(dMat[1] == 1.0); REQUIRE(dMat[2] == 1.5); REQUIRE(dMat[3] == 1.0); REQUIRE(dMat[4] == 0.0); REQUIRE(dMat[5] == 0.5); REQUIRE(dMat[6] == 1.5); REQUIRE(dMat[7] == 0.5); REQUIRE(dMat[8] == 0.0); useBO = false; dMat = MolOps::getDistanceMat(*m, useBO, useAtomWts); REQUIRE(dMat); REQUIRE(dMat[0] == 0.0); REQUIRE(dMat[1] == 1.0); REQUIRE(dMat[2] == 2.0); REQUIRE(dMat[3] == 1.0); REQUIRE(dMat[4] == 0.0); REQUIRE(dMat[5] == 1.0); REQUIRE(dMat[6] == 2.0); REQUIRE(dMat[7] == 1.0); REQUIRE(dMat[8] == 0.0); useBO = false; useAtomWts = true; dMat = MolOps::getDistanceMat(*m, useBO, useAtomWts); REQUIRE(dMat); for (unsigned int i = 0; i < m->getNumAtoms(); ++i) { for (unsigned int j = 0; j < m->getNumAtoms(); ++j) { std::cerr << dMat[i * m->getNumAtoms() + j] << " "; } std::cerr << std::endl; } REQUIRE(dMat[0] == 1.0); REQUIRE(dMat[1] == 1.0); REQUIRE(dMat[2] == 2.0); REQUIRE(dMat[3] == 1.0); REQUIRE(dMat[4] == 1.0); REQUIRE(dMat[5] == 1.0); REQUIRE(dMat[6] == 2.0); REQUIRE(dMat[7] == 1.0); REQUIRE(dMat[8] == 6. / 8.); useBO = true; useAtomWts = true; dMat = MolOps::getDistanceMat(*m, useBO, useAtomWts); REQUIRE(dMat); REQUIRE(dMat[0] == 1.0); REQUIRE(dMat[1] == 1.0); REQUIRE(dMat[2] == 1.5); REQUIRE(dMat[3] == 1.0); REQUIRE(dMat[4] == 1.0); REQUIRE(dMat[5] == 0.5); REQUIRE(dMat[6] == 1.5); REQUIRE(dMat[7] == 0.5); REQUIRE(dMat[8] == 6. / 8.); useBO = false; useAtomWts = false; dMat = MolOps::getDistanceMat(*m, useBO, useAtomWts); REQUIRE(dMat); REQUIRE(dMat[0] == 0.0); REQUIRE(dMat[1] == 1.0); REQUIRE(dMat[2] == 2.0); REQUIRE(dMat[3] == 1.0); REQUIRE(dMat[4] == 0.0); REQUIRE(dMat[5] == 1.0); REQUIRE(dMat[6] == 2.0); REQUIRE(dMat[7] == 1.0); REQUIRE(dMat[8] == 0.0); // limit participating atoms and bonds std::vector activeAtoms = {1, 2}; std::vector activeBonds = {m->getBondWithIdx(1)}; useBO = false; useAtomWts = false; std::unique_ptr dMat2{ MolOps::getDistanceMat(*m, activeAtoms, activeBonds, useBO, useAtomWts)}; REQUIRE(dMat2); REQUIRE(dMat2[0] == 0.0); REQUIRE(dMat2[1] == 1.0); REQUIRE(dMat2[2] == 1.0); REQUIRE(dMat2[3] == 0.0); useBO = true; useAtomWts = true; dMat2.reset( MolOps::getDistanceMat(*m, activeAtoms, activeBonds, useBO, useAtomWts)); REQUIRE(dMat2); REQUIRE(dMat2[0] == 1.0); REQUIRE(dMat2[1] == 0.5); REQUIRE(dMat2[2] == 0.5); REQUIRE(dMat2[3] == 6.0 / 8.0); } TEST_CASE("Testing Atom Ranking") { ROMol *m; std::string smi = "FC(Cl)(Br)C"; m = SmilesToMol(smi); REQUIRE(m); UINT_VECT ranks; ranks.resize(m->getNumAtoms()); Chirality::assignAtomCIPRanks(*m, ranks); unsigned int cip1, cip2; REQUIRE(m->getAtomWithIdx(0)->hasProp(common_properties::_CIPRank)); m->getAtomWithIdx(0)->getProp(common_properties::_CIPRank, cip1); REQUIRE(cip1 == ranks[0]); REQUIRE(m->getAtomWithIdx(2)->hasProp(common_properties::_CIPRank)); m->getAtomWithIdx(2)->getProp(common_properties::_CIPRank, cip2); REQUIRE(cip2 == ranks[2]); REQUIRE(cip1 < cip2); REQUIRE(m->getAtomWithIdx(4)->hasProp(common_properties::_CIPRank)); m->getAtomWithIdx(4)->getProp(common_properties::_CIPRank, cip2); REQUIRE(cip1 > cip2); m->getAtomWithIdx(2)->getProp(common_properties::_CIPRank, cip1); REQUIRE(m->getAtomWithIdx(3)->hasProp(common_properties::_CIPRank)); m->getAtomWithIdx(3)->getProp(common_properties::_CIPRank, cip2); REQUIRE(cip1 < cip2); delete m; smi = "FC(Cl)(Br)C(F)(F)F"; m = SmilesToMol(smi); REQUIRE(m); ranks.resize(m->getNumAtoms()); Chirality::assignAtomCIPRanks(*m, ranks); for (unsigned int i = 0; i < m->getNumAtoms(); i++) { unsigned int cip; REQUIRE(m->getAtomWithIdx(i)->hasProp(common_properties::_CIPRank)); m->getAtomWithIdx(i)->getProp(common_properties::_CIPRank, cip); } delete m; } TEST_CASE("Testing CIP chirality") { ROMol *m; std::string cip; std::string smi = "F[C@]([C@])(Cl)Br"; m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); // make sure the cleanup worked: REQUIRE(m->getAtomWithIdx(2)->getChiralTag() == Atom::CHI_UNSPECIFIED); REQUIRE(!(m->getAtomWithIdx(0)->hasProp(common_properties::_CIPCode))); REQUIRE(m->getAtomWithIdx(1)->hasProp(common_properties::_CIPCode)); REQUIRE(!(m->getAtomWithIdx(2)->hasProp(common_properties::_CIPCode))); m->getAtomWithIdx(1)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "R"); delete m; smi = "F[C@H](C)C"; m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(1)->getChiralTag() == Atom::CHI_UNSPECIFIED); REQUIRE(!(m->getAtomWithIdx(0)->hasProp(common_properties::_CIPCode))); REQUIRE(!(m->getAtomWithIdx(1)->hasProp(common_properties::_CIPCode))); REQUIRE(!(m->getAtomWithIdx(2)->hasProp(common_properties::_CIPCode))); // test Issue 194: REQUIRE(m->getAtomWithIdx(1)->getNumExplicitHs() == 0); delete m; smi = "F[C@]1CC(Cl)C1"; m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(1)->getChiralTag() == Atom::CHI_UNSPECIFIED); REQUIRE(!(m->getAtomWithIdx(1)->hasProp(common_properties::_CIPCode))); delete m; smi = "F[C@H]1C(Cl)CC1"; m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(1)->getChiralTag() != Atom::CHI_UNSPECIFIED); REQUIRE(m->getAtomWithIdx(1)->hasProp(common_properties::_CIPCode)); delete m; smi = "F[C@@](C)(Cl)Br"; m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(!(m->getAtomWithIdx(0)->hasProp(common_properties::_CIPCode))); REQUIRE(m->getAtomWithIdx(1)->hasProp(common_properties::_CIPCode)); REQUIRE(!(m->getAtomWithIdx(2)->hasProp(common_properties::_CIPCode))); m->getAtomWithIdx(1)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "S"); delete m; smi = "F[C@](Br)(C)Cl"; m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(1)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(1)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "R"); delete m; smi = "F[C@](Cl)(Br)C"; m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(1)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(1)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "R"); delete m; smi = "FC(F)(F)[C@](Br)(F)C(Cl)(Cl)Cl"; m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(4)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(4)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "R"); delete m; smi = "C[C@](C=C)(F)Br"; m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(1)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(1)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "S"); delete m; smi = "CC[C@](C=C)(F)Br"; m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(2)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(2)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "S"); delete m; std::cerr << "-------------------------------" << std::endl; smi = "[CH2-][C@](C)(F)Br"; m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(1)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(1)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "S"); delete m; smi = "F[C@]([H])(Cl)Br"; m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(1)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(1)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "R"); delete m; smi = "[C@H](Cl)(F)Br"; m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(0)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(0)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "R"); delete m; smi = "[C@]([H])(Cl)(F)Br"; m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(0)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(0)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "R"); delete m; smi = "F[C@H](Cl)Br"; m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(1)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(1)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "R"); delete m; smi = "CC[C@H](C=C)C"; #ifdef VERBOSE_CANON BOOST_LOG(rdDebugLog) << " ----------------- ------------- ----------------" << std::endl; BOOST_LOG(rdDebugLog) << "\t>" << smi << std::endl; #endif m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(2)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(2)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "R"); delete m; smi = "OC[C@H](C=C)C"; #ifdef VERBOSE_CANON BOOST_LOG(rdDebugLog) << " ----------------- ------------- ----------------" << std::endl; BOOST_LOG(rdDebugLog) << "\t>" << smi << std::endl; #endif m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(2)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(2)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "S"); delete m; smi = "CC[C@H](C=C)O"; #ifdef VERBOSE_CANON BOOST_LOG(rdDebugLog) << " ----------------- ------------- ----------------" << std::endl; BOOST_LOG(rdDebugLog) << "\t>" << smi << std::endl; #endif m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(2)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(2)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "R"); delete m; smi = "OC[C@H](C=C)O"; #ifdef VERBOSE_CANON BOOST_LOG(rdDebugLog) << " ----------------- ------------- ----------------" << std::endl; BOOST_LOG(rdDebugLog) << "\t>" << smi << std::endl; #endif m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(2)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(2)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "S"); delete m; smi = "C[C@H]1C[C@H](C=C1)N"; #ifdef VERBOSE_CANON BOOST_LOG(rdDebugLog) << " ----------------- ------------- ----------------" << std::endl; BOOST_LOG(rdDebugLog) << "\t>" << smi << std::endl; #endif m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(1)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(1)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "S"); REQUIRE(m->getAtomWithIdx(3)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(3)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "R"); // a couple random molecules from the BBB data set delete m; smi = "OC[C@H]1C[C@@H](N2C=NC3=C2N=C(N)N=C3NC4CC4)C=C1"; #ifdef VERBOSE_CANON BOOST_LOG(rdDebugLog) << " ----------------- ------------- ----------------" << std::endl; BOOST_LOG(rdDebugLog) << "\t>" << smi << std::endl; #endif m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(2)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(2)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "S"); REQUIRE(m->getAtomWithIdx(4)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(4)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "R"); delete m; smi = "N[C@H]1O[C@@H](SC1)CO"; #ifdef VERBOSE_CANON BOOST_LOG(rdDebugLog) << " ----------------- ------------- ----------------" << std::endl; BOOST_LOG(rdDebugLog) << "\t>" << smi << std::endl; #endif m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(1)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(1)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "S"); REQUIRE(m->getAtomWithIdx(3)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(3)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "S"); delete m; smi = "C1(N([C@H]2O[C@H](CO)SC2)C=CC(N)=N1)=O"; #ifdef VERBOSE_CANON BOOST_LOG(rdDebugLog) << " ----------------- ------------- ----------------" << std::endl; BOOST_LOG(rdDebugLog) << "\t>" << smi << std::endl; #endif m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(2)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(2)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "S"); REQUIRE(m->getAtomWithIdx(4)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(4)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "S"); // this is Issue 152: smi = "C1[C@H](N)C[C@H](C)C=1"; #ifdef VERBOSE_CANON BOOST_LOG(rdDebugLog) << " ----------------- ------------- ----------------" << std::endl; BOOST_LOG(rdDebugLog) << "\t>" << smi << std::endl; #endif delete m; m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(1)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(1)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "R"); REQUIRE(m->getAtomWithIdx(4)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(4)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "S"); // ----------------------------------------------- // these are related to Issue 397: smi = "C(=O)[C@@H](C)N"; #ifdef VERBOSE_CANON BOOST_LOG(rdDebugLog) << " ----------------- ------------- ----------------" << std::endl; BOOST_LOG(rdDebugLog) << "\t>" << smi << std::endl; #endif delete m; m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(2)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(2)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "R"); smi = "C(=O)[C@@H](CO)N"; #ifdef VERBOSE_CANON BOOST_LOG(rdDebugLog) << " ----------------- ------------- ----------------" << std::endl; BOOST_LOG(rdDebugLog) << "\t>" << smi << std::endl; #endif delete m; m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(2)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(2)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "R"); smi = "C(O)[C@@H](C)N"; #ifdef VERBOSE_CANON BOOST_LOG(rdDebugLog) << " ----------------- ------------- ----------------" << std::endl; BOOST_LOG(rdDebugLog) << "\t>" << smi << std::endl; #endif delete m; m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(2)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(2)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "R"); // ----------------------------------------------- // NOTE: This test gives correct results according to the current // CIP ranking procedure, but the results are actually incorrect. // This arises because of the inclusion of hybridization in the // chiral atom invariants // (see the note in Chirality.cpp:buildCIPInvariants()) smi = "[H][C@@](O)(C=C)C(C)CC"; #ifdef VERBOSE_CANON BOOST_LOG(rdDebugLog) << " ----------------- ------------- ----------------" << std::endl; BOOST_LOG(rdDebugLog) << "\t>" << smi << std::endl; #endif delete m; m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(0)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(0)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "S"); smi = "[H][C@@](O)(C=C)C(C)CO"; #ifdef VERBOSE_CANON BOOST_LOG(rdDebugLog) << " ----------------- ------------- ----------------" << std::endl; BOOST_LOG(rdDebugLog) << "\t>" << smi << std::endl; #endif delete m; m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(0)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(0)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "R"); smi = "[H][C@@]12C[C@@](NC1)(OC2)[H]"; #ifdef VERBOSE_CANON BOOST_LOG(rdDebugLog) << " ----------------- ------------- ----------------" << std::endl; BOOST_LOG(rdDebugLog) << "\t>" << smi << std::endl; #endif delete m; m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(0)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(0)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "R"); REQUIRE(m->getAtomWithIdx(2)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(2)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "S"); smi = "[H][C@@]12C[C@@](C=C1)(CC2)[H]"; #ifdef VERBOSE_CANON BOOST_LOG(rdDebugLog) << " ----------------- ------------- ----------------" << std::endl; BOOST_LOG(rdDebugLog) << "\t>" << smi << std::endl; #endif delete m; m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(0)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(0)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "R"); REQUIRE(m->getAtomWithIdx(2)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(2)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "S"); smi = "[H][C@@]12O[C@@](CC1)(C3C2C(NC3=O)=O)[H]"; #ifdef VERBOSE_CANON BOOST_LOG(rdDebugLog) << " ----------------- ------------- ----------------" << std::endl; BOOST_LOG(rdDebugLog) << "\t>" << smi << std::endl; #endif delete m; m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(0)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(0)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "R"); REQUIRE(m->getAtomWithIdx(2)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(2)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "S"); smi = "[H][C@@]12O[C@@](C=C1)(C3C2C(NC3=O)=O)[H]"; #ifdef VERBOSE_CANON BOOST_LOG(rdDebugLog) << " ----------------- ------------- ----------------" << std::endl; BOOST_LOG(rdDebugLog) << "\t>" << smi << std::endl; #endif delete m; m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m, true); REQUIRE(m->getAtomWithIdx(0)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(0)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "R"); REQUIRE(m->getAtomWithIdx(2)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(2)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "S"); delete m; // ----------------------------------------------- } TEST_CASE("Testing double bond stereochemistry") { for (const bool useLegacy : {true, false}) { UseLegacyStereoPerceptionFixture fx(useLegacy); std::string smi = "F\\C=C/Cl"; auto m = v2::SmilesParse::MolFromSmiles(smi); REQUIRE(m); REQUIRE(m->getBondWithIdx(0)->getStereo() == Bond::STEREONONE); if (useLegacy) { REQUIRE(m->getBondWithIdx(1)->getStereo() == Bond::STEREOZ); } else { REQUIRE(m->getBondWithIdx(1)->getStereo() == Bond::STEREOCIS); REQUIRE(m->getBondWithIdx(1)->getStereoAtoms()[0] == 0); REQUIRE(m->getBondWithIdx(1)->getStereoAtoms()[1] == 3); } REQUIRE(m->getBondWithIdx(2)->getStereo() == Bond::STEREONONE); smi = "F/C=CCl"; m.reset(SmilesToMol(smi)); REQUIRE(m); REQUIRE(m->getBondWithIdx(1)->getStereo() == Bond::STEREONONE); smi = "F/C=C/Cl"; m.reset(SmilesToMol(smi)); REQUIRE(m); if (useLegacy) { REQUIRE(m->getBondWithIdx(1)->getStereo() == Bond::STEREOE); } else { REQUIRE(m->getBondWithIdx(1)->getStereo() == Bond::STEREOTRANS); REQUIRE(m->getBondWithIdx(1)->getStereoAtoms()[0] == 0); REQUIRE(m->getBondWithIdx(1)->getStereoAtoms()[1] == 3); } smi = "F/C=C(/Br)Cl"; m.reset(SmilesToMol(smi)); REQUIRE(m); if (useLegacy) { REQUIRE(m->getBondWithIdx(1)->getStereo() == Bond::STEREOE); } else { REQUIRE(m->getBondWithIdx(1)->getStereo() == Bond::STEREOTRANS); REQUIRE(m->getBondWithIdx(1)->getStereoAtoms()[0] == 0); REQUIRE(m->getBondWithIdx(1)->getStereoAtoms()[1] == 3); } smi = "F/C=C(/Cl)Br"; m.reset(SmilesToMol(smi)); REQUIRE(m); if (useLegacy) { REQUIRE(m->getBondWithIdx(1)->getStereo() == Bond::STEREOZ); } else { REQUIRE(m->getBondWithIdx(1)->getStereo() == Bond::STEREOTRANS); REQUIRE(m->getBondWithIdx(1)->getStereoAtoms()[0] == 0); REQUIRE(m->getBondWithIdx(1)->getStereoAtoms()[1] == 3); } smi = "F/C(Br)=C/Cl"; m.reset(SmilesToMol(smi)); REQUIRE(m); if (useLegacy) { REQUIRE(m->getBondWithIdx(2)->getStereo() == Bond::STEREOZ); } else { REQUIRE(m->getBondWithIdx(2)->getStereo() == Bond::STEREOTRANS); REQUIRE(m->getBondWithIdx(2)->getStereoAtoms()[0] == 0); REQUIRE(m->getBondWithIdx(2)->getStereoAtoms()[1] == 4); } smi = "F/C=C(/Cl)Cl"; m.reset(SmilesToMol(smi)); REQUIRE(m); REQUIRE(m->getBondWithIdx(1)->getStereo() == Bond::STEREONONE); // build a molecule from scratch to test problems // around Issue 180. The molecule corresponds to SMILES // F/C=C(/Br)C auto m2 = std::make_unique(); m2->addAtom(new Atom(9), true, true); m2->addAtom(new Atom(6), true, true); m2->addAtom(new Atom(6), true, true); m2->addAtom(new Atom(35), true, true); m2->addAtom(new Atom(6), true, true); m2->addBond(0, 1, Bond::SINGLE); m2->addBond(1, 2, Bond::DOUBLE); m2->addBond(2, 3, Bond::SINGLE); m2->addBond(2, 4, Bond::SINGLE); m2->getBondWithIdx(0)->setBondDir(Bond::ENDUPRIGHT); m2->getBondWithIdx(2)->setBondDir(Bond::ENDUPRIGHT); m2->getBondWithIdx(3)->setBondDir(Bond::ENDDOWNRIGHT); MolOps::sanitizeMol(*m2); MolOps::assignStereochemistry(*m2); if (useLegacy) { REQUIRE(m2->getBondWithIdx(1)->getStereo() == Bond::STEREOE); } else { REQUIRE(m2->getBondWithIdx(1)->getStereo() == Bond::STEREOTRANS); REQUIRE(m2->getBondWithIdx(1)->getStereoAtoms()[0] == 0); REQUIRE(m2->getBondWithIdx(1)->getStereoAtoms()[1] == 3); } m2->getBondWithIdx(0)->setBondDir(Bond::ENDDOWNRIGHT); MolOps::assignStereochemistry(*m2, true, true); if (useLegacy) { REQUIRE(m2->getBondWithIdx(1)->getStereo() == Bond::STEREOZ); } else { REQUIRE(m2->getBondWithIdx(1)->getStereo() == Bond::STEREOCIS); REQUIRE(m2->getBondWithIdx(1)->getStereoAtoms()[0] == 0); REQUIRE(m2->getBondWithIdx(1)->getStereoAtoms()[1] == 3); } m2.reset(new RWMol()); m2->addAtom(new Atom(9), true, true); m2->addAtom(new Atom(6), true, true); m2->addAtom(new Atom(6), true, true); m2->addAtom(new Atom(35), true, true); m2->addAtom(new Atom(6), true, true); m2->addBond(1, 0, Bond::SINGLE); m2->addBond(1, 2, Bond::DOUBLE); m2->addBond(2, 3, Bond::SINGLE); m2->addBond(2, 4, Bond::SINGLE); m2->getBondWithIdx(0)->setBondDir(Bond::ENDUPRIGHT); m2->getBondWithIdx(2)->setBondDir(Bond::ENDUPRIGHT); m2->getBondWithIdx(3)->setBondDir(Bond::ENDDOWNRIGHT); MolOps::sanitizeMol(*m2); MolOps::assignStereochemistry(*m2); if (useLegacy) { REQUIRE(m2->getBondWithIdx(1)->getStereo() == Bond::STEREOZ); } else { REQUIRE(m2->getBondWithIdx(1)->getStereo() == Bond::STEREOCIS); REQUIRE(m2->getBondWithIdx(1)->getStereoAtoms()[0] == 0); REQUIRE(m2->getBondWithIdx(1)->getStereoAtoms()[1] == 3); } m2->getBondWithIdx(0)->setBondDir(Bond::ENDDOWNRIGHT); MolOps::assignStereochemistry(*m2, true, true); if (useLegacy) { REQUIRE(m2->getBondWithIdx(1)->getStereo() == Bond::STEREOE); } else { REQUIRE(m2->getBondWithIdx(1)->getStereo() == Bond::STEREOTRANS); REQUIRE(m2->getBondWithIdx(1)->getStereoAtoms()[0] == 0); REQUIRE(m2->getBondWithIdx(1)->getStereoAtoms()[1] == 3); } // ---------------------- // test Issue 174: smi = "O\\N=C\\C=N/O"; m2.reset(SmilesToMol(smi)); REQUIRE(m2); if (useLegacy) { REQUIRE(m2->getBondWithIdx(1)->getStereo() == Bond::STEREOE); REQUIRE(m2->getBondWithIdx(3)->getStereo() == Bond::STEREOZ); } else { REQUIRE(m2->getBondWithIdx(1)->getStereo() == Bond::STEREOTRANS); REQUIRE(m2->getBondWithIdx(1)->getStereoAtoms()[0] == 0); REQUIRE(m2->getBondWithIdx(1)->getStereoAtoms()[1] == 3); REQUIRE(m2->getBondWithIdx(3)->getStereo() == Bond::STEREOCIS); REQUIRE(m2->getBondWithIdx(3)->getStereoAtoms()[0] == 2); REQUIRE(m2->getBondWithIdx(3)->getStereoAtoms()[1] == 5); } auto refSmi = MolToSmiles(*m2, 1); m.reset(SmilesToMol(refSmi)); REQUIRE(m); smi = MolToSmiles(*m, 1); REQUIRE(refSmi == smi); } } TEST_CASE("Testing Issue 183") { // ---------------------- // test "unsetting" of redundant bond directions: RWMol *m, *m2; std::string smi; std::string refSmi; smi = "Cl\\C(C)=C(\\C(F)=C(/F)C)/C(C)=C(\\F)C"; m2 = SmilesToMol(smi); REQUIRE(m2); REQUIRE(m2->getBondWithIdx(2)->getStereo() == Bond::STEREOE); REQUIRE(m2->getBondWithIdx(5)->getStereo() == Bond::STEREOE); REQUIRE(m2->getBondWithIdx(10)->getStereo() == Bond::STEREOZ); refSmi = MolToSmiles(*m2, 1); CHECK(refSmi == R"SMI(C/C(F)=C(C)/C(=C(/C)Cl)C(/F)=C(/C)F)SMI"); m = SmilesToMol(refSmi); REQUIRE(m); smi = MolToSmiles(*m, 1); CHECK(refSmi == smi); int nEs = 0, nZs = 0, nDbl = 0; for (RWMol::BondIterator bondIt = m->beginBonds(); bondIt != m->endBonds(); bondIt++) { if ((*bondIt)->getBondType() == Bond::DOUBLE) { nDbl++; if ((*bondIt)->getStereo() == Bond::STEREOE) { nEs++; } else if ((*bondIt)->getStereo() == Bond::STEREOZ) { nZs++; } } } REQUIRE(nDbl == 3); REQUIRE(nEs == 2); REQUIRE(nZs == 1); delete m; delete m2; } TEST_CASE("Testing Issue 188: bad CIP rankings") { ROMol *m; std::string smi; unsigned int cip1, cip2, cip3; smi = "OC[C@H](C=C)C"; m = SmilesToMol(smi); UINT_VECT ranks; ranks.resize(m->getNumAtoms()); Chirality::assignAtomCIPRanks(*m, ranks); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->hasProp(common_properties::_CIPRank)); m->getAtomWithIdx(1)->getProp(common_properties::_CIPRank, cip1); REQUIRE(m->getAtomWithIdx(3)->hasProp(common_properties::_CIPRank)); m->getAtomWithIdx(3)->getProp(common_properties::_CIPRank, cip2); REQUIRE(cip1 > cip2); REQUIRE(m->getAtomWithIdx(5)->hasProp(common_properties::_CIPRank)); m->getAtomWithIdx(5)->getProp(common_properties::_CIPRank, cip3); REQUIRE(cip1 > cip3); REQUIRE(cip2 > cip3); delete m; smi = "CC(=N\\N)/C=N/N"; m = SmilesToMol(smi); REQUIRE(m); ranks.resize(m->getNumAtoms()); Chirality::assignAtomCIPRanks(*m, ranks); REQUIRE(m->getAtomWithIdx(0)->hasProp(common_properties::_CIPRank)); m->getAtomWithIdx(0)->getProp(common_properties::_CIPRank, cip1); REQUIRE(m->getAtomWithIdx(1)->hasProp(common_properties::_CIPRank)); m->getAtomWithIdx(1)->getProp(common_properties::_CIPRank, cip2); REQUIRE(cip2 > cip1); REQUIRE(m->getAtomWithIdx(4)->hasProp(common_properties::_CIPRank)); m->getAtomWithIdx(4)->getProp(common_properties::_CIPRank, cip3); REQUIRE(cip3 > cip1); REQUIRE(cip2 > cip3); delete m; } TEST_CASE("Testing Issue 189: BondDirs not getting properly cleared") { ROMol *m; std::string smi, refSmi; int count; smi = "C(=S)/N=c(/n1C)scc1"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getBondWithIdx(2)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(2)->getStereo() == Bond::STEREOZ); count = 0; for (unsigned int i = 0; i < m->getNumBonds(); i++) { if (m->getBondWithIdx(i)->getBondDir() != Bond::NONE) { count++; } } REQUIRE(count == 2); refSmi = MolToSmiles(*m, 1); count = 0; for (unsigned int i = 0; i < m->getNumBonds(); i++) { if (m->getBondWithIdx(i)->getBondDir() != Bond::NONE) { count++; } } REQUIRE(count == 2); delete m; m = SmilesToMol(refSmi); REQUIRE(m); count = 0; for (unsigned int i = 0; i < m->getNumBonds(); i++) { if (m->getBondWithIdx(i)->getBondDir() != Bond::NONE) { count++; } } REQUIRE(count == 2); smi = MolToSmiles(*m, 1); count = 0; for (unsigned int i = 0; i < m->getNumBonds(); i++) { if (m->getBondWithIdx(i)->getBondDir() != Bond::NONE) { count++; } } REQUIRE(count == 2); REQUIRE(smi == refSmi); delete m; } TEST_CASE("Testing Issue 190: BondDirs incorrectly cleared") { ROMol *m; std::string smi, refSmi; int count; smi = "O\\N=C\\NC(\\C)=N/OC"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getBondWithIdx(1)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(1)->getStereo() == Bond::STEREOE); REQUIRE(m->getBondWithIdx(5)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(5)->getStereo() == Bond::STEREOZ); refSmi = MolToSmiles(*m, 1); count = 0; for (unsigned int i = 0; i < m->getNumBonds(); i++) { if (m->getBondWithIdx(i)->getBondDir() != Bond::NONE) { count++; } } REQUIRE(count == 4); delete m; m = SmilesToMol(refSmi); REQUIRE(m); count = 0; for (unsigned int i = 0; i < m->getNumBonds(); i++) { if (m->getBondWithIdx(i)->getBondDir() != Bond::NONE) { count++; } } REQUIRE(count == 4); smi = MolToSmiles(*m, 1); count = 0; for (unsigned int i = 0; i < m->getNumBonds(); i++) { if (m->getBondWithIdx(i)->getBondDir() != Bond::NONE) { count++; } } REQUIRE(count == 4); REQUIRE(smi == refSmi); delete m; smi = "O\\N=C\\CC(\\C)=N/OC"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getBondWithIdx(1)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(1)->getStereo() == Bond::STEREOE); REQUIRE(m->getBondWithIdx(5)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(5)->getStereo() == Bond::STEREOZ); refSmi = MolToSmiles(*m, 1); count = 0; for (unsigned int i = 0; i < m->getNumBonds(); i++) { if (m->getBondWithIdx(i)->getBondDir() != Bond::NONE) { count++; } } REQUIRE(count == 4); delete m; m = SmilesToMol(refSmi); REQUIRE(m); count = 0; for (unsigned int i = 0; i < m->getNumBonds(); i++) { if (m->getBondWithIdx(i)->getBondDir() != Bond::NONE) { count++; } } REQUIRE(count == 4); smi = MolToSmiles(*m, 1); count = 0; for (unsigned int i = 0; i < m->getNumBonds(); i++) { if (m->getBondWithIdx(i)->getBondDir() != Bond::NONE) { count++; } } REQUIRE(count == 4); REQUIRE(smi == refSmi); delete m; smi = "O\\N=C\\C(=O)C(\\C)=N/OC"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getBondWithIdx(1)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(1)->getStereo() == Bond::STEREOE); REQUIRE(m->getBondWithIdx(6)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(6)->getStereo() == Bond::STEREOZ); refSmi = MolToSmiles(*m, 1); count = 0; for (unsigned int i = 0; i < m->getNumBonds(); i++) { if (m->getBondWithIdx(i)->getBondDir() != Bond::NONE) { count++; } } REQUIRE(count == 4); delete m; m = SmilesToMol(refSmi); REQUIRE(m); count = 0; for (unsigned int i = 0; i < m->getNumBonds(); i++) { if (m->getBondWithIdx(i)->getBondDir() != Bond::NONE) { count++; } } REQUIRE(count == 4); smi = MolToSmiles(*m, 1); count = 0; for (unsigned int i = 0; i < m->getNumBonds(); i++) { if (m->getBondWithIdx(i)->getBondDir() != Bond::NONE) { count++; } } REQUIRE(count == 4); REQUIRE(smi == refSmi); delete m; } TEST_CASE("Testing shortest path code") { { constexpr const char *smi = "CC(OC1C(CCCC3)C3C(CCCC2)C2C1OC(C)=O)=O"; ROMol *m = SmilesToMol(smi); INT_LIST path = MolOps::getShortestPath(*m, 1, 20); REQUIRE(path.size() == 7); auto pi = path.begin(); REQUIRE((*pi) == 1); ++pi; REQUIRE((*pi) == 2); ++pi; REQUIRE((*pi) == 3); ++pi; REQUIRE((*pi) == 16); ++pi; REQUIRE((*pi) == 17); ++pi; REQUIRE((*pi) == 18); ++pi; REQUIRE((*pi) == 20); ++pi; delete m; } { // issue 2219400 constexpr const char *smi = "CC.C"; ROMol *m = SmilesToMol(smi); INT_LIST path = MolOps::getShortestPath(*m, 0, 1); std::cerr << "path: " << path.size() << std::endl; REQUIRE(path.size() == 2); auto pi = path.begin(); REQUIRE((*pi) == 0); ++pi; REQUIRE((*pi) == 1); path = MolOps::getShortestPath(*m, 1, 2); REQUIRE(path.size() == 0); path = MolOps::getShortestPath(*m, 0, 2); REQUIRE(path.size() == 0); delete m; } // fused ring test { constexpr const char *smi = "[H]c1nc2c(C(=O)N([H])C2([H])Cl)c([H])c1Cl"; ROMol *m = SmilesToMol(smi); INT_LIST path = MolOps::getShortestPath(*m, 8, 11); REQUIRE(path.size() == 7); auto pi = path.begin(); REQUIRE((*pi) == 8); ++pi; REQUIRE((*pi) == 7); ++pi; REQUIRE((*pi) == 2); ++pi; ++pi; // two equally long routes here ++pi; // two equally long routes here REQUIRE((*pi) == 10); ++pi; REQUIRE((*pi) == 11); ++pi; delete m; } } TEST_CASE("Testing Issue 210") { ROMol *m, *m2; constexpr const char *smi = "C1CC1"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 3); REQUIRE(m->getRingInfo()->isInitialized()); m2 = MolOps::addHs(*m); REQUIRE(m2->getNumAtoms() == 9); REQUIRE(m2->getRingInfo()->isInitialized()); delete m; delete m2; } TEST_CASE("Testing Issue 211") { ROMol *m; constexpr const char *smi = "P(c1ccccc1)(c1ccccc1)c1ccccc1"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 19); const Atom *at = m->getAtomWithIdx(0); REQUIRE(at->getHybridization() == Atom::SP3); delete m; } TEST_CASE("Testing Issue 212") { ROMol *m, *m2; constexpr const char *smi = "C"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 1); auto *conf = new Conformer(1); m->addConformer(conf); conf->setAtomPos(0, RDGeom::Point3D(0, 0, 0)); m2 = MolOps::addHs(*m, false, true); REQUIRE(m2->getNumAtoms() == 5); REQUIRE_NOTHROW(MolToMolBlock(*m2)); delete m; delete m2; } TEST_CASE("Testing AddHs with coordinates") { ROMol *m, *m2; RDGeom::Point3D v; double bondLength = PeriodicTable::getTable()->getRb0(1) + PeriodicTable::getTable()->getRb0(6); double tetDist = 2. * sin((109.471 / 2.) * M_PI / 180) * bondLength; double sp2Dist = 2. * sin(60. * M_PI / 180) * bondLength; std::string smi; smi = "C"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 1); auto *conf = new Conformer(1); m->addConformer(conf); conf->setAtomPos(0, RDGeom::Point3D(0, 0, 0)); m2 = MolOps::addHs(*m, false, true); const Conformer *conf2 = &(m2->getConformer()); REQUIRE(m2->getNumAtoms() == 5); REQUIRE( feq((conf2->getAtomPos(0) - conf2->getAtomPos(1)).length(), bondLength)); REQUIRE( feq((conf2->getAtomPos(0) - conf2->getAtomPos(2)).length(), bondLength)); REQUIRE( feq((conf2->getAtomPos(0) - conf2->getAtomPos(3)).length(), bondLength)); REQUIRE( feq((conf2->getAtomPos(0) - conf2->getAtomPos(4)).length(), bondLength)); REQUIRE(feq((conf2->getAtomPos(1) - conf2->getAtomPos(2)).length(), tetDist)); REQUIRE(feq((conf2->getAtomPos(1) - conf2->getAtomPos(3)).length(), tetDist)); REQUIRE(feq((conf2->getAtomPos(1) - conf2->getAtomPos(4)).length(), tetDist)); REQUIRE(feq((conf2->getAtomPos(2) - conf2->getAtomPos(3)).length(), tetDist)); REQUIRE(feq((conf2->getAtomPos(2) - conf2->getAtomPos(4)).length(), tetDist)); REQUIRE(feq((conf2->getAtomPos(3) - conf2->getAtomPos(4)).length(), tetDist)); delete m; delete m2; smi = "CC"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 2); conf = new Conformer(2); m->addConformer(conf); conf->setAtomPos(0, RDGeom::Point3D(0, 0, 0)); conf->setAtomPos(1, RDGeom::Point3D(1.54, 0, 0)); m2 = MolOps::addHs(*m, false, true); conf2 = &(m2->getConformer()); REQUIRE(m2->getNumAtoms() == 8); REQUIRE( feq((conf2->getAtomPos(0) - conf2->getAtomPos(2)).length(), bondLength)); REQUIRE( feq((conf2->getAtomPos(0) - conf2->getAtomPos(3)).length(), bondLength)); REQUIRE( feq((conf2->getAtomPos(0) - conf2->getAtomPos(4)).length(), bondLength)); REQUIRE(feq((conf2->getAtomPos(2) - conf2->getAtomPos(3)).length(), tetDist)); REQUIRE(feq((conf2->getAtomPos(2) - conf2->getAtomPos(4)).length(), tetDist)); REQUIRE(feq((conf2->getAtomPos(3) - conf2->getAtomPos(4)).length(), tetDist)); REQUIRE( feq((conf2->getAtomPos(1) - conf2->getAtomPos(5)).length(), bondLength)); REQUIRE( feq((conf2->getAtomPos(1) - conf2->getAtomPos(6)).length(), bondLength)); REQUIRE( feq((conf2->getAtomPos(1) - conf2->getAtomPos(7)).length(), bondLength)); REQUIRE(feq((conf2->getAtomPos(5) - conf2->getAtomPos(6)).length(), tetDist)); REQUIRE(feq((conf2->getAtomPos(5) - conf2->getAtomPos(7)).length(), tetDist)); REQUIRE(feq((conf2->getAtomPos(6) - conf2->getAtomPos(7)).length(), tetDist)); delete m; delete m2; smi = "C=C"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 2); conf = new Conformer(2); m->addConformer(conf); conf->setAtomPos(0, RDGeom::Point3D(0, 0, 0)); conf->setAtomPos(1, RDGeom::Point3D(1.3, 0, 0)); m2 = MolOps::addHs(*m, false, true); conf2 = &(m2->getConformer()); REQUIRE(m2->getNumAtoms() == 6); REQUIRE( feq((conf2->getAtomPos(0) - conf2->getAtomPos(2)).length(), bondLength)); REQUIRE( feq((conf2->getAtomPos(0) - conf2->getAtomPos(3)).length(), bondLength)); REQUIRE( feq((conf2->getAtomPos(1) - conf2->getAtomPos(4)).length(), bondLength)); REQUIRE( feq((conf2->getAtomPos(1) - conf2->getAtomPos(5)).length(), bondLength)); REQUIRE(feq((conf2->getAtomPos(2) - conf2->getAtomPos(3)).length(), sp2Dist)); REQUIRE(feq((conf2->getAtomPos(4) - conf2->getAtomPos(5)).length(), sp2Dist)); delete m; delete m2; { // make sure Hs are on the xy plane if conformer is 2D auto m = "C=C"_smiles; REQUIRE(m.get()); REQUIRE(m->getNumAtoms() == 2); conf = new Conformer(2); m->addConformer(conf); conf->setAtomPos(0, RDGeom::Point3D(0, 0, 0)); conf->setAtomPos(1, RDGeom::Point3D(1.3, 0, 0)); conf->set3D(false); MolOps::addHs(*m, false, true); conf = &(m->getConformer()); REQUIRE(m->getNumAtoms() == 6); REQUIRE(feq(conf->getAtomPos(2).z, 0.0)); REQUIRE(feq(conf->getAtomPos(3).z, 0.0)); REQUIRE(feq(conf->getAtomPos(4).z, 0.0)); REQUIRE(feq(conf->getAtomPos(5).z, 0.0)); } { // make sure NHs are on the same plane as the double bond auto m = "NC=C"_smiles; REQUIRE(m.get()); REQUIRE(m->getNumAtoms() == 3); unsigned int nh2Idx = 0; unsigned int chIdx = 1; unsigned int ch2Idx = 2; conf = new Conformer(3); m->addConformer(conf); conf->setAtomPos(nh2Idx, RDGeom::Point3D(1.759236, 0.825542, 1.347849)); conf->setAtomPos(chIdx, RDGeom::Point3D(0.817392, 0.181048, 2.180373)); conf->setAtomPos(ch2Idx, RDGeom::Point3D(-0.070943, 0.888262, 2.875625)); MolOps::addHs(*m, false, true); conf = &(m->getConformer()); REQUIRE(m->getNumAtoms() == 8); std::vector nh2HIdxs{3, 4}; unsigned int chHIdx = 5; std::vector ch2HIdxs{6, 7}; RDGeom::Point3D nccNormal = (conf->getAtomPos(nh2Idx) - conf->getAtomPos(chIdx)) .crossProduct((conf->getAtomPos(ch2Idx) - conf->getAtomPos(chIdx))); nccNormal.normalize(); RDGeom::Point3D hnhNormal = (conf->getAtomPos(nh2HIdxs[0]) - conf->getAtomPos(nh2Idx)) .crossProduct(conf->getAtomPos(nh2HIdxs[1]) - conf->getAtomPos(nh2Idx)); hnhNormal.normalize(); RDGeom::Point3D hchNormal = (conf->getAtomPos(ch2HIdxs[0]) - conf->getAtomPos(ch2Idx)) .crossProduct(conf->getAtomPos(nh2HIdxs[1]) - conf->getAtomPos(ch2Idx)); hchNormal.normalize(); RDGeom::Point3D hcnNormal = (conf->getAtomPos(chHIdx) - conf->getAtomPos(chIdx)) .crossProduct((conf->getAtomPos(nh2Idx) - conf->getAtomPos(chIdx))); hcnNormal.normalize(); REQUIRE(feq(fabs(nccNormal.dotProduct(hnhNormal)), 1.0)); REQUIRE(feq(fabs(nccNormal.dotProduct(hchNormal)), 1.0)); REQUIRE(feq(fabs(nccNormal.dotProduct(hcnNormal)), 1.0)); } smi = "C#C"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 2); conf = new Conformer(2); m->addConformer(conf); conf->setAtomPos(0, RDGeom::Point3D(0, 0, 0)); conf->setAtomPos(1, RDGeom::Point3D(1.2, 0, 0)); m2 = MolOps::addHs(*m, false, true); conf2 = &(m2->getConformer()); REQUIRE(m2->getNumAtoms() == 4); REQUIRE( feq((conf2->getAtomPos(0) - conf2->getAtomPos(2)).length(), bondLength)); REQUIRE( feq((conf2->getAtomPos(1) - conf2->getAtomPos(3)).length(), bondLength)); REQUIRE(feq((conf2->getAtomPos(0) - conf2->getAtomPos(3)).length(), bondLength + 1.2)); REQUIRE(feq((conf2->getAtomPos(1) - conf2->getAtomPos(2)).length(), bondLength + 1.2)); delete m; delete m2; } TEST_CASE("Sanitization special cases") { ROMol *m; std::string smi, pathName; smi = "CN(=O)=O"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE(m->getAtomWithIdx(1)->getFormalCharge() == 1); delete m; smi = "C[N+](=O)[O-]"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE(m->getAtomWithIdx(1)->getFormalCharge() == 1); delete m; smi = "Cl(=O)(=O)(=O)[O-]"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 5); REQUIRE(m->getAtomWithIdx(0)->getFormalCharge() == 3); delete m; smi = "Cl(=O)(=O)(=O)O"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 5); REQUIRE(m->getAtomWithIdx(0)->getFormalCharge() == 3); delete m; smi = "Br(=O)(=O)(=O)[O-]"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 5); REQUIRE(m->getAtomWithIdx(0)->getFormalCharge() == 3); delete m; smi = "Br(=O)(=O)(=O)O"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 5); REQUIRE(m->getAtomWithIdx(0)->getFormalCharge() == 3); delete m; smi = "I(=O)(=O)(=O)[O-]"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 5); REQUIRE(m->getAtomWithIdx(0)->getFormalCharge() == 3); delete m; smi = "I(=O)(=O)(=O)O"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 5); REQUIRE(m->getAtomWithIdx(0)->getFormalCharge() == 3); delete m; pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; m = MolFileToMol(pathName + "perchlorate1.mol"); REQUIRE(m); REQUIRE(m->getNumAtoms() == 51); REQUIRE(m->getAtomWithIdx(7)->getFormalCharge() == 3); delete m; smi = "CN=N#N"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE(m->getAtomWithIdx(1)->getFormalCharge() == 0); REQUIRE(m->getAtomWithIdx(2)->getFormalCharge() == 1); REQUIRE(m->getAtomWithIdx(3)->getFormalCharge() == -1); REQUIRE(m->getBondBetweenAtoms(2, 3)->getBondType() == Bond::DOUBLE); delete m; smi = "N#N=NC"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE(m->getAtomWithIdx(2)->getFormalCharge() == 0); REQUIRE(m->getAtomWithIdx(1)->getFormalCharge() == 1); REQUIRE(m->getAtomWithIdx(0)->getFormalCharge() == -1); REQUIRE(m->getBondBetweenAtoms(0, 1)->getBondType() == Bond::DOUBLE); delete m; smi = "N#N"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 2); REQUIRE(m->getAtomWithIdx(1)->getFormalCharge() == 0); REQUIRE(m->getAtomWithIdx(0)->getFormalCharge() == 0); REQUIRE(m->getBondBetweenAtoms(0, 1)->getBondType() == Bond::TRIPLE); delete m; smi = "Cl(=O)(=O)O"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE(m->getAtomWithIdx(0)->getFormalCharge() == 2); delete m; smi = "Cl(=O)(=O)[O-]"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE(m->getAtomWithIdx(0)->getFormalCharge() == 2); delete m; smi = "Br(=O)(=O)O"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE(m->getAtomWithIdx(0)->getFormalCharge() == 2); delete m; smi = "Br(=O)(=O)[O-]"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE(m->getAtomWithIdx(0)->getFormalCharge() == 2); delete m; smi = "I(=O)(=O)O"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE(m->getAtomWithIdx(0)->getFormalCharge() == 2); delete m; smi = "I(=O)(=O)[O-]"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE(m->getAtomWithIdx(0)->getFormalCharge() == 2); delete m; smi = "Cl(=O)O"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 3); REQUIRE(m->getAtomWithIdx(0)->getFormalCharge() == 1); delete m; smi = "Cl(=O)[O-]"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 3); REQUIRE(m->getAtomWithIdx(0)->getFormalCharge() == 1); delete m; smi = "Br(=O)O"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 3); REQUIRE(m->getAtomWithIdx(0)->getFormalCharge() == 1); delete m; smi = "Br(=O)[O-]"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 3); REQUIRE(m->getAtomWithIdx(0)->getFormalCharge() == 1); delete m; smi = "I(=O)O"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 3); REQUIRE(m->getAtomWithIdx(0)->getFormalCharge() == 1); delete m; smi = "I(=O)[O-]"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 3); REQUIRE(m->getAtomWithIdx(0)->getFormalCharge() == 1); delete m; smi = "I(O)(O)(O)(O)O"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 6); REQUIRE(m->getAtomWithIdx(0)->getFormalCharge() == 0); delete m; smi = "I(O)(O)O"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE(m->getAtomWithIdx(0)->getFormalCharge() == 0); delete m; smi = "I(=O)(O)(O)(O)"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 5); REQUIRE(m->getAtomWithIdx(0)->getFormalCharge() == 1); delete m; smi = "CC(=O)O[IH2](O)OC(C)=O"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 10); REQUIRE(m->getAtomWithIdx(4)->getFormalCharge() == 0); delete m; } TEST_CASE("Testing Add Confomers") { constexpr const char *smi = "CC"; ROMol *m = SmilesToMol(smi); unsigned int i; for (i = 0; i < 5; i++) { auto *conf = new Conformer(2); conf->setAtomPos(0, RDGeom::Point3D(0.0, 0.0, 0.0)); conf->setAtomPos(1, RDGeom::Point3D(1.5, 0.0, 0.0)); m->addConformer(conf, true); } REQUIRE(m->getNumConformers() == 5); ROMol *m2 = MolOps::addHs(*m, false, true); REQUIRE(m2->getNumConformers() == 5); // const ROMol::CONF_SPTR_LIST &confs = m2->getConformers(); ROMol::ConstConformerIterator ci; i = 0; for (ci = m2->beginConformers(); ci != m2->endConformers(); ci++) { REQUIRE((*ci)->getNumAtoms() == 8); REQUIRE((*ci)->getId() == i); const ROMol *mn = &((*ci)->getOwningMol()); REQUIRE(mn->getNumAtoms() == 8); i++; } // std::cout << m2->getNumAtoms() << " " << m2->getNumConformers() << "\n"; delete m; delete m2; } TEST_CASE("testIssue252") { // lets check if we can sanitize C60 constexpr const char *smi = "C12=C3C4=C5C6=C1C7=C8C9=C1C%10=C%11C(=C29)C3=C2C3=C4C4=C5C5=C9C6=C7C6=" "C7C8=C1C1=C8C%10=C%10C%11=C2C2=C3C3=C4C4=C5C5=C%11C%12=C(C6=C95)C7=C1C1=" "C%12C5=C%11C4=C3C3=C5C(=C81)C%10=C23"; ROMol *mol = SmilesToMol(smi); for (ROMol::BondIterator it = mol->beginBonds(); it != mol->endBonds(); it++) { REQUIRE((*it)->getIsAromatic()); REQUIRE((*it)->getBondType() == Bond::AROMATIC); } std::string asmi = MolToSmiles(*mol); // check if we can do it in the aromatic form ROMol *nmol = SmilesToMol(asmi); for (ROMol::BondIterator it = nmol->beginBonds(); it != nmol->endBonds(); it++) { REQUIRE((*it)->getIsAromatic()); REQUIRE((*it)->getBondType() == Bond::AROMATIC); } std::string nsmi = MolToSmiles(*nmol); delete mol; delete nmol; // This is a check for Issue253 REQUIRE(asmi == nsmi); } TEST_CASE("Issue 276") { constexpr const char *smi = "CP1(C)=CC=CN=C1C"; ROMol *mol = SmilesToMol(smi); REQUIRE(mol); // as of this writing, I'm not 100% sure what the right answer is here, // but the hybridization definitely should *not* be SP2: REQUIRE(mol->getAtomWithIdx(1)->getHybridization() > Atom::SP2); delete mol; } TEST_CASE("testHsAndAromaticity: Additional Aromaticity Cases") { std::string smi; ROMol *mol; smi = "[CH]1-[CH]-[CH]-[CH]-[CH]-[CH]-1"; mol = SmilesToMol(smi); REQUIRE(mol); REQUIRE(mol->getAtomWithIdx(0)->getHybridization() == Atom::SP3); REQUIRE(mol->getAtomWithIdx(0)->getValence(Atom::ValenceType::IMPLICIT) == 0); REQUIRE(mol->getAtomWithIdx(0)->getNumImplicitHs() == 0); REQUIRE(mol->getAtomWithIdx(0)->getNumRadicalElectrons() == 1); REQUIRE(!mol->getAtomWithIdx(0)->getIsAromatic()); REQUIRE(!mol->getBondBetweenAtoms(0, 1)->getIsAromatic()); delete mol; smi = "C1=CC(=C)C(=C)C=C1"; mol = SmilesToMol(smi); REQUIRE(mol); REQUIRE(mol->getAtomWithIdx(0)->getHybridization() == Atom::SP2); REQUIRE(mol->getAtomWithIdx(2)->getHybridization() == Atom::SP2); REQUIRE(mol->getAtomWithIdx(0)->getIsAromatic()); REQUIRE(mol->getBondBetweenAtoms(0, 1)->getIsAromatic()); delete mol; } TEST_CASE( "Testing sf.net issue 1694023 (bad chiral smiles after removing Hs)") { ROMol *m; std::string smi; smi = "[C@@]([H])(F)(Cl)Br"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE(m->getAtomWithIdx(0)->getChiralTag() == Atom::CHI_TETRAHEDRAL_CCW); smi = "[C@@](F)([H])(Cl)Br"; delete m; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE(m->getAtomWithIdx(0)->getChiralTag() == Atom::CHI_TETRAHEDRAL_CW); smi = "[C@@](F)(Cl)([H])Br"; delete m; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE(m->getAtomWithIdx(0)->getChiralTag() == Atom::CHI_TETRAHEDRAL_CCW); smi = "[C@@](F)(Cl)(Br)[H]"; delete m; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE(m->getAtomWithIdx(0)->getChiralTag() == Atom::CHI_TETRAHEDRAL_CW); smi = "[H][C@@](F)(Cl)Br"; delete m; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE(m->getAtomWithIdx(0)->getChiralTag() == Atom::CHI_TETRAHEDRAL_CCW); smi = "F[C@@]([H])(Cl)Br"; delete m; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE(m->getAtomWithIdx(1)->getChiralTag() == Atom::CHI_TETRAHEDRAL_CW); smi = "F[C@@](Cl)([H])Br"; delete m; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE(m->getAtomWithIdx(1)->getChiralTag() == Atom::CHI_TETRAHEDRAL_CCW); smi = "F[C@@](Cl)(Br)[H]"; delete m; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE(m->getAtomWithIdx(1)->getChiralTag() == Atom::CHI_TETRAHEDRAL_CW); smi = "C1CO[C@@H]1Cl"; delete m; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 5); REQUIRE(m->getAtomWithIdx(3)->getChiralTag() == Atom::CHI_TETRAHEDRAL_CCW); smi = "C1CO[C@]1([H])Cl"; delete m; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 5); REQUIRE(m->getAtomWithIdx(3)->getChiralTag() == Atom::CHI_TETRAHEDRAL_CCW); smi = "C1CO[C@@]1(Cl)[H]"; delete m; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 5); REQUIRE(m->getAtomWithIdx(3)->getChiralTag() == Atom::CHI_TETRAHEDRAL_CCW); delete m; } TEST_CASE( "Testing sf.net issue 1719053 (Ring stereochemistry incorrectly removed)") { ROMol *m; std::string smi; smi = "C[C@@H]1CCCCC1"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getChiralTag() == Atom::CHI_UNSPECIFIED); delete m; smi = "C[C@@H]1CC[C@@H](C)CC1"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getChiralTag() != Atom::CHI_UNSPECIFIED); REQUIRE(m->getAtomWithIdx(4)->getChiralTag() != Atom::CHI_UNSPECIFIED); delete m; smi = "C[C@@H]1C(C)CCCC1C"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getChiralTag() == Atom::CHI_UNSPECIFIED); delete m; smi = "C[C@@H]1[C@H](C)CCC[C@H]1C"; m = SmilesToMol(smi); REQUIRE(m); // this is a truly symmetric case, so the stereochem should be removed: REQUIRE(m->getAtomWithIdx(1)->getChiralTag() == Atom::CHI_UNSPECIFIED); REQUIRE(m->getAtomWithIdx(2)->getChiralTag() != Atom::CHI_UNSPECIFIED); REQUIRE(m->getAtomWithIdx(7)->getChiralTag() != Atom::CHI_UNSPECIFIED); delete m; smi = "C[C@@H]1C=C[C@@H](C)C=C1"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getChiralTag() != Atom::CHI_UNSPECIFIED); REQUIRE(m->getAtomWithIdx(4)->getChiralTag() != Atom::CHI_UNSPECIFIED); delete m; smi = "C[N@@]1C=C[C@@H](C)C=C1"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getChiralTag() == Atom::CHI_UNSPECIFIED); REQUIRE(m->getAtomWithIdx(4)->getChiralTag() == Atom::CHI_UNSPECIFIED); // N in rings that aren't 3 rings is not chiral delete m; smi = "C[N@@]1CC[C@@H](C)CC1"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getChiralTag() == Atom::CHI_UNSPECIFIED); REQUIRE(m->getAtomWithIdx(4)->getChiralTag() == Atom::CHI_UNSPECIFIED); delete m; } TEST_CASE("Testing sf.net issue 1811276 (kekulization failing)") { ROMol *m; std::string smi; smi = "[O-]N1C=C[N+](=O)C=C1"; m = SmilesToMol(smi); REQUIRE(m); smi = MolToSmiles(*m); REQUIRE(smi == "O=[n+]1ccn([O-])cc1"); delete m; smi = "o1ccc(=O)cc1"; m = SmilesToMol(smi); REQUIRE(m); smi = MolToSmiles(*m); REQUIRE(smi == "O=c1ccocc1"); delete m; smi = "O=[n+]1ccocc1"; m = SmilesToMol(smi); REQUIRE(m); smi = MolToSmiles(*m); REQUIRE(smi == "O=[n+]1ccocc1"); delete m; smi = "O=[n+]1ccn([O-])cc1"; m = SmilesToMol(smi); REQUIRE(m); smi = MolToSmiles(*m); REQUIRE(smi == "O=[n+]1ccn([O-])cc1"); delete m; smi = "O=n1ccccc1"; m = SmilesToMol(smi); REQUIRE(m); smi = MolToSmiles(*m); REQUIRE(smi == "[O-][n+]1ccccc1"); delete m; } TEST_CASE("Testing sf.net issue 1836576 (sanitization crash)") { // the original form of the test runs foul of the rules for explicit // valence on B: constexpr const char *smi = "[BH]123[BH]45[BH]167[BH]289[BH]312[BH]838[BH]966[Co]74479%10%11%12[CH]" "633[BH]811[CH]345[BH]21[BH]1234[BH]75[BH]911[BH]226[BH]%1011[BH]227[BH]" "633[BH]44[BH]322[CH]%1145[CH]%12271"; std::unique_ptr m{SmilesToMol(smi, false, false)}; REQUIRE(m); unsigned int opThatFailed = MolOps::SANITIZE_NONE; REQUIRE_THROWS_AS(MolOps::sanitizeMol(*m, opThatFailed), MolSanitizeException); REQUIRE(opThatFailed == MolOps::SANITIZE_PROPERTIES); } TEST_CASE("Testing impact of removeHs on chirality") { ROMol *m, *m2; std::string smi, code; smi = "F[C@]([H])(Cl)Br"; m = SmilesToMol(smi, false, false); REQUIRE(m); MolOps::assignStereochemistry(*m, true, true); REQUIRE(m->getAtomWithIdx(1)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(1)->getProp(common_properties::_CIPCode, code); REQUIRE(code == "R"); m2 = MolOps::removeHs(*m); REQUIRE(m2); MolOps::assignStereochemistry(*m2, true, true); REQUIRE(m2->getAtomWithIdx(1)->hasProp(common_properties::_CIPCode)); m2->getAtomWithIdx(1)->getProp(common_properties::_CIPCode, code); REQUIRE(code == "R"); delete m; delete m2; smi = "F[C@H](Cl)Br"; m = SmilesToMol(smi, false, false); REQUIRE(m); MolOps::assignStereochemistry(*m, true, true); REQUIRE(m->getAtomWithIdx(1)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(1)->getProp(common_properties::_CIPCode, code); REQUIRE(code == "R"); m2 = MolOps::removeHs(*m); REQUIRE(m2); MolOps::assignStereochemistry(*m2, true, true); REQUIRE(m2->getAtomWithIdx(1)->hasProp(common_properties::_CIPCode)); m2->getAtomWithIdx(1)->getProp(common_properties::_CIPCode, code); REQUIRE(code == "R"); delete m; delete m2; smi = "[C@]([H])(Cl)(F)Br"; m = SmilesToMol(smi, false, false); REQUIRE(m); MolOps::assignStereochemistry(*m, true, true); REQUIRE(m->getAtomWithIdx(0)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(0)->getProp(common_properties::_CIPCode, code); REQUIRE(code == "R"); m2 = MolOps::removeHs(*m); REQUIRE(m2); MolOps::assignStereochemistry(*m2, true, true); REQUIRE(m2->getAtomWithIdx(0)->hasProp(common_properties::_CIPCode)); m2->getAtomWithIdx(0)->getProp(common_properties::_CIPCode, code); REQUIRE(code == "R"); delete m; delete m2; smi = "[C@H](Cl)(F)Br"; m = SmilesToMol(smi, false, false); REQUIRE(m); MolOps::assignStereochemistry(*m, true, true); REQUIRE(m->getAtomWithIdx(0)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(0)->getProp(common_properties::_CIPCode, code); REQUIRE(code == "R"); m2 = MolOps::removeHs(*m); REQUIRE(m2); MolOps::assignStereochemistry(*m2, true, true); REQUIRE(m2->getAtomWithIdx(0)->hasProp(common_properties::_CIPCode)); m2->getAtomWithIdx(0)->getProp(common_properties::_CIPCode, code); REQUIRE(code == "R"); delete m; delete m2; smi = "[H]1.F[C@]1(Cl)Br"; m = SmilesToMol(smi, false, false); REQUIRE(m); MolOps::assignStereochemistry(*m, true, true); REQUIRE(m->getAtomWithIdx(2)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(2)->getProp(common_properties::_CIPCode, code); REQUIRE(code == "R"); m2 = MolOps::removeHs(*m); REQUIRE(m2); MolOps::assignStereochemistry(*m2, true, true); REQUIRE(m2->getAtomWithIdx(1)->hasProp(common_properties::_CIPCode)); m2->getAtomWithIdx(1)->getProp(common_properties::_CIPCode, code); REQUIRE(code == "R"); delete m; delete m2; smi = "F[C@]1(Cl)Br.[H]1"; m = SmilesToMol(smi, false, false); REQUIRE(m); MolOps::assignStereochemistry(*m, true, true); REQUIRE(m->getAtomWithIdx(1)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(1)->getProp(common_properties::_CIPCode, code); REQUIRE(code == "R"); m2 = MolOps::removeHs(*m); REQUIRE(m2); MolOps::assignStereochemistry(*m2, true, true); REQUIRE(m2->getAtomWithIdx(1)->hasProp(common_properties::_CIPCode)); m2->getAtomWithIdx(1)->getProp(common_properties::_CIPCode, code); REQUIRE(code == "R"); delete m; delete m2; smi = "[H]1.[C@]1(Cl)(F)Br"; m = SmilesToMol(smi, false, false); REQUIRE(m); MolOps::assignStereochemistry(*m, true, true); REQUIRE(m->getAtomWithIdx(1)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(1)->getProp(common_properties::_CIPCode, code); REQUIRE(code == "R"); m2 = MolOps::removeHs(*m); REQUIRE(m2); MolOps::assignStereochemistry(*m2, true, true); REQUIRE(m2->getAtomWithIdx(0)->hasProp(common_properties::_CIPCode)); m2->getAtomWithIdx(0)->getProp(common_properties::_CIPCode, code); REQUIRE(code == "R"); delete m; delete m2; smi = "[C@]1(Cl)(F)Br.[H]1"; m = SmilesToMol(smi, false, false); REQUIRE(m); MolOps::assignStereochemistry(*m, true, true); REQUIRE(m->getAtomWithIdx(0)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(0)->getProp(common_properties::_CIPCode, code); REQUIRE(code == "R"); m2 = MolOps::removeHs(*m); REQUIRE(m2); MolOps::assignStereochemistry(*m2, true, true); REQUIRE(m2->getAtomWithIdx(0)->hasProp(common_properties::_CIPCode)); m2->getAtomWithIdx(0)->getProp(common_properties::_CIPCode, code); REQUIRE(code == "R"); delete m; delete m2; smi = "Cl1.F2.Br3.[C@H]123"; m = SmilesToMol(smi, false, false); REQUIRE(m); MolOps::assignStereochemistry(*m, true, true); REQUIRE(m->getAtomWithIdx(3)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(3)->getProp(common_properties::_CIPCode, code); REQUIRE(code == "R"); delete m; smi = "[C@H]123.Cl1.F2.Br3"; m = SmilesToMol(smi, false, false); REQUIRE(m); MolOps::assignStereochemistry(*m, true, true); REQUIRE(m->getAtomWithIdx(0)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(0)->getProp(common_properties::_CIPCode, code); REQUIRE(code == "R"); delete m; smi = "F2.Cl1.Br3.[C@H]123"; m = SmilesToMol(smi, false, false); REQUIRE(m); MolOps::assignStereochemistry(*m, true, true); REQUIRE(m->getAtomWithIdx(3)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(3)->getProp(common_properties::_CIPCode, code); REQUIRE(code == "R"); delete m; smi = "Cl2.F1.Br3.[C@H]213"; m = SmilesToMol(smi, false, false); REQUIRE(m); MolOps::assignStereochemistry(*m, true, true); REQUIRE(m->getAtomWithIdx(3)->hasProp(common_properties::_CIPCode)); m->getAtomWithIdx(3)->getProp(common_properties::_CIPCode, code); REQUIRE(code == "R"); delete m; } TEST_CASE("Testing SFIssue1894348 (impact of removeHs on bond stereo atoms)") { RWMol *m, *m2; std::string smi; smi = "Cl/C([H])=C/Cl"; m = SmilesToMol(smi, false, false); REQUIRE(m); MolOps::sanitizeMol(*m); MolOps::assignStereochemistry(*m); REQUIRE(m->getBondWithIdx(2)->getStereoAtoms().size() == 2); REQUIRE(m->getBondWithIdx(2)->getStereoAtoms()[0] == 0); REQUIRE(m->getBondWithIdx(2)->getStereoAtoms()[1] == 4); // we remove an H attached to a stereo bond m2 = static_cast(MolOps::removeHs(static_cast(*m))); REQUIRE(m->getBondWithIdx(2)->getStereoAtoms().size() == 2); REQUIRE(m->getBondWithIdx(2)->getStereoAtoms()[0] == 0); REQUIRE(m->getBondWithIdx(2)->getStereoAtoms()[1] == 4); // at first the stereoatoms are gone: REQUIRE(m2->getBondWithIdx(2)->getStereoAtoms().size() == 0); // but they can be re-perceived: MolOps::assignStereochemistry(*m2, true, true); REQUIRE(m2->getBondWithIdx(1)->getStereoAtoms().size() == 2); REQUIRE(m2->getBondWithIdx(1)->getStereoAtoms()[0] == 0); REQUIRE(m2->getBondWithIdx(1)->getStereoAtoms()[1] == 3); delete m; delete m2; smi = "Cl/C([H])=C/Cl"; m = SmilesToMol(smi, false, false); REQUIRE(m); MolOps::sanitizeMol(*m); REQUIRE(m->getBondWithIdx(2)->getStereoAtoms().size() == 0); m2 = static_cast(MolOps::removeHs(static_cast(*m))); // if we don't assign stereocodes in the original we shouldn't have them here: REQUIRE(m2->getBondWithIdx(1)->getStereoAtoms().size() == 0); delete m; delete m2; } TEST_CASE("Testing some aromaticity edge cases") { RWMol *m; std::string smi; // ------ // this was sf.net bug 1934360 smi = "C1=C=NC=N1"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(!m->getAtomWithIdx(0)->getIsAromatic()); REQUIRE(!m->getBondWithIdx(0)->getIsAromatic()); delete m; smi = "C1=CNC=N1"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(0)->getIsAromatic()); REQUIRE(m->getBondWithIdx(0)->getIsAromatic()); delete m; smi = "[C+]1=CNC=N1"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(!m->getAtomWithIdx(1)->getIsAromatic()); REQUIRE(!m->getBondWithIdx(1)->getIsAromatic()); delete m; // ------ // this was sf.net bug 1940646 smi = "C1#CC=C1"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(!m->getAtomWithIdx(0)->getIsAromatic()); REQUIRE(!m->getBondWithIdx(0)->getIsAromatic()); delete m; smi = "C1#CC=CC=C1"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(0)->getIsAromatic()); REQUIRE(m->getBondWithIdx(0)->getIsAromatic()); delete m; // ------ // this was sf.net bug 2091839 smi = "c1cccc[c]1"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(0)->getIsAromatic()); REQUIRE(m->getBondWithIdx(0)->getIsAromatic()); delete m; smi = "C1=CC=CC=[C]1"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(0)->getIsAromatic()); REQUIRE(m->getBondWithIdx(0)->getIsAromatic()); delete m; smi = "c1cccc[n+]1"; // disqualified because N has a radical m = SmilesToMol(smi); REQUIRE(m); REQUIRE(!m->getAtomWithIdx(0)->getIsAromatic()); REQUIRE(!m->getBondWithIdx(0)->getIsAromatic()); delete m; smi = "[N]1C=CC=C1"; // disqualified because N has a radical m = SmilesToMol(smi); REQUIRE(m); REQUIRE(!m->getAtomWithIdx(0)->getIsAromatic()); REQUIRE(m->getAtomWithIdx(0)->getNumRadicalElectrons() == 1); REQUIRE(!m->getBondWithIdx(0)->getIsAromatic()); delete m; smi = "[n]1ccccc1"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(0)->getIsAromatic()); REQUIRE(m->getAtomWithIdx(0)->getNumRadicalElectrons() == 0); REQUIRE(m->getBondWithIdx(0)->getIsAromatic()); delete m; smi = "[H]n1cccc1"; m = SmilesToMol(smi, 0, 0); REQUIRE(m); MolOps::sanitizeMol(*m); REQUIRE(m->getAtomWithIdx(1)->getIsAromatic()); REQUIRE(m->getAtomWithIdx(1)->getNumRadicalElectrons() == 0); REQUIRE(!m->getAtomWithIdx(0)->getIsAromatic()); REQUIRE(m->getAtomWithIdx(0)->getNumRadicalElectrons() == 0); delete m; smi = "[H]"; m = SmilesToMol(smi, 0, 0); REQUIRE(m); MolOps::sanitizeMol(*m); REQUIRE(m->getAtomWithIdx(0)->getNumRadicalElectrons() == 1); delete m; // ------ // this was sf.net bug 2787221. smi = "O=C1C(=O)C=C1"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getIsAromatic()); REQUIRE(m->getBondBetweenAtoms(1, 2)->getIsAromatic()); delete m; } TEST_CASE("Testing sf.net issue 1942657") { const auto smiles = GENERATE("C[C](C)(C)(C)C", "C[CH](C)(C)C", "C[C](=C)(C)C", "C[Si](=C)(=C)=C"); CAPTURE(smiles); CHECK_THROWS_AS(SmilesToMol(smiles), MolSanitizeException); } TEST_CASE("Testing sf.net issue 198608") { RWMol *m; constexpr const char *smi = "C1CC1CC1CC1"; m = SmilesToMol(smi); REQUIRE(m->getRingInfo()->minAtomRingSize(0) == 3); REQUIRE(m->getRingInfo()->minAtomRingSize(3) == 0); REQUIRE(m->getRingInfo()->minBondRingSize(0) == 3); REQUIRE(m->getRingInfo()->minBondRingSize(3) == 0); delete m; } TEST_CASE("Testing hybridization assignment") { { RWMol *m; constexpr const char *smi = "CCC"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getHybridization() == Atom::SP3); REQUIRE(m->getAtomWithIdx(0)->getHybridization() == Atom::SP3); delete m; } { RWMol *m; constexpr const char *smi = "CNC"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getHybridization() == Atom::SP3); REQUIRE(m->getAtomWithIdx(0)->getHybridization() == Atom::SP3); delete m; } { RWMol *m; constexpr const char *smi = "COC"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getHybridization() == Atom::SP3); REQUIRE(m->getAtomWithIdx(0)->getHybridization() == Atom::SP3); delete m; } { RWMol *m; constexpr const char *smi = "C[C-2]C"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getHybridization() == Atom::SP3); REQUIRE(m->getAtomWithIdx(0)->getHybridization() == Atom::SP3); delete m; } { RWMol *m; constexpr const char *smi = "C[CH-]C"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getHybridization() == Atom::SP3); REQUIRE(m->getAtomWithIdx(0)->getHybridization() == Atom::SP3); delete m; } { RWMol *m; constexpr const char *smi = "C[CH]C"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getHybridization() == Atom::SP3); REQUIRE(m->getAtomWithIdx(0)->getHybridization() == Atom::SP3); delete m; } { RWMol *m; constexpr const char *smi = "C[C]C"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getHybridization() == Atom::SP3); REQUIRE(m->getAtomWithIdx(0)->getHybridization() == Atom::SP3); delete m; } { RWMol *m; constexpr const char *smi = "C[C-]C"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getHybridization() == Atom::SP3); REQUIRE(m->getAtomWithIdx(0)->getHybridization() == Atom::SP3); delete m; } { RWMol *m; constexpr const char *smi = "C[CH+]C"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getHybridization() == Atom::SP2); REQUIRE(m->getAtomWithIdx(0)->getHybridization() == Atom::SP3); delete m; } { RWMol *m; constexpr const char *smi = "CC=C"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getHybridization() == Atom::SP2); REQUIRE(m->getAtomWithIdx(0)->getHybridization() == Atom::SP3); delete m; } { RWMol *m; constexpr const char *smi = "CN=C"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getHybridization() == Atom::SP2); REQUIRE(m->getAtomWithIdx(0)->getHybridization() == Atom::SP3); delete m; } { RWMol *m; constexpr const char *smi = "C[C-]=C"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getHybridization() == Atom::SP2); REQUIRE(m->getAtomWithIdx(0)->getHybridization() == Atom::SP3); delete m; } { RWMol *m; constexpr const char *smi = "C[C]=C"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getHybridization() == Atom::SP2); REQUIRE(m->getAtomWithIdx(0)->getHybridization() == Atom::SP3); delete m; } { RWMol *m; constexpr const char *smi = "C[N+]=C"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getHybridization() == Atom::SP2); REQUIRE(m->getAtomWithIdx(0)->getHybridization() == Atom::SP3); delete m; } { RWMol *m; constexpr const char *smi = "C#C"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getHybridization() == Atom::SP); delete m; } { RWMol *m; constexpr const char *smi = "C#[C-]"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getHybridization() == Atom::SP); delete m; } { RWMol *m; constexpr const char *smi = "C#[C]"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getHybridization() == Atom::SP); delete m; } { RWMol *m; constexpr const char *smi = "C[O]"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getHybridization() == Atom::SP3); delete m; } { RWMol *m; constexpr const char *smi = "C[N-]"; m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getHybridization() == Atom::SP3); delete m; } } TEST_CASE("Testing sf.net issue 2196817: handling of aromatic dummies") { { std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; RWMol *m = MolFileToMol(pathName + "dummyArom.mol"); REQUIRE(m); REQUIRE(m->getAtomWithIdx(0)->getAtomicNum() == 0); REQUIRE(m->getAtomWithIdx(0)->getIsAromatic() == true); MolOps::Kekulize(*m); REQUIRE(m->getBondBetweenAtoms(0, 1)->getBondType() == Bond::SINGLE); REQUIRE(m->getBondBetweenAtoms(0, 4)->getBondType() == Bond::SINGLE); delete m; } { constexpr const char *smi = "*1cncc1"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(0)->getAtomicNum() == 0); REQUIRE(m->getBondWithIdx(0)->getIsAromatic() == true); delete m; } { constexpr const char *smi = "*1C=NC=C1"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(0)->getAtomicNum() == 0); REQUIRE(m->getBondWithIdx(0)->getIsAromatic() == true); delete m; } { // case where all must be ignored: constexpr const char *smi = "c1*ccc1-c1*ccc1-c1*ccc1"; RWMol *m = SmilesToMol(smi); REQUIRE(m); delete m; } { std::string smi = "c1*[nH]*c1"; RWMol *m = SmilesToMol(smi); REQUIRE(m); smi = MolToSmiles(*m); REQUIRE(smi == "*1cc*[nH]1"); delete m; smi = "c1***c1"; m = SmilesToMol(smi); REQUIRE(m); smi = MolToSmiles(*m); REQUIRE(smi == "*1:*cc*:1"); delete m; smi = "c:1:*:*:*:*1"; m = SmilesToMol(smi); REQUIRE(m); smi = MolToSmiles(*m); REQUIRE(smi == "*1:*:*c*:1"); delete m; // we don't kekulize rings that are all dummies, this was github #1478 smi = "*:1:*:*:*:*:1"; m = SmilesToMol(smi); REQUIRE(m); smi = MolToSmiles(*m); REQUIRE(smi == "*1:*:*:*:*:1"); delete m; } { std::string smi = "c1*[nH]cc1-c1*[nH]cc1-c1*ccc1"; RWMol *m = SmilesToMol(smi); REQUIRE(m); delete m; smi = "c1*[nH]cc1-c1*ccc1-c1*[nH]cc1"; m = SmilesToMol(smi); REQUIRE(m); delete m; smi = "c1*ccc1-c1*[nH]cc1-c1*[nH1]cc1"; m = SmilesToMol(smi); REQUIRE(m); delete m; } { constexpr const char *smi = "c1*[nH]cc1-c1*[nH]cc1-c1*[nH]cc1"; RWMol *m = SmilesToMol(smi); REQUIRE(m); delete m; } { constexpr const char *smi = "c1ccc(C2CC(n4cc*c4=C2))cc1"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getBondBetweenAtoms(0, 1)->getIsAromatic()); REQUIRE(m->getBondBetweenAtoms(0, 14)->getIsAromatic()); REQUIRE(m->getBondBetweenAtoms(0, 1)->getBondType() == Bond::AROMATIC); REQUIRE(m->getBondBetweenAtoms(0, 14)->getBondType() == Bond::AROMATIC); MolOps::Kekulize(*m); REQUIRE(!m->getBondBetweenAtoms(0, 1)->getIsAromatic()); REQUIRE(!m->getBondBetweenAtoms(0, 14)->getIsAromatic()); REQUIRE((m->getBondBetweenAtoms(0, 1)->getBondType() == Bond::DOUBLE || m->getBondBetweenAtoms(0, 14)->getBondType() == Bond::DOUBLE)); MolOps::setAromaticity(*m); REQUIRE(m->getBondBetweenAtoms(0, 1)->getIsAromatic()); REQUIRE(m->getBondBetweenAtoms(0, 14)->getIsAromatic()); REQUIRE(m->getBondBetweenAtoms(0, 1)->getBondType() == Bond::AROMATIC); REQUIRE(m->getBondBetweenAtoms(0, 14)->getBondType() == Bond::AROMATIC); delete m; } { for (auto smi : {"c12ccccc1**CC2", "c12ccccc1C**C2", "*12ccccc1CCCC2", "*12ccccc1***C2"}) { std::unique_ptr m(SmilesToMol(smi)); REQUIRE(m); for (size_t i = 0; i < 6; ++i) { size_t j = i < 5 ? i + 1 : 0; REQUIRE(m->getBondBetweenAtoms(i, j)->getIsAromatic()); REQUIRE(m->getBondBetweenAtoms(i, j)->getBondType() == Bond::AROMATIC); } for (size_t i = 5; i < 10; ++i) { size_t j = i < 9 ? i + 1 : 0; REQUIRE(!m->getBondBetweenAtoms(i, j)->getIsAromatic()); REQUIRE(m->getBondBetweenAtoms(i, j)->getBondType() == Bond::SINGLE); } MolOps::Kekulize(*m); for (size_t i = 0; i < 6; ++i) { size_t j = i < 5 ? i + 1 : 0; REQUIRE(!m->getBondBetweenAtoms(i, j)->getIsAromatic()); REQUIRE((m->getBondBetweenAtoms(i, j)->getBondType() == Bond::SINGLE || m->getBondBetweenAtoms(i, j)->getBondType() == Bond::DOUBLE)); } for (size_t i = 5; i < 10; ++i) { size_t j = i < 9 ? i + 1 : 0; REQUIRE(!m->getBondBetweenAtoms(i, j)->getIsAromatic()); REQUIRE(m->getBondBetweenAtoms(i, j)->getBondType() == Bond::SINGLE); } } } { for (auto smi : {"c12ccccc1****2", "c12ccccc1***2", "C12=CC=CC=C1****2", "c1ccccc1N1****1", "c1ccccc1C1****1", "c1ccccc1*1****1", "c1ccccc1*1*=**=*1"}) { std::unique_ptr m(SmilesToMol(smi)); REQUIRE(m); for (unsigned int i = 6; i < m->getNumAtoms(); ++i) { REQUIRE(!m->getAtomWithIdx(i)->getIsAromatic()); } } } { for (auto smi : {"C1CC*2ccccc21", "C1C**2ccccc21"}) { std::unique_ptr m(SmilesToMol(smi)); REQUIRE(m); for (unsigned int i = 0; i < 3; ++i) { REQUIRE(!m->getAtomWithIdx(i)->getIsAromatic()); } for (unsigned int i = 3; i < m->getNumAtoms(); ++i) { REQUIRE(m->getAtomWithIdx(i)->getIsAromatic()); } } } { auto m = "*12ccccc1CCC2"_smiles; for (unsigned int i = 0; i < 6; ++i) { REQUIRE(m->getAtomWithIdx(i)->getIsAromatic()); } for (unsigned int i = 6; i < m->getNumAtoms(); ++i) { REQUIRE(!m->getAtomWithIdx(i)->getIsAromatic()); } } { for (auto smi : {"N1****1", "C1=C*2C=CC=C*2C=C1", "N1*C=CC=C1", "C1=CC2=CC=C3C=CC4=CC=C5C=CN1*1*2*3*4*51"}) { std::unique_ptr m(SmilesToMol(smi)); REQUIRE(m); for (const auto b : m->bonds()) { REQUIRE(!b->getIsAromatic()); } } } { ROMOL_SPTR m = "C1=CC2=CC=C3C=CC4=CC=C5C=CN1*1*2*3*4N51"_smiles; for (const auto a : m->atoms()) { if (a->getIdx() == 16 || a->getIdx() == 17) { REQUIRE(!a->getIsAromatic()); } else { REQUIRE(a->getIsAromatic()); } } unsigned int nNonAromaticBonds = 0; for (const auto b : m->bonds()) { if (!b->getIsAromatic()) { ++nNonAromaticBonds; } } REQUIRE(nNonAromaticBonds == 5); } { for (auto smi : {"*1C=CC=C1", "N1*=**=*1", "C1=CC2=CC=C3C=CC4=CC=C5C=CN1N1N2N3N4N51", "C1=CC2=CC=C3C=CC4=CC=C5C=CN1*1N2*3N4N51"}) { std::unique_ptr m(SmilesToMol(smi)); REQUIRE(m); for (const auto b : m->bonds()) { REQUIRE(b->getIsAromatic()); } } } } TEST_CASE("Testing sf.net issue 2208994 : kekulization error") { { constexpr const char *smi = "Cn1ccc(=O)n1C"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getIsAromatic() == true); REQUIRE(m->getBondWithIdx(1)->getIsAromatic() == true); delete m; } { constexpr const char *smi = "c:1:c:c:c:c:c1"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getIsAromatic() == true); REQUIRE(m->getBondWithIdx(1)->getIsAromatic() == true); delete m; } { constexpr const char *smi = "c1:c:c:c:c:c:1"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getIsAromatic() == true); REQUIRE(m->getBondWithIdx(1)->getIsAromatic() == true); delete m; } } TEST_CASE("Testing sf.net issue 2313979: aromaticity assignment hangs") { std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; SDMolSupplier suppl(pathName + "Issue2313979.sdf", false); while (!suppl.atEnd()) { ROMol *m = suppl.next(); REQUIRE(m); std::string nm; m->getProp(common_properties::_Name, nm); BOOST_LOG(rdInfoLog) << " Doing molecule: " << nm << std::endl; BOOST_LOG(rdInfoLog) << " This should finish in a few seconds. >>>" << std::endl; MolOps::sanitizeMol(*(RWMol *)m); delete m; BOOST_LOG(rdInfoLog) << " <<< Done." << std::endl; } } TEST_CASE("Testing sf.net issue 2316677 : canonicalization error") { std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; RWMol *m = MolFileToMol(pathName + "Issue2316677.mol"); REQUIRE(m); std::string smi = MolToSmiles(*m, true); std::cerr << "smi: " << smi << std::endl; REQUIRE(smi == R"SMI(Cc1ccc(S(=O)(=O)\N=C2/CC(=N\C(C)(C)C)/C2=N\C(C)(C)C)cc1)SMI"); delete m; } TEST_CASE( "Testing sf.net issue 2830244: make sure that non-ring aromatic atoms generate errors") { std::unique_ptr m{SmilesToMol("c-C", 0, false)}; SECTION("Kekulization should throw") { CHECK_THROWS_AS(MolOps::Kekulize(*m), MolSanitizeException); } SECTION("Sanitization should throw") { unsigned int opThatFailed = MolOps::SANITIZE_NONE; REQUIRE_THROWS_AS(MolOps::sanitizeMol(*m, opThatFailed), MolSanitizeException); REQUIRE(opThatFailed == MolOps::SANITIZE_KEKULIZE); } } TEST_CASE( "Testing sf.net issue 2951221 : hydrogens added with bad coordinates") { { std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; ROMol *m = MolFileToMol(pathName + "Issue2951221.1.mol"); REQUIRE(m); REQUIRE(m->getConformer().is3D()); ROMol *m2 = MolOps::addHs(*m, false, true); REQUIRE(m2); delete m; REQUIRE(m2->getNumAtoms(false) == 12); RDGeom::Point3D coords[4]; coords[0] = m2->getConformer().getAtomPos(2); coords[1] = m2->getConformer().getAtomPos(0); coords[2] = m2->getConformer().getAtomPos(1); coords[3] = m2->getConformer().getAtomPos(9); double dot = (coords[3] - coords[0]) .dotProduct( (coords[1] - coords[0]).crossProduct(coords[2] - coords[0])); REQUIRE(dot > 1.0); delete m2; } { std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; ROMol *m = MolFileToMol(pathName + "Issue2951221.2.mol"); REQUIRE(m); ROMol *m2 = MolOps::addHs(*m, false, true); REQUIRE(m2); delete m; REQUIRE(m2->getNumAtoms(false) == 5); MolOps::assignChiralTypesFrom3D(*m2); MolOps::assignStereochemistry(*m2, true, true); REQUIRE(m2->getAtomWithIdx(1)->hasProp(common_properties::_CIPCode)); std::string cip; m2->getAtomWithIdx(1)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "S"); delete m2; } { std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; ROMol *m = MolFileToMol(pathName + "Issue2951221.3.mol"); REQUIRE(m); ROMol *m2 = MolOps::addHs(*m, false, true); REQUIRE(m2); delete m; REQUIRE(m2->getNumAtoms(false) == 5); MolOps::assignChiralTypesFrom3D(*m2); MolOps::assignStereochemistry(*m2, true, true); REQUIRE(m2->getAtomWithIdx(1)->hasProp(common_properties::_CIPCode)); std::string cip; m2->getAtomWithIdx(1)->getProp(common_properties::_CIPCode, cip); REQUIRE(cip == "R"); delete m2; } } TEST_CASE( "Testing sf.net issue 2952255 : bad assignment of radicals to early elements") { { constexpr const char *smi = "[C](C)(C)C"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(0)->getNumRadicalElectrons() == 1); delete m; } { constexpr const char *smi = "[C](C)C"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(0)->getNumRadicalElectrons() == 2); delete m; } { constexpr const char *smi = "[CH](C)C"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(0)->getNumRadicalElectrons() == 1); delete m; } { constexpr const char *smi = "[CH+](C)C"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(0)->getNumRadicalElectrons() == 0); delete m; } { constexpr const char *smi = "[C-](C)C"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(0)->getNumRadicalElectrons() == 1); delete m; } { constexpr const char *smi = "C(C)(C)(C)C"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(0)->getNumRadicalElectrons() == 0); delete m; } { constexpr const char *smi = "[N](C)C"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(0)->getNumRadicalElectrons() == 1); delete m; } { constexpr const char *smi = "[N+](C)(C)C"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(0)->getNumRadicalElectrons() == 1); delete m; } { constexpr const char *smi = "[Cl]"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(0)->getNumRadicalElectrons() == 1); delete m; } { constexpr const char *smi = "[Cl-]"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(0)->getNumRadicalElectrons() == 0); delete m; } { constexpr const char *smi = "[Cl]C"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(0)->getNumRadicalElectrons() == 0); delete m; } { constexpr const char *smi = "[Na]"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(0)->getNumRadicalElectrons() == 1); delete m; } { constexpr const char *smi = "[Na+]"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(0)->getNumRadicalElectrons() == 0); delete m; } { constexpr const char *smi = "[Na]C"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(0)->getNumRadicalElectrons() == 0); delete m; } { constexpr const char *smi = "[Mg+]C"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(0)->getNumRadicalElectrons() == 0); delete m; } { constexpr const char *smi = "[Mg]C"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(0)->getNumRadicalElectrons() == 1); delete m; } { constexpr const char *smi = "[Mg+]"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(0)->getNumRadicalElectrons() == 1); delete m; } { constexpr const char *smi = "[Mg+2]"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(0)->getNumRadicalElectrons() == 0); delete m; } } TEST_CASE("Testing sf.net issue 3185548 : problems with SSSR code") { { std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; BOOST_LOG(rdInfoLog) << " Starting file read 1" << std::endl; RWMol *m = MolFileToMol(pathName + "Issue3185548.mol"); BOOST_LOG(rdInfoLog) << " finished" << std::endl; REQUIRE(m); delete m; } { std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; BOOST_LOG(rdInfoLog) << " Starting file read 2" << std::endl; RWMol *m = MolFileToMol(pathName + "Issue3185548.2.mol"); BOOST_LOG(rdInfoLog) << " finished" << std::endl; REQUIRE(m); m->getRingInfo()->reset(); unsigned int nsssr; VECT_INT_VECT sssrs; nsssr = MolOps::findSSSR(*m, sssrs); REQUIRE(nsssr == 40); nsssr = MolOps::symmetrizeSSSR(*m, sssrs); REQUIRE(nsssr == 56); delete m; } } TEST_CASE("Testing Issue 3349243") { { constexpr const char *smi = "c1cccc[n+]1"; RWMol *m = SmilesToMol(smi); REQUIRE(m); MolOps::Kekulize(*m); // just finishing is good REQUIRE(m->getBondWithIdx(0)->getBondType() != Bond::AROMATIC); delete m; } } TEST_CASE("Testing fast find rings") { { constexpr const char *smi = "CCC"; RWMol *m = SmilesToMol(smi, 0, 0); REQUIRE(m); MolOps::fastFindRings(*m); REQUIRE(m->getRingInfo()); REQUIRE(m->getRingInfo()->isInitialized()); REQUIRE(m->getRingInfo()->numRings() == 0); delete m; } { constexpr const char *smi = "C1CC1"; RWMol *m = SmilesToMol(smi, 0, 0); REQUIRE(m); MolOps::fastFindRings(*m); REQUIRE(m->getRingInfo()); REQUIRE(m->getRingInfo()->isInitialized()); REQUIRE(m->getRingInfo()->numRings() == 1); delete m; } { constexpr const char *smi = "CC1CC1"; RWMol *m = SmilesToMol(smi, 0, 0); REQUIRE(m); MolOps::fastFindRings(*m); REQUIRE(m->getRingInfo()); REQUIRE(m->getRingInfo()->isInitialized()); REQUIRE(m->getRingInfo()->numRings() == 1); delete m; } { constexpr const char *smi = "C1CC1.C1CC1"; RWMol *m = SmilesToMol(smi, 0, 0); REQUIRE(m); MolOps::fastFindRings(*m); REQUIRE(m->getRingInfo()); REQUIRE(m->getRingInfo()->isInitialized()); REQUIRE(m->getRingInfo()->numRings() == 2); delete m; } { constexpr const char *smi = "C1C(C)C1"; RWMol *m = SmilesToMol(smi, 0, 0); REQUIRE(m); MolOps::fastFindRings(*m); REQUIRE(m->getRingInfo()); REQUIRE(m->getRingInfo()->isInitialized()); REQUIRE(m->getRingInfo()->numRings() == 1); delete m; } { constexpr const char *smi = "c1c(=O)nc2[nH]cnn2c1O"; RWMol *m = SmilesToMol(smi, 0, 0); REQUIRE(m); MolOps::fastFindRings(*m); REQUIRE(m->getRingInfo()); REQUIRE(m->getRingInfo()->isInitialized()); REQUIRE(m->getRingInfo()->numRings() == 2); delete m; } } TEST_CASE("Testing Issue 3487473") { { constexpr const char *smi = "C*C"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getHybridization() == Atom::UNSPECIFIED); delete m; } { constexpr const char *smi = "C*C"; RWMol *m = SmartsToMol(smi); REQUIRE(m); m->updatePropertyCache(false); MolOps::setConjugation(*m); REQUIRE(m->getAtomWithIdx(1)->getHybridization() == Atom::UNSPECIFIED); delete m; } } TEST_CASE("Testing Issue 3480481") { { std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; RWMol *m = MolFileToMol(pathName + "Issue3480481.mol"); REQUIRE(m); REQUIRE(m->getAtomWithIdx(0)->getIsAromatic() == true); REQUIRE(m->getAtomWithIdx(0)->getValence(Atom::ValenceType::EXPLICIT) == 4); REQUIRE(m->getAtomWithIdx(0)->getValence(Atom::ValenceType::IMPLICIT) == 0); REQUIRE(m->getAtomWithIdx(0)->getFormalCharge() == -1); delete m; } } void aamatchtest(std::string smi1, std::string smi2, bool shouldMatch, int idx1, int idx2) { RWMol *m1 = SmilesToMol(smi1); RWMol *m2 = SmilesToMol(smi2); REQUIRE(m1); REQUIRE(m2); REQUIRE(m2->getAtomWithIdx(idx2)->Match(m1->getAtomWithIdx(idx1)) == shouldMatch); delete m1; delete m2; } TEST_CASE("Testing Atom-Atom matching behavior") { /* Here's what we're testing: | Molecule | Query | Match | | CCO | CCO | Yes | | CC[O-] | CCO | Yes | | CCO | CC[O-] | No | | CC[O-] | CC[O-] | Yes | | CC[O-] | CC[OH] | Yes | | CCOC | CC[OH] | Yes | | CCOC | CCO | Yes | | CCC | CCC | Yes | | CC[14C] | CCC | Yes | | CCC | CC[14C] | No | | CC[14C] | CC[14C] | Yes | | OCO | C | Yes | | OCO | [CH2] | Yes | | OCO | [CH3] | Yes | | O[CH2]O | C | Yes | | O[CH2]O | [CH2] | Yes | | OCO | [CH] | Yes | This is a large superset of issue 3495370 */ // note that in some cases here we have to be fairly careful about Hs on // the query to make sure that it doesn't have radicals (radical handling // added to fix github #165 aamatchtest("CCO", "O", true, 2, 0); aamatchtest("CC[O-]", "O", true, 2, 0); aamatchtest("CCO", "[OH-]", false, 2, 0); aamatchtest("CC[O-]", "[OH-]", true, 2, 0); aamatchtest("CC[O-]", "[OH2]", true, 2, 0); aamatchtest("CCOC", "[OH2]", true, 2, 0); aamatchtest("CCOC", "O", true, 2, 0); aamatchtest("CCC", "C", true, 2, 0); aamatchtest("CC[14C]", "C", true, 2, 0); aamatchtest("CCC", "[14CH4]", false, 2, 0); aamatchtest("CC[14C]", "[14CH4]", true, 2, 0); aamatchtest("CC[13C]", "[14CH4]", false, 2, 0); aamatchtest("OCO", "C", true, 1, 0); aamatchtest("OCO", "[CH4]", true, 1, 0); aamatchtest("O[CH2]O", "C", true, 1, 0); aamatchtest("O[CH2]O", "[CH4]", true, 1, 0); aamatchtest("OCO", "[CH2]", false, 1, 0); // doesn't match due to radical count aamatchtest("O[CH2]O", "[CH2]", false, 1, 0); // doesn't match due to radical count aamatchtest("O[CH]O", "[CH3]", true, 1, 0); aamatchtest("O[CH]O", "[CH2]", false, 1, 0); // doesn't match due to radical count aamatchtest("CC", "*", false, 1, 0); aamatchtest("C*", "*", true, 1, 0); aamatchtest("C[1*]", "*", true, 1, 0); aamatchtest("C[1*]", "[1*]", true, 1, 0); aamatchtest("C*", "[1*]", true, 1, 0); aamatchtest("C[2*]", "[1*]", false, 1, 0); } TEST_CASE("Testing Issue 3525076") { { std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; RWMol *m = MolFileToMol(pathName + "Issue3525076.sdf"); REQUIRE(m); REQUIRE(m->getBondWithIdx(18)->getIsAromatic() == false); REQUIRE(m->getBondWithIdx(18)->getBondType() == Bond::SINGLE); MolOps::Kekulize(*m); REQUIRE(m->getBondWithIdx(18)->getIsAromatic() == false); REQUIRE(m->getBondWithIdx(18)->getBondType() == Bond::SINGLE); MolOps::sanitizeMol(*m); REQUIRE(m->getBondWithIdx(18)->getIsAromatic() == false); REQUIRE(m->getBondWithIdx(18)->getBondType() == Bond::SINGLE); delete m; } } TEST_CASE("Testing canonicalization basics") { // these are all cases that were problematic at one time or another during // the canonicalization rewrite. { constexpr const char *smi = "FC1C(=C/Cl)\\C1"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getBondBetweenAtoms(2, 3)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondBetweenAtoms(2, 3)->getStereo() == Bond::STEREOZ); std::string csmi1 = MolToSmiles(*m, true); RWMol *m2 = SmilesToMol(csmi1); REQUIRE(m2); MatchVectType mv; REQUIRE(SubstructMatch(*m, *m2, mv)); std::map mmap; for (auto &mvit : mv) { mmap[mvit.second] = mvit.first; } REQUIRE(m2->getBondBetweenAtoms(mmap[2], mmap[3])->getBondType() == Bond::DOUBLE); REQUIRE(m2->getBondBetweenAtoms(mmap[2], mmap[3])->getStereo() == Bond::STEREOZ); std::string csmi2 = MolToSmiles(*m2, true); REQUIRE(csmi1 == csmi2); delete m; delete m2; } { constexpr const char *smi = "CC1(C)C2CCC1(C)C(=O)/C2=C\\C(N=N/c1ccccc1)=N/Nc1ccccc1"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getBondBetweenAtoms(10, 11)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondBetweenAtoms(10, 11)->getStereo() == Bond::STEREOZ); REQUIRE(m->getBondBetweenAtoms(12, 21)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondBetweenAtoms(12, 21)->getStereo() == Bond::STEREOE); REQUIRE(m->getBondBetweenAtoms(13, 14)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondBetweenAtoms(13, 14)->getStereo() == Bond::STEREONONE); std::string csmi1 = MolToSmiles(*m, true); RWMol *m2 = SmilesToMol(csmi1); REQUIRE(m2); MatchVectType mv; REQUIRE(SubstructMatch(*m, *m2, mv)); std::map mmap; for (auto &mvit : mv) { mmap[mvit.second] = mvit.first; } REQUIRE(m2->getBondBetweenAtoms(mmap[10], mmap[11])->getBondType() == Bond::DOUBLE); REQUIRE(m2->getBondBetweenAtoms(mmap[10], mmap[11])->getStereo() == Bond::STEREOZ); REQUIRE(m2->getBondBetweenAtoms(mmap[12], mmap[21])->getBondType() == Bond::DOUBLE); REQUIRE(m2->getBondBetweenAtoms(mmap[12], mmap[21])->getStereo() == Bond::STEREOE); REQUIRE(m2->getBondBetweenAtoms(mmap[13], mmap[14])->getBondType() == Bond::DOUBLE); REQUIRE(m2->getBondBetweenAtoms(mmap[13], mmap[14])->getStereo() == Bond::STEREONONE); std::string csmi2 = MolToSmiles(*m2, true); REQUIRE(csmi1 == csmi2); delete m; delete m2; } { constexpr const char *smi = "COc1ccc(OC)c2[nH]c(=O)cc(C)c21"; RWMol *m = SmilesToMol(smi); REQUIRE(m); std::string csmi1 = MolToSmiles(*m, true); delete m; m = SmilesToMol(csmi1); REQUIRE(m); std::string csmi2 = MolToSmiles(*m, true); REQUIRE(csmi1 == csmi2); delete m; } { constexpr const char *smi = "COc1cc(C)c(C(=O)[O-])cc1OC"; RWMol *m = SmilesToMol(smi); REQUIRE(m); std::string csmi1 = MolToSmiles(*m, true); delete m; m = SmilesToMol(csmi1); REQUIRE(m); std::string csmi2 = MolToSmiles(*m, true); REQUIRE(csmi1 == csmi2); delete m; } { constexpr const char *smi = "COc1ccc(C(=O)OC(c2ccc(OC)cc2)C(C)O)cc1"; RWMol *m = SmilesToMol(smi); REQUIRE(m); std::string csmi1 = MolToSmiles(*m, true); delete m; m = SmilesToMol(csmi1); REQUIRE(m); std::string csmi2 = MolToSmiles(*m, true); REQUIRE(csmi1 == csmi2); delete m; } { constexpr const char *smi = "CC(C)C1CCC(C)=CC1=NNC(N)=O"; RWMol *m = SmilesToMol(smi); REQUIRE(m); std::string csmi1 = MolToSmiles(*m, true); delete m; m = SmilesToMol(csmi1); REQUIRE(m); std::string csmi2 = MolToSmiles(*m, true); REQUIRE(csmi1 == csmi2); delete m; } { constexpr const char *smi = "COCCNC(=O)c1ccccc1N1C(=O)C2(C)c3[nH]c4ccccc4c3CCN2C1=O"; RWMol *m = SmilesToMol(smi); REQUIRE(m); std::string csmi1 = MolToSmiles(*m, true); delete m; m = SmilesToMol(csmi1); REQUIRE(m); std::string csmi2 = MolToSmiles(*m, true); REQUIRE(csmi1 == csmi2); delete m; } { constexpr const char *smi = "Cc1c(Br)cc(Br)cc1C(F)(F)F"; RWMol *m = SmilesToMol(smi); REQUIRE(m); std::string csmi1 = MolToSmiles(*m, true); delete m; m = SmilesToMol(csmi1); REQUIRE(m); std::string csmi2 = MolToSmiles(*m, true); REQUIRE(csmi1 == csmi2); delete m; } { std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; RWMol *m = MolFileToMol(pathName + "zinc4235774a.mol"); REQUIRE(m); REQUIRE(m->getBondBetweenAtoms(1, 2)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondBetweenAtoms(1, 2)->getStereo() == Bond::STEREOZ); REQUIRE(m->getBondBetweenAtoms(7, 8)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondBetweenAtoms(7, 8)->getStereo() == Bond::STEREOZ); std::string smi = MolToSmiles(*m, true); RWMol *m2 = SmilesToMol(smi); MatchVectType mv; REQUIRE(SubstructMatch(*m, *m2, mv)); std::map mmap; for (auto &mvit : mv) { mmap[mvit.second] = mvit.first; } REQUIRE(m2->getBondBetweenAtoms(mmap[1], mmap[2])->getBondType() == Bond::DOUBLE); REQUIRE(m2->getBondBetweenAtoms(mmap[7], mmap[8])->getBondType() == Bond::DOUBLE); REQUIRE(m2->getBondBetweenAtoms(mmap[1], mmap[2])->getStereo() == Bond::STEREOZ); REQUIRE(m2->getBondBetweenAtoms(mmap[7], mmap[8])->getStereo() == Bond::STEREOZ); delete m; delete m2; } { std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; RWMol *m = MolFileToMol(pathName + "zinc4235774.mol"); REQUIRE(m); REQUIRE(m->getBondBetweenAtoms(4, 5)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondBetweenAtoms(4, 5)->getStereo() == Bond::STEREOZ); REQUIRE(m->getBondBetweenAtoms(14, 15)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondBetweenAtoms(14, 15)->getStereo() == Bond::STEREOZ); std::string smi = MolToSmiles(*m, true); RWMol *m2 = SmilesToMol(smi); MatchVectType mv; REQUIRE(SubstructMatch(*m, *m2, mv)); std::map mmap; for (auto &mvit : mv) { mmap[mvit.second] = mvit.first; } REQUIRE(m2->getBondBetweenAtoms(mmap[4], mmap[5])->getBondType() == Bond::DOUBLE); REQUIRE(m2->getBondBetweenAtoms(mmap[14], mmap[15])->getBondType() == Bond::DOUBLE); REQUIRE(m2->getBondBetweenAtoms(mmap[4], mmap[5])->getStereo() == Bond::STEREOZ); REQUIRE(m2->getBondBetweenAtoms(mmap[14], mmap[15])->getStereo() == Bond::STEREOZ); delete m; delete m2; } { std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; RWMol *m = MolFileToMol(pathName + "zinc3850436piece.mol"); REQUIRE(m); std::string csmi1 = MolToSmiles(*m, true); delete m; m = SmilesToMol(csmi1); std::string csmi2 = MolToSmiles(*m, true); REQUIRE(csmi1 == csmi2); delete m; } { std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; RWMol *m = MolFileToMol(pathName + "zinc13403961piece.mol"); REQUIRE(m); REQUIRE(m->getBondBetweenAtoms(1, 2)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondBetweenAtoms(1, 2)->getStereo() == Bond::STEREOZ); REQUIRE(m->getBondBetweenAtoms(3, 7)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondBetweenAtoms(3, 7)->getStereo() == Bond::STEREOE); REQUIRE(m->getBondBetweenAtoms(4, 5)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondBetweenAtoms(4, 5)->getStereo() == Bond::STEREOE); std::string csmi1 = MolToSmiles(*m, true); RWMol *m2 = SmilesToMol(csmi1); MatchVectType mv; REQUIRE(SubstructMatch(*m, *m2, mv)); std::map mmap; for (auto &mvit : mv) { mmap[mvit.second] = mvit.first; } REQUIRE(m2->getBondBetweenAtoms(mmap[1], mmap[2])->getBondType() == Bond::DOUBLE); REQUIRE(m2->getBondBetweenAtoms(mmap[1], mmap[2])->getStereo() == Bond::STEREOZ); REQUIRE(m2->getBondBetweenAtoms(mmap[3], mmap[7])->getBondType() == Bond::DOUBLE); REQUIRE(m2->getBondBetweenAtoms(mmap[3], mmap[7])->getStereo() == Bond::STEREOE); REQUIRE(m2->getBondBetweenAtoms(mmap[4], mmap[5])->getBondType() == Bond::DOUBLE); REQUIRE(m2->getBondBetweenAtoms(mmap[4], mmap[5])->getStereo() == Bond::STEREOE); std::string csmi2 = MolToSmiles(*m2, true); REQUIRE(csmi1 == csmi2); delete m; delete m2; } { constexpr const char *smi = "C\\N=c1/s/c(=N\\Cl)/c/1=N/F"; RWMol *m = SmilesToMol(smi); REQUIRE(m); std::string csmi1 = MolToSmiles(*m, true); delete m; m = SmilesToMol(csmi1); REQUIRE(m); std::string csmi2 = MolToSmiles(*m, true); REQUIRE(csmi1 == csmi2); delete m; } { constexpr const char *smi = "Cc1ccc(S(=O)(=O)/N=c2sc(=N\\C(C)(C)C)/c\\2=N/C(C)(C)C)cc1"; RWMol *m = SmilesToMol(smi); REQUIRE(m); std::string csmi1 = MolToSmiles(*m, true); delete m; m = SmilesToMol(csmi1); REQUIRE(m); std::string csmi2 = MolToSmiles(*m, true); REQUIRE(csmi1 == csmi2); delete m; } { std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; RWMol *m = MolFileToMol(pathName + "zinc6624278.mol"); REQUIRE(m); REQUIRE(m->getBondBetweenAtoms(21, 13)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondBetweenAtoms(21, 13)->getStereo() == Bond::STEREOE); REQUIRE(m->getBondBetweenAtoms(5, 12)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondBetweenAtoms(5, 12)->getStereo() == Bond::STEREOZ); std::string csmi1 = MolToSmiles(*m, true); RWMol *m2 = SmilesToMol(csmi1); MatchVectType mv; REQUIRE(SubstructMatch(*m, *m2, mv)); std::map mmap; for (auto &mvit : mv) { mmap[mvit.second] = mvit.first; } REQUIRE(m2->getBondBetweenAtoms(mmap[21], mmap[13])->getBondType() == Bond::DOUBLE); REQUIRE(m2->getBondBetweenAtoms(mmap[21], mmap[13])->getStereo() == Bond::STEREOE); REQUIRE(m2->getBondBetweenAtoms(mmap[5], mmap[12])->getBondType() == Bond::DOUBLE); REQUIRE(m2->getBondBetweenAtoms(mmap[5], mmap[12])->getStereo() == Bond::STEREOZ); std::string csmi2 = MolToSmiles(*m2, true); REQUIRE(csmi1 == csmi2); delete m2; std::string tsmi = MolToSmiles(*m, true, false, 7, false); m2 = SmilesToMol(tsmi); REQUIRE(SubstructMatch(*m, *m2, mv)); mmap.clear(); for (auto &mvit : mv) { mmap[mvit.second] = mvit.first; } REQUIRE(m2->getBondBetweenAtoms(mmap[21], mmap[13])->getBondType() == Bond::DOUBLE); REQUIRE(m2->getBondBetweenAtoms(mmap[21], mmap[13])->getStereo() == Bond::STEREOE); REQUIRE(m2->getBondBetweenAtoms(mmap[5], mmap[12])->getBondType() == Bond::DOUBLE); REQUIRE(m2->getBondBetweenAtoms(mmap[5], mmap[12])->getStereo() == Bond::STEREOZ); csmi2 = MolToSmiles(*m2, true); REQUIRE(csmi1 == csmi2); delete m2; delete m; } { constexpr const char *smi = "F/C=C/C=C(C)/C=C/Cl"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getBondBetweenAtoms(1, 2)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondBetweenAtoms(1, 2)->getStereo() == Bond::STEREOE); REQUIRE(m->getBondBetweenAtoms(3, 4)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondBetweenAtoms(3, 4)->getStereo() == Bond::STEREOE); REQUIRE(m->getBondBetweenAtoms(6, 7)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondBetweenAtoms(6, 7)->getStereo() == Bond::STEREOE); std::string tsmi = MolToSmiles(*m, true, false, 3, false); RWMol *m2 = SmilesToMol(tsmi); MatchVectType mv; REQUIRE(SubstructMatch(*m, *m2, mv)); std::map mmap; mmap.clear(); for (auto &mvit : mv) { mmap[mvit.second] = mvit.first; } REQUIRE(m2->getBondBetweenAtoms(mmap[1], mmap[2])->getBondType() == Bond::DOUBLE); REQUIRE(m2->getBondBetweenAtoms(mmap[1], mmap[2])->getStereo() == Bond::STEREOE); REQUIRE(m2->getBondBetweenAtoms(mmap[3], mmap[4])->getBondType() == Bond::DOUBLE); REQUIRE(m2->getBondBetweenAtoms(mmap[3], mmap[4])->getStereo() == Bond::STEREOE); REQUIRE(m2->getBondBetweenAtoms(mmap[6], mmap[7])->getBondType() == Bond::DOUBLE); REQUIRE(m2->getBondBetweenAtoms(mmap[6], mmap[7])->getStereo() == Bond::STEREOE); std::string csmi1 = MolToSmiles(*m, true); std::string csmi2 = MolToSmiles(*m2, true); REQUIRE(csmi1 == csmi2); delete m2; delete m; } { // this was issue 3528556 std::string smi = "N12.N13.C24.C35.C46.C56"; RWMol *m = SmilesToMol(smi); REQUIRE(m); std::string csmi = MolToSmiles(*m, true); delete m; m = SmilesToMol(csmi); REQUIRE(m); smi = MolToSmiles(*m, true); REQUIRE(csmi == smi); delete m; } { // this was issue 3526831 std::string smi = "CO/N=C/C(=C(\\O)/c1ccc(Cl)cc1)/C=N\\OC"; RWMol *m = SmilesToMol(smi); REQUIRE(m); std::string csmi = MolToSmiles(*m, true); delete m; m = SmilesToMol(csmi); REQUIRE(m); smi = MolToSmiles(*m, true); REQUIRE(csmi == smi); delete m; } } TEST_CASE("Testing sf.net issue 3549146: problems after mergeQueryHs") { { std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; ROMol *m = MolFileToMol(pathName + "Issue3549146.mol", true, false); REQUIRE(m); REQUIRE(m->getNumAtoms() == 16); ROMol *m2 = MolOps::mergeQueryHs(*m); REQUIRE(m2); REQUIRE(m2->getNumAtoms() == 13); REQUIRE(!(m2->getRingInfo()->isInitialized())); delete m; delete m2; } { constexpr const char *smi = "CCC.C"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE((m->getRingInfo()->isInitialized())); m->addBond(1, 3, Bond::SINGLE); REQUIRE(m->getAtomWithIdx(1)->needsUpdatePropertyCache()); REQUIRE(m->getAtomWithIdx(3)->needsUpdatePropertyCache()); REQUIRE((m->getRingInfo()->isInitialized())); m->addBond(0, 2, Bond::SINGLE); REQUIRE((m->getRingInfo()->isInitialized())); REQUIRE(m->getAtomWithIdx(0)->needsUpdatePropertyCache()); REQUIRE(m->getAtomWithIdx(2)->needsUpdatePropertyCache()); delete m; } { constexpr const char *smi = "C1CC1C"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE((m->getRingInfo()->isInitialized())); m->removeBond(2, 3); REQUIRE(!(m->getRingInfo()->isInitialized())); delete m; } { constexpr const char *smi = "C1CC1C"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE((m->getRingInfo()->isInitialized())); m->removeAtom(3); REQUIRE(!(m->getRingInfo()->isInitialized())); delete m; } } TEST_CASE("Testing sf.net issue 249: finding rings consumes all memory") { { constexpr const char *smi = "Cc1cc2cc(c1)C(=O)NCc1cc-3cc(CNC(=O)c4cc(C)cc(c4)C(=O)NCc4cc(cc(CNC2=O)" "c4O)-c2cc4CNC(=O)c5cc(C)cc(c5)C(=O)NCc5cc-3cc(CNC(=O)c3cc(C)cc(c3)C(=" "O)NCc(c2)c4O)c5O)c1O"; ROMol *m = SmilesToMol(smi, 0, 0); REQUIRE(m); REQUIRE(m->getNumAtoms() == 88); m->updatePropertyCache(false); std::cerr << "starting ring finding" << std::endl; MolOps::findSSSR(*m); std::cerr << "done" << std::endl; delete m; } { constexpr const char *smi = "CCCOc1c2CNC(=O)c3cc(cc(c3)C(=O)NCc3cc4cc(CNC(=O)c5cc(C(=O)NCc1cc(c2)" "c1cc2CNC(=O)c6cc(cc(c6)C(=O)NCc6cc4cc(CNC(=O)c4cc(C(=O)NCc(c1)c2OCCC)" "cc(c4)C(=O)NC(COCCC(=O)O)(COCCC(=O)O)COCCC(=O)O)c6OCCC)C(=O)NC(COCCC(=" "O)O)(COCCC(=O)O)COCCC(=O)O)cc(c5)C(=O)NC(COCCC(=O)O)(COCCC(=O)O)COCCC(" "=O)O)c3OCCC)C(=O)NC(COCCC(=O)O)(COCCC(=O)O)COCCC(=O)O"; ROMol *m = SmilesToMol(smi, 0, 0); REQUIRE(m); REQUIRE(m->getNumAtoms() == 196); m->updatePropertyCache(false); std::cerr << "starting ring finding" << std::endl; MolOps::findSSSR(*m); std::cerr << "done" << std::endl; delete m; } { constexpr const char *smi = "CCn1nnc(c1)CN=C(C1CC2C3CCC4C5C3C3C6C2C2C1C1CCC7C(C1)C1C8C9C7C(C(=O)O)" "C(C(=O)O)C7C9C(C9C8C8C%10C1C1C(C2C2C6C6C%11C3C(C5)C3C(C(=O)O)C5C%" "12CC9C9C8C8C(C%10)C%10C%13C(C%14C(C2C1C(=O)O)C6C1C2C%11C3C3C5C(C5C%" "12C9C(C8CC%10)CC5)C(CC3C2C(C(C1C%14CC%13C(=NCc1nnn(c1)CC)O)C(=O)O)C(=" "O)O)C(=NCc1nnn(c1)CC)O)C(=O)O)C(=O)O)CC1C(C4C(CC71)C(=NCc1nnn(c1)CC)O)" "C(=O)O)O"; ROMol *m = SmilesToMol(smi, 0, 0); REQUIRE(m); REQUIRE(m->getNumAtoms() == 167); m->updatePropertyCache(false); std::cerr << "starting ring finding" << std::endl; MolOps::findSSSR(*m); std::cerr << "done" << std::endl; delete m; } { constexpr const char *smi = "C/C=N/" "c1ccc(cc1)c1cc2cc(c1)c1ccc(cc1)N=Cc1ccc(cc1)c1cc(cc(c1)c1ccc(cc1)C)" "c1ccc(cc1)C=Nc1ccc(cc1)c1cc(cc(c1)c1ccc(cc1)/N=C/" "C)c1ccc(cc1)N=Cc1ccc(cc1)c1cc(c3ccc(C=Nc4ccc(c5cc6c7ccc(N=Cc8ccc(c9cc(" "c%10ccc(C=Nc%11ccc2cc%11)cc%10)cc(c9)c2ccc(cc2)C=Nc2ccc(cc2)c2cc(cc(" "c2)c2ccc(cc2)N=Cc2ccc(cc2)c2cc(cc(c2)c2ccc(cc2)C)c2ccc(cc2)C=Nc2ccc(" "cc2)c2cc(c9ccc(N=Cc%10ccc(c%11cc(c%12ccc(C=Nc%13ccc(c(c6)c5)cc%13)cc%" "12)cc(c%11)c5ccc(cc5)C)cc%10)cc9)cc(c2)c2ccc(cc2)/N=C/C)c2ccc(cc2)/" "N=C/C)cc8)cc7)cc4)cc3)cc(c1)c1ccc(cc1)C"; RWMol *m = SmilesToMol(smi, 0, 0); REQUIRE(m); REQUIRE(m->getNumAtoms() == 278); std::cerr << smi << std::endl; std::cerr << "starting sanitization" << std::endl; MolOps::sanitizeMol(*m); std::cerr << "done" << std::endl; delete m; } { constexpr const char *smi = "COc1cc2ccc1n1c(=O)n(c3ccc(cc3OC)c3ccc(c(c3)OC)n3c(=O)n(c4ccc(cc4OC)" "c4ccc(c(c4)OC)n4c(=O)n(c5ccc(cc5OC)c5ccc(n6c(=O)n(c7ccc(c8ccc(n9c(=O)" "n(c%10ccc(c%11ccc(n%12c(=O)n(c%13ccc2cc%13OC)c(=O)n(c%12=O)c2ccc(" "cc2OC)C)c(OC)c%11)cc%10OC)c(=O)n(c9=O)c2ccc(cc2OC)C)c(OC)c8)cc7OC)c(=" "O)n(c6=O)c2ccc(cc2OC)C)c(c5)OC)c(=O)n(c4=O)c2ccc(cc2OC)C)c(=O)n(c3=O)" "c2ccc(cc2OC)C)c(=O)n(c1=O)c1ccc(cc1OC)C"; RWMol *m = SmilesToMol(smi, 0, 0); REQUIRE(m); REQUIRE(m->getNumAtoms() == 204); std::cerr << "starting sanitization" << std::endl; MolOps::sanitizeMol(*m); std::cerr << "done" << std::endl; delete m; } } TEST_CASE("Testing sf.net issue 256: bad atom counts") { { constexpr const char *smi = "*CC[H]"; ROMol *m = SmilesToMol(smi, 0, 0); REQUIRE(m); m->updatePropertyCache(false); REQUIRE(m->getNumAtoms() == 4); REQUIRE(m->getNumAtoms(false) == 8); REQUIRE(m->getNumHeavyAtoms() == 2); delete m; } { constexpr const char *smi = "*CC[2H]"; ROMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE(m->getNumAtoms(false) == 8); REQUIRE(m->getNumHeavyAtoms() == 2); delete m; } } TEST_CASE("Testing sf.net issue 266: ring finding error") { std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; RWMol *m = MolFileToMol(pathName + "Issue266.mol", false); REQUIRE(m); REQUIRE(m->getNumAtoms() == 19); REQUIRE(m->getNumBonds() == 25); std::cerr << "starting ring finding" << std::endl; MolOps::findSSSR(*m); std::cerr << "done" << std::endl; REQUIRE(m->getRingInfo()->numRings() == (m->getNumBonds() - m->getNumAtoms() + 1)); delete m; } TEST_CASE("Testing sf.net issue 272: removing two-coordinate Hs") { { constexpr const char *smi = "C[H-]C"; ROMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 3); delete m; } { constexpr const char *smi = "C[H].C"; ROMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 2); delete m; } } TEST_CASE( "Testing Github issue 8 (impact of removeAtom on bond stereo atoms)") { { constexpr const char *smi = "Cl/C=C/Cl"; RWMol *m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m); REQUIRE(m->getBondWithIdx(1)->getStereoAtoms().size() == 2); m->removeAtom((unsigned int)0); REQUIRE(m->getBondWithIdx(1)->getStereoAtoms().size() == 0); delete m; } { constexpr const char *smi = "CC/C=C/Cl"; RWMol *m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m); INT_VECT &sas = m->getBondWithIdx(2)->getStereoAtoms(); REQUIRE(sas.size() == 2); REQUIRE(std::find(sas.begin(), sas.end(), 1) != sas.end()); m->removeAtom((unsigned int)0); REQUIRE(m->getBondWithIdx(1)->getStereoAtoms().size() == 2); REQUIRE(std::find(sas.begin(), sas.end(), 0) != sas.end()); REQUIRE(std::find(sas.begin(), sas.end(), 1) == sas.end()); delete m; } { constexpr const char *smi = "C/C=C/CC"; RWMol *m = SmilesToMol(smi); REQUIRE(m); MolOps::assignStereochemistry(*m); INT_VECT &sas = m->getBondWithIdx(1)->getStereoAtoms(); REQUIRE(sas.size() == 2); REQUIRE(std::find(sas.begin(), sas.end(), 0) != sas.end()); REQUIRE(std::find(sas.begin(), sas.end(), 3) != sas.end()); m->removeAtom((unsigned int)4); REQUIRE(m->getBondWithIdx(1)->getStereoAtoms().size() == 2); REQUIRE(std::find(sas.begin(), sas.end(), 0) != sas.end()); REQUIRE(std::find(sas.begin(), sas.end(), 3) != sas.end()); delete m; } } TEST_CASE("Testing Github issue 42 (impact of removeAtom on atom stereochem)") { std::string smi = "CCN1CCN(c2cc3[nH]c(C(=O)[C@@]4(CC)CC[C@](C)(O)CC4)nc3cc2Cl)CC1"; RWMol *m = SmilesToMol(smi); REQUIRE(m); constexpr int indices[] = {29, 28, 27, 26, 25, 24, 8, 7, 6, 5, 4, 3, 2, 1, 0, -1}; for (unsigned int i = 0; indices[i] > -1; ++i) { m->removeAtom((unsigned int)indices[i]); } smi = MolToSmiles(*m, true); std::cerr << "smiles: " << smi << std::endl; delete m; } TEST_CASE( "Testing Github issue 65 (kekulization of boron-containing aromatic rings)") { constexpr const char *smi = "C[B-]1=CC=CC=C1"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getIsAromatic()); REQUIRE(m->getBondWithIdx(1)->getIsAromatic()); MolOps::Kekulize(*m); delete m; } TEST_CASE( "Testing Github issue 72 (problems with bad benzothiazolium structure)") { { std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; RWMol *m = MolFileToMol(pathName + "github72.mol"); REQUIRE(m); REQUIRE(!m->getBondBetweenAtoms(0, 8)->getIsAromatic()); REQUIRE(m->getBondBetweenAtoms(1, 6)->getIsAromatic()); delete m; } { std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; RWMol *m = MolFileToMol(pathName + "github72.2.mol"); REQUIRE(m); REQUIRE(m->getBondBetweenAtoms(0, 8)->getIsAromatic()); REQUIRE(m->getBondBetweenAtoms(1, 6)->getIsAromatic()); delete m; } { std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; RWMol *m = MolFileToMol(pathName + "github72.3.mol"); REQUIRE(m); REQUIRE(!m->getBondBetweenAtoms(0, 8)->getIsAromatic()); REQUIRE(m->getBondBetweenAtoms(1, 6)->getIsAromatic()); std::string smi = MolToSmiles(*m, true); delete m; m = SmilesToMol(smi); REQUIRE(m); delete m; } } namespace { void _renumberTest(const ROMol *m) { PRECONDITION(m, "no molecule"); std::vector idxV(m->getNumAtoms()); for (unsigned int i = 0; i < m->getNumAtoms(); ++i) { idxV[i] = i; } std::string refSmi = MolToSmiles(*m, true); for (unsigned int i = 0; i < m->getNumAtoms(); ++i) { std::vector nVect(idxV); std::shuffle(nVect.begin(), nVect.end(), std::mt19937(0xf00d)); ROMol *nm = MolOps::renumberAtoms(*m, nVect); REQUIRE(nm); REQUIRE(nm->getNumAtoms() == m->getNumAtoms()); REQUIRE(nm->getNumBonds() == m->getNumBonds()); // checking the SSS is a test for Github #317 MatchVectType mv; REQUIRE(SubstructMatch(*m, *nm, mv)); REQUIRE(mv.size() == nm->getNumAtoms()); for (unsigned int j = 0; j < m->getNumAtoms(); ++j) { REQUIRE(m->getAtomWithIdx(nVect[j])->getAtomicNum() == nm->getAtomWithIdx(j)->getAtomicNum()); } // checking the conformation is a test for Github #441 REQUIRE(m->getNumConformers() == nm->getNumConformers()); if (m->getNumConformers()) { for (unsigned int j = 0; j < m->getNumAtoms(); ++j) { RDGeom::Point3D po = m->getConformer().getAtomPos(nVect[j]); RDGeom::Point3D pn = nm->getConformer().getAtomPos(j); REQUIRE(po.x == pn.x); REQUIRE(po.y == pn.y); REQUIRE(po.z == pn.z); } // checking conformer dimensionality is a test for Github #584 REQUIRE(m->getConformer().is3D() == nm->getConformer().is3D()); } std::string nSmi = MolToSmiles(*nm, true); if (nSmi != refSmi) { std::cerr << refSmi << std::endl << nSmi << std::endl; } REQUIRE(nSmi == refSmi); delete nm; } } } // namespace TEST_CASE("Testing renumbering atoms") { { constexpr const char *smiles = "CC1CCCC(C)C1C"; ROMol *m = SmilesToMol(smiles); REQUIRE(m); _renumberTest(m); delete m; } { constexpr const char *smiles = "C[C@H]1C[C@H](F)C1"; ROMol *m = SmilesToMol(smiles); REQUIRE(m); _renumberTest(m); delete m; } { constexpr const char *smiles = "C[C@H]1CC[C@H](C/C=C/[C@H](F)Cl)CC1"; ROMol *m = SmilesToMol(smiles); REQUIRE(m); _renumberTest(m); delete m; } { // github issue #441 and #584 std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; RWMol *m = MolFileToMol(pathName + "Issue266.mol"); // no significance to // choice of files, we // just need something // with 2D coords REQUIRE(m); _renumberTest(m); delete m; } { // github issue 1735 renumber empty molecules auto *m = new ROMol; REQUIRE(m); std::vector nVect; auto *m1 = MolOps::renumberAtoms(*m, nVect); delete m; delete m1; } } TEST_CASE( "Testing github issue 141: Kekulization of molecule with aromatic N leaves the explicit H there") { constexpr const char *smiles = "N1C=CC=C1"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); MolOps::Kekulize(*m, true); m->updatePropertyCache(true); REQUIRE(!m->getAtomWithIdx(0)->getIsAromatic()); REQUIRE(m->getAtomWithIdx(0)->getNumImplicitHs() == 1); REQUIRE(m->getAtomWithIdx(0)->getNumExplicitHs() == 0); delete m; } TEST_CASE("Testing ZBO basics") { auto *m = new RWMol(); m->addAtom(new Atom(26), true, true); m->addAtom(new Atom(6), true, true); m->addAtom(new Atom(6), true, true); m->addAtom(new Atom(6), true, true); m->addAtom(new Atom(6), true, true); m->addAtom(new Atom(6), true, true); m->addAtom(new Atom(6), true, true); m->addBond(1, 2, Bond::AROMATIC); m->addBond(2, 3, Bond::AROMATIC); m->addBond(3, 4, Bond::AROMATIC); m->addBond(4, 5, Bond::AROMATIC); m->addBond(5, 6, Bond::AROMATIC); m->addBond(1, 6, Bond::AROMATIC); m->addBond(1, 0, Bond::ZERO); m->addBond(2, 0, Bond::ZERO); m->addBond(3, 0, Bond::ZERO); m->addBond(4, 0, Bond::ZERO); m->addBond(5, 0, Bond::ZERO); m->addBond(6, 0, Bond::ZERO); MolOps::sanitizeMol(*m); REQUIRE(m->getRingInfo()->numAtomRings(0) == 0); REQUIRE(m->getRingInfo()->numAtomRings(1) == 1); REQUIRE(m->getAtomWithIdx(1)->getHybridization() == Atom::SP2); REQUIRE(m->getAtomWithIdx(2)->getHybridization() == Atom::SP2); REQUIRE(m->getAtomWithIdx(3)->getHybridization() == Atom::SP2); REQUIRE(m->getAtomWithIdx(4)->getHybridization() == Atom::SP2); REQUIRE(m->getAtomWithIdx(5)->getHybridization() == Atom::SP2); REQUIRE(m->getAtomWithIdx(6)->getHybridization() == Atom::SP2); REQUIRE(m->getBondWithIdx(0)->getIsAromatic()); REQUIRE(m->getBondWithIdx(1)->getIsAromatic()); REQUIRE(m->getBondWithIdx(2)->getIsAromatic()); REQUIRE(m->getBondWithIdx(3)->getIsAromatic()); REQUIRE(m->getBondWithIdx(4)->getIsAromatic()); REQUIRE(m->getBondWithIdx(5)->getIsAromatic()); delete m; } TEST_CASE("Testing operator= on molecules") { constexpr const char *smi = "CCN1CCN(c2cc3[nH]c(C(=O)[C@@]4(CC)CC[C@](C)(O)CC4)nc3cc2Cl)CC1"; RWMol *m = SmilesToMol(smi); REQUIRE(m); std::string csmi = MolToSmiles(*m, true); RWMol m2 = *m; std::string nsmi = MolToSmiles(m2, true); REQUIRE(nsmi == csmi); RWMol *m3 = SmilesToMol("C2CC2[C@H](F)Cl"); REQUIRE(m3); *m3 = *m; nsmi = MolToSmiles(*m3, true); REQUIRE(nsmi == csmi); delete m3; delete m; } TEST_CASE("Testing github issue 190: Don't merge Hs onto dummy atoms") { constexpr const char *smiles = "*[H]"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getNumAtoms() == 2); delete m; } namespace { inline int getAtNum(const ROMol &, const Atom *at) { return at->getAtomicNum(); } } // namespace TEST_CASE("Testing getMolFragsWithQuery()") { { constexpr const char *smiles = "C1CCC1ONNC"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getNumAtoms() == 8); std::map> res = MolOps::getMolFragsWithQuery(*m, getAtNum); REQUIRE(res.size() == 3); REQUIRE(res.find(6) != res.end()); REQUIRE(res.find(7) != res.end()); REQUIRE(res.find(8) != res.end()); REQUIRE(res.find(5) == res.end()); REQUIRE(res[6]->getNumAtoms() == 5); REQUIRE(res[6]->getNumBonds() == 4); REQUIRE(res[7]->getNumAtoms() == 2); REQUIRE(res[7]->getNumBonds() == 1); REQUIRE(res[8]->getNumAtoms() == 1); REQUIRE(res[8]->getNumBonds() == 0); std::map> otherRes; MolOps::getMolFragsWithQuery(*m, getAtNum, otherRes); REQUIRE(otherRes.size() == 3); REQUIRE(otherRes.find(6) != otherRes.end()); REQUIRE(otherRes.find(7) != otherRes.end()); REQUIRE(otherRes.find(8) != otherRes.end()); REQUIRE(otherRes.find(5) == otherRes.end()); REQUIRE(otherRes[6]->getNumAtoms() == 5); REQUIRE(otherRes[6]->getNumBonds() == 4); REQUIRE(otherRes[7]->getNumAtoms() == 2); REQUIRE(otherRes[7]->getNumBonds() == 1); REQUIRE(otherRes[8]->getNumAtoms() == 1); REQUIRE(otherRes[8]->getNumBonds() == 0); delete m; } { constexpr const char *smiles = "C1CCC1ONNC"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getNumAtoms() == 8); std::vector keep; keep.push_back(6); keep.push_back(8); std::map> res = MolOps::getMolFragsWithQuery(*m, getAtNum, true, &keep); REQUIRE(res.size() == 2); REQUIRE(res.find(6) != res.end()); REQUIRE(res.find(7) == res.end()); REQUIRE(res.find(8) != res.end()); REQUIRE(res[6]->getNumAtoms() == 5); REQUIRE(res[6]->getNumBonds() == 4); REQUIRE(res[8]->getNumAtoms() == 1); REQUIRE(res[8]->getNumBonds() == 0); std::map> otherRes; MolOps::getMolFragsWithQuery(*m, getAtNum, otherRes, true, &keep); REQUIRE(otherRes.size() == 2); REQUIRE(otherRes.find(6) != otherRes.end()); REQUIRE(otherRes.find(7) == otherRes.end()); REQUIRE(otherRes.find(8) != otherRes.end()); REQUIRE(otherRes[6]->getNumAtoms() == 5); REQUIRE(otherRes[6]->getNumBonds() == 4); REQUIRE(otherRes[8]->getNumAtoms() == 1); REQUIRE(otherRes[8]->getNumBonds() == 0); delete m; } { constexpr const char *smiles = "C1CCC1ONNC"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getNumAtoms() == 8); std::vector keep; keep.push_back(6); keep.push_back(8); std::map> res = MolOps::getMolFragsWithQuery(*m, getAtNum, true, &keep, true); REQUIRE(res.size() == 1); REQUIRE(res.find(6) == res.end()); REQUIRE(res.find(7) != res.end()); REQUIRE(res.find(8) == res.end()); REQUIRE(res[7]->getNumAtoms() == 2); REQUIRE(res[7]->getNumBonds() == 1); std::map> otherRes; MolOps::getMolFragsWithQuery(*m, getAtNum, otherRes, true, &keep, true); REQUIRE(otherRes.size() == 1); REQUIRE(otherRes.find(6) == otherRes.end()); REQUIRE(otherRes.find(7) != otherRes.end()); REQUIRE(otherRes.find(8) == otherRes.end()); REQUIRE(otherRes[7]->getNumAtoms() == 2); REQUIRE(otherRes[7]->getNumBonds() == 1); delete m; } } TEST_CASE("Testing github issue 418: removeHs not updating H count") { { auto *m2 = new RWMol(); m2->addAtom(new Atom(7), true, true); m2->addAtom(new Atom(1), true, true); m2->addAtom(new Atom(1), true, true); m2->addAtom(new Atom(1), true, true); m2->addAtom(new Atom(1), true, true); m2->addBond(0, 1, Bond::SINGLE); m2->addBond(0, 2, Bond::SINGLE); m2->addBond(0, 3, Bond::SINGLE); m2->addBond(0, 4, Bond::SINGLE); MolOps::removeHs(*m2, false, true, false); REQUIRE(m2->getAtomWithIdx(0)->getNumExplicitHs() == 4); delete m2; } { constexpr const char *smiles = "[H][N+]([H])([H])[H]"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getNumAtoms() == 1); REQUIRE(m->getAtomWithIdx(0)->getNumExplicitHs() == 4); delete m; } { REQUIRE_THROWS_AS(SmilesToMol("[H]N([H])([H])[H]"), MolSanitizeException); } } TEST_CASE( "Testing github issue 432: problems caused by aromatic Ns with radical electrons") { { constexpr const char *smiles = "C1=NN=N[N]1"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getAtomWithIdx(4)->getNumRadicalElectrons() == 1); REQUIRE(!m->getAtomWithIdx(4)->getIsAromatic()); REQUIRE(!m->getAtomWithIdx(0)->getIsAromatic()); REQUIRE(!m->getBondWithIdx(0)->getIsAromatic()); delete m; } { // test round-tripping: std::string smiles = "C1=NN=N[N]1"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); smiles = MolToSmiles(*m); delete m; m = SmilesToMol(smiles); REQUIRE(m); delete m; } { // test round-tripping: std::string smiles = "OC(=O)C(=O)Nc1cccc(c1)C2=NN=N[N]2"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); smiles = MolToSmiles(*m); delete m; m = SmilesToMol(smiles); REQUIRE(m); delete m; } { constexpr const char *smiles = "C1=C[N]C=C1"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getAtomWithIdx(2)->getNumRadicalElectrons() == 1); REQUIRE(!m->getAtomWithIdx(2)->getIsAromatic()); REQUIRE(!m->getAtomWithIdx(0)->getIsAromatic()); REQUIRE(!m->getBondWithIdx(0)->getIsAromatic()); delete m; } } TEST_CASE( "Testing github issue 443: kekulization problems caused by any bonds") { { std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; RWMol *m = MolFileToMol(pathName + "github443.min.mol"); REQUIRE(m); REQUIRE(!m->getAtomWithIdx(0)->getIsAromatic()); REQUIRE(m->getBondBetweenAtoms(0, 3)); MolOps::Kekulize(*m); delete m; } { std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; RWMol *m = MolFileToMol(pathName + "github443.mol"); REQUIRE(m); REQUIRE(m->getAtomWithIdx(19)->getIsAromatic()); REQUIRE(m->getAtomWithIdx(12)->getIsAromatic()); // we might normally expect these to be aromatic because the outer porphyrin // ring // is 4n+2 aromatic. However, the current fused ring aromaticity perception // uses // the symmetrized SSSR rings and only works if all atoms are aromatic. This // cannot // happen when the Mg is involved // REQUIRE(m->getAtomWithIdx(13)->getIsAromatic()); // REQUIRE(m->getAtomWithIdx(11)->getIsAromatic()); REQUIRE(m->getBondBetweenAtoms(13, 20)); REQUIRE(m->getBondBetweenAtoms(19, 20)); REQUIRE(m->getBondBetweenAtoms(11, 20)); REQUIRE(m->getBondBetweenAtoms(12, 20)); MolOps::Kekulize(*m); delete m; } } TEST_CASE( "Testing github issue 447: Radicals are not correctly assigned when reading from SMILES") { { constexpr const char *smiles = "C[S]"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getNoImplicit()); REQUIRE(m->getAtomWithIdx(1)->getNumRadicalElectrons() == 1); delete m; } { constexpr const char *smiles = "C[SH]C"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getNoImplicit()); REQUIRE(m->getAtomWithIdx(1)->getNumRadicalElectrons() == 1); delete m; } { constexpr const char *smiles = "C[SH3]C"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getNoImplicit()); REQUIRE(m->getAtomWithIdx(1)->getNumRadicalElectrons() == 1); delete m; } { constexpr const char *smiles = "C[SH4]C"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getNoImplicit()); REQUIRE(m->getAtomWithIdx(1)->getNumRadicalElectrons() == 0); delete m; } { constexpr const char *smiles = "C[SH3]C"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getNoImplicit()); REQUIRE(m->getAtomWithIdx(1)->getNumRadicalElectrons() == 1); delete m; } { constexpr const char *smiles = "C[SH2+]C"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getNoImplicit()); REQUIRE(m->getAtomWithIdx(1)->getNumRadicalElectrons() == 1); delete m; } { constexpr const char *smiles = "C[P]C"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getNoImplicit()); REQUIRE(m->getAtomWithIdx(1)->getNumRadicalElectrons() == 1); delete m; } { constexpr const char *smiles = "C[PH2]C"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getAtomWithIdx(1)->getNoImplicit()); REQUIRE(m->getAtomWithIdx(1)->getNumRadicalElectrons() == 1); delete m; } } TEST_CASE("Testing generation of new molecules from molecule fragments") { { constexpr const char *smiles = "c1ccccc1.O.CCC(=O)O"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); INT_VECT fragsMapping; VECT_INT_VECT fragsMolAtomMapping; std::vector frags = MolOps::getMolFrags(*m, false, &fragsMapping, &fragsMolAtomMapping); REQUIRE(frags.size() == 3); REQUIRE(fragsMapping.size() == m->getNumAtoms()); REQUIRE(fragsMapping[2] == 0); REQUIRE(fragsMapping[6] == 1); REQUIRE(fragsMapping[8] == 2); REQUIRE(fragsMolAtomMapping[0].size() == frags[0]->getNumAtoms()); REQUIRE(fragsMolAtomMapping[1].size() == frags[1]->getNumAtoms()); REQUIRE(fragsMolAtomMapping[2].size() == frags[2]->getNumAtoms()); REQUIRE(fragsMolAtomMapping[0][1] == 1); REQUIRE(fragsMolAtomMapping[1][0] == 6); REQUIRE(fragsMolAtomMapping[2][1] == 8); REQUIRE(MolToSmiles(*frags[0], true) == "c1ccccc1"); REQUIRE(MolToSmiles(*frags[1], true) == "O"); REQUIRE(MolToSmiles(*frags[2], true) == "CCC(=O)O"); delete m; } { std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; RWMol *m = MolFileToMol(pathName + "chembl1203199.mol"); REQUIRE(m); constexpr const char *smi = "C[C@H](NC(=O)[C@H]1Cc2c(sc3ccccc23)CN1)c1ccccc1.Cl"; REQUIRE(MolToSmiles(*m, true) == smi); INT_VECT fragsMapping; VECT_INT_VECT fragsMolAtomMapping; std::vector frags = MolOps::getMolFrags( *m, false, &fragsMapping, &fragsMolAtomMapping, true); REQUIRE(frags.size() == 2); REQUIRE(fragsMapping.size() == m->getNumAtoms()); REQUIRE(fragsMapping[2] == 0); REQUIRE(fragsMapping[24] == 1); REQUIRE(fragsMolAtomMapping[0].size() == frags[0]->getNumAtoms()); REQUIRE(fragsMolAtomMapping[1].size() == frags[1]->getNumAtoms()); REQUIRE(fragsMolAtomMapping[0][1] == 1); REQUIRE(fragsMolAtomMapping[1][0] == 24); REQUIRE(frags[0]->getNumConformers() == 1); REQUIRE(frags[1]->getNumConformers() == 1); REQUIRE(frags[0]->getConformer(0).getAtomPos(0).x == m->getConformer(0).getAtomPos(0).x); REQUIRE(frags[0]->getConformer(0).getAtomPos(0).y == m->getConformer(0).getAtomPos(0).y); REQUIRE(frags[0]->getConformer(0).getAtomPos(0).z == m->getConformer(0).getAtomPos(0).z); REQUIRE(frags[0]->getConformer(0).getAtomPos(3).x == m->getConformer(0).getAtomPos(3).x); REQUIRE(frags[0]->getConformer(0).getAtomPos(3).y == m->getConformer(0).getAtomPos(3).y); REQUIRE(frags[0]->getConformer(0).getAtomPos(3).z == m->getConformer(0).getAtomPos(3).z); REQUIRE(frags[1]->getConformer(0).getAtomPos(0).x == m->getConformer(0).getAtomPos(24).x); REQUIRE(frags[1]->getConformer(0).getAtomPos(0).y == m->getConformer(0).getAtomPos(24).y); REQUIRE(frags[1]->getConformer(0).getAtomPos(0).z == m->getConformer(0).getAtomPos(24).z); delete m; } { // confirm bond-only stereogroups are not removed during GetmolFrags constexpr const char *smiles = "Cc1cccc(Cl)c1-c1c(C)cccc1I.Cc1cccc(F)c1-c1c(C)cccc1Cl |wD:8.15,wU:23.23,o1:23,&1:8|"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); INT_VECT fragsMapping; VECT_INT_VECT fragsMolAtomMapping; std::vector frags = MolOps::getMolFrags(*m, false, &fragsMapping, &fragsMolAtomMapping); REQUIRE(frags.size() == 2); REQUIRE(fragsMapping.size() == m->getNumAtoms()); for (const auto &frag : frags) { REQUIRE(frag->getNumAtoms() == 16); REQUIRE(frag->getNumBonds() == 17); REQUIRE(frag->getStereoGroups().size() == 1); REQUIRE(frag->getStereoGroups()[0].getBonds().size() == 1); } RDKit::SmilesWriteParams sps; REQUIRE(MolToCXSmiles(*frags[0], sps, SmilesWrite::CXSmilesFields::CX_ALL_BUT_COORDS) == "Cc1cccc(Cl)c1-c1c(C)cccc1I |wU:7.6,&1:7|"); REQUIRE(MolToCXSmiles(*frags[1], sps, SmilesWrite::CXSmilesFields::CX_ALL_BUT_COORDS) == "Cc1cccc(F)c1-c1c(C)cccc1Cl |wU:7.6,o1:7|"); delete m; } } namespace { void hypervalent_check(const char *smiles) { RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getBondBetweenAtoms(0, 1)->getBondType() == Bond::SINGLE); REQUIRE(m->getAtomWithIdx(1)->getFormalCharge() == -1); delete m; } } // namespace TEST_CASE("Testing github issue 510: Hexafluorophosphate cannot be handled") { hypervalent_check("F[P-](F)(F)(F)(F)F"); // test #1668 too, it's the same thing but with As, Sb, and Bi hypervalent_check("F[As-](F)(F)(F)(F)F"); hypervalent_check("F[Sb-](F)(F)(F)(F)F"); hypervalent_check("F[Bi-](F)(F)(F)(F)F"); hypervalent_check("F[Sb-](F)(F)(F)(F)F"); hypervalent_check("F[Bi-](F)(F)(F)(F)F"); // we also added a valence of 5 for Bi: hypervalent_check("F[Bi-](F)(F)F"); } TEST_CASE( "Testing github issue 526: Bad ring finding in a complex fused ring") { { constexpr const char *smiles = "N1C2[C@@H]3N[C@H]4[C@@H]5N[C@@H]([C@@H]1C35)C24"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getRingInfo()->numRings() == 6); delete m; } { constexpr const char *smiles = "NN1C2C3[C@@H]4[C@@H]1C1[C@H]2N([C@H]3[C@@H]1N4N1C(=O)C2=C(C=CC=C2)C1=" "O)N1C(=O)C2=C(C=CC=C2)C1=O"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getRingInfo()->numRings() == 10); delete m; } } TEST_CASE( "Testing github issue 539: Lack of conjugation in allyl cations, lack of aromaticity perception/ability to kekulize aromatic carbocations such as cyclopropenyl and tropylium") { { constexpr const char *smiles = "C=C-[CH2+]"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); bool allConjugated = true; for (unsigned int i = 0; allConjugated && i < m->getNumBonds(); ++i) { allConjugated = m->getBondWithIdx(i)->getIsConjugated(); } REQUIRE(allConjugated); delete m; } { constexpr std::array testSmiles = { "C1=C[CH+]1", "C1=CC=C[CH+]C=C1", "c1c[cH+]1", "c1ccc[cH+]cc1", }; for (const auto &smiles : testSmiles) { CAPTURE(smiles); auto m = SmilesToMol(smiles); REQUIRE(m); bool allConjugated = true; for (unsigned int i = 0; allConjugated && i < m->getNumBonds(); ++i) { allConjugated = m->getBondWithIdx(i)->getIsConjugated(); } REQUIRE(allConjugated); bool allAromatic = true; for (unsigned int i = 0; allAromatic && i < m->getNumBonds(); ++i) { allAromatic = m->getBondWithIdx(i)->getIsAromatic(); } REQUIRE(allAromatic); delete m; } } { constexpr const char *smiles = "C=C-C"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getAtomWithIdx(2)->getHybridization() == Atom::SP3); REQUIRE(m->getBondWithIdx(1)->getBondType() == Bond::SINGLE); REQUIRE(!m->getBondWithIdx(1)->getIsConjugated()); delete m; } { constexpr const char *smiles = "C=C-O"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getAtomWithIdx(2)->getHybridization() == Atom::SP2); REQUIRE(m->getBondWithIdx(1)->getBondType() == Bond::SINGLE); REQUIRE(m->getBondWithIdx(1)->getIsConjugated()); delete m; } { constexpr const char *smiles = "C=C-N"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getAtomWithIdx(2)->getHybridization() == Atom::SP2); REQUIRE(m->getBondWithIdx(1)->getBondType() == Bond::SINGLE); REQUIRE(m->getBondWithIdx(1)->getIsConjugated()); delete m; } { constexpr const char *smiles = "C=C-[NH3+]"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getAtomWithIdx(2)->getHybridization() == Atom::SP3); REQUIRE(m->getBondWithIdx(1)->getBondType() == Bond::SINGLE); REQUIRE(!m->getBondWithIdx(1)->getIsConjugated()); delete m; } { constexpr const char *smiles = "Cc1ccccc1"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getBondWithIdx(0)->getBondType() == Bond::SINGLE); // the bond to the CH3 should not be conjugated, but the others are REQUIRE(!m->getBondWithIdx(0)->getIsConjugated()); for (unsigned int i = 1; i < m->getNumBonds(); ++i) { REQUIRE(m->getBondWithIdx(i)->getIsConjugated()); } delete m; } { constexpr const char *smiles = "Fc1c[nH]c(=O)[nH]c1=O"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getBondWithIdx(0)->getBondType() == Bond::SINGLE); // the bond to the F should not be conjugated, but the others are REQUIRE(!m->getBondWithIdx(0)->getIsConjugated()); for (unsigned int i = 1; i < m->getNumBonds(); ++i) { REQUIRE(m->getBondWithIdx(i)->getIsConjugated()); } delete m; } } TEST_CASE("Testing adjustQueryProperties()") { { // basics from SMILES std::string smiles = "C1CCC1C"; ROMol *qm = SmilesToMol(smiles); REQUIRE(qm); REQUIRE(qm->getNumAtoms() == 5); ROMol *aqm = MolOps::adjustQueryProperties(*qm); REQUIRE(aqm); REQUIRE(aqm->getNumAtoms() == 5); { smiles = "C1CCC1CC"; ROMol *m = SmilesToMol(smiles); REQUIRE(m); MatchVectType match; REQUIRE(SubstructMatch(*m, *qm, match)); REQUIRE(SubstructMatch(*m, *aqm, match)); delete m; } { smiles = "C1C(C)CC1CC"; ROMol *m = SmilesToMol(smiles); REQUIRE(m); MatchVectType match; REQUIRE(SubstructMatch(*m, *qm, match)); REQUIRE(!SubstructMatch(*m, *aqm, match)); delete m; } delete qm; delete aqm; } { // basics from SMARTS std::string smiles = "C1CCC1*"; ROMol *qm = SmartsToMol(smiles); REQUIRE(qm); REQUIRE(qm->getNumAtoms() == 5); ROMol *aqm = MolOps::adjustQueryProperties(*qm); REQUIRE(aqm); REQUIRE(aqm->getNumAtoms() == 5); { smiles = "C1CCC1CC"; ROMol *m = SmilesToMol(smiles); REQUIRE(m); MatchVectType match; REQUIRE(SubstructMatch(*m, *qm, match)); REQUIRE(SubstructMatch(*m, *aqm, match)); delete m; } { smiles = "C1C(C)CC1CC"; ROMol *m = SmilesToMol(smiles); REQUIRE(m); MatchVectType match; REQUIRE(SubstructMatch(*m, *qm, match)); REQUIRE(!SubstructMatch(*m, *aqm, match)); delete m; } delete qm; delete aqm; } { std::string smiles = "C1CC(*)C1*"; ROMol *qm = SmartsToMol(smiles); REQUIRE(qm); REQUIRE(qm->getNumAtoms() == 6); ROMol *aqm = MolOps::adjustQueryProperties(*qm); REQUIRE(aqm); REQUIRE(aqm->getNumAtoms() == 6); { smiles = "C1CC2C1CC2"; ROMol *m = SmilesToMol(smiles); REQUIRE(m); MatchVectType match; REQUIRE(SubstructMatch(*m, *qm, match)); REQUIRE(SubstructMatch(*m, *aqm, match)); MolOps::AdjustQueryParameters aqp; delete aqm; aqp.adjustDegree = false; aqp.adjustRingCount = false; aqm = MolOps::adjustQueryProperties(*qm, &aqp); REQUIRE(aqm); REQUIRE(aqm->getNumAtoms() == 6); REQUIRE(SubstructMatch(*m, *aqm, match)); delete aqm; aqp.adjustDegree = false; aqp.adjustRingCount = true; aqm = MolOps::adjustQueryProperties(*qm, &aqp); REQUIRE(aqm); REQUIRE(aqm->getNumAtoms() == 6); REQUIRE(!SubstructMatch(*m, *aqm, match)); delete aqm; aqp.adjustDegree = true; aqp.adjustRingCount = false; aqp.adjustDegreeFlags = MolOps::ADJUST_IGNORENONE; aqm = MolOps::adjustQueryProperties(*qm, &aqp); REQUIRE(aqm); REQUIRE(aqm->getNumAtoms() == 6); REQUIRE(!SubstructMatch(*m, *aqm, match)); delete m; } { smiles = "C1CC(C2CC2)C1C2CC2"; ROMol *m = SmilesToMol(smiles); REQUIRE(m); MatchVectType match; delete aqm; aqm = MolOps::adjustQueryProperties(*qm); REQUIRE(SubstructMatch(*m, *qm, match)); REQUIRE(SubstructMatch(*m, *aqm, match)); MolOps::AdjustQueryParameters aqp; delete aqm; aqp.adjustRingCount = true; aqm = MolOps::adjustQueryProperties(*qm, &aqp); REQUIRE(aqm); REQUIRE(aqm->getNumAtoms() == 6); REQUIRE(SubstructMatch(*m, *aqm, match)); delete aqm; aqp.adjustRingCountFlags = MolOps::ADJUST_IGNORENONE; // neither "not dummy" // nor "in ring" // restrictions aqm = MolOps::adjustQueryProperties(*qm, &aqp); REQUIRE(aqm); REQUIRE(aqm->getNumAtoms() == 6); REQUIRE(!SubstructMatch(*m, *aqm, match)); delete aqm; aqp.adjustRingCountFlags = MolOps::ADJUST_IGNOREDUMMIES; // no "in ring" restrictions aqm = MolOps::adjustQueryProperties(*qm, &aqp); REQUIRE(aqm); REQUIRE(aqm->getNumAtoms() == 6); REQUIRE(SubstructMatch(*m, *aqm, match)); delete m; } delete qm; delete aqm; } { // dummies from SMILES std::string smiles = "C1CCC1*"; ROMol *qm = SmilesToMol(smiles); REQUIRE(qm); REQUIRE(qm->getNumAtoms() == 5); ROMol *aqm = MolOps::adjustQueryProperties(*qm); REQUIRE(aqm); REQUIRE(aqm->getNumAtoms() == 5); smiles = "C1CCC1CC"; ROMol *m = SmilesToMol(smiles); REQUIRE(m); MatchVectType match; REQUIRE(!SubstructMatch(*m, *qm, match)); REQUIRE(SubstructMatch(*m, *aqm, match)); delete aqm; MolOps::AdjustQueryParameters aqp; aqp.makeDummiesQueries = false; aqm = MolOps::adjustQueryProperties(*qm, &aqp); REQUIRE(!SubstructMatch(*m, *aqm, match)); delete m; delete qm; delete aqm; } { // dummies from SMILES 2 std::string smiles = "C1CCC1[1*]"; ROMol *qm = SmilesToMol(smiles); REQUIRE(qm); REQUIRE(qm->getNumAtoms() == 5); ROMol *aqm = MolOps::adjustQueryProperties(*qm); REQUIRE(aqm); REQUIRE(aqm->getNumAtoms() == 5); { smiles = "C1CCC1CC"; ROMol *m = SmilesToMol(smiles); REQUIRE(m); MatchVectType match; REQUIRE(!SubstructMatch(*m, *qm, match)); REQUIRE(!SubstructMatch(*m, *aqm, match)); delete m; } delete qm; delete aqm; } { // dummies from SMILES 2 std::string smiles = "C1CCC1[*:1]"; ROMol *qm = SmilesToMol(smiles); qm->getAtomWithIdx(4)->setProp("foo", 2); REQUIRE(qm); REQUIRE(qm->getNumAtoms() == 5); ROMol *aqm = MolOps::adjustQueryProperties(*qm); REQUIRE(aqm); REQUIRE(aqm->getNumAtoms() == 5); REQUIRE(aqm->getAtomWithIdx(4)->getProp("foo") == 2); REQUIRE(aqm->getAtomWithIdx(4)->getAtomMapNum() == 1); { smiles = "C1CCC1CC"; ROMol *m = SmilesToMol(smiles); REQUIRE(m); MatchVectType match; REQUIRE(!SubstructMatch(*m, *qm, match)); REQUIRE(SubstructMatch(*m, *aqm, match)); delete m; } delete qm; delete aqm; } { // CTAB // -- only match rgroups constexpr const char *mb = "adjust.mol\n" " ChemDraw06271617272D\n" "\n" " 7 7 0 0 0 0 0 0 0 0999 V2000\n" " -1.0717 0.4125 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " -1.0717 -0.4125 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " -0.3572 -0.8250 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " 0.3572 -0.4125 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " 0.3572 0.4125 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " -0.3572 0.8250 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " 1.0717 0.8250 0.0000 R 0 0 0 0 0 0 0 0 0 1 0 " "0\n" " 1 2 1 0 \n" " 2 3 2 0 \n" " 3 4 1 0 \n" " 4 5 2 0 \n" " 5 6 1 0 \n" " 6 1 2 0 \n" " 5 7 1 0 \n" "M END\n"; MolOps::AdjustQueryParameters params; params.aromatizeIfPossible = false; params.makeDummiesQueries = true; params.adjustDegreeFlags = (MolOps::ADJUST_IGNOREDUMMIES | MolOps::ADJUST_IGNORECHAINS | MolOps::ADJUST_IGNOREMAPPED); RWMol *m = MolBlockToMol(mb, false, false); MolOps::adjustQueryProperties(*m, ¶ms); MatchVectType match; ROMol *t = SmilesToMol("c1ccccc1Cl"); // shouldn't match (aromaticity): REQUIRE(!SubstructMatch(*t, *m, match)); // adjust aromaticity and then it should match: params.aromatizeIfPossible = true; MolOps::adjustQueryProperties(*m, ¶ms); REQUIRE(SubstructMatch(*t, *m, match)); delete t; // shouldn't match (explicit degree) t = SmilesToMol("c1ccc(Cl)cc1Cl"); REQUIRE(!SubstructMatch(*t, *m, match)); delete m; delete t; } { // CTAB // -- match non rgroups if mapped constexpr const char *mb = "adjust.mol\n" " ChemDraw06271617272D\n" "\n" " 7 7 0 0 0 0 0 0 0 0999 V2000\n" " -1.0717 0.4125 0.0000 C 0 0 0 0 0 0 0 0 0 2 0 " "0\n" " -1.0717 -0.4125 0.0000 C 0 0 0 0 0 0 0 0 0 3 0 " "0\n" " -0.3572 -0.8250 0.0000 C 0 0 0 0 0 0 0 0 0 4 0 " "0\n" " 0.3572 -0.4125 0.0000 C 0 0 0 0 0 0 0 0 0 5 0 " "0\n" " 0.3572 0.4125 0.0000 C 0 0 0 0 0 0 0 0 0 6 0 " "0\n" " -0.3572 0.8250 0.0000 C 0 0 0 0 0 0 0 0 0 7 0 " "0\n" " 1.0717 0.8250 0.0000 R 0 0 0 0 0 0 0 0 0 1 0 " "0\n" " 1 2 1 0 \n" " 2 3 2 0 \n" " 3 4 1 0 \n" " 4 5 2 0 \n" " 5 6 1 0 \n" " 6 1 2 0 \n" " 5 7 1 0 \n" "M END\n"; MolOps::AdjustQueryParameters params; params.aromatizeIfPossible = true; params.makeDummiesQueries = true; params.adjustDegreeFlags = (MolOps::ADJUST_IGNOREDUMMIES | MolOps::ADJUST_IGNORECHAINS | MolOps::ADJUST_IGNOREMAPPED); RWMol *m = MolBlockToMol(mb); MolOps::adjustQueryProperties(*m, ¶ms); MatchVectType match; ROMol *t = SmilesToMol("c1ccccc1Cl"); REQUIRE(SubstructMatch(*t, *m, match)); delete t; // should match (mapped!) t = SmilesToMol("c1c(Cl)cc(Cl)cc1Cl"); REQUIRE(SubstructMatch(*t, *m, match)); delete m; delete t; } { // make atoms generic constexpr const char *smiles = "C1CC1CC"; ROMol *qm = SmilesToMol(smiles); REQUIRE(qm); REQUIRE(qm->getNumAtoms() == 5); { MolOps::AdjustQueryParameters params; params.makeAtomsGeneric = true; ROMol *aqm = MolOps::adjustQueryProperties(*qm, ¶ms); REQUIRE(aqm); REQUIRE(aqm->getNumAtoms() == qm->getNumAtoms()); { MatchVectType match; REQUIRE(SubstructMatch(*qm, *aqm, match)); constexpr const char *smiles = "O1CN1NN"; ROMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(!SubstructMatch(*m, *qm, match)); REQUIRE(SubstructMatch(*m, *aqm, match)); delete m; } delete aqm; } { MolOps::AdjustQueryParameters params; params.makeAtomsGeneric = true; params.makeAtomsGenericFlags = MolOps::ADJUST_IGNORECHAINS; ROMol *aqm = MolOps::adjustQueryProperties(*qm, ¶ms); REQUIRE(aqm); REQUIRE(aqm->getNumAtoms() == qm->getNumAtoms()); { MatchVectType match; REQUIRE(SubstructMatch(*qm, *aqm, match)); std::string smiles = "O1CN1NN"; ROMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(!SubstructMatch(*m, *qm, match)); REQUIRE(!SubstructMatch(*m, *aqm, match)); delete m; smiles = "O1CN1CC"; m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(!SubstructMatch(*m, *qm, match)); REQUIRE(SubstructMatch(*m, *aqm, match)); delete m; } delete aqm; } { MolOps::AdjustQueryParameters params; params.makeAtomsGeneric = true; params.makeAtomsGenericFlags = MolOps::ADJUST_IGNORERINGS; ROMol *aqm = MolOps::adjustQueryProperties(*qm, ¶ms); REQUIRE(aqm); REQUIRE(aqm->getNumAtoms() == qm->getNumAtoms()); { MatchVectType match; REQUIRE(SubstructMatch(*qm, *aqm, match)); std::string smiles = "O1CN1NN"; ROMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(!SubstructMatch(*m, *qm, match)); REQUIRE(!SubstructMatch(*m, *aqm, match)); delete m; smiles = "C1CC1NN"; m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(!SubstructMatch(*m, *qm, match)); REQUIRE(SubstructMatch(*m, *aqm, match)); delete m; } delete aqm; } delete qm; } { // make bonds generic constexpr const char *smiles = "N1C=C1C=C"; ROMol *qm = SmilesToMol(smiles); REQUIRE(qm); REQUIRE(qm->getNumAtoms() == 5); { MolOps::AdjustQueryParameters params; params.makeBondsGeneric = true; ROMol *aqm = MolOps::adjustQueryProperties(*qm, ¶ms); REQUIRE(aqm); REQUIRE(aqm->getNumAtoms() == qm->getNumAtoms()); { MatchVectType match; REQUIRE(SubstructMatch(*qm, *aqm, match)); constexpr const char *smiles = "N1=CC1=CC"; ROMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(!SubstructMatch(*m, *qm, match)); REQUIRE(SubstructMatch(*m, *aqm, match)); delete m; } delete aqm; } { MolOps::AdjustQueryParameters params; params.makeBondsGeneric = true; params.makeBondsGenericFlags = MolOps::ADJUST_IGNORECHAINS; ROMol *aqm = MolOps::adjustQueryProperties(*qm, ¶ms); REQUIRE(aqm); REQUIRE(aqm->getNumAtoms() == qm->getNumAtoms()); { MatchVectType match; REQUIRE(SubstructMatch(*qm, *aqm, match)); std::string smiles = "N1=CC1=C=C"; ROMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(!SubstructMatch(*m, *qm, match)); REQUIRE(!SubstructMatch(*m, *aqm, match)); delete m; smiles = "N1=CC1C=C"; m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(!SubstructMatch(*m, *qm, match)); REQUIRE(SubstructMatch(*m, *aqm, match)); delete m; } delete aqm; } { MolOps::AdjustQueryParameters params; params.makeBondsGeneric = true; params.makeBondsGenericFlags = MolOps::ADJUST_IGNORERINGS; ROMol *aqm = MolOps::adjustQueryProperties(*qm, ¶ms); REQUIRE(aqm); REQUIRE(aqm->getNumAtoms() == qm->getNumAtoms()); { MatchVectType match; REQUIRE(SubstructMatch(*qm, *aqm, match)); std::string smiles = "N1=CC1=C=C"; ROMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(!SubstructMatch(*m, *qm, match)); REQUIRE(!SubstructMatch(*m, *aqm, match)); delete m; smiles = "N1C=C1CC#C"; m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(!SubstructMatch(*m, *qm, match)); REQUIRE(SubstructMatch(*m, *aqm, match)); delete m; } delete aqm; } delete qm; } { // heavy atom degree std::string smiles = "C1CC(*)C1*"; ROMol *qm = SmartsToMol(smiles); REQUIRE(qm); REQUIRE(qm->getNumAtoms() == 6); MolOps::AdjustQueryParameters params; params.adjustDegree = false; params.adjustHeavyDegree = true; ROMol *aqm = MolOps::adjustQueryProperties(*qm, ¶ms); REQUIRE(aqm); REQUIRE(aqm->getNumAtoms() == 6); { smiles = "C1CC(C)C1(C)"; ROMol *m = SmilesToMol(smiles); REQUIRE(m); MatchVectType match; REQUIRE(SubstructMatch(*m, *qm, match)); REQUIRE(SubstructMatch(*m, *aqm, match)); delete m; } { smiles = "C1CC([2H])C1(C)"; ROMol *m = SmilesToMol(smiles); REQUIRE(m); MatchVectType match; REQUIRE(SubstructMatch(*m, *qm, match)); REQUIRE(!SubstructMatch(*m, *aqm, match)); delete m; } delete qm; delete aqm; } { // ring-chain membership std::string smiles = "CC1CCC1"; ROMol *qm = SmartsToMol(smiles); REQUIRE(qm); REQUIRE(qm->getNumAtoms() == 5); MolOps::AdjustQueryParameters params; params.adjustRingChain = true; params.adjustDegree = false; ROMol *aqm = MolOps::adjustQueryProperties(*qm, ¶ms); REQUIRE(aqm); REQUIRE(aqm->getNumAtoms() == 5); { smiles = "C1CCC12CCC2"; ROMol *m = SmilesToMol(smiles); REQUIRE(m); MatchVectType match; REQUIRE(SubstructMatch(*m, *qm, match)); REQUIRE(!SubstructMatch(*m, *aqm, match)); delete m; } { smiles = "C1CCC1CCC"; ROMol *m = SmilesToMol(smiles); REQUIRE(m); MatchVectType match; REQUIRE(SubstructMatch(*m, *qm, match)); REQUIRE(SubstructMatch(*m, *aqm, match)); delete m; } delete qm; delete aqm; } } TEST_CASE( "Testing github issue 678: failure in AddHs when addCoords is true and coords are all zero") { { constexpr const char *smiles = "CC"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); auto *conf = new Conformer(2); m->addConformer(conf); MolOps::addHs(*m, false, true); REQUIRE(m->getNumAtoms() == 8); delete m; } { // single connected atom with degenerate coords constexpr const char *mb = "example\n" " Mrv0541 12171503572D \n" "\n" " 7 8 0 0 0 0 999 V2000\n" " -3.5063 2.5339 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " -3.5063 1.7089 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " -2.6813 2.5339 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " -2.6813 1.7089 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " -1.8563 2.5339 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " -1.8563 1.7089 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " -1.8563 2.5339 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " 1 2 1 0 0 0 0\n" " 1 3 1 0 0 0 0\n" " 3 4 1 0 0 0 0\n" " 2 4 1 0 0 0 0\n" " 3 5 1 0 0 0 0\n" " 5 6 1 0 0 0 0\n" " 4 6 1 0 0 0 0\n" " 5 7 1 0 0 0 0\n" "M END\n"; RWMol *m = MolBlockToMol(mb); REQUIRE(m); MolOps::addHs(*m, false, true); REQUIRE(m->getNumAtoms() == 19); delete m; } { // doubly connected atom(s) with degenerate coords constexpr const char *mb = "example\n" " Mrv0541 12171503572D \n" "\n" " 7 8 0 0 0 0 999 V2000\n" " -3.5063 2.5339 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " -3.5063 2.5339 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " -2.6813 2.5339 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " -2.6813 1.7089 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " -1.8563 2.5339 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " -1.8563 1.7089 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " -1.2729 3.1173 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " 1 2 1 0 0 0 0\n" " 1 3 1 0 0 0 0\n" " 3 4 1 0 0 0 0\n" " 2 4 1 0 0 0 0\n" " 3 5 1 0 0 0 0\n" " 5 6 1 0 0 0 0\n" " 4 6 1 0 0 0 0\n" " 5 7 1 0 0 0 0\n" "M END\n"; RWMol *m = MolBlockToMol(mb); REQUIRE(m); MolOps::addHs(*m, false, true); REQUIRE(m->getNumAtoms() == 19); delete m; } { // triply connected atom(s) with degenerate coords constexpr const char *mb = "example\n" " Mrv0541 12171503572D \n" "\n" " 7 8 0 0 0 0 999 V2000\n" " -3.5063 2.5339 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " -3.5063 1.7089 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " -3.5063 2.5339 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " -3.5063 2.5339 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " -1.8563 2.5339 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " -1.8563 1.7089 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " -1.2729 3.1173 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " 1 2 1 0 0 0 0\n" " 1 3 1 0 0 0 0\n" " 3 4 1 0 0 0 0\n" " 2 4 1 0 0 0 0\n" " 3 5 1 0 0 0 0\n" " 5 6 1 0 0 0 0\n" " 4 6 1 0 0 0 0\n" " 5 7 1 0 0 0 0\n" "M END\n"; RWMol *m = MolBlockToMol(mb); REQUIRE(m); MolOps::addHs(*m, false, true); REQUIRE(m->getNumAtoms() == 19); delete m; } } TEST_CASE( "Testing github issue 717: AddHs cip rank is declared should be unsigned int") { // single connected atom with degenerate coords constexpr const char *mb = "mol\n" " Mrv1561 01051606293D\n" "\n" " 4 3 0 0 0 0 999 V2000\n" " -0.0080 -0.0000 -0.0004 C 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " 1.5343 -0.0050 0.0032 C 0 0 1 0 0 0 0 0 0 0 0 " "0\n" " 2.1517 -0.8276 1.4332 Cl 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " 2.0123 -0.6447 -1.1142 F 0 0 0 0 0 0 0 0 0 0 0 " "0\n" " 2 3 1 0 0 0 0\n" " 2 4 1 0 0 0 0\n" " 2 1 1 0 0 0 0\n" "M END\n"; RWMol *m = MolBlockToMol(mb); REQUIRE(m); MolOps::assignChiralTypesFrom3D(*m); MolOps::assignStereochemistry(*m, true, true); MolOps::addHs(*m, false, true); REQUIRE(m->getNumAtoms() == 8); delete m; } TEST_CASE("Testing findPotentialStereoBonds") { { // starting point: full sanitization auto m1 = "BrC(=NN=c1nn[nH][nH]1)c1ccncc1"_smiles; REQUIRE(m1); constexpr const char *smiles = "Br/C(=N\\N=c1/nn[nH][nH]1)c1ccncc1"; // possible problem reported by // Steve Roughley ROMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getNumAtoms() == 15); REQUIRE(m->getBondWithIdx(1)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(1)->getStereoAtoms().size() == 2); REQUIRE(m->getBondWithIdx(3)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(3)->getStereoAtoms().size() == 2); delete m; // partial sanitization: m = SmilesToMol(smiles, false, false); REQUIRE(m); m->updatePropertyCache(true); MolOps::findSSSR(*m); MolOps::findPotentialStereoBonds(*m, false); REQUIRE(m->getNumAtoms() == 15); REQUIRE(m->getBondWithIdx(1)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(1)->getStereoAtoms().size() == 2); REQUIRE(m->getBondWithIdx(3)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(3)->getStereoAtoms().size() == 2); delete m; } // this next block is for github1230: FindPotentialStereoBonds() failure { // simple constexpr const char *smiles = "CC=CC"; ROMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE(m->getBondWithIdx(1)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(1)->getStereo() == Bond::STEREONONE); MolOps::findPotentialStereoBonds(*m, true); REQUIRE(m->getBondWithIdx(1)->getStereo() == Bond::STEREOANY); delete m; } { // simple2 constexpr const char *smiles = "CC=C(C)C"; ROMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getNumAtoms() == 5); REQUIRE(m->getBondWithIdx(1)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(1)->getStereo() == Bond::STEREONONE); MolOps::findPotentialStereoBonds(*m, true); REQUIRE(m->getBondWithIdx(1)->getStereo() == Bond::STEREONONE); delete m; } { // the real problem constexpr const char *smiles = "CC/C=C/C(\\C=C/CC)=C(CC)CO"; ROMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getNumAtoms() == 14); REQUIRE(m->getBondWithIdx(2)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(2)->getStereo() == Bond::STEREOE); REQUIRE(m->getBondWithIdx(5)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(5)->getStereo() == Bond::STEREOZ); REQUIRE(m->getBondWithIdx(8)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(8)->getStereo() == Bond::STEREONONE); MolOps::findPotentialStereoBonds(*m, true); REQUIRE(m->getBondWithIdx(2)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(2)->getStereo() == Bond::STEREOANY); REQUIRE(m->getBondWithIdx(5)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(5)->getStereo() == Bond::STEREOANY); REQUIRE(m->getBondWithIdx(8)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(8)->getStereo() == Bond::STEREOANY); delete m; } { // repeat the real problem, but set the cleanIt argument to false constexpr const char *smiles = "CC/C=C/C(\\C=C/CC)=C(CC)CO"; ROMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getNumAtoms() == 14); REQUIRE(m->getBondWithIdx(2)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(2)->getStereo() == Bond::STEREOE); REQUIRE(m->getBondWithIdx(5)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(5)->getStereo() == Bond::STEREOZ); REQUIRE(m->getBondWithIdx(8)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(8)->getStereo() == Bond::STEREONONE); MolOps::findPotentialStereoBonds(*m, false); REQUIRE(m->getBondWithIdx(2)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(2)->getStereo() == Bond::STEREOE); REQUIRE(m->getBondWithIdx(5)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(5)->getStereo() == Bond::STEREOZ); REQUIRE(m->getBondWithIdx(8)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(8)->getStereo() == Bond::STEREOANY); delete m; } { // just do document that we still don't do this one, which is much harder constexpr const char *smiles = "CC/C=C/C(/C=C/CC)=C(CC)CO"; ROMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getNumAtoms() == 14); REQUIRE(m->getBondWithIdx(2)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(2)->getStereo() == Bond::STEREOE); REQUIRE(m->getBondWithIdx(5)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(5)->getStereo() == Bond::STEREOE); REQUIRE(m->getBondWithIdx(8)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(8)->getStereo() == Bond::STEREONONE); MolOps::findPotentialStereoBonds(*m, true); REQUIRE(m->getBondWithIdx(2)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(2)->getStereo() == Bond::STEREOANY); REQUIRE(m->getBondWithIdx(5)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(5)->getStereo() == Bond::STEREOANY); REQUIRE(m->getBondWithIdx(8)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(8)->getStereo() == Bond::STEREONONE); delete m; } } TEST_CASE("Testing Bond::setStereo(Bond::STEREOCIS / Bond::STEREOTRANS)") { // tests to make sure neighboring bond stereo is handled properly { constexpr const char *smiles[] = { "CC=CC", "CC=C/C=C/C", "CC=C/C=C\\C", "CC=C\\C=C/C", "CC=C\\C=C\\C", "C(C)=CC", "C(C)=C/C=C/C", "C(C)=C/C=C\\C", "C(C)=C\\C=C/C", "C(C)=C\\C=C\\C"}; constexpr Bond::BondStereo stereos[] = {Bond::STEREOCIS, Bond::STEREOTRANS}; constexpr Bond::BondStereo ezstros[] = {Bond::STEREOZ, Bond::STEREOE}; for (auto &smile : smiles) { ROMol *m = SmilesToMol(smile); MolOps::findPotentialStereoBonds(*m); Bond *bond = m->getBondWithIdx(1); for (size_t j = 0; j < 2; ++j) { Bond::BondStereo desired_stereo = stereos[j]; bond->setStereo(desired_stereo); bool doIsomericSmiles = true; bool doKekule = false; int rootedAtAtom = -1; bool canonical = false; std::string isosmi = MolToSmiles(*m, doIsomericSmiles, doKekule, rootedAtAtom, canonical); ROMol *isomol = SmilesToMol(isosmi); Bond *isobond = isomol->getBondWithIdx(1); const Bond::BondStereo expected_ez_stereo = ezstros[j]; REQUIRE(isobond->getStereo() == expected_ez_stereo); std::string round_trip_isosmi = MolToSmiles( *m, doIsomericSmiles, doKekule, rootedAtAtom, canonical); REQUIRE(isosmi == round_trip_isosmi); BOOST_LOG(rdInfoLog) << isosmi << " == " << round_trip_isosmi << " " << desired_stereo << std::endl; delete isomol; } delete m; } } // tests enumerating all possible smiles with halogens still yield // the same isomeric canonical smiles strings. { constexpr const char *smiles[] = {"ClC=CF", "FC=CCl", "C(Cl)=CF", "C(F)=CCl"}; constexpr Bond::BondStereo stereos[] = {Bond::STEREOCIS, Bond::STEREOTRANS}; for (auto desired_stereo : stereos) { std::string refSmiles; for (auto &smile : smiles) { ROMol *m = SmilesToMol(smile); MolOps::findPotentialStereoBonds(*m); REQUIRE(m->getNumAtoms() == 4); Bond *doubleBond = m->getBondWithIdx(1); doubleBond->setStereo(desired_stereo); bool doIsomericSmiles = true; std::string isocansmi = MolToSmiles(*m, doIsomericSmiles); if (refSmiles.empty()) { refSmiles = isocansmi; } BOOST_LOG(rdInfoLog) << refSmiles << " == " << isocansmi << " " << desired_stereo << std::endl; REQUIRE(refSmiles == isocansmi); delete m; } } } } TEST_CASE("Testing Bond::setStereoAtoms(...)") { // tests to make sure setStereoAtoms works as expected bool doIsomericSmiles = true; constexpr const char *unspec_smiles = "FC(Cl)=C(Br)I"; ROMol *m = SmilesToMol(unspec_smiles); Bond *doubleBond = m->getBondWithIdx(2); REQUIRE(doubleBond->getBondType() == 2); doubleBond->setStereoAtoms(0, 4); doubleBond->setStereo(Bond::STEREOCIS); BOOST_LOG(rdInfoLog) << MolToSmiles(*m, doIsomericSmiles) << std::endl; REQUIRE(MolToSmiles(*m, doIsomericSmiles) == "F/C(Cl)=C(\\Br)I"); // this should be the same as the previous doubleBond->setStereoAtoms(0, 5); doubleBond->setStereo(Bond::STEREOTRANS); BOOST_LOG(rdInfoLog) << MolToSmiles(*m, doIsomericSmiles) << std::endl; REQUIRE(MolToSmiles(*m, doIsomericSmiles) == "F/C(Cl)=C(\\Br)I"); doubleBond->setStereoAtoms(0, 4); doubleBond->setStereo(Bond::STEREOTRANS); BOOST_LOG(rdInfoLog) << MolToSmiles(*m, doIsomericSmiles) << std::endl; REQUIRE(MolToSmiles(*m, doIsomericSmiles) == "F/C(Cl)=C(/Br)I"); // this should be the same as the previous doubleBond->setStereoAtoms(0, 5); doubleBond->setStereo(Bond::STEREOCIS); BOOST_LOG(rdInfoLog) << MolToSmiles(*m, doIsomericSmiles) << std::endl; REQUIRE(MolToSmiles(*m, doIsomericSmiles) == "F/C(Cl)=C(/Br)I"); doubleBond->setStereoAtoms(3, 4); doubleBond->setStereo(Bond::STEREOTRANS); BOOST_LOG(rdInfoLog) << MolToSmiles(*m, doIsomericSmiles) << std::endl; REQUIRE(MolToSmiles(*m, doIsomericSmiles) == "F/C(Cl)=C(\\Br)I"); // this should be the same as the previous doubleBond->setStereoAtoms(3, 5); doubleBond->setStereo(Bond::STEREOCIS); BOOST_LOG(rdInfoLog) << MolToSmiles(*m, doIsomericSmiles) << std::endl; REQUIRE(MolToSmiles(*m, doIsomericSmiles) == "F/C(Cl)=C(\\Br)I"); doubleBond->setStereoAtoms(3, 4); doubleBond->setStereo(Bond::STEREOCIS); BOOST_LOG(rdInfoLog) << MolToSmiles(*m, doIsomericSmiles) << std::endl; REQUIRE(MolToSmiles(*m, doIsomericSmiles) == "F/C(Cl)=C(/Br)I"); // this should be the same as the previous doubleBond->setStereoAtoms(3, 5); doubleBond->setStereo(Bond::STEREOTRANS); BOOST_LOG(rdInfoLog) << MolToSmiles(*m, doIsomericSmiles) << std::endl; REQUIRE(MolToSmiles(*m, doIsomericSmiles) == "F/C(Cl)=C(/Br)I"); delete m; } TEST_CASE("Testing github #754 : loss of double bond geometry with removeHs") { { // starting point: full sanitization constexpr const char *smiles = "[H]C([H])([H])/C([H])=C(/[H])C([H])([H])[H]"; // possible problem // reported by // Steve Roughley RWMol *m = SmilesToMol(smiles, false, false); REQUIRE(m); MolOps::sanitizeMol(*m); MolOps::assignStereochemistry(*m, true, true); REQUIRE(m->getNumAtoms() == 12); REQUIRE(m->getBondWithIdx(5)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(5)->getStereo() == Bond::STEREOZ); delete m; m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE(m->getBondWithIdx(1)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondWithIdx(1)->getStereo() == Bond::STEREOZ); delete m; } { // another basic test constexpr const char *smiles = "[H]/C(C)=C/C"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE(m->getBondBetweenAtoms(0, 2)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondBetweenAtoms(0, 2)->getStereo() == Bond::STEREOZ); delete m; } { // H following the C: constexpr const char *smiles = "CC(\\[H])=C/C"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE(m->getBondBetweenAtoms(1, 2)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondBetweenAtoms(1, 2)->getStereo() == Bond::STEREOZ); delete m; } { // bond dir already set : constexpr const char *smiles = "[H]/C(/C)=C\\C"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE(m->getBondBetweenAtoms(0, 2)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondBetweenAtoms(0, 2)->getStereo() == Bond::STEREOE); delete m; } { // chained bonds : constexpr const char *smiles = "[H]/C(C=C/C)=C\\C"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getNumAtoms() == 6); REQUIRE(m->getBondBetweenAtoms(0, 4)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondBetweenAtoms(0, 4)->getStereo() == Bond::STEREOE); REQUIRE(m->getBondBetweenAtoms(1, 2)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondBetweenAtoms(1, 2)->getStereo() == Bond::STEREOE); delete m; } } TEST_CASE("Testing github #805 : Pre-condition Violation: bad bond type") { { std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; ROMol *m = MolFileToMol(pathName + "pubchem_87396055.sdf"); REQUIRE(m); REQUIRE(m->getNumAtoms() == 20); REQUIRE(m->getBondBetweenAtoms(2, 6)->getBondType() == Bond::SINGLE); REQUIRE(m->getAtomWithIdx(2)->getFormalCharge() == 1); REQUIRE(m->getAtomWithIdx(6)->getFormalCharge() == -1); REQUIRE(m->getBondBetweenAtoms(2, 9)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondBetweenAtoms(2, 9)->getStereo() != Bond::STEREONONE); REQUIRE(m->getBondBetweenAtoms(3, 10)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondBetweenAtoms(3, 10)->getStereo() != Bond::STEREONONE); std::string smi = MolToSmiles(*m, true); REQUIRE(smi == R"SMI(CCO/[P+]([O-])=C1\CSC(c2cccs2)\C1=[P+](\[O-])OCC)SMI"); delete m; } { std::string smi = "O=P(/O)=C/C"; ROMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 5); REQUIRE(m->getBondBetweenAtoms(0, 1)->getBondType() == Bond::SINGLE); REQUIRE(m->getAtomWithIdx(1)->getFormalCharge() == 1); REQUIRE(m->getAtomWithIdx(0)->getFormalCharge() == -1); REQUIRE(m->getBondBetweenAtoms(1, 3)->getBondType() == Bond::DOUBLE); REQUIRE(m->getBondBetweenAtoms(1, 3)->getStereo() != Bond::STEREONONE); smi = MolToSmiles(*m, true); REQUIRE(smi == "C/C=[P+](/[O-])O"); delete m; } } TEST_CASE( "Testing github #518 : Rings containing all dummy atoms with single bonds are flagged as aromatic") { { constexpr const char *smi = "*-1-*-*-*-1"; ROMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE(!m->getBondWithIdx(0)->getIsAromatic()); REQUIRE(m->getBondWithIdx(0)->getBondType() == Bond::SINGLE); REQUIRE(!m->getAtomWithIdx(0)->getIsAromatic()); delete m; } { // in this case we leave it aromatic since it's all dummies constexpr const char *smi = "*:1:*:*:*:1"; ROMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); REQUIRE(!m->getBondWithIdx(0)->getIsAromatic()); REQUIRE(m->getBondWithIdx(0)->getBondType() == Bond::AROMATIC); REQUIRE(!m->getAtomWithIdx(0)->getIsAromatic()); delete m; } { constexpr const char *smi = "*-1-*-C-*-*-*-1"; ROMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 6); REQUIRE(!m->getBondWithIdx(0)->getIsAromatic()); REQUIRE(m->getBondWithIdx(0)->getBondType() == Bond::SINGLE); REQUIRE(!m->getAtomWithIdx(0)->getIsAromatic()); delete m; } { constexpr const char *smi = "C1=CC=*2*(=C1)*1=CC=CC=*1*1=CC=CC=*21"; ROMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 18); REQUIRE(!m->getBondBetweenAtoms(4, 6)->getIsAromatic()); REQUIRE(m->getBondBetweenAtoms(4, 6)->getBondType() == Bond::SINGLE); REQUIRE(!m->getBondBetweenAtoms(11, 12)->getIsAromatic()); REQUIRE(m->getBondBetweenAtoms(11, 12)->getBondType() == Bond::SINGLE); REQUIRE(!m->getBondBetweenAtoms(3, 17)->getIsAromatic()); REQUIRE(m->getBondBetweenAtoms(3, 17)->getBondType() == Bond::SINGLE); delete m; } } TEST_CASE("Testing simple aromaticity") { { constexpr const char *smiles = "c1ccccc1"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getBondWithIdx(0)->getIsAromatic() == true); REQUIRE(m->getAtomWithIdx(0)->getIsAromatic() == true); MolOps::Kekulize(*m, true); REQUIRE(m->getBondWithIdx(0)->getIsAromatic() == false); REQUIRE(m->getAtomWithIdx(0)->getIsAromatic() == false); MolOps::setAromaticity(*m, MolOps::AROMATICITY_SIMPLE); REQUIRE(m->getBondWithIdx(0)->getIsAromatic() == true); REQUIRE(m->getAtomWithIdx(0)->getIsAromatic() == true); delete m; } { constexpr const char *smiles = "c1[nH]ccc1"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getBondWithIdx(0)->getIsAromatic() == true); REQUIRE(m->getAtomWithIdx(0)->getIsAromatic() == true); MolOps::Kekulize(*m, true); REQUIRE(m->getBondWithIdx(0)->getIsAromatic() == false); REQUIRE(m->getAtomWithIdx(0)->getIsAromatic() == false); MolOps::setAromaticity(*m, MolOps::AROMATICITY_SIMPLE); REQUIRE(m->getBondWithIdx(0)->getIsAromatic() == true); REQUIRE(m->getAtomWithIdx(0)->getIsAromatic() == true); delete m; } { // ring size constraints constexpr const char *smiles = "c1cccoocc1"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getBondWithIdx(0)->getIsAromatic() == true); REQUIRE(m->getAtomWithIdx(0)->getIsAromatic() == true); MolOps::Kekulize(*m, true); REQUIRE(m->getBondWithIdx(0)->getIsAromatic() == false); REQUIRE(m->getAtomWithIdx(0)->getIsAromatic() == false); MolOps::setAromaticity(*m, MolOps::AROMATICITY_SIMPLE); REQUIRE(m->getBondWithIdx(0)->getIsAromatic() == false); REQUIRE(m->getAtomWithIdx(0)->getIsAromatic() == false); delete m; } { // ring size constraints constexpr const char *smiles = "c1coo1"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getBondWithIdx(0)->getIsAromatic() == true); REQUIRE(m->getAtomWithIdx(0)->getIsAromatic() == true); MolOps::Kekulize(*m, true); REQUIRE(m->getBondWithIdx(0)->getIsAromatic() == false); REQUIRE(m->getAtomWithIdx(0)->getIsAromatic() == false); MolOps::setAromaticity(*m, MolOps::AROMATICITY_SIMPLE); REQUIRE(m->getBondWithIdx(0)->getIsAromatic() == false); REQUIRE(m->getAtomWithIdx(0)->getIsAromatic() == false); delete m; } { // fused rings are not considered constexpr const char *smiles = "C1=CC2=CC=CC=CC2=C1"; // azulene RWMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getBondWithIdx(0)->getIsAromatic() == true); REQUIRE(m->getAtomWithIdx(0)->getIsAromatic() == true); MolOps::Kekulize(*m, true); REQUIRE(m->getBondWithIdx(0)->getIsAromatic() == false); REQUIRE(m->getAtomWithIdx(0)->getIsAromatic() == false); MolOps::setAromaticity(*m, MolOps::AROMATICITY_SIMPLE); REQUIRE(m->getBondWithIdx(0)->getIsAromatic() == false); REQUIRE(m->getAtomWithIdx(0)->getIsAromatic() == false); delete m; } } TEST_CASE("Testing MMFF94 aromaticity") { // test one known difference between RDKit and MMFF94 aromaticity models: // the latter does not recognize azulene as aromatic constexpr const char *smiles = "C1=CC=C2C=CC=C2C=C1"; RWMol *m = SmilesToMol(smiles); MolOps::Kekulize(*m, true); MolOps::setAromaticity(*m, MolOps::AROMATICITY_RDKIT); int arombondcount = 0; for (auto b : m->bonds()) { if (b->getIsAromatic()) { arombondcount++; } } // all bonds, except the fused one, should be aromatic REQUIRE(arombondcount == 10); REQUIRE(m->getBondBetweenAtoms(3, 7)->getIsAromatic() == false); MolOps::setAromaticity(*m, MolOps::AROMATICITY_MMFF94); arombondcount = 0; for (auto b : m->bonds()) { if (b->getIsAromatic()) { arombondcount++; } } // no aromatics here REQUIRE(arombondcount == 0); delete m; } //! really dumb aromaticity: any conjugated ring bond is aromatic int customAromaticity(RWMol &m) { m.updatePropertyCache(); MolOps::setConjugation(m); MolOps::fastFindRings(m); int res = 0; for (ROMol::BondIterator bIt = m.beginBonds(); bIt != m.endBonds(); ++bIt) { if ((*bIt)->getIsConjugated() && queryIsBondInRing(*bIt)) { (*bIt)->setIsAromatic(true); (*bIt)->getBeginAtom()->setIsAromatic(true); (*bIt)->getEndAtom()->setIsAromatic(true); ++res; } } return res; } TEST_CASE("Testing custom aromaticity") { { constexpr const char *smiles = "C1=CC=CC=C1"; RWMol *m = SmilesToMol(smiles, 0, false); REQUIRE(m); REQUIRE(m->getBondWithIdx(0)->getIsAromatic() == false); REQUIRE(m->getAtomWithIdx(0)->getIsAromatic() == false); MolOps::setAromaticity(*m, MolOps::AROMATICITY_CUSTOM, customAromaticity); REQUIRE(m->getBondWithIdx(0)->getIsAromatic() == true); REQUIRE(m->getAtomWithIdx(0)->getIsAromatic() == true); delete m; } { constexpr const char *smiles = "C1CC=CC=C1"; RWMol *m = SmilesToMol(smiles, 0, false); REQUIRE(m); REQUIRE(m->getBondWithIdx(0)->getIsAromatic() == false); REQUIRE(m->getAtomWithIdx(0)->getIsAromatic() == false); MolOps::setAromaticity(*m, MolOps::AROMATICITY_CUSTOM, customAromaticity); REQUIRE(m->getBondWithIdx(0)->getIsAromatic() == false); REQUIRE(m->getAtomWithIdx(0)->getIsAromatic() == false); REQUIRE(m->getBondWithIdx(2)->getIsAromatic() == true); REQUIRE(m->getAtomWithIdx(2)->getIsAromatic() == true); delete m; } } TEST_CASE( "Testing github issue #1730: setAromaticity() should work even if there are aromatic atoms present") { constexpr const char *smiles = "C1=CC=CC=C1-c2ccccc2"; RWMol *m = SmilesToMol(smiles, 0, false); m->updatePropertyCache(); REQUIRE(m); REQUIRE(m->getBondWithIdx(0)->getIsAromatic() == false); REQUIRE(m->getAtomWithIdx(0)->getIsAromatic() == false); REQUIRE(m->getBondWithIdx(6)->getIsAromatic() == true); REQUIRE(m->getAtomWithIdx(6)->getIsAromatic() == true); MolOps::setAromaticity(*m); REQUIRE(m->getBondWithIdx(0)->getIsAromatic() == true); REQUIRE(m->getAtomWithIdx(0)->getIsAromatic() == true); delete m; } TEST_CASE("Testing error reporting for kekulization") { std::stringstream sstrm; rdErrorLog->SetTee(sstrm); { sstrm.str(""); constexpr const char *smi = "c1ccccc1"; ROMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(sstrm.str() == ""); delete m; } { sstrm.str(""); REQUIRE_THROWS_AS(SmilesToMol("c1cccc1"), MolSanitizeException); REQUIRE(sstrm.str().find("0 1 2 3 4") != std::string::npos); } { sstrm.str(""); REQUIRE_THROWS_AS(SmilesToMol("c1ccccc1.c1cccc1"), MolSanitizeException); REQUIRE(sstrm.str().find("6 7 8 9 10") != std::string::npos); } { sstrm.str(""); REQUIRE_THROWS_AS(SmilesToMol("c1cccc1.c1cccc1"), MolSanitizeException); REQUIRE(sstrm.str().find("0 1 2 3 4") != std::string::npos); } rdErrorLog->ClearTee(); } TEST_CASE( "Testing github issue #868: inappropriate warning from MergeQueryHs") { std::stringstream sstrm; rdWarningLog->SetTee(sstrm); { sstrm.str(""); constexpr const char *sma = "[SX3](=O)[O-,#1]"; RWMol *m = SmartsToMol(sma); REQUIRE(m); REQUIRE(m->getNumAtoms() == 3); MolOps::mergeQueryHs(*m); REQUIRE( sstrm.str().find( "merging explicit H queries involved in ORs is not supported") != std::string::npos); REQUIRE(sstrm.str().find("This query will not be merged") != std::string::npos); delete m; } { sstrm.str(""); constexpr const char *sma = "[SX3](=O)[O-,H1]"; RWMol *m = SmartsToMol(sma); REQUIRE(m); REQUIRE(m->getNumAtoms() == 3); MolOps::mergeQueryHs(*m); REQUIRE(sstrm.str().find("merging explicit H queries involved in " "ORs is not supported") == std::string::npos); REQUIRE(sstrm.str().find("This query will not be merged") == std::string::npos); delete m; } { sstrm.str(""); constexpr const char *sma = "[SX3](=O)[O-,H]"; RWMol *m = SmartsToMol(sma); REQUIRE(m); REQUIRE(m->getNumAtoms() == 3); MolOps::mergeQueryHs(*m); REQUIRE(sstrm.str().find("merging explicit H queries involved in " "ORs is not supported") == std::string::npos); REQUIRE(sstrm.str().find("This query will not be merged") == std::string::npos); delete m; } // test atom type query merging for (int aromatic = 0; aromatic < 2; ++aromatic) { sstrm.str(""); REQUIRE(sstrm.str() == ""); RWMol m; auto qa = new QueryAtom(); qa->setQuery(makeAtomTypeQuery(1, aromatic)); qa->expandQuery(makeAtomNumQuery(6), Queries::CompositeQueryType::COMPOSITE_OR); m.addAtom(qa, true, true); MolOps::mergeQueryHs(m); REQUIRE(sstrm.str().find("merging explicit H queries involved in " "ORs is not supported") != std::string::npos); REQUIRE(sstrm.str().find("This query will not be merged") != std::string::npos); } { sstrm.str(""); REQUIRE(sstrm.str() == ""); // github 7687 - merge with more than one option in or constexpr const char *sma = "[#6]-[#1,#6,#7]"; RWMol *m = SmartsToMol(sma); REQUIRE(m); REQUIRE(m->getNumAtoms() == 2); MolOps::mergeQueryHs(*m); std::cerr << sstrm.str() << std::endl; REQUIRE(sstrm.str().find("merging explicit H queries involved in " "ORs is not supported") != std::string::npos); REQUIRE(sstrm.str().find("This query will not be merged") != std::string::npos); delete m; } rdWarningLog->ClearTee(); } TEST_CASE( "Testing github issue 908: AddHs() using 3D coordinates with 2D conformations") { constexpr const char *mb = "\n RDKit 2D\n\n 4 3 0 0 0 0 0 0 0 0999 " "V2000\n " " -0.0000 -1.5000 0.0000 Br 0 0 0 0 0 0 0 0 0 0 0 " "0\n -0.0000 -0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 " "0 " " 0\n 1.2990 0.7500 0.0000 F 0 0 0 0 0 0 0 0 0 0 " " " "0 0\n -1.2990 0.7500 0.0000 Cl 0 0 0 0 0 0 0 0 0 " "0 " " 0 0\n 2 1 1 1\n 2 3 1 0\n 2 4 1 0\nM END\n"; RWMol *m = MolBlockToMol(mb); REQUIRE(m); REQUIRE(m->getNumAtoms() == 4); MolOps::addHs(*m, false, true); REQUIRE(m->getNumAtoms() == 5); REQUIRE(feq(m->getConformer().getAtomPos(4).z, 0.0)); delete m; } TEST_CASE( "Testing github issue 962: Kekulization issues post successful smiles parsing") { { constexpr const char *smi = "C2*c1ccccc1C2"; RWMol *m = SmilesToMol(smi, 0, false); REQUIRE(m); REQUIRE(m->getNumAtoms() == 9); REQUIRE(m->getBondBetweenAtoms(0, 1)); REQUIRE(m->getBondBetweenAtoms(2, 1)); m->updatePropertyCache(); MolOps::Kekulize(*m); REQUIRE(m->getBondBetweenAtoms(0, 1)->getBondType() == Bond::SINGLE); REQUIRE(m->getBondBetweenAtoms(2, 1)->getBondType() == Bond::SINGLE); delete m; } { // this one did not cause problems before, but verify! constexpr const char *smi = "*2Cc1ccccc1C2"; RWMol *m = SmilesToMol(smi, 0, false); REQUIRE(m); REQUIRE(m->getNumAtoms() == 9); REQUIRE(m->getBondBetweenAtoms(0, 1)); REQUIRE(m->getBondBetweenAtoms(0, 8)); m->updatePropertyCache(); MolOps::Kekulize(*m); REQUIRE(m->getBondBetweenAtoms(0, 1)->getBondType() == Bond::SINGLE); REQUIRE(m->getBondBetweenAtoms(0, 8)->getBondType() == Bond::SINGLE); delete m; } } TEST_CASE( "Testing github issue 1021: AssignStereochemistry() giving incorrect results after FastFindRings()") { constexpr const char *smi = "C[C@H]1CC2CCCC(C1)[C@H]2N"; RWMol *m = SmilesToMol(smi); REQUIRE(m); REQUIRE(m->getNumAtoms() == 11); REQUIRE(m->getAtomWithIdx(1)->getChiralTag() != Atom::CHI_UNSPECIFIED); REQUIRE(m->getAtomWithIdx(9)->getChiralTag() != Atom::CHI_UNSPECIFIED); m->clearComputedProps(); bool cleanit = true, force = true; MolOps::assignStereochemistry(*m, cleanit, force); REQUIRE(m->getAtomWithIdx(1)->getChiralTag() != Atom::CHI_UNSPECIFIED); REQUIRE(m->getAtomWithIdx(9)->getChiralTag() != Atom::CHI_UNSPECIFIED); m->clearComputedProps(); MolOps::fastFindRings(*m); MolOps::assignStereochemistry(*m, cleanit, force); REQUIRE(m->getAtomWithIdx(1)->getChiralTag() != Atom::CHI_UNSPECIFIED); REQUIRE(m->getAtomWithIdx(9)->getChiralTag() != Atom::CHI_UNSPECIFIED); delete m; } TEST_CASE( "Testing github issue 607: AssignAtomChiralTagsFromStructure() not recognizing chiral S") { { std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; ROMol *m = MolFileToMol(pathName + "1a9u.zwitterion.sdf"); REQUIRE(m); REQUIRE(m->getNumAtoms() == 27); MolOps::assignChiralTypesFrom3D(*m); REQUIRE(m->getAtomWithIdx(26)->getAtomicNum() == 16); REQUIRE(m->getAtomWithIdx(26)->getChiralTag() != Atom::CHI_UNSPECIFIED); delete m; } { std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; ROMol *m = MolFileToMol(pathName + "1a9u.sdf"); REQUIRE(m); REQUIRE(m->getNumAtoms() == 27); MolOps::assignChiralTypesFrom3D(*m); REQUIRE(m->getAtomWithIdx(26)->getAtomicNum() == 16); REQUIRE(m->getAtomWithIdx(26)->getChiralTag() != Atom::CHI_UNSPECIFIED); delete m; } { // convert S -> Se and test again std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; ROMol *m = MolFileToMol(pathName + "1a9u.zwitterion.sdf"); REQUIRE(m); REQUIRE(m->getNumAtoms() == 27); m->getAtomWithIdx(26)->setAtomicNum(34); MolOps::assignChiralTypesFrom3D(*m); REQUIRE(m->getAtomWithIdx(26)->getChiralTag() != Atom::CHI_UNSPECIFIED); delete m; } { // convert S -> Se and test again std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; ROMol *m = MolFileToMol(pathName + "1a9u.sdf"); REQUIRE(m); REQUIRE(m->getNumAtoms() == 27); m->getAtomWithIdx(26)->setAtomicNum(34); MolOps::assignChiralTypesFrom3D(*m); REQUIRE(m->getAtomWithIdx(26)->getChiralTag() != Atom::CHI_UNSPECIFIED); delete m; } } TEST_CASE("Testing github issue 1204: Support tetravalent and hexavalent Te") { { constexpr const char *smiles = "F[Te](F)(F)(F)(F)F"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); delete m; } { constexpr const char *smiles = "F[Te](F)(F)(F)"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); delete m; } } TEST_CASE( "Testing github issue 1478: Aromatic rings composed solely of dummy atoms should not be kekulized") { { // basics constexpr const char *smiles = "*:1:*:*:*:*:*:1"; RWMol *m = SmilesToMol(smiles, false); REQUIRE(m); m->updatePropertyCache(); MolOps::Kekulize(*m); for (unsigned int i = 0; i < m->getNumBonds(); ++i) { REQUIRE(m->getBondWithIdx(i)->getBondType() == Bond::AROMATIC); } delete m; } { // fused rings where one is kekulized constexpr const char *smiles = "*:1:*:*:*:*:2:*:1cccc2"; RWMol *m = SmilesToMol(smiles, false); REQUIRE(m); m->updatePropertyCache(); MolOps::Kekulize(*m); REQUIRE(m->getBondBetweenAtoms(0, 1)->getBondType() == Bond::AROMATIC); REQUIRE(m->getBondBetweenAtoms(6, 7)->getBondType() != Bond::AROMATIC); delete m; } } TEST_CASE( "Testing github issue 1439: RemoveHs() removes H atom attached to dummy if it came from AddHs()") { constexpr const char *smiles = "F"; RWMol *m = SmilesToMol(smiles); REQUIRE(m); MolOps::addHs(*m); REQUIRE(m->getNumAtoms() == 2); m->getAtomWithIdx(0)->setAtomicNum(0); MolOps::removeHs(*m); REQUIRE(m->getNumAtoms() == 2); delete m; } TEST_CASE( "Testing github issue 1281: RDKit gets stuck on PubChem CID 102128817") { { // basics constexpr const char *smiles = "COC1=CC=C(C=C1)C2C3=C(C=CC4=CC=CC=C43)OC5=CC6=C(C=C5)C7=NC8=C9C=CC1=" "CC9=C(N8)N=C3C4=C5C=CC(=C4)OC4=C(C(C8=C(C=CC9=CC=CC=C98)OC8=CC9=C(C=" "C8)C8=NC9=NC9=C%10C=C(C=CC%10=C(N9)N=C9C%10=C(C=C(C=C%10)OC%10=C2C2=" "CC=CC=C2C=C%10)C(=N9)NC2=NC(=N8)C8=C2C=C(C=C8)OC2=C(C(C8=C(C=CC9=CC=" "CC=C98)OC8=CC9=C(C=C8)C(=NC5=N3)N=C9NC6=N7)C3=CC=C(C=C3)OC)C3=CC=CC=" "C3C=C2)OC2=C(C(C3=C(O1)C=CC1=CC=CC=C13)C1=CC=C(C=C1)OC)C1=CC=CC=C1C=" "C2)C1=CC=C(C=C1)OC)C1=CC=CC=C1C=C4"; { std::unique_ptr m{SmilesToMol(smiles, 0, false)}; REQUIRE(m); REQUIRE(m->getNumAtoms() == 204); REQUIRE(m->getNumBonds() == 244); REQUIRE_THROWS_AS(MolOps::findSSSR(*m), ValueErrorException); } { REQUIRE_THROWS_AS(SmilesToMol(smiles), ValueErrorException); } } } TEST_CASE( "Testing Github issue 1605: Inappropriate bad valence exception during partial sanitization") { constexpr const char *smiles = "C1=CC=CC=C1N(=O)=O"; { // easy to test; we shouldn't throw an exception. :-) RWMol *m = SmilesToMol(smiles, 0, false); REQUIRE(m); unsigned int failed; MolOps::sanitizeMol( *m, failed, MolOps::SANITIZE_SETAROMATICITY | MolOps::SANITIZE_ADJUSTHS); REQUIRE(!failed); delete m; } } TEST_CASE("Testing Github issue 1622: add MDL aromaticity perception") { { // rings that should be aromatic constexpr const char *aromaticSmis[] = { "C1=CC=CC=C1", // benzene, of course // heterocyclics "N1=CC=CC=C1", // pyridine "N1=CC=CC=N1", // pyridazine "N1=CC=CN=C1", // pyrimidine "N1=CC=NC=C1", // pyrazine "N1=CN=CN=C1", // 1,3,5-triazine // polycyclic aromatics "C1=CC2=CC=CC=CC2=C1", // azulene "C1=CC=CC2=CC=CC=C12", // 6-6 fused "C1=CC2=CC=CC=CC=C12", // 4-8 fused "C1=CC=C2C(=C1)N=CC=N2", // 6-6 with Ns "C1=CN=CC2C=CC=CC1=2", // 6-6 "C1=CC=C2C(=C1)N=C3C=CC=CC3=N2", // 6-6-6 "C1=CN=NC2C=CC=CC1=2", // 6-6 with Ns // macrocycle aromatics "C1=CC=CC=CC=CC=C1", // 10 atoms "C1=CC=CC=CC=CC=CC=CC=CC=CC=C1", // 18 atoms "N1=CN=NC=CC=CC=CC=CC=CC=CC=CC=CC=CC=CC=CC=CC=C1"}; for (auto smi : aromaticSmis) { int debugParse = 0; bool sanitize = false; RWMol *mol = SmilesToMol(smi, debugParse, sanitize); REQUIRE(mol); unsigned int whatFailed = 0; constexpr unsigned int sanitFlags = MolOps::SANITIZE_ALL ^ MolOps::SANITIZE_SETAROMATICITY; MolOps::sanitizeMol(*mol, whatFailed, sanitFlags); MolOps::setAromaticity(*mol, MolOps::AROMATICITY_MDL); REQUIRE(mol->getAtomWithIdx(0)->getIsAromatic()); delete mol; } } { // rings that should not be aromatic constexpr const char *nonaromaticSmis[] = { "C1=C[N]C=C1", // radicals are not two electron donors // exocyclic double bonds disqualify us "C1(=O)C=CNC=C1", "C1(=C)C=CC(=C)C=C1", "C1(=O)C=CC(=O)C=C1", "C1#CC=CC=C1", // not benzyne // five-membered heterocycles "C1=COC=C1", // furan "C1=CSC=C1", // thiophene "C1=CNC=C1", // pyrrole "C1=COC=N1", // oxazole "C1=CSC=N1", // thiazole "C1=CNC=N1", // imidzole "C1=CNN=C1", // pyrazole "C1=CON=C1", // isoxazole "C1=CSN=C1", // isothiazole "C1=CON=N1", // 1,2,3-oxadiazole "C1=CNN=N1", // 1,2,3-triazole "N1=CSC=N1", // 1,3,4-thiadiazole "C1=CS(=O)C=C1", // not sure how to classify this example from the // OEChem docs // outside the second rows "C1=CC=C[Si]=C1", "C1=CC=CC=P1", // 5-membered heterocycles outside the second row "C1=C[Se]C=C1", "C1=C[Te]C=C1", }; for (auto smi : nonaromaticSmis) { int debugParse = 0; bool sanitize = false; RWMol *mol = SmilesToMol(smi, debugParse, sanitize); REQUIRE(mol); unsigned int whatFailed = 0; constexpr unsigned int sanitFlags = MolOps::SANITIZE_ALL ^ MolOps::SANITIZE_SETAROMATICITY; MolOps::sanitizeMol(*mol, whatFailed, sanitFlags); MolOps::setAromaticity(*mol, MolOps::AROMATICITY_MDL); REQUIRE(!(mol->getAtomWithIdx(0)->getIsAromatic())); delete mol; } } { // ring systems where part is aromatic, part not constexpr const char *mixedaromaticSmis[] = { "O1C=CC2=CC=CC=C12", "S1C=CC2=CC=CC=C12", "N1C2=CC=CC=C2C2=CC=CC=C12", "N1C=CC2=CC=CC=C12", "N1C=NC2=CC=CC=C12", "N1C=NC2=CN=CN=C12", "C1CCCC2=CC3=CCCCC3=CC2=1"}; for (auto smi : mixedaromaticSmis) { int debugParse = 0; bool sanitize = false; RWMol *mol = SmilesToMol(smi, debugParse, sanitize); REQUIRE(mol); unsigned int whatFailed = 0; constexpr unsigned int sanitFlags = MolOps::SANITIZE_ALL ^ MolOps::SANITIZE_SETAROMATICITY; MolOps::sanitizeMol(*mol, whatFailed, sanitFlags); MolOps::setAromaticity(*mol, MolOps::AROMATICITY_MDL); REQUIRE(!(mol->getAtomWithIdx(0)->getIsAromatic())); REQUIRE((mol->getAtomWithIdx(mol->getNumAtoms() - 1)->getIsAromatic())); delete mol; } } } TEST_CASE( "Testing Github issue 1703: Dative bonds interfere with kekulization and the perception of aromaticity") { { // start with zero-order bonds SmilesParserParams ps; ps.sanitize = false; std::unique_ptr mol(SmilesToMol("C1=CC=NC=N1.[Fe]", ps)); REQUIRE(mol); mol->addBond(5, 6, Bond::ZERO); MolOps::sanitizeMol(*mol); REQUIRE(mol->getBondBetweenAtoms(0, 1)->getIsAromatic()); REQUIRE(mol->getAtomWithIdx(5)->getIsAromatic()); MolOps::Kekulize(*mol); } { // and dative bonds: SmilesParserParams ps; ps.sanitize = false; std::unique_ptr mol(SmilesToMol("C1=CC=NC=N1->[Fe]", ps)); REQUIRE(mol); MolOps::sanitizeMol(*mol); REQUIRE(mol->getBondBetweenAtoms(0, 1)->getIsAromatic()); REQUIRE(mol->getAtomWithIdx(5)->getIsAromatic()); MolOps::Kekulize(*mol); } } TEST_CASE( "Testing github issue 1614: AssignStereochemistry incorrectly removing CIS/TRANS bond stereo") { { RWMol m; m.addAtom(new Atom(9), true, true); m.addAtom(new Atom(6), true, true); m.addAtom(new Atom(6), true, true); m.addAtom(new Atom(17), true, true); m.addBond(0, 1, Bond::SINGLE); m.addBond(2, 3, Bond::SINGLE); m.addBond(1, 2, Bond::DOUBLE); m.getBondBetweenAtoms(1, 2)->setStereoAtoms(0, 3); m.getBondBetweenAtoms(1, 2)->setStereo(Bond::STEREOTRANS); m.updatePropertyCache(); { RWMol nm(m); bool force = true, cleanIt = true; MolOps::setDoubleBondNeighborDirections(nm); MolOps::assignStereochemistry(nm, cleanIt, force); REQUIRE(nm.getBondBetweenAtoms(1, 2)->getStereo() > Bond::STEREOANY); std::string smi = MolToSmiles(nm, true); std::cerr << smi << std::endl; REQUIRE(smi == "F/C=C/Cl"); } { RWMol nm(m); MolOps::addHs(nm); bool force = true, cleanIt = true; MolOps::setDoubleBondNeighborDirections(nm); MolOps::assignStereochemistry(nm, cleanIt, force); REQUIRE(nm.getBondBetweenAtoms(1, 2)->getStereo() > Bond::STEREOANY); std::string smi = MolToSmiles(nm, true); std::cerr << smi << std::endl; REQUIRE(smi == "[H]/C(F)=C(/[H])Cl"); } } { RWMol m; m.addAtom(new Atom(9), true, true); m.addAtom(new Atom(6), true, true); m.addAtom(new Atom(6), true, true); m.addAtom(new Atom(17), true, true); m.addBond(0, 1, Bond::SINGLE); m.addBond(3, 2, Bond::SINGLE); m.addBond(1, 2, Bond::DOUBLE); m.getBondBetweenAtoms(1, 2)->setStereoAtoms(0, 3); m.getBondBetweenAtoms(1, 2)->setStereo(Bond::STEREOTRANS); m.updatePropertyCache(); { RWMol nm(m); bool force = true, cleanIt = true; MolOps::setDoubleBondNeighborDirections(nm); MolOps::assignStereochemistry(nm, cleanIt, force); REQUIRE(nm.getBondBetweenAtoms(1, 2)->getStereo() > Bond::STEREOANY); std::string smi = MolToSmiles(nm, true); std::cerr << smi << std::endl; REQUIRE(smi == "F/C=C/Cl"); } } { RWMol m; m.addAtom(new Atom(9), true, true); m.addAtom(new Atom(6), true, true); m.addAtom(new Atom(6), true, true); m.addAtom(new Atom(17), true, true); m.addBond(1, 0, Bond::SINGLE); m.addBond(2, 3, Bond::SINGLE); m.addBond(1, 2, Bond::DOUBLE); m.getBondBetweenAtoms(1, 2)->setStereoAtoms(0, 3); m.getBondBetweenAtoms(1, 2)->setStereo(Bond::STEREOTRANS); m.updatePropertyCache(); { RWMol nm(m); bool force = true, cleanIt = true; MolOps::setDoubleBondNeighborDirections(nm); MolOps::assignStereochemistry(nm, cleanIt, force); REQUIRE(nm.getBondBetweenAtoms(1, 2)->getStereo() > Bond::STEREOANY); std::string smi = MolToSmiles(nm, true); std::cerr << smi << std::endl; REQUIRE(smi == "F/C=C/Cl"); } } { RWMol m; m.addAtom(new Atom(9), true, true); m.addAtom(new Atom(6), true, true); m.addAtom(new Atom(6), true, true); m.addAtom(new Atom(17), true, true); m.addBond(0, 1, Bond::SINGLE); m.addBond(2, 3, Bond::SINGLE); m.addBond(1, 2, Bond::DOUBLE); m.addAtom(new Atom(6), true, true); m.addAtom(new Atom(6), true, true); m.addBond(1, 5, Bond::SINGLE); m.addBond(2, 4, Bond::SINGLE); m.getBondBetweenAtoms(1, 2)->setStereoAtoms(0, 3); m.getBondBetweenAtoms(1, 2)->setStereo(Bond::STEREOTRANS); m.updatePropertyCache(); { RWMol nm(m); bool force = true, cleanIt = true; MolOps::setDoubleBondNeighborDirections(nm); MolOps::assignStereochemistry(nm, cleanIt, force); REQUIRE(nm.getBondBetweenAtoms(1, 2)->getStereo() > Bond::STEREOANY); std::string smi = MolToSmiles(nm, true); std::cerr << smi << std::endl; REQUIRE(smi == "C/C(F)=C(/C)Cl"); } } { RWMol *m = SmilesToMol("F/C=C(\\C/C=C/C)C/C=C\\F", false, false); REQUIRE(m); MolOps::sanitizeMol(*m); { RWMol nm(*m); MolOps::setDoubleBondNeighborDirections(nm); bool force = true, cleanIt = true; MolOps::assignStereochemistry(nm, cleanIt, force); REQUIRE(nm.getBondBetweenAtoms(4, 5)->getStereo() == Bond::STEREOE); REQUIRE(nm.getBondBetweenAtoms(8, 9)->getStereo() == Bond::STEREOZ); REQUIRE(nm.getBondBetweenAtoms(1, 2)->getStereo() == Bond::STEREOE); } delete m; } { RWMol *m = SmilesToMol("FC=C(C/C=C/C)C/C=C\\F", false, false); REQUIRE(m); MolOps::sanitizeMol(*m); REQUIRE(m->getBondBetweenAtoms(1, 2)); m->getBondBetweenAtoms(1, 2)->setStereoAtoms(0, 3); m->getBondBetweenAtoms(1, 2)->setStereo(Bond::STEREOCIS); { RWMol nm(*m); MolOps::setDoubleBondNeighborDirections(nm); bool force = true, cleanIt = true; MolOps::assignStereochemistry(nm, cleanIt, force); REQUIRE(nm.getBondBetweenAtoms(4, 5)->getStereo() == Bond::STEREOE); REQUIRE(nm.getBondBetweenAtoms(8, 9)->getStereo() == Bond::STEREOZ); REQUIRE(nm.getBondBetweenAtoms(1, 2)->getStereo() == Bond::STEREOE); } delete m; } { RWMol *m = SmilesToMol("F/C=C(\\C/C=C/C)C/C=C\\C", false, false); REQUIRE(m); MolOps::sanitizeMol(*m); { RWMol nm(*m); MolOps::setDoubleBondNeighborDirections(nm); bool force = true, cleanIt = true; MolOps::assignStereochemistry(nm, cleanIt, force); REQUIRE(nm.getBondBetweenAtoms(4, 5)->getStereo() == Bond::STEREOE); REQUIRE(nm.getBondBetweenAtoms(8, 9)->getStereo() == Bond::STEREOZ); REQUIRE(nm.getBondBetweenAtoms(1, 2)->getStereo() == Bond::STEREOE); } delete m; } { RWMol *m = SmilesToMol("FC=C(C/C=C/C)C/C=C\\C", false, false); REQUIRE(m); MolOps::sanitizeMol(*m); REQUIRE(m->getBondBetweenAtoms(1, 2)); m->getBondBetweenAtoms(1, 2)->setStereoAtoms(0, 3); m->getBondBetweenAtoms(1, 2)->setStereo(Bond::STEREOCIS); { RWMol nm(*m); MolOps::setDoubleBondNeighborDirections(nm); bool force = true, cleanIt = true; MolOps::assignStereochemistry(nm, cleanIt, force); REQUIRE(nm.getBondBetweenAtoms(4, 5)->getStereo() == Bond::STEREOE); REQUIRE(nm.getBondBetweenAtoms(8, 9)->getStereo() == Bond::STEREOZ); REQUIRE(nm.getBondBetweenAtoms(1, 2)->getStereo() == Bond::STEREOE); } delete m; } } TEST_CASE( "Testing Github issue 1810: removeHs() should not remove H atoms that are contributing to the definition of a stereo bond") { { std::unique_ptr mol(SmilesToMol("F/C=C/[H]")); REQUIRE(mol); REQUIRE(mol->getNumAtoms() == 4); REQUIRE(mol->getBondBetweenAtoms(1, 2)->getStereo() == Bond::STEREOE); } { std::unique_ptr mol(SmilesToMol("F/C=C(/F)[H]")); REQUIRE(mol); REQUIRE(mol->getNumAtoms() == 4); REQUIRE(mol->getBondBetweenAtoms(1, 2)->getStereo() == Bond::STEREOE); } { std::unique_ptr mol(SmilesToMol("F/C=C(/[H])F")); REQUIRE(mol); REQUIRE(mol->getNumAtoms() == 4); REQUIRE(mol->getBondBetweenAtoms(1, 2)->getStereo() == Bond::STEREOZ); } { std::unique_ptr mol(SmilesToMol("FC=C(F)[H]", false, false)); REQUIRE(mol); MolOps::sanitizeMol(*mol); REQUIRE(mol->getNumAtoms() == 5); mol->getBondBetweenAtoms(1, 2)->setStereoAtoms(0, 4); mol->getBondBetweenAtoms(1, 2)->setStereo(Bond::STEREOTRANS); MolOps::removeHs(*mol); REQUIRE(mol->getNumAtoms() == 4); REQUIRE(mol->getBondBetweenAtoms(1, 2)->getStereoAtoms()[0] == 0); REQUIRE(mol->getBondBetweenAtoms(1, 2)->getStereoAtoms()[1] == 3); } } TEST_CASE( "Testing Github issue 1936: Bad aromaticity for rings with radical carbocations") { { std::unique_ptr mol(SmilesToMol("C1=CC=C[C+]C=C1")); REQUIRE(mol); REQUIRE(mol->getNumAtoms() == 7); REQUIRE(mol->getAtomWithIdx(4)->getNumRadicalElectrons() == 1); REQUIRE(!mol->getAtomWithIdx(0)->getIsAromatic()); } { // the original report std::string pathName = getenv("RDBASE"); pathName += "/Code/GraphMol/test_data/"; std::unique_ptr mol(MolFileToMol(pathName + "github1936.mol")); REQUIRE(mol); REQUIRE(mol->getNumAtoms() == 7); REQUIRE(mol->getAtomWithIdx(4)->getNumRadicalElectrons() == 1); REQUIRE(!mol->getAtomWithIdx(0)->getIsAromatic()); } } TEST_CASE( "Testing Github issue 1928: incorrect aromatic SMILES generated for structure") { { std::unique_ptr mol( SmilesToMol("N1C2=CC3=CC=CC=C3OC1=CC1=C(O2)C=CC=C1")); REQUIRE(mol); REQUIRE(mol->getNumAtoms() == 19); REQUIRE(mol->getBondBetweenAtoms(0, 1)->getBondType() == Bond::SINGLE); REQUIRE(!mol->getBondBetweenAtoms(0, 1)->getIsAromatic()); REQUIRE(!mol->getAtomWithIdx(0)->getIsAromatic()); } { // the original report std::unique_ptr mol(SmilesToMol( "C12=C3C=CC=C1CCC(=O)C2=C4OC5=CC=CC6=C5C(=C(N4)O3)C(=O)CC6")); REQUIRE(mol); REQUIRE(mol->getNumAtoms() == 27); REQUIRE(mol->getBondBetweenAtoms(20, 21)->getBondType() == Bond::SINGLE); REQUIRE(!mol->getBondBetweenAtoms(20, 21)->getIsAromatic()); REQUIRE(!mol->getAtomWithIdx(21)->getIsAromatic()); } } TEST_CASE("Testing Github issue 1990: removeHs screws up bond stereo") { { std::unique_ptr mol(SmilesToMol("F/C=C/F")); REQUIRE(mol); MolOps::addHs(*mol); MolOps::removeHs(*mol); REQUIRE(mol->getNumAtoms() == 4); REQUIRE(mol->getBondWithIdx(1)->getStereoAtoms().size() == 2); } { // make sure that stereo is not removed when it comes from Hs: std::unique_ptr mol(SmilesToMol("F/C=C/F")); REQUIRE(mol); MolOps::addHs(*mol); REQUIRE(mol->getBondWithIdx(1)->getStereoAtoms().size() == 2); mol->getBondWithIdx(1)->getStereoAtoms()[0] = 4; MolOps::removeHs(*mol); REQUIRE(mol->getBondWithIdx(1)->getStereoAtoms().size() == 2); mol->getBondWithIdx(1)->getStereoAtoms()[0] = 0; } { // make sure that stereo is not removed when it comes from Hs: std::unique_ptr mol(SmilesToMol("F/C=C/F")); REQUIRE(mol); MolOps::addHs(*mol); REQUIRE(mol->getBondWithIdx(1)->getStereoAtoms().size() == 2); mol->getBondWithIdx(1)->getStereoAtoms()[1] = 5; MolOps::removeHs(*mol); REQUIRE(mol->getBondWithIdx(1)->getStereoAtoms().size() == 2); mol->getBondWithIdx(1)->getStereoAtoms()[1] = 3; } } TEST_CASE("Testing removeAndTrackIsotopes parameter") { struct IsotopicHsCount { IsotopicHsCount(const ROMol &mol) { for (auto b : mol.bonds()) { const auto ba = b->getBeginAtom(); const auto ea = b->getEndAtom(); if (ba->getAtomicNum() == 1 && ba->getIsotope()) { ++d_map[ea->getIdx()]; } else if (ea->getAtomicNum() == 1 && ea->getIsotope()) { ++d_map[ba->getIdx()]; } } } // get the number of isotopic Hs attached to atom idx unsigned int at(unsigned int idx) { auto it = d_map.find(idx); return (it == d_map.end() ? 0 : it->second); } // count total number of isotopes in the molecule unsigned int total() { return std::accumulate( d_map.begin(), d_map.end(), 0U, [](unsigned int s, const std::pair &p) { return s + p.second; }); } static void countExplicitImplicitHs(const ROMol &m, unsigned int &expl, unsigned int &impl) { expl = 0; impl = 0; for (auto a : m.atoms()) { expl += a->getNumExplicitHs(); impl += a->getNumImplicitHs(); } } std::map d_map; }; // CHEMBL2024142 auto m = "[2H]C1=C(C(=C2C(=C1[2H])C(=O)C(=C(C2=O)C([2H])([2H])[2H])C/C=C(\\C)/CC([2H])([2H])/C=C(/CC/C=C(\\C)/CCC=C(C)C)\\C([2H])([2H])[2H])[2H])[2H]"_smiles; REQUIRE(m.get()); std::unique_ptr m_isotopicHsPerHeavy( new IsotopicHsCount(*m)); unsigned int m_numExplicitHs; unsigned int m_numImplicitHs; IsotopicHsCount::countExplicitImplicitHs(*m, m_numExplicitHs, m_numImplicitHs); REQUIRE(m_numExplicitHs == 0); REQUIRE(m_numImplicitHs == 28); REQUIRE(m_isotopicHsPerHeavy->total() == 12); MolOps::RemoveHsParameters ps; ps.removeAndTrackIsotopes = true; std::unique_ptr mNoH(removeHs(*static_cast(m.get()), ps)); REQUIRE(mNoH->getAtomWithIdx(0)->getAtomicNum() == 6); REQUIRE(mNoH->getAtomWithIdx(0)->hasProp(common_properties::_isotopicHs)); std::vector isoHs; REQUIRE(mNoH->getAtomWithIdx(0)->getPropIfPresent( common_properties::_isotopicHs, isoHs)); REQUIRE(isoHs.size() == 1); REQUIRE(isoHs.front() == 2); REQUIRE(mNoH->getAtomWithIdx(30)->getAtomicNum() == 6); REQUIRE(!mNoH->getAtomWithIdx(30)->hasProp(common_properties::_isotopicHs)); IsotopicHsCount mNoH_isotopicHsPerHeavy(*mNoH); unsigned int mNoH_numExplicitHs; unsigned int mNoH_numImplicitHs; IsotopicHsCount::countExplicitImplicitHs(*mNoH, mNoH_numExplicitHs, mNoH_numImplicitHs); REQUIRE(mNoH_numExplicitHs == 0); REQUIRE(mNoH_numImplicitHs == 40); REQUIRE(mNoH_isotopicHsPerHeavy.total() == 0); std::unique_ptr mH(MolOps::addHs(*mNoH)); std::unique_ptr mH_isotopicHsPerHeavy( new IsotopicHsCount(*mH)); unsigned int mH_numExplicitHs; unsigned int mH_numImplicitHs; IsotopicHsCount::countExplicitImplicitHs(*mH, mH_numExplicitHs, mH_numImplicitHs); REQUIRE(mH_numExplicitHs == 0); REQUIRE(mH_numImplicitHs == 0); MatchVectType match; REQUIRE(SubstructMatch(*mH, *m, match)); REQUIRE(match.size() == m->getNumAtoms()); REQUIRE(mH_isotopicHsPerHeavy->total() == 12); std::unique_ptr mH2(MolOps::removeHs(*mH)); REQUIRE(m->getNumAtoms() == mH2->getNumAtoms()); std::unique_ptr mH2_isotopicHsPerHeavy( new IsotopicHsCount(*mH2)); unsigned int mH2_numExplicitHs; unsigned int mH2_numImplicitHs; IsotopicHsCount::countExplicitImplicitHs(*mH2, mH2_numExplicitHs, mH2_numImplicitHs); MatchVectType matchH2; REQUIRE(SubstructMatch(*m, *mH2, matchH2)); REQUIRE(matchH2.size() == m->getNumAtoms()); REQUIRE(mH2_isotopicHsPerHeavy->total() == 12); REQUIRE(mH2_numExplicitHs == 0); REQUIRE(mH2_numImplicitHs == 28); for (auto p : matchH2) { REQUIRE(mH2_isotopicHsPerHeavy->at(p.first) == m_isotopicHsPerHeavy->at(p.second)); } // shuffle atoms before adding Hs; result should not change std::vector randomOrder(mNoH->getNumAtoms()); std::iota(randomOrder.begin(), randomOrder.end(), 0U); std::shuffle(randomOrder.begin(), randomOrder.end(), std::default_random_engine()); std::unique_ptr mNoHRen(MolOps::renumberAtoms(*mNoH, randomOrder)); mH.reset(MolOps::addHs(*mNoHRen)); mH_isotopicHsPerHeavy.reset(new IsotopicHsCount(*mH)); IsotopicHsCount::countExplicitImplicitHs(*mH, mH_numExplicitHs, mH_numImplicitHs); REQUIRE(mH_numExplicitHs == 0); REQUIRE(mH_numImplicitHs == 0); MatchVectType matchRen; REQUIRE(SubstructMatch(*mH, *m, matchRen)); REQUIRE(match != matchRen); REQUIRE(match.size() == matchRen.size()); REQUIRE(mH_isotopicHsPerHeavy->total() == 12); mH2.reset(MolOps::removeHs(*mH)); REQUIRE(m->getNumAtoms() == mH2->getNumAtoms()); mH2_isotopicHsPerHeavy.reset(new IsotopicHsCount(*mH2)); IsotopicHsCount::countExplicitImplicitHs(*mH2, mH2_numExplicitHs, mH2_numImplicitHs); MatchVectType matchH2Ren; REQUIRE(SubstructMatch(*m, *mH2, matchH2Ren)); REQUIRE(matchH2 != matchH2Ren); REQUIRE(matchH2.size() == matchH2Ren.size()); REQUIRE(mH2_isotopicHsPerHeavy->total() == 12); REQUIRE(mH2_numExplicitHs == 0); REQUIRE(mH2_numImplicitHs == 28); for (auto p : matchH2Ren) { REQUIRE(mH2_isotopicHsPerHeavy->at(p.first) == m_isotopicHsPerHeavy->at(p.second)); } // Add isotopes incrementally only on some atoms at a time // This should add 4 isotopes UINT_VECT onlyOnAtoms{0, 12}; mH.reset(MolOps::addHs(*mNoH, false, false, &onlyOnAtoms)); mH_isotopicHsPerHeavy.reset(new IsotopicHsCount(*mH)); REQUIRE(mH_isotopicHsPerHeavy->total() == 4); REQUIRE(mH_isotopicHsPerHeavy->at(0) == 1); REQUIRE(mH_isotopicHsPerHeavy->at(12) == 3); // This should add 4 more isotopes onlyOnAtoms = UINT_VECT{1, 2, 18}; mH.reset(MolOps::addHs(*mH, false, false, &onlyOnAtoms)); mH_isotopicHsPerHeavy.reset(new IsotopicHsCount(*mH)); REQUIRE(mH_isotopicHsPerHeavy->total() == 8); REQUIRE(mH_isotopicHsPerHeavy->at(0) == 1); REQUIRE(mH_isotopicHsPerHeavy->at(1) == 1); REQUIRE(mH_isotopicHsPerHeavy->at(2) == 1); REQUIRE(mH_isotopicHsPerHeavy->at(12) == 3); REQUIRE(mH_isotopicHsPerHeavy->at(18) == 2); // This should add the last 4 isotopes onlyOnAtoms = UINT_VECT{5, 32}; mH.reset(MolOps::addHs(*mH, false, false, &onlyOnAtoms)); mH_isotopicHsPerHeavy.reset(new IsotopicHsCount(*mH)); REQUIRE(mH_isotopicHsPerHeavy->total() == 12); REQUIRE(mH_isotopicHsPerHeavy->at(0) == 1); REQUIRE(mH_isotopicHsPerHeavy->at(1) == 1); REQUIRE(mH_isotopicHsPerHeavy->at(2) == 1); REQUIRE(mH_isotopicHsPerHeavy->at(5) == 1); REQUIRE(mH_isotopicHsPerHeavy->at(12) == 3); REQUIRE(mH_isotopicHsPerHeavy->at(18) == 2); REQUIRE(mH_isotopicHsPerHeavy->at(32) == 3); match.clear(); REQUIRE(SubstructMatch(*mH, *m, match)); REQUIRE(match.size() == mH->getNumAtoms()); for (auto p : match) { auto m_nIso = m_isotopicHsPerHeavy->at(p.first); if (!m_nIso) { continue; } auto mH_nIso = mH_isotopicHsPerHeavy->at(p.second); REQUIRE(m_nIso == mH_nIso); } // Check that chirality on centers which bear both non-isotopic // and isotopic Hs is preserved after... std::set chiralTypeSet; std::set chiralTypeSetAfterAddHs; std::set chiralTypeSetAfterRemoveAllHsAddHs; std::set chiralTypeSetAfterRemoveAllHsAddHsRemoveHs; for (unsigned int i : {0, 1}) { unsigned int hIdx = i + 24; std::string expectedCipCode(1, 'R' + i); std::unique_ptr mChiral(new ROMol(*m)); mChiral->getAtomWithIdx(23)->setChiralTag(Atom::CHI_TETRAHEDRAL_CW); mChiral->getAtomWithIdx(hIdx)->setIsotope(0); MolOps::assignStereochemistry(*mChiral, true, true); REQUIRE(mChiral->getAtomWithIdx(23)->getProp( common_properties::_CIPCode) == expectedCipCode); chiralTypeSet.insert(mChiral->getAtomWithIdx(23)->getChiralTag()); // 1) ...Adding Hs mH.reset(MolOps::addHs(*mChiral)); MolOps::assignStereochemistry(*mH, true, true); match.clear(); REQUIRE(mH->getAtomWithIdx(23)->getProp( common_properties::_CIPCode) == expectedCipCode); chiralTypeSetAfterAddHs.insert(mH->getAtomWithIdx(23)->getChiralTag()); // 2) ...Removing all Hs including isotopes and then putting them back mNoH.reset(MolOps::removeHs(*static_cast(mChiral.get()), ps)); mH.reset(MolOps::addHs(*mNoH)); MolOps::assignStereochemistry(*mH, true, true); match.clear(); REQUIRE(SubstructMatch(*mH, *mChiral, match)); REQUIRE(match.size() == mChiral->getNumAtoms()); REQUIRE(mH->getAtomWithIdx(match[23].second) ->getProp(common_properties::_CIPCode) == expectedCipCode); chiralTypeSetAfterRemoveAllHsAddHs.insert( mH->getAtomWithIdx(match[23].second)->getChiralTag()); // 3) ...Removing non-isotopic Hs mNoH.reset(MolOps::removeHs(*mH)); MolOps::assignStereochemistry(*mNoH, true, true); REQUIRE(mNoH->getAtomWithIdx(match[23].second) ->getProp(common_properties::_CIPCode) == expectedCipCode); chiralTypeSetAfterRemoveAllHsAddHsRemoveHs.insert( mNoH->getAtomWithIdx(match[23].second)->getChiralTag()); } // CIP chirality is preserved because when all Hs are removed // the parity is inverted on one of the enantiomers such that // chirality is preserved also when Hs are implicit. // So we must find a single parity before calling removeHs, // and two afterwards. REQUIRE((chiralTypeSet.size() == 1 && *chiralTypeSet.begin() == Atom::CHI_TETRAHEDRAL_CW)); REQUIRE((chiralTypeSetAfterAddHs.size() == 1 && *chiralTypeSetAfterAddHs.begin() == Atom::CHI_TETRAHEDRAL_CW)); REQUIRE(chiralTypeSetAfterRemoveAllHsAddHs.size() == 2); REQUIRE(chiralTypeSetAfterRemoveAllHsAddHsRemoveHs.size() == 2); // Check that chirality on centers which bear different // H isotopes is preserved after... chiralTypeSet.clear(); chiralTypeSetAfterAddHs.clear(); chiralTypeSetAfterRemoveAllHsAddHs.clear(); chiralTypeSetAfterRemoveAllHsAddHsRemoveHs.clear(); for (unsigned int i : {0, 1}) { unsigned int hIdx = i + 24; std::string expectedCipCode(1, 'R' + i); std::unique_ptr mChiral(new ROMol(*m)); mChiral->getAtomWithIdx(23)->setChiralTag(Atom::CHI_TETRAHEDRAL_CCW); mChiral->getAtomWithIdx(hIdx)->setIsotope(3); MolOps::assignStereochemistry(*mChiral, true, true); REQUIRE(mChiral->getAtomWithIdx(23)->getProp( common_properties::_CIPCode) == expectedCipCode); chiralTypeSet.insert(mChiral->getAtomWithIdx(23)->getChiralTag()); // 1) ...Adding Hs mH.reset(MolOps::addHs(*mChiral)); MolOps::assignStereochemistry(*mH, true, true); match.clear(); REQUIRE(mH->getAtomWithIdx(23)->getProp( common_properties::_CIPCode) == expectedCipCode); chiralTypeSetAfterAddHs.insert(mH->getAtomWithIdx(23)->getChiralTag()); // 2) ...Removing all Hs including isotopes and then putting them back mNoH.reset(MolOps::removeHs(*static_cast(mChiral.get()), ps)); mH.reset(MolOps::addHs(*mNoH)); MolOps::assignStereochemistry(*mH, true, true); match.clear(); REQUIRE(SubstructMatch(*mH, *mChiral, match)); REQUIRE(match.size() == mChiral->getNumAtoms()); REQUIRE(mH->getAtomWithIdx(match[23].second) ->getProp(common_properties::_CIPCode) == expectedCipCode); chiralTypeSetAfterRemoveAllHsAddHs.insert( mH->getAtomWithIdx(match[23].second)->getChiralTag()); // 3) ...Removing non-isotopic Hs mNoH.reset(MolOps::removeHs(*mH)); MolOps::assignStereochemistry(*mNoH, true, true); REQUIRE(mNoH->getAtomWithIdx(match[23].second) ->getProp(common_properties::_CIPCode) == expectedCipCode); chiralTypeSetAfterRemoveAllHsAddHsRemoveHs.insert( mNoH->getAtomWithIdx(match[23].second)->getChiralTag()); } // In this case we should find a single parity throughout // as inverting the positions of 2H and 3H will trigger // an inversion in CIP chirality without need for parity change REQUIRE((chiralTypeSet.size() == 1 && *chiralTypeSet.begin() == Atom::CHI_TETRAHEDRAL_CCW)); REQUIRE((chiralTypeSetAfterAddHs.size() == 1 && *chiralTypeSetAfterAddHs.begin() == Atom::CHI_TETRAHEDRAL_CCW)); REQUIRE((chiralTypeSetAfterRemoveAllHsAddHs.size() == 1 && *chiralTypeSetAfterRemoveAllHsAddHs.begin() == Atom::CHI_TETRAHEDRAL_CCW)); REQUIRE((chiralTypeSetAfterRemoveAllHsAddHsRemoveHs.size() == 1 && *chiralTypeSetAfterRemoveAllHsAddHsRemoveHs.begin() == Atom::CHI_TETRAHEDRAL_CCW)); } TEST_CASE( "Testing github issue 3854: AddHs creates H atom with nan coordinates on edge case 2D structure") { constexpr const char *molb = R"CTAB( RDKit 2D 7 8 0 0 1 0 0 0 0 0999 V2000 5.0014 0.4125 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 4.1764 0.4125 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 3.3514 0.4125 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 2.9389 1.1271 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 3.3514 1.8412 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 4.1764 1.8412 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 4.5889 1.1271 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 2 1 1 1 7 1 1 1 2 3 1 0 2 7 1 0 3 4 1 0 4 5 1 0 5 6 1 0 7 6 1 0 M END)CTAB"; bool sanitize = true; bool removeHs = false; std::unique_ptr m(MolBlockToMol(molb, sanitize, removeHs)); REQUIRE(m); REQUIRE(m->getNumAtoms() == 7); bool explicitOnly = false; bool addCoords = true; std::vector onlyOnAtoms = {1, 6}; std::unique_ptr m2( MolOps::addHs(*m, explicitOnly, addCoords, &onlyOnAtoms)); REQUIRE(m2); REQUIRE(m2->getNumAtoms() == 9); auto conf = m2->getConformer(); for (auto i = 7; i < 9; ++i) { auto atom_pos = conf.getAtomPos(i); REQUIRE((!isnan(atom_pos.x) && !isnan(atom_pos.y) && !isnan(atom_pos.z))); } // check that we bisect the correct angle and point outside the rings auto v71 = conf.getAtomPos(7) - conf.getAtomPos(1); auto v21 = conf.getAtomPos(2) - conf.getAtomPos(1); auto v01 = conf.getAtomPos(0) - conf.getAtomPos(1); auto v61 = conf.getAtomPos(6) - conf.getAtomPos(1); REQUIRE(fabs(fabs(v71.dotProduct(v01)) - fabs(v71.dotProduct(v21))) < 1e-3); REQUIRE(v71.dotProduct(v61) < -1e-4); auto v86 = conf.getAtomPos(8) - conf.getAtomPos(6); auto v06 = conf.getAtomPos(0) - conf.getAtomPos(6); auto v56 = conf.getAtomPos(5) - conf.getAtomPos(6); auto v16 = conf.getAtomPos(1) - conf.getAtomPos(6); REQUIRE(fabs(fabs(v86.dotProduct(v56)) - fabs(v86.dotProduct(v06))) < 1e-3); REQUIRE(v86.dotProduct(v16) < -1e-4); } TEST_CASE("Testing ring family calculation") { { constexpr const char *smiles = "C(C1C2C3C41)(C2C35)C45"; // cubane ROMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getNumAtoms() == 8); REQUIRE(!m->getRingInfo()->areRingFamiliesInitialized()); MolOps::findRingFamilies(*m); REQUIRE(m->getRingInfo()->isInitialized()); REQUIRE(m->getRingInfo()->areRingFamiliesInitialized()); int numURF = RDL_getNofURF(m->getRingInfo()->dp_urfData.get()); int numRC = RDL_getNofRC(m->getRingInfo()->dp_urfData.get()); REQUIRE(numRC == 6); REQUIRE(numURF == 6); int numRings = m->getRingInfo()->numRingFamilies(); REQUIRE(numRings == 6); numRings = m->getRingInfo()->numRings(); REQUIRE(numRings == 6); delete m; } { constexpr const char *smiles = "C1CC2CCC1CC1CCC(CC1)CC1CCC(CC1)CC1CCC(CC1)C2"; ROMol *m = SmilesToMol(smiles); REQUIRE(m); REQUIRE(m->getNumAtoms() == 28); REQUIRE(!m->getRingInfo()->areRingFamiliesInitialized()); MolOps::findRingFamilies(*m); REQUIRE(m->getRingInfo()->isInitialized()); REQUIRE(m->getRingInfo()->areRingFamiliesInitialized()); int numURF = RDL_getNofURF(m->getRingInfo()->dp_urfData.get()); int numRC = RDL_getNofRC(m->getRingInfo()->dp_urfData.get()); REQUIRE(numURF == 5); REQUIRE(numRC == 20); int numRings = m->getRingInfo()->numRings(); REQUIRE(numRings == 14); REQUIRE(m->getRingInfo()->numRingFamilies() == 5); delete m; } } TEST_CASE("Testing adding coordinates to a terminal atom") { auto mol = R"CTAB( RDKit 2D 6 6 0 0 0 0 0 0 0 0999 V2000 1.5000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 0.7500 -1.2990 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 -0.7500 -1.2990 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 -1.5000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 -0.7500 1.2990 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 0.7500 1.2990 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 1 2 2 0 2 3 1 0 3 4 2 0 4 5 1 0 5 6 2 0 6 1 1 0 M END)CTAB"_ctab; auto atom = new Atom(0); auto idx = mol->addAtom(atom); delete atom; mol->addBond(idx, 0); MolOps::setTerminalAtomCoords(static_cast(*mol), idx, 0); auto &coord = mol->getConformer().getAtomPos(idx); REQUIRE((coord.x > 2.499 && coord.x < 2.501)); REQUIRE((coord.y > -0.001 && coord.y < 0.001)); REQUIRE((coord.z > -0.001 && coord.z < 0.001)); } TEST_CASE("testing get3DDistanceMat()") { auto mol = R"CTAB(bogus example RDKit 3D 3 2 0 0 0 0 0 0 0 0999 V2000 0.0000 0.0000 0.1000 C 0 0 0 0 0 0 0 0 0 0 0 0 1.2000 0.0000 0.1000 C 0 0 0 0 0 0 0 0 0 0 0 0 2.5000 0.0000 0.1000 O 0 0 0 0 0 0 0 0 0 0 0 0 1 2 2 0 2 3 1 0 M END)CTAB"_ctab; REQUIRE(mol); double *dm = MolOps::get3DDistanceMat(*mol); REQUIRE(dm); REQUIRE(dm[0] == 0.0); REQUIRE(dm[1] == 1.2); REQUIRE(dm[2] == 2.5); REQUIRE(dm[3] == 1.2); REQUIRE(dm[4] == 0.0); REQUIRE(dm[5] == 1.3); REQUIRE(dm[6] == 2.5); REQUIRE(dm[7] == 1.3); REQUIRE(dm[8] == 0.0); // this will use a cached version: double *dm2 = MolOps::get3DDistanceMat(*mol); REQUIRE(dm == dm2); int confId = -1; bool useAtomWts = true; dm = MolOps::get3DDistanceMat(*mol, confId, useAtomWts); REQUIRE(dm); REQUIRE(dm[0] == 1.0); REQUIRE(dm[1] == 1.2); REQUIRE(dm[2] == 2.5); REQUIRE(dm[3] == 1.2); REQUIRE(dm[4] == 1.0); REQUIRE(dm[5] == 1.3); REQUIRE(dm[6] == 2.5); REQUIRE(dm[7] == 1.3); REQUIRE(dm[8] == 6.0 / 8.0); } TEST_CASE( "Testing github issue 5099: Removing H preserving only wedged ones strips all H") { constexpr const char *smi{"FC([H])(O)Cl"}; std::unique_ptr m{SmilesToMol(smi, false, false)}; REQUIRE(m); REQUIRE(m->getNumAtoms() == 5); m->getBondBetweenAtoms(1, 2)->setBondDir(Bond::BondDir::BEGINWEDGE); auto ps = MolOps::RemoveHsParameters(); ps.showWarnings = false; // Remove all but Wedged/dashed H ps.removeWithWedgedBond = false; ps.removeDefiningBondStereo = true; ps.removeDegreeZero = true; ps.removeDummyNeighbors = true; ps.removeHigherDegrees = true; ps.removeHydrides = true; ps.removeInSGroups = true; ps.removeIsotopes = true; ps.removeMapped = true; ps.removeNonimplicit = true; ps.removeOnlyHNeighbors = true; ps.removeWithQuery = true; removeHs(*m, ps); // H shouldn't be removed REQUIRE(m->getNumAtoms() == 5); } TEST_CASE("Testing hasQueryHs") { const auto has_no_query_hs = std::make_pair(false, false); const auto has_only_query_hs = std::make_pair(true, false); const auto has_unmergeable_hs = std::make_pair(true, true); auto m0 = "CCCC"_smarts; REQUIRE(RDKit::MolOps::hasQueryHs(*m0) == has_no_query_hs); auto m = "[#1]"_smarts; REQUIRE(RDKit::MolOps::hasQueryHs(*m) == has_only_query_hs); auto m2 = "[#1,N]"_smarts; REQUIRE(RDKit::MolOps::hasQueryHs(*m2) == has_unmergeable_hs); // remove the negation auto recursive = "[$(C-[H])]"_smarts; REQUIRE(RDKit::MolOps::hasQueryHs(*recursive) == has_only_query_hs); auto recursive_or = "[$([C,#1])]"_smarts; REQUIRE(RDKit::MolOps::hasQueryHs(*recursive_or) == has_unmergeable_hs); // from rd_filters for something bigger auto keto_def_heterocycle = "[$(c([C;!R;!$(C-[N,O,S]);!$(C-[H])](=O))1naaaa1),$(c([C;!R;!$(C-[N,O,S]);!$(C-[H])](=O))1naa[n,s,o]1)]"_smarts; REQUIRE(RDKit::MolOps::hasQueryHs(*keto_def_heterocycle) == has_only_query_hs); auto github7687 = "[#1,#6,#7]"_smarts; REQUIRE(RDKit::MolOps::hasQueryHs(*github7687) == has_unmergeable_hs); auto github7687b = "[1;#7,#1,#6]"_smarts; REQUIRE(RDKit::MolOps::hasQueryHs(*github7687b) == has_unmergeable_hs); auto github7687c = "[1,#1,#6]"_smarts; REQUIRE(RDKit::MolOps::hasQueryHs(*github7687c) == has_unmergeable_hs); } TEST_CASE("Testing isRingFused") { auto molOrig = "C1C(C2CC3CCCCC3C12)C1CCCCC1"_smiles; { RWMol mol(*molOrig); auto ri = mol.getRingInfo(); REQUIRE(ri->numRings() == 4); boost::dynamic_bitset<> fusedRings(ri->numRings()); for (size_t i = 0; i < ri->numRings(); ++i) { fusedRings.set(i, ri->isRingFused(i)); } REQUIRE(fusedRings.count() == 3); REQUIRE(fusedRings.size() - fusedRings.count() == 1); auto query = "[$(C1CCC1)]-@[$(C1CCCCC1)]"_smarts; MatchVectType matchVect; SubstructMatch(mol, *query, matchVect); REQUIRE(matchVect.size() == 2); mol.removeBond(matchVect.at(0).second, matchVect.at(1).second); MolOps::sanitizeMol(mol); REQUIRE(MolToSmiles(mol) == "C1CCC(CC2CCC2C2CCCCC2)CC1"); REQUIRE(ri->numRings() == 3); fusedRings.resize(ri->numRings()); for (size_t i = 0; i < ri->numRings(); ++i) { fusedRings.set(i, ri->isRingFused(i)); } REQUIRE(fusedRings.count() == 0); REQUIRE(fusedRings.size() - fusedRings.count() == 3); } { RWMol mol(*molOrig); auto ri = mol.getRingInfo(); REQUIRE(ri->numRings() == 4); boost::dynamic_bitset<> fusedRings(ri->numRings()); for (size_t i = 0; i < ri->numRings(); ++i) { fusedRings.set(i, ri->isRingFused(i)); } REQUIRE(fusedRings.count() == 3); REQUIRE(fusedRings.size() - fusedRings.count() == 1); std::vector fusedBonds(ri->numRings()); for (size_t i = 0; i < ri->numRings(); ++i) { fusedBonds[i] = ri->numFusedBonds(i); } REQUIRE(std::count(fusedBonds.begin(), fusedBonds.end(), 0) == 1); REQUIRE(std::count(fusedBonds.begin(), fusedBonds.end(), 1) == 2); REQUIRE(std::count(fusedBonds.begin(), fusedBonds.end(), 2) == 1); auto query = "[$(C1CCCCC1-!@[CX4;R1;r4])].[$(C1C(-!@[CX4;R1;r6])CC1)]"_smarts; MatchVectType matchVect; SubstructMatch(mol, *query, matchVect); REQUIRE(matchVect.size() == 2); mol.addBond(matchVect.at(0).second, matchVect.at(1).second, Bond::SINGLE); MolOps::sanitizeMol(mol); REQUIRE(MolToSmiles(mol) == "C1CCC2C(C1)CC1C2C2C3CCCCC3C12"); REQUIRE(ri->numRings() == 5); fusedRings.resize(ri->numRings()); for (size_t i = 0; i < ri->numRings(); ++i) { fusedRings.set(i, ri->isRingFused(i)); } REQUIRE(fusedRings.count() == 5); REQUIRE(fusedRings.size() - fusedRings.count() == 0); fusedBonds.resize(ri->numRings()); for (size_t i = 0; i < ri->numRings(); ++i) { fusedBonds[i] = ri->numFusedBonds(i); } REQUIRE(std::count(fusedBonds.begin(), fusedBonds.end(), 0) == 0); REQUIRE(std::count(fusedBonds.begin(), fusedBonds.end(), 1) == 2); REQUIRE(std::count(fusedBonds.begin(), fusedBonds.end(), 2) == 3); } }