Repository: rdkit/rdkit — pull request #6877 (cpp)
Title: Set squiggle bonds to STEREOANY with stereo atoms

Fixes #6876 

This allows squiggle bonds in mol blocks to be stereo enumerated.  If we round trip through CXSmiles, the bonds are stereo enumerable so this is intended behavior.

Resolved issues:
title: Wavy bonds in mol blocks can't be stereo enumerated
body: Test case.

If we round trip them through cx smiles, they can be stereo enumerated.

```
  SECTION("ensure we can enumerate stereo on either double bonds") {
    auto mol = R"CTAB(
  Mrv2004 11072316002D          

  0  0  0     0  0            999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 8 8 0 0 1
M  V30 BEGIN ATOM
M  V30 1 C 1.859 2.7821 0 0
M  V30 2 C 3.2818 2.1928 0 0
M  V30 3 C 3.8711 0.77 0 0
M  V30 4 C 3.2818 -0.6528 0 0
M  V30 5 C 1.859 -1.2421 0 0
M  V30 6 C 0.4362 -0.6528 0 0
M  V30 7 C -0.1533 0.77 0 0
M  V30 8 C 0.4362 2.1928 0 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 1 2
M  V30 2 1 2 3
M  V30 3 1 4 3 CFG=2
M  V30 4 2 4 5
M  V30 5 1 5 6
M  V30 6 1 6 7
M  V30 7 1 7 8
M  V30 8 1 1 8
M  V30 END BOND
M  V30 END CTAB
M  END)CTAB"_ctab;
      //mol->debugMol(std::cerr);
      std::string smi = MolToCXSmiles(*mol, SmilesWriteParams());
      std::cout << smi << std::endl;
      auto f = SmilesToMol(smi);
      f->getBondWithIdx(0)->setStereo(Bond::BondStereo::STEREOCIS);
      mol->getBondWithIdx(3)->setStereo(Bond::BondStereo::STEREOCIS);
      delete f;
  }
}
```

Hints:

