diff --git a/Code/GraphMol/FindStereo.cpp b/Code/GraphMol/FindStereo.cpp index b3e88715c..25a9f0a2c 100644 --- a/Code/GraphMol/FindStereo.cpp +++ b/Code/GraphMol/FindStereo.cpp @@ -654,31 +654,53 @@ void flagRingStereo(ROMol &mol, if (!knownAtoms[aidx] && (!possibleAtoms || !possibleAtoms->test(aidx))) { continue; } - if (!ringIsOddSized) { - // find the index of the atom on the opposite side of the even-sized - // ring - auto oppositeIdx = aring[(ai + halfSize) % sz]; - bool toAtomOppositePossible = false; - auto oppositeAtom = mol.getAtomWithIdx(oppositeIdx); - for (auto bond : mol.atomBonds(oppositeAtom)) { - auto bidx = bond->getIdx(); - if ((knownBonds[bidx] || - (possibleBonds && possibleBonds->test(bidx))) && - std::find(bring.begin(), bring.end(), bidx) == bring.end()) { - toAtomOppositePossible = true; - break; - } - } - if (knownAtoms[oppositeIdx] || - (possibleAtoms && possibleAtoms->test(oppositeIdx)) || - toAtomOppositePossible) { - nHere += 1 + toAtomOppositePossible; - possibleAtomsInRing.set(aidx); - possibleAtomsInRing.set(oppositeIdx); - mol.getAtomWithIdx(aidx)->setProp( - common_properties::_ringStereoOtherAtom, oppositeIdx); - continue; + for (unsigned int ringDivisor : {2, 3}) { + bool ringIsMultipleOfDivisor = ((sz % ringDivisor) == 0); + auto incrementSize = sz / ringDivisor; + + if (ringIsMultipleOfDivisor) { + // find the two indices of the atoms 1/3 the way around the ring + + unsigned int otherFoundByBondCount = 0; + unsigned int otherFoundByAtomCount = 0; + for (unsigned int indexIncrement = incrementSize; indexIncrement < sz; + indexIncrement += incrementSize) { + auto otherIdx = aring[(ai + indexIncrement) % sz]; + auto otherAtom = mol.getAtomWithIdx(otherIdx); + + for (auto bond : mol.atomBonds(otherAtom)) { + auto bidx = bond->getIdx(); + if ((knownBonds[bidx] || + (possibleBonds && possibleBonds->test(bidx))) && + std::find(bring.begin(), bring.end(), bidx) == bring.end()) { + otherFoundByBondCount++; + break; + } + } + if (otherFoundByBondCount == 0) { + if (knownAtoms[otherIdx] || + (possibleAtoms && possibleAtoms->test(otherIdx))) { + otherFoundByAtomCount++; + } + } + } + + if (otherFoundByBondCount == ringDivisor - 1 || + otherFoundByAtomCount == ringDivisor - 1) { + nHere += 1 + otherFoundByBondCount; + for (unsigned int indexIncrement = 0; indexIncrement < sz; + indexIncrement += incrementSize) { + possibleAtomsInRing.set(aring[(ai + indexIncrement) % sz]); + } + if (ringDivisor == 2) { + mol.getAtomWithIdx(aidx)->setProp( + common_properties::_ringStereoOtherAtom, + aring[(ai + incrementSize) % sz]); + } + + continue; + } } } diff --git a/Code/GraphMol/SmilesParse/catch_tests.cpp b/Code/GraphMol/SmilesParse/catch_tests.cpp index 7217ebf6e..a83fa2ccd 100644 --- a/Code/GraphMol/SmilesParse/catch_tests.cpp +++ b/Code/GraphMol/SmilesParse/catch_tests.cpp @@ -2301,8 +2301,8 @@ TEST_CASE("ring bond stereochemistry in CXSMILES") { {"C1CCCCC=CCCC1 |c:5|", "C1=C\\CCCCCCCC/1 |c:0|"}, {"C1CCCC/C=C/CCC1 |c:5|", "C1=C\\CCCCCCCC/1 |c:0|"}, {"C1=CCCCCCCCC1 |ctu:0|", "C1=CCCCCCCCC1 |ctu:0|"}, - {"C=CCCCCCCCC |ctu:0|", - "C=CCCCCCCCC"} // we don't write the markers for non-ring bonds + {"C=CCCCCCCCC |ctu:0|", "C=CCCCCCCCC"} + // we don't write the markers for non-ring bonds }; for (const auto &[smi, val] : tests) { std::unique_ptr m{SmilesToMol(smi)}; @@ -2993,45 +2993,71 @@ TEST_CASE("Ignore atom map numbers") { } TEST_CASE("Github #7340", "[Reaction][CX][CXSmiles]") { - SECTION("Test getCXExtensions with a Vector"){ + SECTION("Test getCXExtensions with a Vector") { // Create the MOL_SPTR_VECT to hold the molecular pointers const auto mols = { - "CCO* |$;;;_R1$(0,0,0;1.5,0,0;1.5,1.5,0;0,1.5,0)|"_smiles, - "C1CCCCC1 |$;label2;$|"_smiles, - "CC(=O)O |$;label1;$|"_smiles, - "*-C-* |$star_e;;star_e$,Sg:n:1::ht|"_smiles, + "CCO* |$;;;_R1$(0,0,0;1.5,0,0;1.5,1.5,0;0,1.5,0)|"_smiles, + "C1CCCCC1 |$;label2;$|"_smiles, + "CC(=O)O |$;label1;$|"_smiles, + "*-C-* |$star_e;;star_e$,Sg:n:1::ht|"_smiles, }; std::vector mol_vect; mol_vect.reserve(mols.size()); - for (const auto& mol : mols) { - mol_vect.push_back(mol.get()); + for (const auto &mol : mols) { + mol_vect.push_back(mol.get()); } // Write to smiles to populate atom and bond output order properties - for (const auto& entry : mol_vect) { + for (const auto &entry : mol_vect) { MolToSmiles(*entry); } - std::string cxExt = SmilesWrite::getCXExtensions(mol_vect, RDKit::SmilesWrite::CXSmilesFields::CX_ALL); + std::string cxExt = SmilesWrite::getCXExtensions( + mol_vect, RDKit::SmilesWrite::CXSmilesFields::CX_ALL); - CHECK(cxExt == "|(0,1.5,;1.5,1.5,;1.5,0,;0,0,;0,0,;0,0,;0,0,;0,0,;0,0,;0,0,;0,0,;0,0,;0,0,;0,0,;0,0,;0,0,;0,0,),$_R1;;;;;label2;;;;;;label1;;;star_e;;star_e$,Sg:n:15::ht:::|"); + CHECK( + cxExt == + "|(0,1.5,;1.5,1.5,;1.5,0,;0,0,;0,0,;0,0,;0,0,;0,0,;0,0,;0,0,;0,0,;0,0,;0,0,;0,0,;0,0,;0,0,;0,0,),$_R1;;;;;label2;;;;;;label1;;;star_e;;star_e$,Sg:n:15::ht:::|"); } - SECTION("Expects an error"){ + SECTION("Expects an error") { const auto mols = { - "CCO* |$;;;_R1$(0,0,0;1.5,0,0;1.5,1.5,0;0,1.5,0)|"_smiles, - "C1CCCCC1 |$;label2;$|"_smiles, - "CC(=O)O |$;label1;$|"_smiles, - "*-C-* |$star_e;;star_e$,Sg:n:1::ht|"_smiles, + "CCO* |$;;;_R1$(0,0,0;1.5,0,0;1.5,1.5,0;0,1.5,0)|"_smiles, + "C1CCCCC1 |$;label2;$|"_smiles, + "CC(=O)O |$;label1;$|"_smiles, + "*-C-* |$star_e;;star_e$,Sg:n:1::ht|"_smiles, }; std::vector mol_vect; mol_vect.reserve(mols.size()); - for (const auto& mol : mols) { - mol_vect.push_back(mol.get()); + for (const auto &mol : mols) { + mol_vect.push_back(mol.get()); } - CHECK_THROWS_AS(SmilesWrite::getCXExtensions(mol_vect, RDKit::SmilesWrite::CXSmilesFields::CX_ALL), ValueErrorException); + CHECK_THROWS_AS(SmilesWrite::getCXExtensions( + mol_vect, RDKit::SmilesWrite::CXSmilesFields::CX_ALL), + ValueErrorException); + } +} + +TEST_CASE("trimethylcyclohexane") { + SECTION("Basic") { + UseLegacyStereoPerceptionFixture useLegacy(false); + + auto smi = "C[C@H]1C[C@@H](C)C[C@@H](C)C1"; + RDKit::v2::SmilesParse::SmilesParserParams smilesParserParams; + auto m1 = RDKit::v2::SmilesParse::MolFromSmiles(smi, smilesParserParams); + auto smiOut = RDKit::MolToCXSmiles(*m1); + CHECK(smiOut == "C[C@@H]1C[C@H](C)C[C@H](C)C1"); + } + SECTION("WithEnhancedStereo") { + UseLegacyStereoPerceptionFixture useLegacy(false); + + auto smi = "C[C@H]1C[C@@H](C)C[C@@H](C)C1 |o1:1,o2:6,o3:3|"; + RDKit::v2::SmilesParse::SmilesParserParams smilesParserParams; + auto m1 = RDKit::v2::SmilesParse::MolFromSmiles(smi, smilesParserParams); + auto smiOut = RDKit::MolToCXSmiles(*m1); + CHECK(smiOut == "C[C@H]1C[C@H](C)C[C@H](C)C1 |o1:1,o2:3,o3:6|"); } }