RGD and Stereo Groups (rdkit/rdkit#6247)

<!--
Thanks for contributing a pull request!
-->
#### Reference Issue
<!-- Example: Fixes #1234 -->

Fixes #6146

#### What does this implement/fix? Explain your changes.

Modified R group decomposition to copy stereo groups from the input structure to the core and R groups as described in the issue.  In addition to RGD changes I had to modify `ChemTransforms::replaceCore`

The decomposition from Issue #6146 now looks good:

![image](https://user-images.githubusercontent.com/24233741/228696687-4c1273d5-efbb-4bc9-a798-dc7bf2a277ff.png)

#### Any other comments?

RGD and enhanced stereochemistry

**Describe the bug**

Enhanced stereochemistry is not carried over from input structures to R groups during RGD.

**To Reproduce**

```python

# %%

import sys
from rdkit import Chem
from rdkit.Chem import AllChem
from rdkit.Chem.rdRGroupDecomposition import RGroupDecomposition, RGroupDecompositionParameters, \
    RGroupMatching, RGroupScore, RGroupLabels, RGroupCoreAlignment, RGroupLabelling
from rdkit.Chem.Draw import IPythonConsole

IPythonConsole.ipython_useSVG = True

from rdkit.Chem import PandasTools
from rdkit.Chem import Draw
from IPython.display import HTML

IPythonConsole.drawOptions.addAtomIndices = False
IPythonConsole.drawOptions.addStereoAnnotation = True

import pandas as pd

def do_decomp(options, core, mols):
  decomp = RGroupDecomposition([core], options)
  display_mols = []
  for mol in mols:
    if decomp.Add(mol) > -1:
      display_mols.append(mol)
    else:
      print("Failed to add mol")
  decomp.Process()
  groups = decomp.GetRGroupsAsColumns()
  df = PandasTools.RGroupDecompositionToFrame(groups, display_mols, include_core=True)
  PandasTools.ChangeMoleculeRendering(df)
  return HTML(df.to_html())
# %%

block1="""
  ChemDraw02032311272D

  0  0  0     0  0              0 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 16 17 0 0 1
M  V30 BEGIN ATOM
M  V30 1 C 0.348623 1.031250 0.000000 0
M  V30 2 C 0.348623 0.206250 0.000000 0
M  V30 3 C 1.063094 -0.206250 0.000000 0
M  V30 4 C 1.777565 0.206250 0.000000 0
M  V30 5 C 1.777565 1.031250 0.000000 0
M  V30 6 C 1.063094 1.443750 0.000000 0
M  V30 7 C 1.063094 -1.031250 0.000000 0
M  V30 8 N 1.777565 -1.443750 0.000000 0
M  V30 9 C 0.348623 -1.443750 0.000000 0
M  V30 10 C -0.365848 -1.031250 0.000000 0
M  V30 11 C -0.452084 -0.210769 0.000000 0
M  V30 12 C -1.259056 -0.039242 0.000000 0
M  V30 13 C -1.671556 -0.753713 0.000000 0
M  V30 14 C -1.119523 -1.366808 0.000000 0
M  V30 15 O -2.492036 -0.839949 0.000000 0
M  V30 16 Cl 2.492036 1.443750 0.000000 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 2 1 2
M  V30 2 1 2 3
M  V30 3 2 3 4
M  V30 4 1 4 5
M  V30 5 2 5 6
M  V30 6 1 6 1
M  V30 7 1 3 7
M  V30 8 1 7 8 CFG=1
M  V30 9 1 7 9
M  V30 10 1 9 10
M  V30 11 1 10 11
M  V30 12 1 11 12
M  V30 13 1 12 13
M  V30 14 1 13 14
M  V30 15 1 14 10
M  V30 16 1 13 15 CFG=3
M  V30 17 1 5 16
M  V30 END BOND
M  V30 BEGIN COLLECTION
M  V30 MDLV30/STEABS ATOMS=(2 7 13)
M  V30 END COLLECTION
M  V30 END CTAB
M  END"""

mol1 = Chem.MolFromMolBlock(block1)
mol1

# %%
block2 = """
  ChemDraw02032311382D

  0  0  0     0  0              0 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 18 19 0 0 0
M  V30 BEGIN ATOM
M  V30 1 C 0.348623 1.388485 0.000000 0
M  V30 2 C 0.348623 0.563485 0.000000 0
M  V30 3 C 1.063094 0.150985 0.000000 0
M  V30 4 C 1.777565 0.563485 0.000000 0
M  V30 5 C 1.777565 1.388485 0.000000 0
M  V30 6 C 1.063094 1.800985 0.000000 0
M  V30 7 C 1.063094 -0.674015 0.000000 0
M  V30 8 N 1.777565 -1.086515 0.000000 0
M  V30 9 C 0.348623 -1.086515 0.000000 0
M  V30 10 C -0.365848 -0.674015 0.000000 0
M  V30 11 C -0.452084 0.146466 0.000000 0
M  V30 12 C -1.259056 0.317993 0.000000 0
M  V30 13 C -1.671556 -0.396478 0.000000 0
M  V30 14 C -1.119523 -1.009572 0.000000 0
M  V30 15 O -2.492036 -0.482714 0.000000 0
M  V30 16 Cl 2.492036 1.800986 0.000000 0
M  V30 17 O -0.063877 -1.800986 0.000000 0
M  V30 18 C 0.761123 -1.800986 0.000000 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 2 1 2
M  V30 2 1 2 3
M  V30 3 2 3 4
M  V30 4 1 4 5
M  V30 5 2 5 6
M  V30 6 1 6 1
M  V30 7 1 3 7
M  V30 8 1 7 8 CFG=1
M  V30 9 1 7 9
M  V30 10 1 9 10
M  V30 11 1 10 11
M  V30 12 1 11 12
M  V30 13 1 12 13
M  V30 14 1 13 14
M  V30 15 1 14 10
M  V30 16 1 13 15 CFG=3
M  V30 17 1 5 16
M  V30 18 1 9 17 CFG=1
M  V30 19 1 9 18 CFG=3
M  V30 END BOND
M  V30 BEGIN COLLECTION
M  V30 MDLV30/STEABS ATOMS=(2 7 9)
M  V30 MDLV30/STEREL1 ATOMS=(1 13)
M  V30 END COLLECTION
M  V30 END CTAB
M  END"""
mol2 = Chem.MolFromMolBlock(block2)
mol2
# %%
query_block = """
  ChemDraw02132309392D

  0  0  0     0  0              0 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 10 10 0 0 1
M  V30 BEGIN ATOM
M  V30 1 C -1.071690 1.031297 0.000000 0
M  V30 2 C -1.071690 0.206260 0.000000 0
M  V30 3 C -0.357230 -0.206259 0.000000 0
M  V30 4 C 0.357230 0.206260 0.000000 0
M  V30 5 C 0.357230 1.031297 0.000000 0
M  V30 6 C -0.357230 1.443816 0.000000 0
M  V30 7 C -0.357230 -1.031297 0.000000 0
M  V30 8 N 0.357230 -1.443816 0.000000 0
M  V30 9 R1 -1.071690 -1.443816 0.000000 0
M  V30 10 Cl 1.071690 1.443816 0.000000 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 2 1 2
M  V30 2 1 2 3
M  V30 3 2 3 4
M  V30 4 1 4 5
M  V30 5 2 5 6
M  V30 6 1 6 1
M  V30 7 1 3 7
M  V30 8 1 7 8 CFG=1
M  V30 9 1 7 9
M  V30 10 1 5 10
M  V30 END BOND
M  V30 END CTAB
M  END
"""
query= Chem.MolFromMolBlock(query_block)
query

# %%
options = RGroupDecompositionParameters()
options.onlyMatchAtRGroups = False
options.allowMultipleRGroupsOnUnlabelled = True
do_decomp(options, query, [mol1, mol2])
# %%

```

**Expected behavior**

I like to see `abs`, `or` and `and` enhanced stereo chemistry labels being properly carried over to the R groups which is not happening.  I understand that a single `or1` or `and1` in a group make little sense but it would be nice to see when viewing the decomposition.

The structures are examples created by a customer.  They have proprietary data with multiple `and` and `or` centers that get scrambled on decomposition.  Unfortunately, I can't share those.

**Screenshots**

This is what the current RGD looks like:

![image](https://user-images.githubusercontent.com/24233741/222020086-efe36b97-8540-467d-b3de-62f0c9f7b750.png)

**Configuration (please complete the following information):**
 - RDKit version:  Master as of 2/28/2023
 - OS: [e.g. Ubuntu 20.04] Ubuntu 20.04
 - Python version (if relevant):
 - Are you using conda?
 - If you are using conda, which channel did you install the rdkit from?
 - If you are not using conda: how did you install the RDKit?

**Additional context**

@bp-kelley This is an example of some of the RGD issues we've been seeing.

diff --git a/Code/GraphMol/ChemTransforms/ChemTransforms.cpp b/Code/GraphMol/ChemTransforms/ChemTransforms.cpp
index 38d9882712c..a7b47469235 100644
--- a/Code/GraphMol/ChemTransforms/ChemTransforms.cpp
+++ b/Code/GraphMol/ChemTransforms/ChemTransforms.cpp
@@ -31,6 +31,7 @@
 #include <RDGeneral/FileParseException.h>
 #include <RDGeneral/BadFileException.h>
 #include <GraphMol/ChemTransforms/ChemTransforms.h>
+
 namespace RDKit {
 namespace details {
 void updateSubMolConfs(const ROMol &mol, RWMol &res,
@@ -52,6 +53,26 @@ void updateSubMolConfs(const ROMol &mol, RWMol &res,
     res.addConformer(newConf, false);
   }
 }
+void copyStereoGroups(const std::map<const Atom *, Atom*> &molAtomMap, const ROMol &mol, RWMol &newMol) {
+  // Copy over any stereo groups that lie in the new molecule
+  if (!mol.getStereoGroups().empty()) {
+    std::vector<StereoGroup> newStereoGroups;
+    for (auto &stereoGroup : mol.getStereoGroups()) {
+      std::vector<Atom *> newStereoAtoms;
+      for (const auto stereoGroupAtom : stereoGroup.getAtoms()) {
+        if (auto found = molAtomMap.find(stereoGroupAtom);
+            found != molAtomMap.end()) {
+          newStereoAtoms.push_back(found->second);
+        }
+      }
+      if (!newStereoAtoms.empty()) {
+        newStereoGroups.emplace_back(stereoGroup.getGroupType(),
+                                     newStereoAtoms);
+      }
+    }
+    newMol.setStereoGroups(std::move(newStereoGroups));
+  }
+}
 }  // namespace details

 namespace {
@@ -62,10 +83,10 @@ struct SideChainMapping {

   SideChainMapping(int molIndex)
       : molIndex(molIndex), coreIndex(-1), useMatch(false) {}
+
   SideChainMapping(int molIndex, int coreIndex, bool useMatch)
       : molIndex(molIndex), coreIndex(coreIndex), useMatch(useMatch) {}
 };
-
 }  // namespace

 ROMol *deleteSubstructs(const ROMol &mol, const ROMol &query, bool onlyFrags,
@@ -488,6 +509,7 @@ ROMol *replaceCore(const ROMol &mol, const ROMol &core,
   auto *newMol = new RWMol(mol);
   std::vector<Atom *> keepList;
   std::map<Atom *, int> dummyAtomMap;
+  std::map<const Atom *, Atom *> molAtomMap;

   // go through the matches in query order, not target molecule
   //  order
@@ -516,6 +538,7 @@ ROMol *replaceCore(const ROMol &mol, const ROMol &core,
       Atom *sidechainAtom = newMol->getAtomWithIdx(mappingInfo.molIndex);
       // we're keeping the sidechain atoms:
       keepList.push_back(sidechainAtom);
+      molAtomMap[mol.getAtomWithIdx(mappingInfo.molIndex)] = sidechainAtom;

       // loop over our neighbors and see if any are in the match:
       std::list<unsigned int> nbrList;
@@ -700,25 +723,27 @@ ROMol *replaceCore(const ROMol &mol, const ROMol &core,
                                       endAtom->getIdx());
       }
     }
+  }

-    // make a guess at the position of the dummy atoms showing the attachment
-    // point:
-    for (auto citer = mol.beginConformers(); citer != mol.endConformers();
-         ++citer) {
-      Conformer &newConf = newMol->getConformer((*citer)->getId());
-      for (auto iter = dummyAtomMap.begin(); iter != dummyAtomMap.end();
-           ++iter) {
-        newConf.setAtomPos(iter->first->getIdx(),
-                           (*citer)->getAtomPos(iter->second));
-      }
+  // make a guess at the position of the dummy atoms showing the attachment
+  // point:
+  for (auto citer = mol.beginConformers(); citer != mol.endConformers();
+       ++citer) {
+    Conformer &newConf = newMol->getConformer((*citer)->getId());
+    for (auto iter = dummyAtomMap.begin(); iter != dummyAtomMap.end(); ++iter) {
+      newConf.setAtomPos(iter->first->getIdx(),
+                         (*citer)->getAtomPos(iter->second));
     }
-
-    // clear computed props and do basic updates on
-    // the resulting molecule, but allow unhappiness:
-    newMol->clearComputedProps(true);
-    newMol->updatePropertyCache(false);
   }

+  // copy over stereo groups
+  details::copyStereoGroups(molAtomMap, mol, *newMol);
+
+  // clear computed props and do basic updates on
+  // the resulting molecule, but allow unhappiness:
+  newMol->clearComputedProps(true);
+  newMol->updatePropertyCache(false);
+
   return static_cast<ROMol *>(newMol);
 }

@@ -971,6 +996,7 @@ void parseQueryDefFile(std::istream *inStream,
     queryDefs[qname] = msptr;
   }
 }
+
 void parseQueryDefFile(const std::string &filename,
                        std::map<std::string, ROMOL_SPTR> &queryDefs,
                        bool standardize, const std::string &delimiter,
@@ -985,6 +1011,7 @@ void parseQueryDefFile(const std::string &filename,
   parseQueryDefFile(&inStream, queryDefs, standardize, delimiter, comment,
                     nameColumn, smartsColumn);
 }
+
 void parseQueryDefText(const std::string &queryDefText,
                        std::map<std::string, ROMOL_SPTR> &queryDefs,
                        bool standardize, const std::string &delimiter,
diff --git a/Code/GraphMol/ChemTransforms/ChemTransforms.h b/Code/GraphMol/ChemTransforms/ChemTransforms.h
index 978aeb64cfb..ef03b1c3679 100644
--- a/Code/GraphMol/ChemTransforms/ChemTransforms.h
+++ b/Code/GraphMol/ChemTransforms/ChemTransforms.h
@@ -270,6 +270,9 @@ namespace details {
 //! not recommended for use in other code
 RDKIT_CHEMTRANSFORMS_EXPORT void updateSubMolConfs(
     const ROMol &mol, RWMol &res, boost::dynamic_bitset<> &removedAtoms);
+RDKIT_CHEMTRANSFORMS_EXPORT void copyStereoGroups(
+    const std::map<const Atom *, Atom *> &molAtomMap, const ROMol &mol,
+    RWMol &newMol);
 }  // namespace details

 }  // namespace RDKit
diff --git a/Code/GraphMol/RGroupDecomposition/RGroupCore.cpp b/Code/GraphMol/RGroupDecomposition/RGroupCore.cpp
index 9017134d865..2b09f51af19 100644
--- a/Code/GraphMol/RGroupDecomposition/RGroupCore.cpp
+++ b/Code/GraphMol/RGroupDecomposition/RGroupCore.cpp
@@ -14,7 +14,6 @@
 #include "GraphMol/Substruct/SubstructUtils.h"

 namespace RDKit {
-
 namespace {
 // From answer 12 in
 // https://stackoverflow.com/questions/5279051/how-can-i-create-cartesian-product-of-vector-of-vectors
@@ -71,6 +70,7 @@ std::pair<RWMOL_SPTR, bool> RCore::extractCoreFromMolMatch(
   std::set<int> atomIndicesToKeep;
   std::vector<Bond *> newBonds;
   std::map<Atom *, int> dummyAtomMap;
+  std::map<const Atom *, Atom *> molAtomMap;
   bool hasCoreDummies = false;
   for (auto &pair : match) {
     const auto queryAtom = core->getAtomWithIdx(pair.first);
@@ -90,6 +90,7 @@ std::pair<RWMOL_SPTR, bool> RCore::extractCoreFromMolMatch(
       continue;
     } else {
       atomIndicesToKeep.insert(pair.second);
+      molAtomMap[mol.getAtomWithIdx(pair.second)] = targetAtom;
       int neighborNumber = -1;
 #ifdef VERBOSE
       std::cerr << "Atom Chirality In " << targetAtom->getChiralTag()
@@ -124,6 +125,8 @@ std::pair<RWMOL_SPTR, bool> RCore::extractCoreFromMolMatch(
             // Hydrogen needed to define chirality is present in target but
             // not mapped to core.  Copy it to the extracted core
             hydrogensToAdd.push_back(static_cast<int>(targetNeighborIndex));
+            molAtomMap[mol.getAtomWithIdx(targetNeighborIndex)] =
+                targetNeighborAtom;
           } else if (isChiral) {
             // There is a heavy sidechain in the decomp that is connected to
             // the core by an unknown bond (onlyMatchAtRGroups = False and
@@ -260,6 +263,9 @@ std::pair<RWMOL_SPTR, bool> RCore::extractCoreFromMolMatch(
     }
   }

+  // Copy over any stereo groups that lie in the extracted core
+  details::copyStereoGroups(molAtomMap, mol, *extractedCore);
+
   extractedCore->clearComputedProps(true);
   extractedCore->updatePropertyCache(false);

@@ -810,5 +816,4 @@ int RCore::matchingIndexToCoreIndex(int matchingIndex) const {
                   "Matched atom missing core index");
   return atom->getProp<int>(RLABEL_CORE_INDEX);
 }
-
 }  // namespace RDKit

diff --git a/Code/GraphMol/RGroupDecomposition/testRGroupDecomp.cpp b/Code/GraphMol/RGroupDecomposition/testRGroupDecomp.cpp
index 1a61505776e..26f7ad5d71e 100644
--- a/Code/GraphMol/RGroupDecomposition/testRGroupDecomp.cpp
+++ b/Code/GraphMol/RGroupDecomposition/testRGroupDecomp.cpp
@@ -1128,9 +1128,10 @@ Br[*:3]
 Br[*:3]
 )RES");
   }
-  {  // repeat that without symmetrization (testing #3224)
-     // Still three groups added, but bromine and fluorine
-     // are not aligned between R1 and R3
+  {
+    // repeat that without symmetrization (testing #3224)
+    // Still three groups added, but bromine and fluorine
+    // are not aligned between R1 and R3
     RGroupDecompositionParameters ps;
     ps.matchingStrategy = RDKit::NoSymmetrization;
     RGroupDecomposition decomp(*core, ps);
@@ -3549,7 +3550,7 @@ void testRGroupCoordinatesAddedToCore() {
   BOOST_LOG(rdInfoLog)
       << "********************************************************\n";
   BOOST_LOG(rdInfoLog)
-      << "Test that cordinates for R groups are properly added to core when the core has coordinates and the target does not"
+      << "Test that coordinates for R groups are properly added to core when the core has coordinates and the target does not"
       << std::endl;
   auto core = R"CTAB(ACS Document 1996
   ChemDraw05202112262D
@@ -3609,7 +3610,7 @@ M  END
       auto coreAtoms = core->atoms();
       auto originalAtom = std::find_if(
           coreAtoms.begin(), coreAtoms.end(), [rGroupNum](const auto &a) {
-              return static_cast<int>(a->getIsotope()) == rGroupNum;
+            return static_cast<int>(a->getIsotope()) == rGroupNum;
           });
       TEST_ASSERT(originalAtom != coreAtoms.end());
       const auto &originalPoint =
@@ -3625,6 +3626,188 @@ M  END
   TEST_ASSERT(numberGroups == 2);
 }

+void testStereoGroupsPreserved() {
+  BOOST_LOG(rdInfoLog)
+      << "********************************************************\n";
+  BOOST_LOG(rdInfoLog)
+      << "Test that stereo group information is copied from input structure to core and R groups"
+      << std::endl;
+  auto core = R"CTAB(
+  ChemDraw02132309392D
+
+  0  0  0     0  0              0 V3000
+M  V30 BEGIN CTAB
+M  V30 COUNTS 10 10 0 0 1
+M  V30 BEGIN ATOM
+M  V30 1 C -1.071690 1.031297 0.000000 0
+M  V30 2 C -1.071690 0.206260 0.000000 0
+M  V30 3 C -0.357230 -0.206259 0.000000 0
+M  V30 4 C 0.357230 0.206260 0.000000 0
+M  V30 5 C 0.357230 1.031297 0.000000 0
+M  V30 6 C -0.357230 1.443816 0.000000 0
+M  V30 7 C -0.357230 -1.031297 0.000000 0
+M  V30 8 N 0.357230 -1.443816 0.000000 0
+M  V30 9 R1 -1.071690 -1.443816 0.000000 0
+M  V30 10 Cl 1.071690 1.443816 0.000000 0
+M  V30 END ATOM
+M  V30 BEGIN BOND
+M  V30 1 2 1 2
+M  V30 2 1 2 3
+M  V30 3 2 3 4
+M  V30 4 1 4 5
+M  V30 5 2 5 6
+M  V30 6 1 6 1
+M  V30 7 1 3 7
+M  V30 8 1 7 8 CFG=1
+M  V30 9 1 7 9
+M  V30 10 1 5 10
+M  V30 END BOND
+M  V30 END CTAB
+M  END
+)CTAB"_ctab;
+
+  auto mol1 = R"CTAB(
+  ChemDraw02032311272D
+
+  0  0  0     0  0              0 V3000
+M  V30 BEGIN CTAB
+M  V30 COUNTS 16 17 0 0 1
+M  V30 BEGIN ATOM
+M  V30 1 C 0.348623 1.031250 0.000000 0
+M  V30 2 C 0.348623 0.206250 0.000000 0
+M  V30 3 C 1.063094 -0.206250 0.000000 0
+M  V30 4 C 1.777565 0.206250 0.000000 0
+M  V30 5 C 1.777565 1.031250 0.000000 0
+M  V30 6 C 1.063094 1.443750 0.000000 0
+M  V30 7 C 1.063094 -1.031250 0.000000 0
+M  V30 8 N 1.777565 -1.443750 0.000000 0
+M  V30 9 C 0.348623 -1.443750 0.000000 0
+M  V30 10 C -0.365848 -1.031250 0.000000 0
+M  V30 11 C -0.452084 -0.210769 0.000000 0
+M  V30 12 C -1.259056 -0.039242 0.000000 0
+M  V30 13 C -1.671556 -0.753713 0.000000 0
+M  V30 14 C -1.119523 -1.366808 0.000000 0
+M  V30 15 O -2.492036 -0.839949 0.000000 0
+M  V30 16 Cl 2.492036 1.443750 0.000000 0
+M  V30 END ATOM
+M  V30 BEGIN BOND
+M  V30 1 2 1 2
+M  V30 2 1 2 3
+M  V30 3 2 3 4
+M  V30 4 1 4 5
+M  V30 5 2 5 6
+M  V30 6 1 6 1
+M  V30 7 1 3 7
+M  V30 8 1 7 8 CFG=1
+M  V30 9 1 7 9
+M  V30 10 1 9 10
+M  V30 11 1 10 11
+M  V30 12 1 11 12
+M  V30 13 1 12 13
+M  V30 14 1 13 14
+M  V30 15 1 14 10
+M  V30 16 1 13 15 CFG=3
+M  V30 17 1 5 16
+M  V30 END BOND
+M  V30 BEGIN COLLECTION
+M  V30 MDLV30/STEABS ATOMS=(2 7 13)
+M  V30 END COLLECTION
+M  V30 END CTAB
+M  END
+)CTAB"_ctab;
+
+  auto mol2 = R"CTAB(
+  ChemDraw02032311382D
+
+  0  0  0     0  0              0 V3000
+M  V30 BEGIN CTAB
+M  V30 COUNTS 18 19 0 0 0
+M  V30 BEGIN ATOM
+M  V30 1 C 0.348623 1.388485 0.000000 0
+M  V30 2 C 0.348623 0.563485 0.000000 0
+M  V30 3 C 1.063094 0.150985 0.000000 0
+M  V30 4 C 1.777565 0.563485 0.000000 0
+M  V30 5 C 1.777565 1.388485 0.000000 0
+M  V30 6 C 1.063094 1.800985 0.000000 0
+M  V30 7 C 1.063094 -0.674015 0.000000 0
+M  V30 8 N 1.777565 -1.086515 0.000000 0
+M  V30 9 C 0.348623 -1.086515 0.000000 0
+M  V30 10 C -0.365848 -0.674015 0.000000 0
+M  V30 11 C -0.452084 0.146466 0.000000 0
+M  V30 12 C -1.259056 0.317993 0.000000 0
+M  V30 13 C -1.671556 -0.396478 0.000000 0
+M  V30 14 C -1.119523 -1.009572 0.000000 0
+M  V30 15 O -2.492036 -0.482714 0.000000 0
+M  V30 16 Cl 2.492036 1.800986 0.000000 0
+M  V30 17 O -0.063877 -1.800986 0.000000 0
+M  V30 18 C 0.761123 -1.800986 0.000000 0
+M  V30 END ATOM
+M  V30 BEGIN BOND
+M  V30 1 2 1 2
+M  V30 2 1 2 3
+M  V30 3 2 3 4
+M  V30 4 1 4 5
+M  V30 5 2 5 6
+M  V30 6 1 6 1
+M  V30 7 1 3 7
+M  V30 8 1 7 8 CFG=1
+M  V30 9 1 7 9
+M  V30 10 1 9 10
+M  V30 11 1 10 11
+M  V30 12 1 11 12
+M  V30 13 1 12 13
+M  V30 14 1 13 14
+M  V30 15 1 14 10
+M  V30 16 1 13 15 CFG=3
+M  V30 17 1 5 16
+M  V30 18 1 9 17 CFG=1
+M  V30 19 1 9 18 CFG=3
+M  V30 END BOND
+M  V30 BEGIN COLLECTION
+M  V30 MDLV30/STEABS ATOMS=(2 7 9)
+M  V30 MDLV30/STEREL1 ATOMS=(1 13)
+M  V30 END COLLECTION
+M  V30 END CTAB
+M  END
+)CTAB"_ctab;
+
+  RGroupDecompositionParameters params;
+  params.matchingStrategy = GreedyChunks;
+  params.allowMultipleRGroupsOnUnlabelled = true;
+  params.onlyMatchAtRGroups = false;
+  RGroupDecomposition decomp(*core, params);
+  auto result = decomp.add(*mol1);
+  TEST_ASSERT(result == 0);
+  result = decomp.add(*mol2);
+  TEST_ASSERT(result == 1);
+  decomp.process();
+
+  RGroupRows rows = decomp.getRGroupsAsRows();
+  TEST_ASSERT(rows.size() == 2);
+  TEST_ASSERT(rows[0].size() == 2);
+  TEST_ASSERT(rows[1].size() == 2);
+
+  auto core1 = rows[0]["Core"];
+  TEST_ASSERT(core1->getStereoGroups().size() == 1);
+  TEST_ASSERT(core1->getStereoGroups()[0].getGroupType() ==
+              StereoGroupType::STEREO_ABSOLUTE);
+  auto r1 = rows[0]["R1"];
+  TEST_ASSERT(r1->getStereoGroups().size() == 1);
+  TEST_ASSERT(r1->getStereoGroups()[0].getGroupType() ==
+              StereoGroupType::STEREO_ABSOLUTE);
+
+  auto core2 = rows[1]["Core"];
+  TEST_ASSERT(core2->getStereoGroups().size() == 1);
+  TEST_ASSERT(core2->getStereoGroups()[0].getGroupType() ==
+              StereoGroupType::STEREO_ABSOLUTE);
+  auto r2 = rows[1]["R1"];
+  TEST_ASSERT(r2->getStereoGroups().size() == 2);
+  TEST_ASSERT(r2->getStereoGroups()[0].getGroupType() ==
+              StereoGroupType::STEREO_ABSOLUTE);
+  TEST_ASSERT(r2->getStereoGroups()[1].getGroupType() ==
+              StereoGroupType::STEREO_OR);
+}
+
 int main() {
   RDLog::InitLogs();
   boost::logging::disable_logs("rdApp.debug");
@@ -3685,6 +3868,7 @@ int main() {
   testGitHub5631();
   testGithub5613();
   testRGroupCoordinatesAddedToCore();
+  testStereoGroupsPreserved();
   BOOST_LOG(rdInfoLog)
       << "********************************************************\n";
   return 0;


Fixes #6241 (rdkit/rdkit#6257)

Adds a new function to `MolFileStereochem.h` and the python wrapper to apply the "standard" semantics of the chiral flag

Add a function to translate the MDL chiral flag into enhanced stereo groups

The "chiral flag" is a feature that was used in MDL v2000 mol files, but was superseded by Enhanced Stereo Groups, which are much more flexible, in the v3000 format.

It would be useful to have some infrastructure that allows us to "translate" the stereochemistry read from a v2000 mol block into Enhanced Stereo Groups. If my understanding of the related documentation is correct, this code should do the following:

For all atoms which are not already included in an Enhanced Stereo Group (these might have been defined at some point after reading the original v2000 input),
- if the `RDKit::common_properties::_MolFileChiralFlag` property is set to "1", find or create the "ABS" Enhanced Stereo Group on the mol (which must be unique), and add the atoms to it.
- if the `RDKit::common_properties::_MolFileChiralFlag` property is set to "0", create a new "AND" Enhanced Stereo Group, and add all atoms to it.

diff --git a/Code/GraphMol/FileParsers/MolFileParser.cpp b/Code/GraphMol/FileParsers/MolFileParser.cpp
index cb3c9e5cf6d..df8a93560e0 100644
--- a/Code/GraphMol/FileParsers/MolFileParser.cpp
+++ b/Code/GraphMol/FileParsers/MolFileParser.cpp
@@ -1388,7 +1388,8 @@ void setRGPProps(const std::string_view symb, Atom *res) {
   res->setProp(common_properties::dummyLabel, symbc);
 }

-void lookupAtomicNumber(Atom *res, const std::string &symb, bool strictParsing) {
+void lookupAtomicNumber(Atom *res, const std::string &symb,
+                        bool strictParsing) {
   try {
     res->setAtomicNum(PeriodicTable::getTable()->getAtomicNumber(symb));
   } catch (const Invar::Invariant &e) {
@@ -3057,9 +3058,7 @@ bool ParseV3000CTAB(std::istream *inStream, unsigned int &line, RWMol *mol,
     chiralFlag = FileParserUtils::toUnsigned(splitLine[4]);
   }

-  if (chiralFlag) {
-    mol->setProp(common_properties::_MolFileChiralFlag, chiralFlag);
-  }
+  mol->setProp(common_properties::_MolFileChiralFlag, chiralFlag);

   if (nAtoms) {
     ParseV3000AtomBlock(inStream, line, nAtoms, mol, conf, strictParsing);
@@ -3440,9 +3439,7 @@ RWMol *MolDataStreamToMol(std::istream *inStream, unsigned int &line,
     }
   }

-  if (chiralFlag) {
-    res->setProp(common_properties::_MolFileChiralFlag, chiralFlag);
-  }
+  res->setProp(common_properties::_MolFileChiralFlag, chiralFlag);

   Conformer *conf = nullptr;
   try {
diff --git a/Code/GraphMol/FileParsers/MolFileStereochem.cpp b/Code/GraphMol/FileParsers/MolFileStereochem.cpp
index b179e80ca6c..49ba6e92c28 100644
--- a/Code/GraphMol/FileParsers/MolFileStereochem.cpp
+++ b/Code/GraphMol/FileParsers/MolFileStereochem.cpp
@@ -321,4 +321,59 @@ void markUnspecifiedStereoAsUnknown(ROMol &mol, int confId) {
   }
 }

+void translateChiralFlagToStereoGroups(ROMol &mol,
+                                       StereoGroupType zeroFlagGroupType) {
+  if (!mol.hasProp(common_properties::_MolFileChiralFlag)) {
+    return;
+  }
+  int flagVal = 0;
+  mol.getProp(common_properties::_MolFileChiralFlag, flagVal);
+  mol.clearProp(common_properties::_MolFileChiralFlag);
+
+  StereoGroupType sgType =
+      flagVal ? StereoGroupType::STEREO_ABSOLUTE : zeroFlagGroupType;
+
+  auto sgs = mol.getStereoGroups();
+
+  boost::dynamic_bitset<> sgAtoms(mol.getNumAtoms());
+  const StereoGroup *absGroup = nullptr;
+  for (const auto &sg : sgs) {
+    for (const auto aptr : sg.getAtoms()) {
+      sgAtoms.set(aptr->getIdx());
+    }
+    // if we already have an ABS group, we'll add to it
+    if (sgType == StereoGroupType::STEREO_ABSOLUTE && !absGroup &&
+        sg.getGroupType() == StereoGroupType::STEREO_ABSOLUTE) {
+      absGroup = &sg;
+    }
+  }
+  ROMol::ATOM_PTR_VECT stereoAts;
+  for (const auto atom : mol.atoms()) {
+    if (!sgAtoms[atom->getIdx()] &&
+        (atom->getChiralTag() == Atom::ChiralType::CHI_TETRAHEDRAL_CCW ||
+         atom->getChiralTag() == Atom::ChiralType::CHI_TETRAHEDRAL_CW)) {
+      stereoAts.push_back(atom);
+    }
+  }
+  if (!stereoAts.empty()) {
+    if (!absGroup) {
+      sgs.emplace_back(sgType, stereoAts);
+      mol.setStereoGroups(sgs);
+    } else {
+      std::vector<StereoGroup> newSgs;
+      for (const auto &sg : sgs) {
+        if (&sg != absGroup) {
+          newSgs.push_back(sg);
+        } else {
+          ROMol::ATOM_PTR_VECT newStereoAtoms = sg.getAtoms();
+          newStereoAtoms.insert(newStereoAtoms.end(), stereoAts.begin(),
+                                stereoAts.end());
+          newSgs.emplace_back(StereoGroupType::STEREO_ABSOLUTE, newStereoAtoms);
+        }
+      }
+      mol.setStereoGroups(newSgs);
+    }
+  }
+}
+
 }  // namespace RDKit
diff --git a/Code/GraphMol/FileParsers/MolFileStereochem.h b/Code/GraphMol/FileParsers/MolFileStereochem.h
index 405d68fe094..47a691ba912 100644
--- a/Code/GraphMol/FileParsers/MolFileStereochem.h
+++ b/Code/GraphMol/FileParsers/MolFileStereochem.h
@@ -1,5 +1,5 @@
 //
-//  Copyright (C) 2004-2021 Greg Landrum and other RDKit contributors
+//  Copyright (C) 2004-2023 Greg Landrum and other RDKit contributors
 //
 //   @@ All Rights Reserved @@
 //  This file is part of the RDKit.
@@ -82,5 +82,27 @@ RDKIT_FILEPARSERS_EXPORT void invertMolBlockWedgingInfo(ROMol &mol);
 RDKIT_FILEPARSERS_EXPORT void markUnspecifiedStereoAsUnknown(ROMol &mol,
                                                              int confId = -1);

+//! generate enhanced stereo groups based on the status of the chiral flag
+/// property
+/*
+ \param mol: molecule to be modified
+ \param zeroFlagGroupType: how to handle non-grouped stereo centers when the
+        chiral flag is set to zero
+
+  If the chiral flag is set to a value of 1 then all specified tetrahedral
+  chiral centers which are not already in StereoGroups will be added to an
+  ABS StereoGroup.
+
+  If the chiral flag is set to a value of 0 then all specified tetrahedral
+  chiral centers will be added to a StereoGroup of the type zeroFlagGroupType
+
+  If there is no chiral flag set (i.e. the property is not present), the
+  molecule will not be modified.
+
+*/
+RDKIT_FILEPARSERS_EXPORT void translateChiralFlagToStereoGroups(
+    ROMol &mol,
+    StereoGroupType zeroFlagGroupType = StereoGroupType::STEREO_AND);
+
 }  // namespace RDKit
 #endif
diff --git a/Code/GraphMol/FileParsers/molfile_stereo_catch.cpp b/Code/GraphMol/FileParsers/molfile_stereo_catch.cpp
index 477b21d993e..4b295a3cee6 100644
--- a/Code/GraphMol/FileParsers/molfile_stereo_catch.cpp
+++ b/Code/GraphMol/FileParsers/molfile_stereo_catch.cpp
@@ -32,8 +32,150 @@ TEST_CASE("Github #5863: failure in WedgeMolBonds") {
     std::unique_ptr<ROMol> frag(Subgraphs::pathToSubmol(*mol, env));
     REQUIRE(frag);
     WedgeMolBonds(*frag, &frag->getConformer());
+    INFO(MolToV3KMolBlock(*frag));
     CHECK(frag->getBondBetweenAtoms(9, 10)->getBondDir() !=
           Bond::BondDir::NONE);
-    std::cerr << MolToV3KMolBlock(*frag) << std::endl;
   }
-}
\ No newline at end of file
+}
+
+TEST_CASE("translating the chiral flag to stereo groups") {
+  SECTION("basics") {
+    auto withFlag = R"CTAB(
+  Mrv2211 03302308372D
+
+  5  4  0  0  1  0            999 V2000
+   -6.5625    3.9286    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
+   -5.8480    4.3411    0.0000 C   0  0  1  0  0  0  0  0  0  0  0  0
+   -5.1336    3.9286    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
+   -6.1775    5.0826    0.0000 F   0  0  0  0  0  0  0  0  0  0  0  0
+   -5.4384    5.0893    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
+  1  2  1  0  0  0  0
+  2  3  1  0  0  0  0
+  2  5  1  1  0  0  0
+  2  4  1  6  0  0  0
+M  END
+)CTAB"_ctab;
+    REQUIRE(withFlag);
+    int flag;
+    CHECK(withFlag->getPropIfPresent(common_properties::_MolFileChiralFlag,
+                                     flag));
+    CHECK(flag == 1);
+    RWMol zeroFlag(*withFlag);
+    zeroFlag.setProp(common_properties::_MolFileChiralFlag, 0);
+    RWMol noFlag(*withFlag);
+    noFlag.clearProp(common_properties::_MolFileChiralFlag);
+    CHECK(withFlag->getStereoGroups().empty());
+
+    translateChiralFlagToStereoGroups(*withFlag);
+    CHECK(!withFlag->hasProp(common_properties::_MolFileChiralFlag));
+    auto sgs = withFlag->getStereoGroups();
+    REQUIRE(sgs.size() == 1);
+    CHECK(sgs[0].getGroupType() == StereoGroupType::STEREO_ABSOLUTE);
+    CHECK(sgs[0].getAtoms().size() == 1);
+    CHECK(sgs[0].getAtoms()[0]->getIdx() == 1);
+
+    {
+      RWMol cp(zeroFlag);
+      translateChiralFlagToStereoGroups(cp);
+      CHECK(!cp.hasProp(common_properties::_MolFileChiralFlag));
+      auto sgs = cp.getStereoGroups();
+      REQUIRE(sgs.size() == 1);
+      CHECK(sgs[0].getGroupType() == StereoGroupType::STEREO_AND);
+      CHECK(sgs[0].getAtoms().size() == 1);
+      CHECK(sgs[0].getAtoms()[0]->getIdx() == 1);
+    }
+    {
+      RWMol cp(zeroFlag);
+      translateChiralFlagToStereoGroups(cp, StereoGroupType::STEREO_OR);
+      CHECK(!cp.hasProp(common_properties::_MolFileChiralFlag));
+      auto sgs = cp.getStereoGroups();
+      REQUIRE(sgs.size() == 1);
+      CHECK(sgs[0].getGroupType() == StereoGroupType::STEREO_OR);
+      CHECK(sgs[0].getAtoms().size() == 1);
+      CHECK(sgs[0].getAtoms()[0]->getIdx() == 1);
+    }
+
+    translateChiralFlagToStereoGroups(noFlag);
+    CHECK(!noFlag.hasProp(common_properties::_MolFileChiralFlag));
+    CHECK(noFlag.getStereoGroups().empty());
+  }
+
+  SECTION("explicit zero chiral flag") {
+    auto zeroFlag = R"CTAB(
+  Mrv2211 03302308372D
+
+  5  4  0  0  o  0            999 V2000
+   -6.5625    3.9286    0.0000 N   0  0  0  0  0  0  0  0  0  0  0  0
+   -5.8480    4.3411    0.0000 C   0  0  1  0  0  0  0  0  0  0  0  0
+   -5.1336    3.9286    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0
+   -6.1775    5.0826    0.0000 F   0  0  0  0  0  0  0  0  0  0  0  0
+   -5.4384    5.0893    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
+  1  2  1  0  0  0  0
+  2  3  1  0  0  0  0
+  2  5  1  1  0  0  0
+  2  4  1  6  0  0  0
+M  END
+)CTAB"_ctab;
+    REQUIRE(zeroFlag);
+    int flag;
+    CHECK(zeroFlag->getPropIfPresent(common_properties::_MolFileChiralFlag,
+                                     flag));
+    CHECK(flag == 0);
+    {
+      RWMol cp(*zeroFlag);
+      translateChiralFlagToStereoGroups(cp);
+      CHECK(!cp.hasProp(common_properties::_MolFileChiralFlag));
+      auto sgs = cp.getStereoGroups();
+      REQUIRE(sgs.size() == 1);
+      CHECK(sgs[0].getGroupType() == StereoGroupType::STEREO_AND);
+      CHECK(sgs[0].getAtoms().size() == 1);
+      CHECK(sgs[0].getAtoms()[0]->getIdx() == 1);
+    }
+  }
+
+  SECTION("multiple stereocenters") {
+    auto noFlag = "C[C@@](N)(F)C[C@](C)(O)F"_smiles;
+    REQUIRE(noFlag);
+    RWMol withFlag(*noFlag);
+    withFlag.setProp(common_properties::_MolFileChiralFlag, 1);
+
+    CHECK(withFlag.getStereoGroups().empty());
+    translateChiralFlagToStereoGroups(withFlag);
+    CHECK(!withFlag.hasProp(common_properties::_MolFileChiralFlag));
+    auto sgs = withFlag.getStereoGroups();
+    REQUIRE(sgs.size() == 1);
+    CHECK(sgs[0].getGroupType() == StereoGroupType::STEREO_ABSOLUTE);
+    CHECK(sgs[0].getAtoms().size() == 2);
+    CHECK(sgs[0].getAtoms()[0]->getIdx() == 1);
+    CHECK(sgs[0].getAtoms()[1]->getIdx() == 5);
+
+    {
+      RWMol zeroFlag(*noFlag);
+      zeroFlag.setProp(common_properties::_MolFileChiralFlag, 0);
+      translateChiralFlagToStereoGroups(zeroFlag);
+      CHECK(!zeroFlag.hasProp(common_properties::_MolFileChiralFlag));
+      auto sgs = zeroFlag.getStereoGroups();
+      REQUIRE(sgs.size() == 1);
+      CHECK(sgs[0].getGroupType() == StereoGroupType::STEREO_AND);
+      CHECK(sgs[0].getAtoms().size() == 2);
+      CHECK(sgs[0].getAtoms()[0]->getIdx() == 1);
+      CHECK(sgs[0].getAtoms()[1]->getIdx() == 5);
+    }
+  }
+
+  SECTION("pre-existing ABS stereogroup") {
+    auto withFlag = "C[C@@](N)(F)C[C@](C)(O)F |a:1|"_smiles;
+    REQUIRE(withFlag);
+    withFlag->setProp(common_properties::_MolFileChiralFlag, 1);
+
+    CHECK(withFlag->getStereoGroups().size() == 1);
+    translateChiralFlagToStereoGroups(*withFlag);
+    CHECK(!withFlag->hasProp(common_properties::_MolFileChiralFlag));
+    auto sgs = withFlag->getStereoGroups();
+    REQUIRE(sgs.size() == 1);
+    CHECK(sgs[0].getGroupType() == StereoGroupType::STEREO_ABSOLUTE);
+    CHECK(sgs[0].getAtoms().size() == 2);
+    CHECK(sgs[0].getAtoms()[0]->getIdx() == 1);
+    CHECK(sgs[0].getAtoms()[1]->getIdx() == 5);
+  }
+}
diff --git a/Code/GraphMol/Wrap/MolOps.cpp b/Code/GraphMol/Wrap/MolOps.cpp
index d199197ef9e..f1c97c09d74 100644
--- a/Code/GraphMol/Wrap/MolOps.cpp
+++ b/Code/GraphMol/Wrap/MolOps.cpp
@@ -2878,6 +2878,27 @@ A note on the flags controlling which atoms/bonds are modified:
                 Chirality::getUseLegacyStereoPerception,
                 "returns whether or not the legacy stereo perception code is "
                 "being used");
+
+    python::def(
+        "TranslateChiralFlagToStereoGroups", translateChiralFlagToStereoGroups,
+        (python::arg("mol"),
+         python::arg("zeroFlagGroupType") = StereoGroupType::STEREO_AND),
+        R"DOC(Generate enhanced stereo groups based on the status of the chiral flag property.
+
+  Arguments:
+   - mol: molecule to be modified
+   - zeroFlagGroupType: how to handle non-grouped stereo centers when the
+          chiral flag is set to zero
+
+  If the chiral flag is set to a value of 1 then all specified tetrahedral
+  chiral centers which are not already in StereoGroups will be added to an
+  ABS StereoGroup.
+
+  If the chiral flag is set to a value of 0 then all specified tetrahedral
+  chiral centers will be added to a StereoGroup of the type zeroFlagGroupType
+
+  If there is no chiral flag set (i.e. the property is not present), the
+  molecule will not be modified.)DOC");
   }
 };
 }  // namespace RDKit
