Files
rdkit/Code/GraphMol/molopstest.cpp
Ricardo Rodriguez d7cc6b67bf Convert graphmolMolOpsTest to catch2 (#8978)
* move test to catch2

* fix one broken test

* make some stuff constexpr

* refactor some checks involving exceptions

* replace CHECK_INVARIANT

* replace CHECK_INVARIANT

* clean up commented out debug logging

* initialize var

* remove redundant REQUIRE_MSG
2025-12-30 07:05:50 +01:00

8010 lines
256 KiB
C++

// 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 <algorithm>
#include <map>
#include <cmath>
#include <random>
#include <catch2/catch_all.hpp>
#include <RDGeneral/utils.h>
#include <RDGeneral/RDLog.h>
#include <GraphMol/RDKitBase.h>
#include <GraphMol/RDKitQueries.h>
#include <GraphMol/Chirality.h>
#include <GraphMol/SmilesParse/SmilesParse.h>
#include <GraphMol/SmilesParse/SmilesWrite.h>
#include <GraphMol/SmilesParse/SmartsWrite.h>
#include <GraphMol/FileParsers/FileParsers.h>
#include <GraphMol/FileParsers/MolSupplier.h>
#include <GraphMol/FileParsers/MolWriters.h>
#include <GraphMol/Substruct/SubstructMatch.h>
#include <GraphMol/QueryOps.h>
#include <GraphMol/test_fixtures.h>
#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<ROMOL_SPTR> frags;
std::vector<std::unique_ptr<ROMol>> 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";
// VECT_INT_VECT_I ri;
// for (ri == bfrs.begin(); ri != bfrs.end(); ri++) {
for (auto bring : bfrs) {
INT_VECT_I mi;
BOOST_LOG(rdInfoLog) << "( ";
// for (mi = (*ri).begin(); mi != (*ri).end(); mi++) {
for (mi = bring.begin(); mi != bring.end(); mi++) {
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<RWMol &>(*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<const QueryAtom *>(m->getAtomWithIdx(0)));
REQUIRE(sma == "C");
m2 = MolOps::addHs(*m);
REQUIRE(m2->getNumAtoms() == 8);
sma = SmartsWrite::GetAtomSmarts(
static_cast<const QueryAtom *>(m2->getAtomWithIdx(0)));
REQUIRE(sma == "C");
delete m;
m = MolOps::mergeQueryHs(*m2);
REQUIRE(m->getNumAtoms() == 2);
sma = SmartsWrite::GetAtomSmarts(
static_cast<const QueryAtom *>(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<const QueryAtom *>(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<const QueryAtom *>(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<int> activeAtoms = {1, 2};
std::vector<const Bond *> activeBonds = {m->getBondWithIdx(1)};
useBO = false;
useAtomWts = false;
std::unique_ptr<double[]> 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<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(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);
BOOST_LOG(rdInfoLog) << "ref: " << refSmi << std::endl;
m = SmilesToMol(refSmi);
REQUIRE(m);
smi = MolToSmiles(*m, 1);
BOOST_LOG(rdInfoLog) << "smi: " << smi << std::endl;
REQUIRE(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);
INT_LIST_CI 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);
INT_LIST_CI 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);
INT_LIST_CI 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<unsigned int> nh2HIdxs{3, 4};
unsigned int chHIdx = 5;
std::vector<unsigned int> 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<RWMol> 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<RWMol *>(MolOps::removeHs(static_cast<const ROMol &>(*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<RWMol *>(MolOps::removeHs(static_cast<const ROMol &>(*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<RWMol> 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<RWMol> 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<RWMol> 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<RWMol> 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<RWMol> 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 == "Cc1ccc(S(=O)(=O)/N=C2\\CC(=N\\C(C)(C)C)/C2=N\\C(C)(C)C)cc1");
delete m;
}
TEST_CASE(
"Testing sf.net issue 2830244: make sure that non-ring aromatic atoms generate errors") {
std::unique_ptr<RWMol> 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<int, int> mmap;
for (MatchVectType::const_iterator mvit = mv.begin(); mvit != mv.end();
++mvit) {
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<int, int> mmap;
for (MatchVectType::const_iterator mvit = mv.begin(); mvit != mv.end();
++mvit) {
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<int, int> mmap;
for (MatchVectType::const_iterator mvit = mv.begin(); mvit != mv.end();
++mvit) {
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<int, int> mmap;
for (MatchVectType::const_iterator mvit = mv.begin(); mvit != mv.end();
++mvit) {
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<int, int> mmap;
for (MatchVectType::const_iterator mvit = mv.begin(); mvit != mv.end();
++mvit) {
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<int, int> mmap;
for (MatchVectType::const_iterator mvit = mv.begin(); mvit != mv.end();
++mvit) {
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 (MatchVectType::const_iterator mvit = mv.begin(); mvit != mv.end();
++mvit) {
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<int, int> mmap;
mmap.clear();
for (MatchVectType::const_iterator mvit = mv.begin(); mvit != mv.end();
++mvit) {
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<unsigned int> 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<unsigned int> 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<unsigned int> 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<int, boost::shared_ptr<ROMol>> 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<int, std::unique_ptr<ROMol>> 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<int> keep;
keep.push_back(6);
keep.push_back(8);
std::map<int, boost::shared_ptr<ROMol>> 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<int, std::unique_ptr<ROMol>> 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<int> keep;
keep.push_back(6);
keep.push_back(8);
std::map<int, boost::shared_ptr<ROMol>> 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<int, std::unique_ptr<ROMol>> 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<ROMOL_SPTR> 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<ROMOL_SPTR> 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<ROMOL_SPTR> 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;
}
{
std::vector<std::string> smilesVec;
smilesVec.emplace_back("C1=C[CH+]1");
smilesVec.emplace_back("C1=CC=C[CH+]C=C1");
smilesVec.emplace_back("c1c[cH+]1");
smilesVec.emplace_back("c1ccc[cH+]cc1");
for (std::vector<std::string>::const_iterator smiles = smilesVec.begin();
smiles != smilesVec.end(); ++smiles) {
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);
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<int>("foo", 2);
REQUIRE(qm);
REQUIRE(qm->getNumAtoms() == 5);
ROMol *aqm = MolOps::adjustQueryProperties(*qm);
REQUIRE(aqm);
REQUIRE(aqm->getNumAtoms() == 5);
REQUIRE(aqm->getAtomWithIdx(4)->getProp<int>("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, &params);
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, &params);
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, &params);
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, &params);
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, &params);
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, &params);
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, &params);
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, &params);
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, &params);
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, &params);
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, &params);
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 <int> 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 == "CCO/[P+]([O-])=C1\\CSC(c2cccs2)\\C1=[P+](\\[O-])OCC");
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;
QueryAtom *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<RWMol> 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<RWMol> 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<RWMol> 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<RWMol> mol(SmilesToMol("F/C=C/[H]"));
REQUIRE(mol);
REQUIRE(mol->getNumAtoms() == 4);
REQUIRE(mol->getBondBetweenAtoms(1, 2)->getStereo() == Bond::STEREOE);
}
{
std::unique_ptr<RWMol> mol(SmilesToMol("F/C=C(/F)[H]"));
REQUIRE(mol);
REQUIRE(mol->getNumAtoms() == 4);
REQUIRE(mol->getBondBetweenAtoms(1, 2)->getStereo() == Bond::STEREOE);
}
{
std::unique_ptr<RWMol> mol(SmilesToMol("F/C=C(/[H])F"));
REQUIRE(mol);
REQUIRE(mol->getNumAtoms() == 4);
REQUIRE(mol->getBondBetweenAtoms(1, 2)->getStereo() == Bond::STEREOZ);
}
{
std::unique_ptr<RWMol> 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<RWMol> 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<RWMol> 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<ROMol> 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<ROMol> 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<RWMol> 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<RWMol> 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<RWMol> 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<unsigned int, unsigned int> &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<unsigned int, unsigned int> 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<IsotopicHsCount> 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<ROMol> mNoH(removeHs(*static_cast<ROMol *>(m.get()), ps));
REQUIRE(mNoH->getAtomWithIdx(0)->getAtomicNum() == 6);
REQUIRE(mNoH->getAtomWithIdx(0)->hasProp(common_properties::_isotopicHs));
std::vector<unsigned int> 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<ROMol> mH(MolOps::addHs(*mNoH));
std::unique_ptr<IsotopicHsCount> 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<ROMol> mH2(MolOps::removeHs(*mH));
REQUIRE(m->getNumAtoms() == mH2->getNumAtoms());
std::unique_ptr<IsotopicHsCount> 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<unsigned int> randomOrder(mNoH->getNumAtoms());
std::iota(randomOrder.begin(), randomOrder.end(), 0U);
std::shuffle(randomOrder.begin(), randomOrder.end(),
std::default_random_engine());
std::unique_ptr<ROMol> 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<Atom::ChiralType> chiralTypeSet;
std::set<Atom::ChiralType> chiralTypeSetAfterAddHs;
std::set<Atom::ChiralType> chiralTypeSetAfterRemoveAllHsAddHs;
std::set<Atom::ChiralType> chiralTypeSetAfterRemoveAllHsAddHsRemoveHs;
for (unsigned int i : {0, 1}) {
unsigned int hIdx = i + 24;
std::string expectedCipCode(1, 'R' + i);
std::unique_ptr<ROMol> 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<std::string>(
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<std::string>(
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<ROMol *>(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<std::string>(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<std::string>(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<ROMol> 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<std::string>(
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<std::string>(
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<ROMol *>(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<std::string>(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<std::string>(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<ROMol> m(MolBlockToMol(molb, sanitize, removeHs));
REQUIRE(m);
REQUIRE(m->getNumAtoms() == 7);
bool explicitOnly = false;
bool addCoords = true;
std::vector<unsigned> onlyOnAtoms = {1, 6};
std::unique_ptr<ROMol> 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);
}
#ifdef RDK_USE_URF
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;
}
}
#endif
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<ROMol &>(*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<RWMol> 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&#7,#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<unsigned int> 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);
}
}