// // 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 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(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(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 mol = std::unique_ptr(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 mol = std::unique_ptr(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 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 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 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 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 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 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 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(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; }