Files
rdkit/Code/GraphMol/FileParsers/testAtropisomers.cpp
Yakov Pechersky 0986d22c58 Deterministic kekulize, independent of atom and bond order (#9125)
* Make kekulization deterministic

* Add tautomer order-independence regression (python)

* Adjust tautomer tests for deterministic kekulization

* Update graphmol wedged-bond kekulization checks

* SmilesParse: update aromatic bond index expectations

* SmilesParse: refresh cxsmilesTest expected files

* Depictor: update testDepictor expected MolBlocks

* Depictor: update depictorCatch expectations

* Depictor Wrap: update expected MolBlock for pyDepictor

* MarvinParse: update testMrvToMol expected outputs

* FileParsers: refresh testAtropisomers expected outputs

* FileParsers: update tests for deterministic kekulization

* MolDraw2D: refresh brittle bond assertions

* RascalMCES: update expected cluster size

* MinimalLib: make cffi wedging check order-independent

* documentation fix

* MinimalLib: update Kekulé bond table in aligned-coords test

* Hoist duplicated lambdas to TEST_CASE scope

* Remove unused originalWedges variable

* Remove redundant bounds check; clarify wedge-end preference

* Pre-sort allAtms by wedge-end + rank

* Use mol.atomNeighbors() for neighbor iteration

* Check inAllAtms before linear-scanning done

* Drop redundant optsV/wedgedOptsV sorts

* Remove unused Canon.h include

* Add canonical parameter to Kekulize; skip ranking during sanitization

* Test canonical re-kekulization preserves stereo across atom orderings

* MinimalLib: update Kekulé bond orders in invertedWedges

* Change Kekulize canonical default to false, expose in Python wrappers

* keep rank order, push_back

* Revert "RascalMCES: update expected cluster size"

This reverts commit a81bb39495.

* docstring change

* expose new flag to python wrapper

* document changes in ReleaseNotes.md

* revert minimallib test changes again

* canonical = true defaults

* Revert "revert minimallib test changes again"

This reverts commit 039e1d84da.

* Reapply "RascalMCES: update expected cluster size"

This reverts commit 7b83a7a3e8.

---------

Co-authored-by: greg landrum <greg.landrum@gmail.com>
2026-03-19 08:43:13 +01:00

622 lines
24 KiB
C++

//
// Copyright (C) 2002-2021 Collaboartive Drug Discovery 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.
//
#include <RDGeneral/RDLog.h>
#include <GraphMol/RDKitBase.h>
#include <GraphMol/FileParsers/FileParsers.h>
#include <GraphMol/FileParsers/SequenceParsers.h>
#include <GraphMol/FileParsers/SequenceWriters.h>
#include <GraphMol/FileParsers/MolFileStereochem.h>
#include <GraphMol/Depictor/RDDepictor.h>
#include <GraphMol/ChemReactions/Reaction.h>
#include <GraphMol/ChemReactions/ReactionParser.h>
#include <GraphMol/SmilesParse/SmilesParse.h>
#include <GraphMol/SmilesParse/SmilesWrite.h>
#include <GraphMol/SmilesParse/SmilesWrite.h>
#include <GraphMol/Atropisomers.h>
#include <GraphMol/Chirality.h>
#include <GraphMol/test_fixtures.h>
#include <string>
#include <fstream>
#include <sstream>
#include <vector>
#include <boost/lexical_cast.hpp>
#include <filesystem>
using namespace RDKit;
class MolAtropTest {
public:
public:
std::string testToRun;
bool generateExpectedFiles;
MolAtropTest() {
testToRun = "";
generateExpectedFiles = false;
}
public:
class MolTest {
public:
unsigned int atomCount;
unsigned int bondCount;
std::string fileName;
bool expectedResult;
MolTest(std::string fileNameInit, bool expectedResultInit,
int atomCountInit, int bondCountInit)
: atomCount(atomCountInit),
bondCount(bondCountInit),
fileName(fileNameInit),
expectedResult(expectedResultInit) {};
};
class KekuleTest {
public:
unsigned int atomCount;
unsigned int bondCount;
std::string smiles;
std::string expectedOutput;
bool expectedResult;
KekuleTest(std::string smilesInit, std::string nameInit,
bool expectedResultInit, int atomCountInit, int bondCountInit)
: atomCount(atomCountInit),
bondCount(bondCountInit),
smiles(smilesInit),
expectedOutput(nameInit),
expectedResult(expectedResultInit) {};
};
void generateNewExpectedFilesIfSoSpecified(std::string filename,
std::string dataToWrite) {
if (generateExpectedFiles) {
std::ofstream out;
out.open(filename);
out << dataToWrite;
}
}
std::string GetExpectedValue(std::string expectedFileName) {
std::stringstream expectedMolStr;
std::ifstream in;
in.open(expectedFileName);
expectedMolStr << in.rdbuf();
return expectedMolStr.str();
}
void testMolFiles(const MolTest *molFileTest) {
BOOST_LOG(rdInfoLog) << "testing mol files with atropisomers" << std::endl;
std::string rdbase = getenv("RDBASE");
std::string fName = rdbase +
"/Code/GraphMol/FileParsers/test_data/atropisomers/" +
molFileTest->fileName;
// first pass - no sanitiaton, but reapplyMolBlockWedging is ON
try {
std::unique_ptr<RWMol> mol(MolFileToMol(fName, false, false, false));
RDKit::Chirality::removeNonExplicit3DChirality(*mol);
TEST_ASSERT(mol != nullptr);
TEST_ASSERT(mol->getNumAtoms() == molFileTest->atomCount)
TEST_ASSERT(mol->getNumBonds() == molFileTest->bondCount)
{
MolOps::Kekulize(*mol);
RDKit::Chirality::reapplyMolBlockWedging(*mol);
std::string outMolStr = MolToMolBlock(*mol, true, 0, true, true);
generateNewExpectedFilesIfSoSpecified(fName + ".NEW.sdf", outMolStr);
TEST_ASSERT(GetExpectedValue(fName + ".expected.sdf") == outMolStr);
}
// 2nd pass without reapplying the mol block wedging -
// the itropisomers will be marked automatically
// SANITIZATION IS ON
mol = std::unique_ptr<RWMol>(MolFileToMol(fName, true, false, false));
RDKit::Chirality::removeNonExplicit3DChirality(*mol);
{
MolOps::Kekulize(*mol);
std::string outMolStr = MolToMolBlock(*mol, true, 0, true, true);
generateNewExpectedFilesIfSoSpecified(fName + ".NEW2.sdf", outMolStr);
TEST_ASSERT(GetExpectedValue(fName + ".expected2.sdf") == outMolStr);
}
// CXSMILES
mol = std::unique_ptr<RWMol>(MolFileToMol(fName, false, false, false));
RDKit::Chirality::removeNonExplicit3DChirality(*mol);
{
std::string expectedFileName = fName + ".expected.cxsmi";
SmilesWriteParams ps;
ps.canonical = false;
ps.doKekule = true;
unsigned int flags = SmilesWrite::CXSmilesFields::CX_COORDS |
SmilesWrite::CXSmilesFields::CX_MOLFILE_VALUES |
SmilesWrite::CXSmilesFields::CX_ATOM_PROPS |
SmilesWrite::CXSmilesFields::CX_BOND_CFG |
SmilesWrite::CXSmilesFields::CX_ENHANCEDSTEREO
//| SmilesWrite::CXSmilesFields::CX_ALL
;
std::string smilesOut =
MolToCXSmiles(*mol, ps, flags, RestoreBondDirOptionTrue);
generateNewExpectedFilesIfSoSpecified(fName + ".NEW.cxsmi", smilesOut);
TEST_ASSERT(GetExpectedValue(fName + ".expected.cxsmi") == smilesOut);
}
BOOST_LOG(rdInfoLog) << "done" << std::endl;
} catch (const std::exception &e) {
if (molFileTest->expectedResult != false) {
throw;
}
return;
}
TEST_ASSERT(molFileTest->expectedResult == true);
return;
}
void testAromAtropMolFile(const MolTest *molFileTest) {
BOOST_LOG(rdInfoLog) << "testing aromtizing mol files with atropisomers"
<< std::endl;
std::string rdbase = getenv("RDBASE");
std::string fName = rdbase +
"/Code/GraphMol/FileParsers/test_data/atropisomers/" +
molFileTest->fileName;
UseLegacyStereoPerceptionFixture useLegacy(false);
std::unique_ptr<RWMol> mol =
std::unique_ptr<RWMol>(MolFileToMol(fName, false, false, false));
TEST_ASSERT(mol->getNumAtoms() == molFileTest->atomCount)
TEST_ASSERT(mol->getNumBonds() == molFileTest->bondCount)
testAromAtropMol(mol.get(), molFileTest->expectedResult, fName);
}
void testAromAtropMol(RWMol *mol, bool expectedResult, std::string fName) {
try {
RDKit::Chirality::removeNonExplicit3DChirality(*mol);
auto sanitizeOps =
MolOps::SANITIZE_SETAROMATICITY + MolOps::SANITIZE_CLEANUPCHIRALITY +
MolOps::SANITIZE_SETHYBRIDIZATION + MolOps::SANITIZE_SETCONJUGATION;
unsigned int operationThatFailed = 0;
RDKit::MolOps::sanitizeMol(*mol, operationThatFailed, sanitizeOps);
for (const auto &atom : mol->atoms()) {
if (atom->getChiralTag() != Atom::CHI_UNSPECIFIED) {
BOOST_LOG(rdInfoLog) << "atom with stereo" << std::endl;
}
}
for (const auto &bond : mol->bonds()) {
if (bond->getBondDir() != Bond::BondDir::NONE) {
BOOST_LOG(rdInfoLog) << "bond with wedging" << std::endl;
}
}
{
SmilesWriteParams ps;
ps.canonical = true;
ps.doKekule = false;
ps.allBondsExplicit = false;
ps.allHsExplicit = false;
ps.doIsomericSmiles = true;
ps.doKekule = false;
ps.doRandom = false;
ps.rootedAtAtom = -1;
unsigned int flags = SmilesWrite::CXSmilesFields::CX_COORDS |
SmilesWrite::CXSmilesFields::CX_MOLFILE_VALUES |
SmilesWrite::CXSmilesFields::CX_ATOM_PROPS |
SmilesWrite::CXSmilesFields::CX_BOND_CFG |
SmilesWrite::CXSmilesFields::CX_ENHANCEDSTEREO;
std::string smilesOut =
MolToCXSmiles(*mol, ps, flags, RestoreBondDirOptionClear);
std::string expectedFileName = fName + ".expectedArom.cxsmi";
generateNewExpectedFilesIfSoSpecified(fName + ".NEWArom.cxsmi",
smilesOut);
TEST_ASSERT(GetExpectedValue(fName + ".expectedArom.cxsmi") ==
smilesOut);
}
BOOST_LOG(rdInfoLog) << "done" << std::endl;
} catch (const std::exception &e) {
if (expectedResult != false) {
throw;
}
return;
}
TEST_ASSERT(expectedResult == true);
}
void testKekuleWedgeError(RWMol *mol, std::string expectedSmi,
bool expectedResult, unsigned int expectedAtomCount,
unsigned int expectedBondCount) {
BOOST_LOG(rdInfoLog) << "testing aromatic atropisomers" << std::endl;
TEST_ASSERT(mol != nullptr);
TEST_ASSERT(mol->getNumAtoms() == expectedAtomCount)
TEST_ASSERT(mol->getNumBonds() == expectedBondCount)
try {
SmilesWriteParams ps;
ps.canonical = true;
ps.doKekule = true;
ps.allBondsExplicit = false;
ps.allHsExplicit = false;
ps.doIsomericSmiles = true;
ps.doRandom = false;
ps.rootedAtAtom = -1;
unsigned int flags = SmilesWrite::CXSmilesFields::CX_MOLFILE_VALUES |
SmilesWrite::CXSmilesFields::CX_ATOM_PROPS |
SmilesWrite::CXSmilesFields::CX_BOND_CFG |
SmilesWrite::CXSmilesFields::CX_ENHANCEDSTEREO;
std::string smilesOut =
MolToCXSmiles(*mol, ps, flags, RestoreBondDirOptionClear);
TEST_ASSERT(expectedSmi == smilesOut);
} catch (const std::exception &e) {
if (expectedResult != false) {
throw;
}
return;
}
TEST_ASSERT(expectedResult == true);
return;
}
void testKekuleWedgeErrorSmiles(const KekuleTest *kekuleTest) {
BOOST_LOG(rdInfoLog) << "testing aromatic atropisomers" << std::endl;
UseLegacyStereoPerceptionFixture useLegacy(false);
RDKit::SmilesParserParams params;
params.allowCXSMILES = true; // recognize and parse CXSMILES
params.debugParse = false; // disable debugging in the SMILES parser
params.parseName = true; // parse (and set) the molecule name as well
params.removeHs = false; // do not remove Hs
params.replacements = nullptr; // no SMILES "macros"
params.sanitize = true; // do not sanitize the molecule
params.skipCleanup =
false; // do not skip the final cleanup stage (for internal RDKit use)
params.strictCXSMILES =
false; // do not throw an exception if the CXSMILES parsing fails
std::unique_ptr<RWMol> mol =
std::unique_ptr<RWMol>(SmilesToMol(kekuleTest->smiles, params));
testKekuleWedgeError(mol.get(), kekuleTest->expectedOutput,
kekuleTest->expectedResult, kekuleTest->atomCount,
kekuleTest->bondCount);
}
void testKekuleWedgeErrorMol(const MolTest *kekuleTest) {
BOOST_LOG(rdInfoLog) << "testing aromatic atropisomers" << std::endl;
UseLegacyStereoPerceptionFixture useLegacy(false);
std::string rdbase = getenv("RDBASE");
std::string fName = rdbase +
"/Code/GraphMol/FileParsers/test_data/atropisomers/" +
kekuleTest->fileName;
std::unique_ptr<RWMol> mol(MolFileToMol(fName, true, false, false));
testKekuleWedgeError(mol.get(), GetExpectedValue(fName + ".expected.cxsmi"),
kekuleTest->expectedResult, kekuleTest->atomCount,
kekuleTest->bondCount);
}
void RunTests() {
// the molecule tests
if (testToRun == "" || testToRun == "sdfTests") {
std::list<MolTest> sdfTests{
MolTest("atropWedgeTest.sdf", true, 16, 16),
MolTest("AtropTest.sdf", true, 38, 41),
MolTest("AtropManyChiralsEnhanced.sdf", true, 20, 20),
MolTest("AtropManyChiralsEnhanced2.sdf", true, 20, 20),
MolTest("AtropManyChirals.sdf", true, 20, 20),
MolTest("BMS-986142.sdf", true, 42, 47),
MolTest("BMS-986142_3d_chiral.sdf", true, 72, 77),
MolTest("BMS-986142_3d.sdf", true, 72, 77),
MolTest("BMS-986142_atrop1.sdf", true, 42, 47),
MolTest("BMS-986142_atrop2.sdf", true, 42, 47),
MolTest("BMS-986142_atrop3.sdf", true, 42, 47),
MolTest("BMS-986142_atrop4.sdf", true, 42, 47),
MolTest("BMS-986142_atrop5.sdf", true, 42, 47),
MolTest("BMS-986142_atrop6.sdf", true, 42, 47),
MolTest("BMS-986142_atrop7.sdf", true, 42, 47),
MolTest("BMS-986142_atrop8.sdf", true, 42, 47),
MolTest("BMS-986142_atropBad2.sdf", true, 42, 47),
MolTest("JDQ443.sdf", true, 38, 44),
MolTest("JDQ443_3d.sdf", true, 66, 72),
MolTest("JDQ443_atrop1.sdf", true, 38, 44),
MolTest("JDQ443_atrop2.sdf", true, 38, 44),
MolTest("JDQ443_atrop3.sdf", true, 38, 44),
MolTest("JDQ443_atropBad1.sdf", true, 38, 44),
MolTest("RP-6306.sdf", true, 24, 26),
MolTest("RP-6306_atrop1.sdf", true, 24, 26),
MolTest("RP-6306_atrop2.sdf", true, 24, 26),
MolTest("RP-6306_atrop3.sdf", true, 24, 26),
MolTest("RP-6306_atrop4.sdf", true, 24, 26),
MolTest("RP-6306_atrop5.sdf", true, 24, 26),
MolTest("RP-6306_atropBad1.sdf", true, 24, 26),
MolTest("RP-6306_atropBad2.sdf", true, 24, 26),
// note the rp-6306_3d.sdf is backwards from the 2D versions
// the 2D version were based on images from drug hunter
// the 3D version came from PUBCHEM
MolTest("RP-6306_3d.sdf", true, 44, 46),
MolTest("Sotorasib.sdf", true, 41, 45),
MolTest("Sotorasib_atrop1.sdf", true, 41, 45),
MolTest("Sotorasib_atrop2.sdf", true, 41, 45),
MolTest("Sotorasib_atrop3.sdf", true, 41, 45),
MolTest("Sotorasib_atrop4.sdf", true, 41, 45),
MolTest("Sotorasib_atrop5.sdf", true, 41, 45),
MolTest("Sotorasib_atropBad1.sdf", true, 41, 45),
MolTest("Sotorasib_atropBad2.sdf", true, 41, 45),
// note the sotorasib_3d.sdf is backwards from the 2D versions
// the 2D version were based on images from drug hunter
// the 3D version came from PUBCHEM
MolTest("Sotorasib_3d.sdf", true, 71, 75),
MolTest("ZM374979.sdf", true, 45, 49),
MolTest("ZM374979_atrop1.sdf", true, 45, 49),
MolTest("ZM374979_atrop2.sdf", true, 45, 49),
MolTest("ZM374979_atrop3.sdf", true, 45, 49),
MolTest("ZM374979_atropBad1.sdf", true, 45, 49),
MolTest("Mrtx1719.sdf", true, 33, 37),
// note the Mrtx1719_3d.sdf is backwards from the 2D versions
// the 2D version were based on images from drug hunter
// the 3D version came from PUBCHEM
MolTest("Mrtx1719_3d.sdf", true, 51, 55),
MolTest("Mrtx1719_atrop1.sdf", true, 33, 37),
MolTest("Mrtx1719_atrop2.sdf", true, 33, 37),
MolTest("Mrtx1719_atrop3.sdf", true, 33, 37),
MolTest("Mrtx1719_atropBad1.sdf", true, 33, 37),
// macrocycles
MolTest("macrocycle-9-meta-wedge.sdf", true, 24, 26),
MolTest("macrocycle-9-ortho-wedge.sdf", true, 24, 26),
MolTest("macrocycle-8-meta-wedge.sdf", true, 23, 25),
MolTest("macrocycle-8-ortho-wedge.sdf", true, 23, 25),
MolTest("macrocycle-7-meta-wedge.sdf", true, 22, 24),
MolTest("macrocycle-7-ortho-wedge.sdf", true, 22, 24),
MolTest("macrocycle-6-meta-wedge.sdf", true, 21, 23),
MolTest("macrocycle-6-ortho-wedge.sdf", true, 21, 23),
MolTest("macrocycle-5-meta-wedge.sdf", true, 20, 22),
MolTest("macrocycle-5-ortho-wedge.sdf", true, 20, 22),
MolTest("macrocycle-9-meta-Cl-ortho-wedge.sdf", true, 25, 27),
MolTest("macrocycle-8-meta-Cl-ortho-wedge.sdf", true, 24, 26),
MolTest("macrocycle-7-meta-Cl-ortho-wedge.sdf", true, 23, 25),
MolTest("macrocycle-6-meta-Cl-ortho-wedge.sdf", true, 22, 24),
MolTest("macrocycle-5-meta-Cl-ortho-wedge.sdf", true, 21, 23),
MolTest("macrocycle-9-meta-broken-wedge.sdf", true, 24, 25),
MolTest("macrocycle-9-ortho-broken-wedge.sdf", true, 24, 25),
MolTest("macrocycle-8-meta-broken-wedge.sdf", true, 23, 24),
MolTest("macrocycle-8-ortho-broken-wedge.sdf", true, 23, 24),
MolTest("macrocycle-7-meta-broken-wedge.sdf", true, 22, 23),
MolTest("macrocycle-7-ortho-broken-wedge.sdf", true, 22, 23),
MolTest("macrocycle-6-meta-broken-wedge.sdf", true, 21, 22),
MolTest("macrocycle-6-ortho-broken-wedge.sdf", true, 21, 22),
MolTest("macrocycle-5-meta-broken-wedge.sdf", true, 20, 21),
MolTest("macrocycle-5-ortho-broken-wedge.sdf", true, 20, 21),
MolTest("macrocycle-9-meta-hash.sdf", true, 24, 26),
MolTest("macrocycle-9-ortho-hash.sdf", true, 24, 26),
MolTest("macrocycle-8-meta-hash.sdf", true, 23, 25),
MolTest("macrocycle-8-ortho-hash.sdf", true, 23, 25),
MolTest("macrocycle-7-meta-hash.sdf", true, 22, 24),
MolTest("macrocycle-7-ortho-hash.sdf", true, 22, 24),
MolTest("macrocycle-6-meta-hash.sdf", true, 21, 23),
MolTest("macrocycle-6-ortho-hash.sdf", true, 21, 23),
MolTest("macrocycle-5-meta-hash.sdf", true, 20, 22),
MolTest("macrocycle-5-ortho-hash.sdf", true, 20, 22),
MolTest("macrocycle-9-meta-Cl-ortho-hash.sdf", true, 25, 27),
MolTest("macrocycle-8-meta-Cl-ortho-hash.sdf", true, 24, 26),
MolTest("macrocycle-7-meta-Cl-ortho-hash.sdf", true, 23, 25),
MolTest("macrocycle-6-meta-Cl-ortho-hash.sdf", true, 22, 24),
MolTest("macrocycle-5-meta-Cl-ortho-hash.sdf", true, 21, 23),
MolTest("macrocycle-9-meta-broken-hash.sdf", true, 24, 25),
MolTest("macrocycle-9-ortho-broken-hash.sdf", true, 24, 25),
MolTest("macrocycle-8-meta-broken-hash.sdf", true, 23, 24),
MolTest("macrocycle-8-ortho-broken-hash.sdf", true, 23, 24),
MolTest("macrocycle-7-meta-broken-hash.sdf", true, 22, 23),
MolTest("macrocycle-7-ortho-broken-hash.sdf", true, 22, 23),
MolTest("macrocycle-6-meta-broken-hash.sdf", true, 21, 22),
MolTest("macrocycle-6-ortho-broken-hash.sdf", true, 21, 22),
MolTest("macrocycle-5-meta-broken-hash.sdf", true, 20, 21),
MolTest("macrocycle-5-ortho-broken-hash.sdf", true, 20, 21),
};
for (auto sdfTest : sdfTests) {
BOOST_LOG(rdInfoLog) << "Test: " << sdfTest.fileName << std::endl;
testMolFiles(&sdfTest);
}
}
if (testToRun == "" || testToRun == "AromAtropMol") {
std::list<MolTest> sdfTests{
MolTest("BMS-986142_atrop1.sdf", true, 42, 47),
MolTest("BMS-986142_3d_chiral.sdf", true, 72, 77),
MolTest("BMS-986142_3d.sdf", true, 72, 77),
};
for (auto sdfTest : sdfTests) {
BOOST_LOG(rdInfoLog) << "Test: " << sdfTest.fileName << std::endl;
testAromAtropMolFile(&sdfTest);
}
}
if (testToRun == "" || testToRun == "KekuleWedgeError") {
std::list<KekuleTest> kekuleTests{
KekuleTest(
"CC1C(C2C(Cl)=CC=CC=2C)=C(Cl)C=CC=1 |wU:3.3,(17.57,-4.18,;17.57,-5.18,;16.71,-5.67,;15.84,-5.18,;15.84,-4.18,;16.71,-3.67,;14.98,-3.67,;14.11,-4.18,;14.11,-5.18,;14.98,-5.67,;14.98,-6.67,;16.71,-6.67,;15.84,-7.18,;17.57,-7.18,;18.44,-6.67,;18.44,-5.67,)|",
"CC1=C(C2=C(C)C=CC=C2Cl)C(Cl)=CC=C1 |wU:2.11|", true, 16, 17),
KekuleTest("CC1C(C2C(Cl)=CC=CC=2C)=C(Cl)C=CC=1 |wD:3.3|",
"CC1=C(C2=C(C)C=CC=C2Cl)C(Cl)=CC=C1 |wU:2.11|", true, 16,
17),
KekuleTest(
"CC1C=CC(O)=C(C)C=1N1C2C(=NC=CC=2OC2C=NC=CC=2)C(C(=O)N)=C1N |wD:8.7|",
"CC1=C(O)C=CC(C)=C1N1C(N)=C(C(N)=O)C2=NC=CC(OC3=CC=CN=C3)=C21 |wD:9.10|",
true, 29, 32),
};
for (auto kekuleTest : kekuleTests) {
BOOST_LOG(rdInfoLog) << "Test: " << kekuleTest.smiles << std::endl;
testKekuleWedgeErrorSmiles(&kekuleTest);
}
}
if (testToRun == "" || testToRun == "KekuleWedgeErrorMol") {
std::list<MolTest> kekuleTests{
MolTest("atropWedgeError2.mol", true, 16, 17),
MolTest("atropWedgeError.mol", true, 29, 32),
};
for (auto kekuleTest : kekuleTests) {
BOOST_LOG(rdInfoLog) << "Test: " << kekuleTest.fileName << std::endl;
testKekuleWedgeErrorMol(&kekuleTest);
}
}
}
};
void testLookForAtropisomersInSDdfFiles(std::string fileName,
unsigned int expectedHits,
unsigned int expectedMisses) {
BOOST_LOG(rdInfoLog) << "Looking for atropisomers in " << fileName
<< std::endl;
std::string rdbase = getenv("RDBASE");
std::string fName =
rdbase + "/Code/GraphMol/FileParsers/test_data/atropisomers/" + fileName;
std::ifstream in;
in.open(fName);
std::string line;
unsigned int foundCount = 0;
unsigned int notFoundCount = 0;
while (!in.eof()) {
std::string molBlock = "";
while (std::getline(in, line)) {
if (line.find("$$$$") != std::string::npos) {
break;
}
molBlock += line + "\n";
}
if (molBlock.length() < 10) {
continue; // try for another;
}
std::unique_ptr<RWMol> mol(MolBlockToMol(molBlock, false, false, false));
TEST_ASSERT(mol != nullptr);
auto hasAtropisomers = RDKit::Atropisomers::doesMolHaveAtropisomers(*mol);
if (hasAtropisomers) {
BOOST_LOG(rdInfoLog) << "Found atropisomers in " << fileName << std::endl;
foundCount++;
printf("Atropisomers- %d hits %d misses\r", foundCount, notFoundCount);
std::flush(std::cout);
std::ofstream out;
out.open(fName + "_" + std::to_string(foundCount) + ".sdf");
out << molBlock << std::endl;
} else {
notFoundCount++;
if (notFoundCount % 100 == 0) {
printf("Atropisomers- %d hits %d misses\r", foundCount,
notFoundCount);
std::flush(std::cout);
}
}
}
printf("\nFinal results:\nFound atropisomers in %s - %d hits %d misses\n",
fileName.c_str(), foundCount, notFoundCount);
TEST_ASSERT(foundCount == expectedHits);
TEST_ASSERT(notFoundCount == expectedMisses);
}
void testSulfinamideExamplesHaveNoAtropisomers() {
const std::vector<std::string> controlFiles = {
"sulfinamide-double-bond-O-R.mol",
"sulfinamide-single-bond-O-R.mol",
"sulfinamide-single-bond-O-S.mol",
};
std::string rdbase = getenv("RDBASE");
std::stringstream warningCapture;
rdWarningLog->SetTee(warningCapture);
for (const auto &file : controlFiles) {
auto fName = rdbase +
"/Code/GraphMol/FileParsers/test_data/atropisomers/" +
file;
BOOST_LOG(rdInfoLog) << "Validating absence of atropisomers in " << file
<< std::endl;
auto mol = std::unique_ptr<RWMol>(MolFileToMol(fName, true, false, false));
TEST_ASSERT(mol);
Atropisomers::detectAtropisomerChirality(*mol, &mol->getConformer());
TEST_ASSERT(!Atropisomers::doesMolHaveAtropisomers(*mol));
}
rdWarningLog->ClearTee();
TEST_ASSERT(warningCapture.str().empty());
}
int main(int argc, char *argv[]) {
(void)argc;
(void)argv;
RDLog::InitLogs();
boost::logging::enable_logs("rdApp.info");
BOOST_LOG(rdInfoLog) << " ---- Running with POSIX locale ----- " << std::endl;
MolAtropTest molAtropTest;
if (argc > 1) {
molAtropTest.testToRun = argv[1];
}
if (argc > 2 && std::string(argv[2]) == "generate") {
molAtropTest.generateExpectedFiles = true;
}
RDLog::InitLogs();
BOOST_LOG(rdInfoLog) << " ---- Running with POSIX locale ----- " << std::endl;
molAtropTest.RunTests();
testLookForAtropisomersInSDdfFiles("TestMultInSDF.sdf", 1, 4);
testSulfinamideExamplesHaveNoAtropisomers();
return 0;
}