Fix patch:
diff --git a/Code/GraphMol/Chirality.cpp b/Code/GraphMol/Chirality.cpp
index f748b413ca5..8bc03e8ba66 100644
--- a/Code/GraphMol/Chirality.cpp
+++ b/Code/GraphMol/Chirality.cpp
@@ -185,7 +185,7 @@ void updateDoubleBondNeighbors(ROMol &mol, Bond *dblBond, const Conformer *conf,
   // Don't do any direction setting if we've seen a squiggle bond, but do mark
   // the double bond as a crossed bond and return
   if (squiggleBondSeen) {
-    dblBond->setBondDir(Bond::EITHERDOUBLE);
+    Chirality::detail::setStereoanyFromSquiggleBond(mol, dblBond);
     return;
   }
   if (!bond1) {
@@ -200,7 +200,7 @@ void updateDoubleBondNeighbors(ROMol &mol, Bond *dblBond, const Conformer *conf,
   // Don't do any direction setting if we've seen a squiggle bond, but do mark
   // the double bond as a crossed bond and return
   if (squiggleBondSeen) {
-    dblBond->setBondDir(Bond::EITHERDOUBLE);
+      Chirality::detail::setStereoanyFromSquiggleBond(mol, dblBond);
     return;
   }
   if (!bond2) {
@@ -259,7 +259,7 @@ void updateDoubleBondNeighbors(ROMol &mol, Bond *dblBond, const Conformer *conf,
       }
     }
     if (linear) {
-      dblBond->setBondDir(Bond::EITHERDOUBLE);
+        Chirality::detail::setStereoanyFromSquiggleBond(mol, dblBond);
       return;
     }
 
@@ -919,6 +919,41 @@ bool has_protium_neighbor(const ROMol &mol, const Atom *atom) {
   return false;
 }
 
+void setStereoanyFromSquiggleBond(ROMol &mol, Bond *bond,
+				  Bond::BondStereo stereo) {
+    // NOTE:  moved from parse_doublebond_stereo CXSmilesOps
+    // the cis/trans/unknown marker is relative to the lowest numbered atom
+    // connected to the lowest numbered double bond atom and the
+    // highest-numbered atom connected to the highest-numbered double bond
+    // atom find those
+    auto begAtom = bond->getBeginAtom();
+    auto endAtom = bond->getEndAtom();
+    if (begAtom->getIdx() > endAtom->getIdx()) {
+        std::swap(begAtom, endAtom);
+    }
+    if (begAtom->getDegree() > 1 && endAtom->getDegree() > 1) {
+        unsigned int begControl = mol.getNumAtoms();
+        for (auto nbr : mol.atomNeighbors(begAtom)) {
+            if (nbr == endAtom) {
+                continue;
+            }
+            begControl = std::min(nbr->getIdx(), begControl);
+        }
+        unsigned int endControl = 0;
+        for (auto nbr : mol.atomNeighbors(endAtom)) {
+            if (nbr == begAtom) {
+                continue;
+            }
+            endControl = std::max(nbr->getIdx(), endControl);
+        }
+        if (begAtom != bond->getBeginAtom()) {
+            std::swap(begControl, endControl);
+        }
+        bond->setStereoAtoms(begControl, endControl);
+        bond->setStereo(stereo);
+        mol.setProp("_needsDetectBondStereo", 1);
+    }
+}
 }  // namespace detail
 
 typedef std::pair<int, int> INT_PAIR;
diff --git a/Code/GraphMol/Chirality.h b/Code/GraphMol/Chirality.h
index ebf92c972e2..8aa2e4d02f1 100644
--- a/Code/GraphMol/Chirality.h
+++ b/Code/GraphMol/Chirality.h
@@ -234,6 +234,8 @@ RDKIT_GRAPHMOL_EXPORT int pickBondToWedge(const Atom *atom, const ROMol &mol,
                                           const INT_VECT &nChiralNbrs,
                                           const INT_MAP_INT &resSoFar,
                                           int noNbrs);
+RDKIT_GRAPHMOL_EXPORT void setStereoanyFromSquiggleBond(ROMol &mol, Bond *bond,
+							Bond::BondStereo stereo=Bond::STEREOANY);
 }  // namespace detail
 
 //! picks the bonds which should be wedged
diff --git a/Code/GraphMol/SmilesParse/CXSmilesOps.cpp b/Code/GraphMol/SmilesParse/CXSmilesOps.cpp
index b434826e68d..27548e01230 100644
--- a/Code/GraphMol/SmilesParse/CXSmilesOps.cpp
+++ b/Code/GraphMol/SmilesParse/CXSmilesOps.cpp
@@ -1147,37 +1147,7 @@ bool parse_doublebond_stereo(Iterator &first, Iterator last, RDKit::RWMol &mol,
         return false;
       }
 
-      // the cis/trans/unknown marker is relative to the lowest numbered atom
-      // connected to the lowest numbered double bond atom and the
-      // highest-numbered atom connected to the highest-numbered double bond
-      // atom find those
-      auto begAtom = bond->getBeginAtom();
-      auto endAtom = bond->getEndAtom();
-      if (begAtom->getIdx() > endAtom->getIdx()) {
-        std::swap(begAtom, endAtom);
-      }
-      if (begAtom->getDegree() > 1 && endAtom->getDegree() > 1) {
-        unsigned int begControl = mol.getNumAtoms();
-        for (auto nbr : mol.atomNeighbors(begAtom)) {
-          if (nbr == endAtom) {
-            continue;
-          }
-          begControl = std::min(nbr->getIdx(), begControl);
-        }
-        unsigned int endControl = 0;
-        for (auto nbr : mol.atomNeighbors(endAtom)) {
-          if (nbr == begAtom) {
-            continue;
-          }
-          endControl = std::max(nbr->getIdx(), endControl);
-        }
-        if (begAtom != bond->getBeginAtom()) {
-          std::swap(begControl, endControl);
-        }
-        bond->setStereoAtoms(begControl, endControl);
-        bond->setStereo(stereo);
-        mol.setProp(SmilesParseOps::detail::_needsDetectBondStereo, 1);
-      }
+      Chirality::detail::setStereoanyFromSquiggleBond(mol, bond, stereo);
     }
     if (first < last && *first == ',') {
       ++first;
diff --git a/Code/GraphMol/catch_chirality.cpp b/Code/GraphMol/catch_chirality.cpp
index c3d5c2f5849..2ae73537461 100644
--- a/Code/GraphMol/catch_chirality.cpp
+++ b/Code/GraphMol/catch_chirality.cpp
@@ -3799,8 +3799,49 @@ TEST_CASE(
       CHECK(cp.getBondWithIdx(1)->getStereoAtoms().empty());
     }
   }