diff --git a/ReleaseNotes.md b/ReleaseNotes.md
index ed8dcd520ab..fd56319c6e9 100644
--- a/ReleaseNotes.md
+++ b/ReleaseNotes.md
@@ -12,6 +12,7 @@
 - The doIsomericSmiles parameter in Java and C# ROMol.MolToSmiles() now defaults to true (previously it was false), thus aligning to the C++ and Python behavior.
 - Double bonds which are marked as crossed (i.e. `bond.GetBondDir() == Bond.BondDir.EITHERDOUBLE`) now have their BondStereo set to `Bond.BondStereo.STEREOANY` and the BondDir information removed by default when molecules are parsed or `AssignStereochemistry()` is called with the `cleanIt` argument set to True.
 - The conformers generated for molecules with three-coordinate chiral centers will be somewhat different due to the fix for #5883.
+- Molecules which come from Mol or SDF files will now always have the "_MolFileChiralFlag" property set to the value of the chiral flag in the CTAB. In previous versions the property was not set if the chiral flag was 0.

 ## Bug Fixes:

diff --git a/Code/GraphMol/FileParsers/testMolWriter.cpp b/Code/GraphMol/FileParsers/testMolWriter.cpp
index 3f2f8818dda..78275f62864 100644
--- a/Code/GraphMol/FileParsers/testMolWriter.cpp
+++ b/Code/GraphMol/FileParsers/testMolWriter.cpp
@@ -697,7 +697,8 @@ void testMolFileChiralFlag() {
     std::string mb = MolToMolBlock(*m1);
     delete m1;
     m1 = MolBlockToMol(mb);
-    TEST_ASSERT(!m1->hasProp(common_properties::_MolFileChiralFlag));
+    TEST_ASSERT(m1->hasProp(common_properties::_MolFileChiralFlag));
+    TEST_ASSERT(m1->getProp<int>(common_properties::_MolFileChiralFlag) == 0);
     delete m1;
   }
   {
diff --git a/Code/GraphMol/Wrap/rough_test.py b/Code/GraphMol/Wrap/rough_test.py
index ef86bd8a9ec..2fce864decb 100644
--- a/Code/GraphMol/Wrap/rough_test.py
+++ b/Code/GraphMol/Wrap/rough_test.py
@@ -1,5 +1,5 @@
 #
-#  Copyright (C) 2003-2021  Greg Landrum and other RDKit contributors
+#  Copyright (C) 2003-2023  Greg Landrum and other RDKit contributors
 #         All Rights Reserved
 #
 """ This is a rough coverage test of the python wrapper
@@ -4376,6 +4376,7 @@ def testSDProps(self):
         'NCI_AIDS_Antiviral_Screen_IC50': '2.00E-04\tM\t=\t2.46E-05\t3',
         '_Name': 48,
         'CAS_RN': '15716-70-8',
+        '_MolFileChiralFlag': 0,
         '_MolFileComments': '15716-70-8',
         'NCI_AIDS_Antiviral_Screen_EC50': '2.00E-04\tM\t>\t2.00E-04\t3',
         'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
@@ -4386,6 +4387,7 @@ def testSDProps(self):
         'NCI_AIDS_Antiviral_Screen_IC50': '2.00E-04\tM\t=\t9.80E-05\t3',
         '_Name': 78,
         'CAS_RN': '6290-84-2',
+        '_MolFileChiralFlag': 0,
         '_MolFileComments': '6290-84-2',
         'NCI_AIDS_Antiviral_Screen_EC50': '2.00E-04\tM\t>\t2.00E-04\t3',
         'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
@@ -4396,6 +4398,7 @@ def testSDProps(self):
         'NCI_AIDS_Antiviral_Screen_IC50': '2.00E-04\tM\t=\t4.60E-05\t4',
         '_Name': 128,
         'CAS_RN': '5395-10-8',
+        '_MolFileChiralFlag': 0,
         '_MolFileComments': '5395-10-8',
         'NCI_AIDS_Antiviral_Screen_EC50': '2.00E-04\tM\t>\t2.00E-04\t4',
         'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
@@ -4406,6 +4409,7 @@ def testSDProps(self):
         'NCI_AIDS_Antiviral_Screen_IC50': '6.75E-04\tM\t>\t6.75E-04\t2',
         '_Name': 163,
         'CAS_RN': '81-11-8',
+        '_MolFileChiralFlag': 0,
         '_MolFileComments': '81-11-8',
         'NCI_AIDS_Antiviral_Screen_EC50': '6.75E-04\tM\t>\t6.75E-04\t2',
         'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
@@ -4416,6 +4420,7 @@ def testSDProps(self):
         'NCI_AIDS_Antiviral_Screen_IC50': '2.00E-04\tM\t>\t2.00E-04\t2',
         '_Name': 164,
         'CAS_RN': '5325-43-9',
+        '_MolFileChiralFlag': 0,
         '_MolFileComments': '5325-43-9',
         'NCI_AIDS_Antiviral_Screen_EC50': '2.00E-04\tM\t>\t2.00E-04\t2',
         'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
@@ -4425,6 +4430,7 @@ def testSDProps(self):
         'NSC': 170,
         '_Name': 170,
         'CAS_RN': '999-99-9',
+        '_MolFileChiralFlag': 0,
         '_MolFileComments': '999-99-9',
         'NCI_AIDS_Antiviral_Screen_EC50': '9.47E-04\tM\t>\t9.47E-04\t1',
         'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
@@ -4436,6 +4442,7 @@ def testSDProps(self):
         '6.46E-04\tM\t=\t5.80E-04\t2\n1.81E-03\tM\t=\t6.90E-04\t2',
         '_Name': 180,
         'CAS_RN': '69-72-7',
+        '_MolFileChiralFlag': 0,
         '_MolFileComments': '69-72-7',
         'NCI_AIDS_Antiviral_Screen_EC50':
         '6.46E-04\tM\t>\t6.46E-04\t2\n1.81E-03\tM\t>\t1.81E-03\t2',
@@ -4447,6 +4454,7 @@ def testSDProps(self):
         'NCI_AIDS_Antiviral_Screen_IC50': '1.44E-04\tM\t=\t2.49E-05\t2',
         '_Name': 186,
         'CAS_RN': '518-75-2',
+        '_MolFileChiralFlag': 0,
         '_MolFileComments': '518-75-2',
         'NCI_AIDS_Antiviral_Screen_EC50': '1.44E-04\tM\t>\t1.44E-04\t2',
         'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
@@ -4457,6 +4465,7 @@ def testSDProps(self):
         'NCI_AIDS_Antiviral_Screen_IC50': '2.00E-04\tM\t=\t3.38E-06\t2',
         '_Name': 192,
         'CAS_RN': '2217-55-2',
+        '_MolFileChiralFlag': 0,
         '_MolFileComments': '2217-55-2',
         'NCI_AIDS_Antiviral_Screen_EC50': '2.00E-04\tM\t>\t2.00E-04\t2',
         'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
@@ -4466,6 +4475,7 @@ def testSDProps(self):
         'NSC': 203,
         '_Name': 203,
         'CAS_RN': '1155-00-6',
+        '_MolFileChiralFlag': 0,
         '_MolFileComments': '1155-00-6',
         'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
       },
@@ -4475,6 +4485,7 @@ def testSDProps(self):
         'NCI_AIDS_Antiviral_Screen_IC50': '1.33E-03\tM\t>\t1.33E-03\t2',
         '_Name': 210,
         'CAS_RN': '5325-75-7',
+        '_MolFileChiralFlag': 0,
         '_MolFileComments': '5325-75-7',
         'NCI_AIDS_Antiviral_Screen_EC50': '1.33E-03\tM\t>\t1.33E-03\t2',
         'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
@@ -4486,6 +4497,7 @@ def testSDProps(self):
         '2.00E-04\tM\t>\t2.00E-04\t8\n2.00E-03\tM\t=\t1.12E-03\t2',
         '_Name': 211,
         'CAS_RN': '5325-76-8',
+        '_MolFileChiralFlag': 0,
         '_MolFileComments': '5325-76-8',
         'NCI_AIDS_Antiviral_Screen_EC50':
         '2.00E-04\tM\t>\t7.42E-05\t8\n2.00E-03\tM\t=\t6.35E-05\t2',
@@ -4497,6 +4509,7 @@ def testSDProps(self):
         'NCI_AIDS_Antiviral_Screen_IC50': '2.00E-04\tM\t>\t2.00E-04\t4',
         '_Name': 213,
         'CAS_RN': '119-80-2',
+        '_MolFileChiralFlag': 0,
         '_MolFileComments': '119-80-2',
         'NCI_AIDS_Antiviral_Screen_EC50': '2.00E-04\tM\t>\t2.00E-04\t4',
         'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
@@ -4507,6 +4520,7 @@ def testSDProps(self):
         'NCI_AIDS_Antiviral_Screen_IC50': '2.00E-04\tM\t>\t2.00E-04\t4',
         '_Name': 220,
         'CAS_RN': '5325-83-7',
+        '_MolFileChiralFlag': 0,
         '_MolFileComments': '5325-83-7',
         'NCI_AIDS_Antiviral_Screen_EC50': '2.00E-04\tM\t>\t2.00E-04\t4',
         'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
@@ -4517,6 +4531,7 @@ def testSDProps(self):
         'NCI_AIDS_Antiviral_Screen_IC50': '2.00E-04\tM\t>\t2.00E-04\t2',
         '_Name': 229,
         'CAS_RN': '5325-88-2',
+        '_MolFileChiralFlag': 0,
         '_MolFileComments': '5325-88-2',
         'NCI_AIDS_Antiviral_Screen_EC50': '2.00E-04\tM\t>\t2.00E-04\t2',
         'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
@@ -4527,6 +4542,7 @@ def testSDProps(self):
         'NCI_AIDS_Antiviral_Screen_IC50': '2.00E-04\tM\t>\t2.00E-04\t4',
         '_Name': 256,
         'CAS_RN': '5326-06-7',
+        '_MolFileChiralFlag': 0,
         '_MolFileComments': '5326-06-7',
         'NCI_AIDS_Antiviral_Screen_EC50': '2.00E-04\tM\t>\t2.00E-04\t4',
         'NCI_AIDS_Antiviral_Screen_Conclusion': 'CI'
@@ -7214,6 +7230,38 @@ def test_DativeBondsToHaptic(self):
     backfemol = Chem.rdmolops.DativeBondsToHaptic(newfemol)
     self.assertEqual(Chem.MolToSmiles(femol), Chem.MolToSmiles(backfemol))

+  def testTranslateChiralFlag(self):
+    mol = Chem.MolFromSmiles("C[C@@](N)(F)C[C@](C)(O)F |a:1|")
+    flagMol = Chem.Mol(mol)
+    flagMol.SetIntProp("_MolFileChiralFlag",1)
+    Chem.TranslateChiralFlagToStereoGroups(flagMol)
+    sgs = flagMol.GetStereoGroups()
+    self.assertEqual(len(sgs),1)
+    self.assertEqual(len(sgs[0].GetAtoms()), 2)
+    self.assertEqual(sgs[0].GetGroupType(), Chem.StereoGroupType.STEREO_ABSOLUTE)
+
+    flagMol = Chem.Mol(mol)
+    flagMol.SetIntProp("_MolFileChiralFlag",0)
+    Chem.TranslateChiralFlagToStereoGroups(flagMol)
+    sgs = flagMol.GetStereoGroups()
+    self.assertEqual(len(sgs),2)
+    self.assertEqual(sgs[0].GetGroupType(), Chem.StereoGroupType.STEREO_ABSOLUTE)
+    self.assertEqual(len(sgs[0].GetAtoms()), 1)
+
+    self.assertEqual(sgs[1].GetGroupType(), Chem.StereoGroupType.STEREO_AND)
+    self.assertEqual(len(sgs[1].GetAtoms()), 1)
+
+    flagMol = Chem.Mol(mol)
+    flagMol.SetIntProp("_MolFileChiralFlag",0)
+    Chem.TranslateChiralFlagToStereoGroups(flagMol, Chem.StereoGroupType.STEREO_OR)
+    sgs = flagMol.GetStereoGroups()
+    self.assertEqual(len(sgs),2)
+    self.assertEqual(sgs[0].GetGroupType(), Chem.StereoGroupType.STEREO_ABSOLUTE)
+    self.assertEqual(len(sgs[0].GetAtoms()), 1)
+
+    self.assertEqual(sgs[1].GetGroupType(), Chem.StereoGroupType.STEREO_OR)
+    self.assertEqual(len(sgs[1].GetAtoms()), 1)
+

 if __name__ == '__main__':
   if "RDTESTCASE" in os.environ:


Fixes #6479 (rdkit/rdkit#6482)

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

StereoGroup information should not impact canonicalization when CXSMILES isn't being generated

**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

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|'));

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");
 	    }
 	}