Repository: rdkit/rdkit — pull request #6482 (cpp)
Title: Fixes #6479

This makes sure that information which is not going to be in the output SMILES doesn't end up getting used in the canonicalization.


Resolved issues:
title: StereoGroup information should not impact canonicalization when CXSMILES isn't being generated
body: **Describe the bug**
Information about StereoGroups is used in the canonicalization process (unless includeChirality is false). This is fine if we're generating CXSMILES (where you see the stereogroup info), but problematic when generating SMILES.

We should only be including information in the canonicalization which will eventually show up in the SMILES.

Here's the demo:
```
In [16]: Chem.CanonSmiles('F[C@H](Cl)NCO[C@H](F)Cl |&1:6|',useChiral=True)
Out[16]: 'F[C@H](Cl)NCO[C@H](F)Cl'

In [17]: Chem.CanonSmiles('F[C@H](Cl)OCN[C@H](F)Cl |&1:6|',useChiral=True)
Out[17]: 'F[C@H](Cl)OCN[C@H](F)Cl'

In [18]: Chem.CanonSmiles(_16)==Chem.CanonSmiles(_17)
Out[18]: True
```

**Configuration (please complete the following information):**
 - RDKit version: release and master
 - OS: all


Hints:

Fix patch:
diff --git a/Code/GraphMol/Canon.cpp b/Code/GraphMol/Canon.cpp
index 55a80ec73c6..53277f71b5c 100644
--- a/Code/GraphMol/Canon.cpp
+++ b/Code/GraphMol/Canon.cpp
@@ -1228,7 +1228,8 @@ void canonicalizeFragment(ROMol &mol, int atomIdx,
             }
           }
         } else if (size_t sgidx;
-                   msI.obj.atom->getPropIfPresent("_stereoGroup", sgidx)) {
+                   msI.obj.atom->getPropIfPresent("_stereoGroup", sgidx) &&
+                   mol.getStereoGroups().size() > sgidx) {
           // make sure that the reference atom in the stereogroup is CCW
           auto &sg = mol.getStereoGroups()[sgidx];
           bool swapIt =
diff --git a/Code/GraphMol/FileParsers/cdxml_parser_catch.cpp b/Code/GraphMol/FileParsers/cdxml_parser_catch.cpp
index da6534bc2f5..882599250ce 100644
--- a/Code/GraphMol/FileParsers/cdxml_parser_catch.cpp
+++ b/Code/GraphMol/FileParsers/cdxml_parser_catch.cpp
@@ -426,7 +426,7 @@ TEST_CASE("CDXML") {
   SECTION("Enhanced Stereo") {
     auto fname = cdxmlbase + "beta-cypermethrin.cdxml";
     std::vector<std::string> expected = {
-        "CC1(C)[C@H](C=C(Cl)Cl)[C@@H]1C(=O)O[C@H](C#N)c1cccc(Oc2ccccc2)c1"};
+        "CC1(C)[C@H](C=C(Cl)Cl)[C@H]1C(=O)O[C@@H](C#N)c1cccc(Oc2ccccc2)c1"};
     std::vector<std::string> expected_cx = {
         "CC1(C)[C@H](C=C(Cl)Cl)[C@@H]1C(=O)O[C@H](C#N)c1cccc(Oc2ccccc2)c1 |&1:3,&2:8,12|"};
     auto mols = CDXMLFileToMols(fname);
@@ -441,7 +441,7 @@ TEST_CASE("CDXML") {
   SECTION("Enhanced Stereo 2") {
     auto fname = cdxmlbase + "beta-cypermethrin-or-abs.cdxml";
     std::vector<std::string> expected = {
-        "CC1(C)[C@H](C=C(Cl)Cl)[C@@H]1C(=O)O[C@H](C#N)c1cccc(Oc2ccccc2)c1"};
+        "CC1(C)[C@H](C=C(Cl)Cl)[C@H]1C(=O)O[C@@H](C#N)c1cccc(Oc2ccccc2)c1"};
     std::vector<std::string> expected_cx = {
         "CC1(C)[C@H](C=C(Cl)Cl)[C@@H]1C(=O)O[C@H](C#N)c1cccc(Oc2ccccc2)c1 |o1:8,12|"};
     auto mols = CDXMLFileToMols(fname);
diff --git a/Code/GraphMol/MolHash/hashfunctions.cpp b/Code/GraphMol/MolHash/hashfunctions.cpp
index 9869ebb778e..223162677a7 100644
--- a/Code/GraphMol/MolHash/hashfunctions.cpp
+++ b/Code/GraphMol/MolHash/hashfunctions.cpp
@@ -295,6 +295,14 @@ void NormalizeHCount(Atom *aptr) {
   aptr->setNumExplicitHs(hcount);
 }
 
+namespace {
+std::string convertToSmilesWithCXFlags(
+    const RWMol &mol, bool doingCXSmiles,
+    SmilesWriteParams ps = SmilesWriteParams()) {
+  return SmilesWrite::detail::MolToSmiles(mol, ps, doingCXSmiles);
+}
+}  // namespace
+
 std::string AnonymousGraph(RWMol *mol, bool elem, bool useCXSmiles,
                            unsigned cxFlagsToSkip = 0) {
   PRECONDITION(mol, "bad molecule");
@@ -325,7 +333,7 @@ std::string AnonymousGraph(RWMol *mol, bool elem, bool useCXSmiles,
   bool force = true;
   MolOps::assignStereochemistry(*mol, cleanIt, force);
 
-  result = MolToSmiles(*mol);
+  result = convertToSmilesWithCXFlags(*mol, useCXSmiles);
 
   if (useCXSmiles) {
     addCXExtensions(mol, result, cxFlagsToSkip | SmilesWrite::CX_RADICALS);
@@ -359,7 +367,7 @@ std::string MesomerHash(RWMol *mol, bool netq, bool useCXSmiles,
   bool force = true;
   MolOps::assignStereochemistry(*mol, cleanIt, force);
 
-  result = MolToSmiles(*mol);
+  result = convertToSmilesWithCXFlags(*mol, useCXSmiles);
   if (netq) {
     sprintf(buffer, "_%d", charge);
     result += buffer;
@@ -637,7 +645,7 @@ std::string TautomerHashv2(RWMol *mol, bool proto, bool useCXSmiles,
   SmilesWriteParams ps;
   ps.allBondsExplicit = true;
   ps.allHsExplicit = true;
-  result = MolToSmiles(*mol, ps);
+  result = convertToSmilesWithCXFlags(*mol, useCXSmiles, ps);
   char buffer[32];
   if (!proto) {
     sprintf(buffer, "_%d_%d", hcount, charge);
@@ -687,7 +695,7 @@ std::string TautomerHash(RWMol *mol, bool proto, bool useCXSmiles,
   bool cleanIt = true;
   bool force = true;
   MolOps::assignStereochemistry(*mol, cleanIt, force);
-  result = MolToSmiles(*mol);
+  result = convertToSmilesWithCXFlags(*mol, useCXSmiles);
   if (!proto) {
     sprintf(buffer, "_%d_%d", hcount, charge);
   } else {
@@ -805,7 +813,7 @@ std::string ExtendedMurckoScaffold(RWMol *mol, bool useCXSmiles,
   MolOps::assignStereochemistry(*mol, cleanIt, force);
 
   std::string result;
-  result = MolToSmiles(*mol);
+  result = convertToSmilesWithCXFlags(*mol, useCXSmiles);
   if (useCXSmiles) {
     addCXExtensions(mol, result, cxFlagsToSkip | SmilesWrite::CX_RADICALS);
   }
@@ -849,7 +857,7 @@ std::string MurckoScaffoldHash(RWMol *mol, bool useCXSmiles,
   MolOps::assignStereochemistry(*mol, cleanIt, force);
 
   std::string result;
-  result = MolToSmiles(*mol);
+  result = convertToSmilesWithCXFlags(*mol, useCXSmiles);
   if (useCXSmiles) {
     addCXExtensions(mol, result, cxFlagsToSkip | SmilesWrite::CX_RADICALS);
   }
@@ -1029,7 +1037,7 @@ std::string RegioisomerHash(RWMol *mol, bool useCXSmiles,
   bool force = true;
   MolOps::assignStereochemistry(*mol, cleanIt, force);
 
-  std::string result = MolToSmiles(*mol);
+  auto result = convertToSmilesWithCXFlags(*mol, useCXSmiles);
   if (useCXSmiles) {
     addCXExtensions(mol, result, cxFlagsToSkip);
   }
@@ -1172,7 +1180,7 @@ std::string MolHash(RWMol *mol, HashFunction func, bool useCXSmiles,
       result = AnonymousGraph(mol, true, useCXSmiles, cxFlagsToSkip);
       break;
     case HashFunction::CanonicalSmiles:
-      result = MolToSmiles(*mol);
+      result = convertToSmilesWithCXFlags(*mol, useCXSmiles);
       if (useCXSmiles) {
         addCXExtensions(mol, result, cxFlagsToSkip);
       }
diff --git a/Code/GraphMol/RGroupDecomposition/catch_rgd.cpp b/Code/GraphMol/RGroupDecomposition/catch_rgd.cpp
index 9f6b099c444..be248c5a147 100644
--- a/Code/GraphMol/RGroupDecomposition/catch_rgd.cpp
+++ b/Code/GraphMol/RGroupDecomposition/catch_rgd.cpp
@@ -462,22 +462,22 @@ TEST_CASE("substructure parameters and RGD: enhanced stereo") {
       CHECK(flatten_whitespace(toJSON(rows)) == flatten_whitespace(R"JSON(
 [
   {
-    "Core":"C1C[C@]([*:1])([*:2])N1",
+    "Core":"C1C[C@@]([*:1])([*:2])N1",
     "R1":"F[*:1]",
     "R2":"[H][*:2]"
   },
   {
-    "Core":"C1C[C@]([*:1])([*:2])N1",
+    "Core":"C1C[C@@]([*:1])([*:2])N1",
     "R1":"O[*:1]",
     "R2":"F[*:2]"
   },
   {
-    "Core":"C1C[C@]([*:1])([*:2])N1",
+    "Core":"C1C[C@@]([*:1])([*:2])N1",
     "R1":"[H][*:1]",
     "R2":"F[*:2]"
   },
   {
-    "Core":"C1C[C@]([*:1])([*:2])N1",
+    "Core":"C1C[C@@]([*:1])([*:2])N1",
     "R1":"Cl[*:1]",
     "R2":"[H][*:2]"
   }
@@ -496,22 +496,22 @@ TEST_CASE("substructure parameters and RGD: enhanced stereo") {
       CHECK(flatten_whitespace(toJSON(rows)) == flatten_whitespace(R"JSON(
 [
   {
-    "Core":"C1C[C@]([*:1])([*:2])N1",
+    "Core":"C1C[C@@]([*:1])([*:2])N1",
     "R1":"F[*:1]",
     "R2":"[H][*:2]"
   },
   {
-    "Core":"C1C[C@]([*:1])([*:2])N1",
+    "Core":"C1C[C@@]([*:1])([*:2])N1",
     "R1":"O[*:1]",
     "R2":"F[*:2]"
   },
   {
-    "Core":"C1C[C@]([*:1])([*:2])N1",
+    "Core":"C1C[C@@]([*:1])([*:2])N1",
     "R1":"[H][*:1]",
     "R2":"F[*:2]"
   },
   {
-    "Core":"C1C[C@]([*:1])([*:2])N1",
+    "Core":"C1C[C@@]([*:1])([*:2])N1",
     "R1":"Cl[*:1]",
     "R2":"[H][*:2]"
   }
@@ -537,7 +537,7 @@ TEST_CASE("substructure parameters and RGD: enhanced stereo") {
       CHECK(flatten_whitespace(toJSON(rows)) == flatten_whitespace(R"JSON(
 [
   {
-    "Core":"C1C[C@]([*:1])([*:2])N1",
+    "Core":"C1C[C@@]([*:1])([*:2])N1",
     "R1":"F[*:1]",
     "R2":"[H][*:2]"
   },
@@ -565,7 +565,7 @@ TEST_CASE("substructure parameters and RGD: enhanced stereo") {
       CHECK(flatten_whitespace(toJSON(rows)) == flatten_whitespace(R"JSON(
 [
   {
-    "Core":"C1C[C@]([*:1])([*:2])N1",
+    "Core":"C1C[C@@]([*:1])([*:2])N1",
     "R1":"F[*:1]",
     "R2":"[H][*:2]"
   },
@@ -580,7 +580,7 @@ TEST_CASE("substructure parameters and RGD: enhanced stereo") {
     "R2":"[H][*:2]"
   },
   {
-    "Core":"C1C[C@]([*:1])([*:2])N1",
+    "Core":"C1C[C@@]([*:1])([*:2])N1",
     "R1":"Cl[*:1]",
     "R2":"[H][*:2]"
   }
diff --git a/Code/GraphMol/SmilesParse/SmilesWrite.cpp b/Code/GraphMol/SmilesParse/SmilesWrite.cpp
index 6615e7febbc..aa2f86380f6 100644
--- a/Code/GraphMol/SmilesParse/SmilesWrite.cpp
+++ b/Code/GraphMol/SmilesParse/SmilesWrite.cpp
@@ -460,7 +460,10 @@ static bool SortBasedOnFirstElement(
   return a.first < b.first;
 }
 
-std::string MolToSmiles(const ROMol &mol, const SmilesWriteParams &params) {
+namespace SmilesWrite {
+namespace detail {
+std::string MolToSmiles(const ROMol &mol, const SmilesWriteParams &params,
+                        bool doingCXSmiles) {
   if (!mol.getNumAtoms()) {
     return "";
   }
@@ -520,6 +523,16 @@ std::string MolToSmiles(const ROMol &mol, const SmilesWriteParams &params) {
         MolOps::assignStereochemistry(*tmol, true);
       }
     }
+
+    if (!doingCXSmiles) {
+      // remove any stereo groups that may be present. Otherwise they will be
+      // used in the canonicalization
+      std::vector<StereoGroup> noStereoGroups;
+      tmol->setStereoGroups(noStereoGroups);
+
+      // if other CXSMILES features are added to the canonicalization code
+      // in the future, they should be removed here.
+    }
 #if 0
       std::cout << "----------------------------" << std::endl;
       std::cout << "MolToSmiles:"<< std::endl;
@@ -649,10 +662,17 @@ std::string MolToSmiles(const ROMol &mol, const SmilesWriteParams &params) {
               true);
   return result;
 }
+}  // namespace detail
+}  // namespace SmilesWrite
+std::string MolToSmiles(const ROMol &mol, const SmilesWriteParams &params) {
+  bool doingCXSmiles = false;
+  return SmilesWrite::detail::MolToSmiles(mol, params, doingCXSmiles);
+}
 
 std::string MolToCXSmiles(const ROMol &mol, const SmilesWriteParams &params,
                           std::uint32_t flags) {
-  auto res = MolToSmiles(mol, params);
+  bool doingCXSmiles = true;
+  auto res = SmilesWrite::detail::MolToSmiles(mol, params, doingCXSmiles);
   if (!res.empty()) {
     if (!params.doIsomericSmiles) {
       flags &= ~(SmilesWrite::CXSmilesFields::CX_ENHANCEDSTEREO |
diff --git a/Code/GraphMol/SmilesParse/SmilesWrite.h b/Code/GraphMol/SmilesParse/SmilesWrite.h
index 6e14d4218fe..20b9c2d67ea 100644
--- a/Code/GraphMol/SmilesParse/SmilesWrite.h
+++ b/Code/GraphMol/SmilesParse/SmilesWrite.h
@@ -90,6 +90,12 @@ RDKIT_SMILESPARSE_EXPORT std::string GetAtomSmiles(const Atom *atom,
 RDKIT_SMILESPARSE_EXPORT std::string GetBondSmiles(
     const Bond *bond, int atomToLeftIdx = -1, bool doKekule = false,
     bool allBondsExplicit = false);
+
+namespace detail {
+RDKIT_SMILESPARSE_EXPORT std::string MolToSmiles(
+    const ROMol &mol, const SmilesWriteParams &params, bool doingCXSmiles);
+}
+
 }  // namespace SmilesWrite
 
 //! \brief returns canonical SMILES for a molecule
diff --git a/Code/GraphMol/catch_canon.cpp b/Code/GraphMol/catch_canon.cpp
index c468a6fcf3a..a7fc7add736 100644
--- a/Code/GraphMol/catch_canon.cpp
+++ b/Code/GraphMol/catch_canon.cpp
@@ -348,3 +348,54 @@ TEST_CASE("more enhanced stereo canonicalization") {
     CHECK(MolToCXSmiles(*m1) == MolToCXSmiles(*m2));
   }
 }
+
+TEST_CASE("ensure unused features are not used") {
+  SECTION("isotopes") {
+    auto mol = "[13CH3]OC"_smiles;
+    REQUIRE(mol);
+    std::vector<unsigned int> ranks;
+    bool breakTies = false;
+    bool includeChirality = true;
+    bool includeIsotopes = true;
+    Canon::rankMolAtoms(*mol, ranks, breakTies, includeChirality,
+                        includeIsotopes);
+    CHECK(ranks[0] != ranks[2]);
+
+    includeIsotopes = false;
+    Canon::rankMolAtoms(*mol, ranks, breakTies, includeChirality,
+                        includeIsotopes);
+    CHECK(ranks[0] == ranks[2]);
+  }
+  SECTION("chirality") {
+    auto mol = "F[C@H](Cl)OC(F)Cl"_smiles;
+    REQUIRE(mol);
+    std::vector<unsigned int> ranks;
+    bool breakTies = false;
+    bool includeChirality = true;
+    bool includeIsotopes = true;
+    Canon::rankMolAtoms(*mol, ranks, breakTies, includeChirality,
+                        includeIsotopes);
+    CHECK(ranks[1] != ranks[4]);
+
+    includeChirality = false;
+    Canon::rankMolAtoms(*mol, ranks, breakTies, includeChirality,
+                        includeIsotopes);
+    CHECK(ranks[1] == ranks[4]);
+  }
+  SECTION("chirality and stereogroups") {
+    auto mol = "F[C@H](Cl)O[C@H](F)Cl |o1:1|"_smiles;
+    REQUIRE(mol);
+    std::vector<unsigned int> ranks;
+    bool breakTies = false;
+    bool includeChirality = true;
+    bool includeIsotopes = true;
+    Canon::rankMolAtoms(*mol, ranks, breakTies, includeChirality,
+                        includeIsotopes);
+    CHECK(ranks[1] != ranks[4]);
+
+    includeChirality = false;
+    Canon::rankMolAtoms(*mol, ranks, breakTies, includeChirality,
+                        includeIsotopes);
+    CHECK(ranks[1] == ranks[4]);
+  }
+}
diff --git a/Code/PgSQL/rdkit/expected/rdkit-91.out b/Code/PgSQL/rdkit/expected/rdkit-91.out
index ec6be8b755a..b7cc07c5e78 100644
--- a/Code/PgSQL/rdkit/expected/rdkit-91.out
+++ b/Code/PgSQL/rdkit/expected/rdkit-91.out
@@ -1340,9 +1340,9 @@ select mol_to_smarts(mol_adjust_query_properties('*c1ncc(*)cc1'::qmol));
 
 -- CXSmiles
 SELECT mol_to_smiles(mol_from_smiles('C[C@H](F)[C@H](C)[C@@H](C)Br |a:1,o1:3,5|'));
-        mol_to_smiles         
-------------------------------
- C[C@H](F)[C@@H](C)[C@H](C)Br
+         mol_to_smiles         
+-------------------------------
+ C[C@@H]([C@H](C)F)[C@@H](C)Br
 (1 row)
 
 SELECT mol_to_cxsmiles(mol_from_smiles('C[C@H](F)[C@H](C)[C@@H](C)Br |a:1,o1:3,5|'));


Test patch:
diff --git a/Code/GraphMol/MolHash/Wrap/testMolHash.py b/Code/GraphMol/MolHash/Wrap/testMolHash.py
index 431d6770e3a..c7a05aafde3 100644
--- a/Code/GraphMol/MolHash/Wrap/testMolHash.py
+++ b/Code/GraphMol/MolHash/Wrap/testMolHash.py
@@ -52,7 +52,7 @@ def testCxSmiles(self):
       'C[C@@H](O)[C@@H](C)[C@@H](C)C[C@H](C1=CN=CN1)C1=CNC=N1 |o1:8,5,&1:1,3,r,c:11,18,t:9,15|')
 
     self.assertEqual(rdMolHash.MolHash(m, rdMolHash.HashFunction.HetAtomTautomer),
-                     'C[C@H](CC([C]1[CH][N][CH][N]1)[C]1[CH][N][CH][N]1)[C@@H](C)[C@H](C)[O]_3_0')
+                     'C[C@H]([C@@H](C)[O])[C@@H](C)CC([C]1[CH][N][CH][N]1)[C]1[CH][N][CH][N]1_3_0')
 
     self.assertEqual(
       rdMolHash.MolHash(m, rdMolHash.HashFunction.HetAtomTautomer, True),
diff --git a/Code/GraphMol/MolHash/catch_tests.cpp b/Code/GraphMol/MolHash/catch_tests.cpp
index d6f9249d87f..c146a1dad7b 100644
--- a/Code/GraphMol/MolHash/catch_tests.cpp
+++ b/Code/GraphMol/MolHash/catch_tests.cpp
@@ -241,7 +241,7 @@ TEST_CASE("MolHash with CX extensions", "[molhash]") {
       auto hsh = MolHash::MolHash(&cp, MolHash::HashFunction::HetAtomTautomer);
       CHECK(
           hsh ==
-          "C[C@H](CC([C]1[CH][N][CH][N]1)[C]1[CH][N][CH][N]1)[C@@H](C)[C@H](C)[O]_3_0");
+          "C[C@H]([C@@H](C)[O])[C@@H](C)CC([C]1[CH][N][CH][N]1)[C]1[CH][N][CH][N]1_3_0");
     }
     {
       RWMol cp(*mol);
diff --git a/Code/GraphMol/SmilesParse/catch_tests.cpp b/Code/GraphMol/SmilesParse/catch_tests.cpp
index 2a865983844..a7fbf003603 100644
--- a/Code/GraphMol/SmilesParse/catch_tests.cpp
+++ b/Code/GraphMol/SmilesParse/catch_tests.cpp
@@ -20,6 +20,7 @@
 #include <GraphMol/QueryBond.h>
 #include <GraphMol/QueryOps.h>
 #include <GraphMol/Chirality.h>
+#include <GraphMol/Canon.h>
 #include <GraphMol/SmilesParse/SmilesParse.h>
 #include <GraphMol/SmilesParse/SmilesWrite.h>
 #include <GraphMol/SmilesParse/SmartsWrite.h>
@@ -2420,4 +2421,102 @@ TEST_CASE("smilesSymbol in SMARTS", "[smarts][smilesSymbol]") {
     m->getAtomWithIdx(0)->setProp(common_properties::smilesSymbol, "Xa");
     CHECK(MolToSmarts(*m) == "[Xa;C,N,O]C");
   }
-}
\ No newline at end of file
+}
+
+TEST_CASE("ensure unused features are not used") {
+  SECTION("isotopes") {
+    auto mol1 = "FOCN[15F]"_smiles;
+    REQUIRE(mol1);
+    auto mol2 = "[15F]OCNF"_smiles;
+    REQUIRE(mol2);
+    std::vector<unsigned int> ranks;
+    SmilesWriteParams ps;
+    ps.doIsomericSmiles = true;
+    auto smiles = MolToSmiles(*mol1, ps);
+    CHECK(smiles == "FOCN[15F]");
+    smiles = MolToSmiles(*mol2, ps);
+    CHECK(smiles == "FNCO[15F]");
+
+    ps.doIsomericSmiles = false;
+    smiles = MolToSmiles(*mol1, ps);
+    CHECK(smiles == "FNCOF");
+    smiles = MolToSmiles(*mol2, ps);
+    CHECK(smiles == "FNCOF");
+  }
+
+  SECTION("chirality") {
+    auto mol1 = "FC(Cl)OCN[C@H](F)Cl"_smiles;
+    REQUIRE(mol1);
+    auto mol2 = "FC(Cl)NCO[C@H](F)Cl"_smiles;
+    REQUIRE(mol2);
+    std::vector<unsigned int> ranks;
+    SmilesWriteParams ps;
+    ps.doIsomericSmiles = true;
+    auto smiles = MolToSmiles(*mol1, ps);
+    CHECK(smiles == "FC(Cl)OCN[C@H](F)Cl");
+    smiles = MolToSmiles(*mol2, ps);
+    CHECK(smiles == "FC(Cl)NCO[C@H](F)Cl");
+
+    ps.doIsomericSmiles = false;
+    smiles = MolToSmiles(*mol1, ps);
+    CHECK(smiles == "FC(Cl)NCOC(F)Cl");
+    smiles = MolToSmiles(*mol2, ps);
+    CHECK(smiles == "FC(Cl)NCOC(F)Cl");
+  }
+
+  SECTION("ring stereo") {
+    auto mol1 = "CC1CCC(CC1)NO[C@@H]1CC[C@H](C)CC1"_smiles;
+    REQUIRE(mol1);
+    auto mol2 = "CC1CCC(CC1)ON[C@@H]1CC[C@H](C)CC1"_smiles;
+    REQUIRE(mol2);
+    std::vector<unsigned int> ranks;
+    SmilesWriteParams ps;
+    ps.doIsomericSmiles = true;
+    auto smiles = MolToSmiles(*mol1, ps);
+    CHECK(smiles == "CC1CCC(NO[C@H]2CC[C@@H](C)CC2)CC1");
+    smiles = MolToSmiles(*mol2, ps);
+    CHECK(smiles == "CC1CCC(ON[C@H]2CC[C@@H](C)CC2)CC1");
+
+    ps.doIsomericSmiles = false;
+    smiles = MolToSmiles(*mol1, ps);
+    CHECK(smiles == "CC1CCC(NOC2CCC(C)CC2)CC1");
+    smiles = MolToSmiles(*mol2, ps);
+    CHECK(smiles == "CC1CCC(NOC2CCC(C)CC2)CC1");
+  }
+
+  SECTION("enhanced stereo") {
+    // if we aren't doing CXSMILES then the enhanced stereo shouldn't enter into
+    // consideration in canonicalization
+    auto mol1 = "F[C@H](Cl)NCO[C@H](F)Cl |&1:6|"_smiles;
+    REQUIRE(mol1);
+    auto mol2 = "F[C@H](Cl)OCN[C@H](F)Cl |&1:6|"_smiles;
+    REQUIRE(mol2);
+    std::vector<unsigned int> ranks;
+    SmilesWriteParams ps;
+    ps.doIsomericSmiles = true;
+    auto smiles = MolToSmiles(*mol1, ps);
+    CHECK(smiles == "F[C@H](Cl)NCO[C@H](F)Cl");
+    smiles = MolToSmiles(*mol2, ps);
+    CHECK(smiles == "F[C@H](Cl)NCO[C@H](F)Cl");
+
+    smiles = MolToCXSmiles(*mol1, ps);
+    CHECK(smiles == "F[C@H](Cl)NCO[C@H](F)Cl |&1:6|");
+    smiles = MolToCXSmiles(*mol2, ps);
+    CHECK(smiles == "F[C@H](Cl)OCN[C@H](F)Cl |&1:6|");
+
+    ps.doIsomericSmiles = false;
+    smiles = MolToSmiles(*mol1, ps);
+    CHECK(smiles == "FC(Cl)NCOC(F)Cl");
+    smiles = MolToSmiles(*mol2, ps);
+    CHECK(smiles == "FC(Cl)NCOC(F)Cl");
+  }
+
+  SECTION("problematic cases") {
+    auto mol1 =
+        "[C@H]1CC(C[C@@H](C)[C@@H](C)O)C[C@@H](C)C1 |o1:1,11,o2:5,7|"_smiles;
+    REQUIRE(mol1);
+    Canon::canonicalizeEnhancedStereo(*mol1);
+    auto smiles = MolToSmiles(*mol1);
+    CHECK(smiles == "C[C@H]1C[CH]CC(C[C@@H](C)[C@@H](C)O)C1");
+  }
+}
diff --git a/Code/JavaWrappers/gmwrapper/src-test/org/RDKit/WrapperTests.java b/Code/JavaWrappers/gmwrapper/src-test/org/RDKit/WrapperTests.java
index 8402c6129a7..3b2faec175c 100644
--- a/Code/JavaWrappers/gmwrapper/src-test/org/RDKit/WrapperTests.java
+++ b/Code/JavaWrappers/gmwrapper/src-test/org/RDKit/WrapperTests.java
@@ -342,7 +342,9 @@ public void cdxmlReader() {
 	assertEquals(prods.size(), 1);
 	for(int idx = 0; idx < prods.size(); idx++) {
 	    if(idx == 0) {
-		assertEquals(prods.get(idx).MolToSmiles(true), "CC1(C)[C@H](C=C(Cl)Cl)[C@@H]1C(=O)O[C@H](C#N)c1cccc(Oc2ccccc2)c1");
+            System.out.print(prods.get(idx).MolToSmiles(true));
+            System.out.print("\n");
+		assertEquals(prods.get(idx).MolToSmiles(true), "CC1(C)[C@H](C=C(Cl)Cl)[C@H]1C(=O)O[C@@H](C#N)c1cccc(Oc2ccccc2)c1");
 	    }
 	}