-}
+  SECTION("ensure we can enumerate stereo on either double bonds") {
+    auto mol = R"CTAB(
+  Mrv2004 11072316002D          
 
+  0  0  0     0  0            999 V3000
+M  V30 BEGIN CTAB
+M  V30 COUNTS 8 8 0 0 1
+M  V30 BEGIN ATOM
+M  V30 1 C 1.859 2.7821 0 0
+M  V30 2 C 3.2818 2.1928 0 0
+M  V30 3 C 3.8711 0.77 0 0
+M  V30 4 C 3.2818 -0.6528 0 0
+M  V30 5 C 1.859 -1.2421 0 0
+M  V30 6 C 0.4362 -0.6528 0 0
+M  V30 7 C -0.1533 0.77 0 0
+M  V30 8 C 0.4362 2.1928 0 0
+M  V30 END ATOM
+M  V30 BEGIN BOND
+M  V30 1 1 1 2
+M  V30 2 1 2 3
+M  V30 3 1 4 3 CFG=2
+M  V30 4 2 4 5
+M  V30 5 1 5 6
+M  V30 6 1 6 7
+M  V30 7 1 7 8
+M  V30 8 1 1 8
+M  V30 END BOND
+M  V30 END CTAB
+M  END)CTAB"_ctab;
+      //mol->debugMol(std::cerr);
+      std::string smi = MolToCXSmiles(*mol, SmilesWriteParams());
+      std::unique_ptr<ROMol> f(SmilesToMol(smi));
+      mol->getBondWithIdx(3)->setStereo(Bond::BondStereo::STEREOCIS);
+      f->getBondWithIdx(0)->setStereo(Bond::BondStereo::STEREOCIS);
+      CHECK(MolToSmiles(*mol) == "C1=C\\CCCCCC/1");
+      CHECK(MolToSmiles(*f) == "C1=C\\CCCCCC/1");
+      mol->getBondWithIdx(3)->setStereo(Bond::BondStereo::STEREOTRANS);
+      f->getBondWithIdx(0)->setStereo(Bond::BondStereo::STEREOTRANS);
+      CHECK(MolToSmiles(*mol) == "C1=C/CCCCCC/1");
+      CHECK(MolToSmiles(*f) == "C1=C/CCCCCC/1");
+  }
+    
+}
 TEST_CASE("adding two wedges to chiral centers") {
   SECTION("basics") {
     auto mol = R"CTAB(


Test patch:
diff --git a/Code/GraphMol/FileParsers/test1.cpp b/Code/GraphMol/FileParsers/test1.cpp
index 7550487ba06..30fda253451 100644
--- a/Code/GraphMol/FileParsers/test1.cpp
+++ b/Code/GraphMol/FileParsers/test1.cpp
@@ -4769,10 +4769,11 @@ void testGithub1034() {
         explicit_unknown_stereo)
 
     TEST_ASSERT(m->getBondWithIdx(0)->getBondType() == Bond::DOUBLE);
-    TEST_ASSERT(m->getBondWithIdx(0)->getStereo() == Bond::STEREONONE);
+    TEST_ASSERT(m->getBondWithIdx(0)->getStereo() == Bond::STEREOANY);  // now set in detectStereochemistry
+    ROMol(*m).getBondWithIdx(0)->setStereo(Bond::STEREOCIS); // ensure we have stereo atoms
     MolOps::sanitizeMol(*m);
     TEST_ASSERT(m->getBondWithIdx(0)->getBondType() == Bond::DOUBLE);
-    TEST_ASSERT(m->getBondWithIdx(0)->getStereo() == Bond::STEREONONE);
+    TEST_ASSERT(m->getBondWithIdx(0)->getStereo() == Bond::STEREOANY);
     TEST_ASSERT(m->getBondWithIdx(1)->getBondType() == Bond::SINGLE);
     TEST_ASSERT(m->getBondWithIdx(1)->getBondDir() == Bond::NONE);
     TEST_ASSERT(m->getBondWithIdx(2)->getBondType() == Bond::SINGLE);