Fixes #7552 (rdkit/rdkit#7564)

This adds code to recognize tetrahedral centers which are involved in more than one ring with < members and be more tolerant when checking their geometries.

AllChem.EmbedMolecule returns -1

Howdy,

I want to get the 3D conformation information for any given molecule; however, some molecules fail to produce the conformer. Following is the code that reproduces the issue.

`from rdkit import Chem`
`from rdkit.Chem import AllChem`
`sample_smiles = 'O=CCC1OC2COC12'`
`mol = Chem.AddHs(Chem.MolFromSmiles(sample_smiles))`
`AllChem.EmbedMolecule(mol) # -1`

If the molecule can generate conformation information, `AllChem.EmbedMolecule()` would return 0; I have identified many smiles/molecules that result in -1 when calling `AllChem.EmbedMolecule(mol)` , you may find some of those smiles in the picture below.

![image](https://github.com/rdkit/rdkit/assets/40327643/bab1a067-2e7d-492e-b4fc-782ab9260f5e)

Appreciate any suggestions!

diff --git a/Code/DistGeom/ChiralSet.h b/Code/DistGeom/ChiralSet.h
index 6b9a077dfe6..a1b26035cb0 100644
--- a/Code/DistGeom/ChiralSet.h
+++ b/Code/DistGeom/ChiralSet.h
@@ -17,6 +17,11 @@

 namespace DistGeom {

+enum class ChiralSetStructureFlags : std::uint64_t {
+  IN_FUSED_SMALL_RINGS =
+      1 << 0,  // a chiral center involved in fusing 2 or more small rings
+};
+
 /*! \brief Class used to store a quartet of points and chiral volume bounds on
  *them
  *
@@ -30,17 +35,20 @@ class RDKIT_DISTGEOMETRY_EXPORT ChiralSet {
   unsigned int d_idx4;
   double d_volumeLowerBound;
   double d_volumeUpperBound;
+  std::uint64_t d_structureFlags =
+      0;  // used to track special cases that may make embedding more difficult

   ChiralSet(unsigned int pid0, unsigned int pid1, unsigned int pid2,
             unsigned int pid3, unsigned int pid4, double lowerVolBound,
-            double upperVolBound)
+            double upperVolBound, std::uint64_t structureFlags = 0)
       : d_idx0(pid0),
         d_idx1(pid1),
         d_idx2(pid2),
         d_idx3(pid3),
         d_idx4(pid4),
         d_volumeLowerBound(lowerVolBound),
-        d_volumeUpperBound(upperVolBound) {
+        d_volumeUpperBound(upperVolBound),
+        d_structureFlags(structureFlags) {
     CHECK_INVARIANT(lowerVolBound <= upperVolBound, "Inconsistent bounds\n");
     d_volumeLowerBound = lowerVolBound;
     d_volumeUpperBound = upperVolBound;
diff --git a/Code/GraphMol/DistGeomHelpers/Embedder.cpp b/Code/GraphMol/DistGeomHelpers/Embedder.cpp
index 1ce159a81df..ee97ccde868 100644
--- a/Code/GraphMol/DistGeomHelpers/Embedder.cpp
+++ b/Code/GraphMol/DistGeomHelpers/Embedder.cpp
@@ -308,12 +308,21 @@ bool _volumeTest(const DistGeom::ChiralSetPtr &chiralSet,
   RDGeom::Point3D v4 = p0 - p4;
   v4.normalize();

+  // be more tolerant of tethrahedral centers that are involved in multiple
+  // small rings
+  double volScale = 1;
+  if (chiralSet->d_structureFlags &
+      static_cast<std::uint64_t>(
+          DistGeom::ChiralSetStructureFlags::IN_FUSED_SMALL_RINGS)) {
+    volScale = 0.25;
+  }
+
   RDGeom::Point3D crossp = v1.crossProduct(v2);
   double vol = crossp.dotProduct(v3);
   if (verbose) {
     std::cerr << "   " << fabs(vol) << std::endl;
   }
-  if (fabs(vol) < MIN_TETRAHEDRAL_CHIRAL_VOL) {
+  if (fabs(vol) < volScale * MIN_TETRAHEDRAL_CHIRAL_VOL) {
     return false;
   }
   crossp = v1.crossProduct(v2);
@@ -321,7 +330,7 @@ bool _volumeTest(const DistGeom::ChiralSetPtr &chiralSet,
   if (verbose) {
     std::cerr << "   " << fabs(vol) << std::endl;
   }
-  if (fabs(vol) < MIN_TETRAHEDRAL_CHIRAL_VOL) {
+  if (fabs(vol) < volScale * MIN_TETRAHEDRAL_CHIRAL_VOL) {
     return false;
   }
   crossp = v1.crossProduct(v3);
@@ -329,7 +338,7 @@ bool _volumeTest(const DistGeom::ChiralSetPtr &chiralSet,
   if (verbose) {
     std::cerr << "   " << fabs(vol) << std::endl;
   }
-  if (fabs(vol) < MIN_TETRAHEDRAL_CHIRAL_VOL) {
+  if (fabs(vol) < volScale * MIN_TETRAHEDRAL_CHIRAL_VOL) {
     return false;
   }
   crossp = v2.crossProduct(v3);
@@ -337,7 +346,7 @@ bool _volumeTest(const DistGeom::ChiralSetPtr &chiralSet,
   if (verbose) {
     std::cerr << "   " << fabs(vol) << std::endl;
   }
-  return fabs(vol) >= MIN_TETRAHEDRAL_CHIRAL_VOL;
+  return fabs(vol) >= volScale * MIN_TETRAHEDRAL_CHIRAL_VOL;
 }

 bool _sameSide(const RDGeom::Point3D &v1, const RDGeom::Point3D &v2,
@@ -1035,19 +1044,33 @@ void findChiralSets(const ROMol &mol, DistGeom::VECT_CHIRALSET &chiralCenters,
           nbrs.insert(nbrs.end(), atom->getIdx());
         }

+        // set a flag for tetrahedral centers that are in multiple small rings
+        auto numSmallRings = 0u;
+        constexpr int smallRingSize = 5;
+        for (const auto sz : mol.getRingInfo()->atomRingSizes(atom->getIdx())) {
+          if (sz < smallRingSize) {
+            ++numSmallRings;
+          }
+        }
+        std::uint64_t structureFlags = 0;
+        if (numSmallRings > 1) {
+          structureFlags = static_cast<std::uint64_t>(
+              DistGeom::ChiralSetStructureFlags::IN_FUSED_SMALL_RINGS);
+        }
+
         // now create a chiral set and set the upper and lower bound on the
         // volume
         if (chiralType == Atom::CHI_TETRAHEDRAL_CCW) {
           // positive chiral volume
-          auto *cset =
-              new DistGeom::ChiralSet(atom->getIdx(), nbrs[0], nbrs[1], nbrs[2],
-                                      nbrs[3], volLowerBound, volUpperBound);
+          auto *cset = new DistGeom::ChiralSet(atom->getIdx(), nbrs[0], nbrs[1],
+                                               nbrs[2], nbrs[3], volLowerBound,
+                                               volUpperBound, structureFlags);
           DistGeom::ChiralSetPtr cptr(cset);
           chiralCenters.push_back(cptr);
         } else if (chiralType == Atom::CHI_TETRAHEDRAL_CW) {
-          auto *cset =
-              new DistGeom::ChiralSet(atom->getIdx(), nbrs[0], nbrs[1], nbrs[2],
-                                      nbrs[3], -volUpperBound, -volLowerBound);
+          auto *cset = new DistGeom::ChiralSet(atom->getIdx(), nbrs[0], nbrs[1],
+                                               nbrs[2], nbrs[3], -volUpperBound,
+                                               -volLowerBound, structureFlags);
           DistGeom::ChiralSetPtr cptr(cset);
           chiralCenters.push_back(cptr);
         } else {
@@ -1059,8 +1082,9 @@ void findChiralSets(const ROMol &mol, DistGeom::VECT_CHIRALSET &chiralCenters,
             // the coordMap
             // there's no sense doing 3-rings because those are a nightmare
           } else {
-            auto *cset = new DistGeom::ChiralSet(
-                atom->getIdx(), nbrs[0], nbrs[1], nbrs[2], nbrs[3], 0.0, 0.0);
+            auto *cset = new DistGeom::ChiralSet(atom->getIdx(), nbrs[0],
+                                                 nbrs[1], nbrs[2], nbrs[3], 0.0,
+                                                 0.0, structureFlags);
             DistGeom::ChiralSetPtr cptr(cset);
             tetrahedralCenters.push_back(cptr);
           }

diff --git a/Code/GraphMol/DistGeomHelpers/catch_tests.cpp b/Code/GraphMol/DistGeomHelpers/catch_tests.cpp
index 530c4a6f282..3eae46fa321 100644
--- a/Code/GraphMol/DistGeomHelpers/catch_tests.cpp
+++ b/Code/GraphMol/DistGeomHelpers/catch_tests.cpp
@@ -985,4 +985,34 @@ TEST_CASE("terminal groups in pruning") {
       CHECK(cids.size() >= 2);
     }
   }
+}
+
+TEST_CASE("github #7552") {
+  DGeomHelpers::EmbedParameters ps = DGeomHelpers::ETKDGv3;
+  ps.randomSeed = 0xf00d;
+  SECTION("as reported") {
+    auto mol = "O=CCC1OC2COC12"_smiles;
+    REQUIRE(mol);
+    MolOps::addHs(*mol);
+    CHECK(DGeomHelpers::EmbedMolecule(*mol, ps) == 0);
+  }
+  SECTION("as reported, bulk") {
+    std::vector<std::string> smileses{
+        "O=CCC1OC2COC12",      "O=C1OC2CCC12C#N",    "CC1C2CC3OC2C13O",
+        "CC12CC1C3(C)OCC23",   "OC1C2COC13COC23",    "OC1C2C3C2N4C3CC14",
+        "CC1OC12C3CC2(O)C3",   "OC1C2CC3C2CCC13",    "CN1CC2(O)C3CC3C12",
+        "C1OC2C3C4C5C4C12N35", "C1OC2CC3OC12C=C3",   "C1C2OC3C1OC23",
+        "CC1(O)CC2CCC12",      "CC12NC(=O)C1C3OC23", "OC1CC2(NCCC12)C#N",
+        "CC12C3C1C(=O)C3C2O",  "C1C=C2C3OC4C3N1C24", "CC12C3C1C4=NC3C2O4",
+        "C1OC23C=CC4C2N4C13",  "OCC12CNC1C(=O)N2",   "CC1C2C3C1C(C#C)n23",
+
+    };
+    for (const auto &smiles : smileses) {
+      INFO(smiles);
+      auto mol = v2::SmilesParse::MolFromSmiles(smileses[0]);
+      REQUIRE(mol);
+      MolOps::addHs(*mol);
+      CHECK(DGeomHelpers::EmbedMolecule(*mol, ps) == 0);
+    }
+  }
 }
\ No newline at end of file


Fixes #7364 (rdkit/rdkit#7657)

make sure we only use the H-stripped molecule in the BCUT calculation

Pre-condition violations for BCUT descriptor calculations

**Describe the bug**
I receive pre-condition violations for every molecule I've tested when calculating BCUT descriptors. Our standardization code adds H's to each molecule first. The violations print out in my notebook multiple times per molecule and (sometimes multiple) per descriptor, resulting in thousands of outputs and eventually crashing my notebook. The descriptors that cause it are:
```
('BCUT2D_MWHI',
 'BCUT2D_MWLOW',
 'BCUT2D_CHGHI',
 'BCUT2D_CHGLO',
 'BCUT2D_LOGPHI',
 'BCUT2D_LOGPLOW',
 'BCUT2D_MRHI',
 'BCUT2D_MRLOW')
```
And the violation repeatedly reads:

```
****
Pre-condition Violation
bad result vector size
Violation occurred on line 40 in file /project/build/temp.linux-aarch64-cpython-39/rdkit/Code/GraphMol/Descriptors/Crippen.cpp
Failed Expression: logpContribs.size() == mol.getNumAtoms() && mrContribs.size() == mol.getNumAtoms()
----------
Stacktrace:
----------
****
```
It also seems to be somewhat dependent on first calling AddHs and/or passing the descriptors in an iterable vs a single descriptor, although I don't know why that's the case.

**To Reproduce**
```
smiles='C#CCC(C(=O)c1ccc(C)cc1)N1CCCC1'

from rdkit import Chem
mol=Chem.MolFromSmiles(smiles)
mol=Chem.AddHs(mol)

from rdkit.Chem import Descriptors
from rdkit.ML.Descriptors import MoleculeDescriptors
calc = MoleculeDescriptors.MolecularDescriptorCalculator([x[0] for x in Descriptors._descList])

desc_names = calc.GetDescriptorNames()

calc=MoleculeDescriptors.MolecularDescriptorCalculator(['BCUT2D_MWHI'])
descriptors = calc.CalcDescriptors(mol)
```
**Expected behavior**
I'd like the warnings not to appear and not crash my notebook.

**Screenshots**
<img width="808" alt="image" src="https://github.com/rdkit/rdkit/assets/64031328/44e91a75-a5ce-4af1-8ac2-20d842dcaf71">

**Configuration (please complete the following information):**
 - RDKit version: 2023.9.5 (It happens for 2023.9.X but not 2023.3.3)
 - OS: [e.g. Ubuntu 20.04] Apple M2 Max running Ubuntu Lunar 23.04 ARM64 on a VM.
 - Python version (if relevant):
 Python 3.9.17 (main, Aug 21 2023, 10:36:40)
 [GCC 12.3.0] on linux
 - Are you using conda? No
 - If you are using conda, which channel did you install the rdkit from? NA
 - If you are not using conda: how did you install the RDKit? `pip install rdkit==2023.9.5`

**Additional context**
I believe it is NOT ARM-specific since my colleagues see the same issue on regular Linux/Ubuntu with the 2023.9.X versions.

diff --git a/Code/GraphMol/Descriptors/BCUT.cpp b/Code/GraphMol/Descriptors/BCUT.cpp
index 35ec90b9627..8b160c21095 100644
--- a/Code/GraphMol/Descriptors/BCUT.cpp
+++ b/Code/GraphMol/Descriptors/BCUT.cpp
@@ -68,8 +68,8 @@ std::unique_ptr<Eigen::MatrixXd> make_burden(const ROMol &m) {

 std::pair<double, double> BCUT2D(std::unique_ptr<Eigen::MatrixXd> &burden,
                                  const std::vector<double> &atom_props) {
-  if(atom_props.empty()) {
-    return std::pair<double,double>(0.0,0.0);
+  if (atom_props.empty()) {
+    return std::pair<double, double>(0.0, 0.0);
   }

   for (unsigned int i = 0; i < atom_props.size(); ++i) {
@@ -107,16 +107,15 @@ std::pair<double, double> BCUT2D(const ROMol &m,
 }

 std::vector<double> BCUT2D(const ROMol &m) {
-  unsigned int num_atoms = m.getNumAtoms();
-  if(num_atoms == 0) {
-      return std::vector<double>(NUM_BCUTS, 0.0);
+  if (!m.getNumAtoms()) {
+    return std::vector<double>(NUM_BCUTS, 0.0);
   }
-
-  std::unique_ptr<ROMol> mol(MolOps::removeHs(m));
+  std::unique_ptr<ROMol> mol(MolOps::removeAllHs(m));
+  unsigned int numAtoms = mol->getNumAtoms();
   std::vector<double> masses;
   std::vector<double> charges;
-  masses.reserve(num_atoms);
-  charges.reserve(num_atoms);
+  masses.reserve(numAtoms);
+  charges.reserve(numAtoms);

   RDKit::computeGasteigerCharges(*mol, 12, true);
   for (auto &atom : mol->atoms()) {
@@ -125,13 +124,12 @@ std::vector<double> BCUT2D(const ROMol &m) {
         atom->getProp<double>(common_properties::_GasteigerCharge));
   }

-  std::vector<double> slogp(num_atoms, 0.0);
-  std::vector<double> cmr(num_atoms, 0.0);
+  std::vector<double> slogp(numAtoms, 0.0);
+  std::vector<double> cmr(numAtoms, 0.0);
   getCrippenAtomContribs(*mol, slogp, cmr);
-
   // polarizability? - need model
   // slogp?  sasa?
-  auto burden = make_burden(m);
+  auto burden = make_burden(*mol);
   auto atom_bcut = BCUT2D(burden, masses);
   auto gasteiger = BCUT2D(burden, charges);
   auto logp = BCUT2D(burden, slogp);

diff --git a/Code/GraphMol/Descriptors/catch_tests.cpp b/Code/GraphMol/Descriptors/catch_tests.cpp
index 8c512bc732c..bcac0b2dd7c 100644
--- a/Code/GraphMol/Descriptors/catch_tests.cpp
+++ b/Code/GraphMol/Descriptors/catch_tests.cpp
@@ -20,6 +20,7 @@
 #include <GraphMol/Descriptors/OxidationNumbers.h>
 #include <GraphMol/Descriptors/PMI.h>
 #include <GraphMol/Descriptors/DCLV.h>
+#include <GraphMol/Descriptors/BCUT.h>

 using namespace RDKit;

@@ -605,3 +606,16 @@ TEST_CASE("DCLV") {
     CHECK(dclv.getVDWVolume() == Catch::Approx(139.97).epsilon(0.05));
   }
 }
+
+TEST_CASE("Github #7364: BCUT descriptors failing for moleucles with Hs") {
+  SECTION("as reported") {
+    auto m = "CCOC#CCC(C(=O)c1ccc(C)cc1)N1CCCC1"_smiles;
+    REQUIRE(m);
+    RWMol m2 = *m;
+    MolOps::addHs(m2);
+    auto ref = Descriptors::BCUT2D(*m);
+    auto val = Descriptors::BCUT2D(m2);
+    CHECK(ref.size() == val.size());
+    CHECK(ref == val);
+  }
+}
\ No newline at end of file


Fixes #6757 (rdkit/rdkit#7664)

Simple change to make sure we have called `assignStereochemistry()` before doing the work in `numAtomStereoCenters()` and `numUnspecifiedAtomStereoCenters()`

rdMolDescriptors.CalcNumAtomStereoCenters fails on Sanitized Molecule

**Describe the bug**
`rdMolDescriptors.CalcNumAtomStereoCenters` fails to find stereo centers if a molecule is sanitized (twice).
I had assumed this was deuterium-related, but it appears to affect other stereo centers as well (see https://github.com/rdkit/rdkit/discussions/6657).

**To Reproduce**
```python
from rdkit import Chem
from rdkit.Chem import rdMolDescriptors

smi = "[2H][C@@](C)(N)C(=O)O"
mol1 = Chem.MolFromSmiles(smi)
cip_count1 = sum([atom.HasProp("_CIPCode") for atom in mol1.GetAtoms()])
stereocenters1 = rdMolDescriptors.CalcNumAtomStereoCenters(mol1)

mol2 = Chem.Mol(mol1)
Chem.SanitizeMol(mol2)
cip_count2 = sum([atom.HasProp("_CIPCode") for atom in mol2.GetAtoms()])
try:
    stereocenters2 = rdMolDescriptors.CalcNumAtomStereoCenters(mol2)
except: # ValueError: numStereoCenters called without stereo being assigned
    stereocenters2 = 0

assert cip_count1 == cip_count2
assert stereocenters1 == stereocenters2 # Fails. 1 != 0
```

**Expected behavior**
One stereo center should be detected for both molecules.
The properties  `_CIPCode` and `_ChiralityPossible` are still correct so I've been counting atom CIPCodes as a workaround.

**Configuration:**
 - RDKit version: 2022.9.5 (linked discussion is on 2023.03.3)
 - OS: Ubuntu 22.04.3
 - Python version: 3.9
 - Are you using conda? No
 - How did you install the RDKit? rdkit-pypi via Poetry

diff --git a/Code/GraphMol/Chirality.cpp b/Code/GraphMol/Chirality.cpp
index e5a40ddee69..d4c1f35e931 100644
--- a/Code/GraphMol/Chirality.cpp
+++ b/Code/GraphMol/Chirality.cpp
@@ -2771,6 +2771,10 @@ void assignStereochemistry(ROMol &mol, bool cleanIt, bool force,
     return;
   }

+  if (mol.needsUpdatePropertyCache()) {
+    mol.updatePropertyCache(false);
+  }
+
   if (!Chirality::getUseLegacyStereoPerception()) {
     Chirality::stereoPerception(mol, cleanIt, flagPossibleStereoCenters);
   } else {
diff --git a/Code/GraphMol/Descriptors/Lipinski.cpp b/Code/GraphMol/Descriptors/Lipinski.cpp
index e0b044aed34..d80727f8184 100644
--- a/Code/GraphMol/Descriptors/Lipinski.cpp
+++ b/Code/GraphMol/Descriptors/Lipinski.cpp
@@ -506,36 +506,46 @@ bool hasStereoAssigned(const ROMol &mol) {
   return mol.hasProp(common_properties::_StereochemDone);
 }
 }  // namespace
-const std::string NumAtomStereoCentersVersion = "1.0.0";
+const std::string NumAtomStereoCentersVersion = "1.0.1";
 unsigned int numAtomStereoCenters(const ROMol &mol) {
+  std::unique_ptr<ROMol> tmol;
+  const ROMol *mptr = &mol;
   if (!hasStereoAssigned(mol)) {
-    throw ValueErrorException(
-        "numStereoCenters called without stereo being assigned");
+    tmol.reset(new ROMol(mol));
+    constexpr bool cleanIt = true;
+    constexpr bool force = true;
+    constexpr bool flagPossible = true;
+    MolOps::assignStereochemistry(*tmol, cleanIt, force, flagPossible);
+    mptr = tmol.get();
   }

   unsigned int res = 0;
-  for (ROMol::ConstAtomIterator atom = mol.beginAtoms(); atom != mol.endAtoms();
-       ++atom) {
-    if ((*atom)->hasProp(common_properties::_ChiralityPossible)) {
-      res++;
+  for (const auto &atom : mptr->atoms()) {
+    if (atom->hasProp(common_properties::_ChiralityPossible)) {
+      ++res;
     }
   }
   return res;
 }

-const std::string NumUnspecifiedAtomStereoCentersVersion = "1.0.0";
+const std::string NumUnspecifiedAtomStereoCentersVersion = "1.0.1";
 unsigned int numUnspecifiedAtomStereoCenters(const ROMol &mol) {
+  std::unique_ptr<ROMol> tmol;
+  const ROMol *mptr = &mol;
   if (!hasStereoAssigned(mol)) {
-    throw ValueErrorException(
-        "numUnspecifiedStereoCenters called without stereo being assigned");
+    tmol.reset(new ROMol(mol));
+    constexpr bool cleanIt = true;
+    constexpr bool force = true;
+    constexpr bool flagPossible = true;
+    MolOps::assignStereochemistry(*tmol, cleanIt, force, flagPossible);
+    mptr = tmol.get();
   }

   unsigned int res = 0;
-  for (ROMol::ConstAtomIterator atom = mol.beginAtoms(); atom != mol.endAtoms();
-       ++atom) {
-    if ((*atom)->hasProp(common_properties::_ChiralityPossible) &&
-        (*atom)->getChiralTag() == Atom::CHI_UNSPECIFIED) {
-      res++;
+  for (const auto &atom : mptr->atoms()) {
+    if (atom->hasProp(common_properties::_ChiralityPossible) &&
+        atom->getChiralTag() == Atom::CHI_UNSPECIFIED) {
+      ++res;
     }
   }
   return res;

diff --git a/Code/GraphMol/Descriptors/catch_tests.cpp b/Code/GraphMol/Descriptors/catch_tests.cpp
index bcac0b2dd7c..594fddb1fca 100644
--- a/Code/GraphMol/Descriptors/catch_tests.cpp
+++ b/Code/GraphMol/Descriptors/catch_tests.cpp
@@ -618,4 +618,22 @@ TEST_CASE("Github #7364: BCUT descriptors failing for moleucles with Hs") {
     CHECK(ref.size() == val.size());
     CHECK(ref == val);
   }
+}
+
+TEST_CASE(
+    "Github #6757: numAtomStereoCenters fails if molecule is sanitized a second time") {
+  SECTION("as reported") {
+    auto m = "C[C@H](F)Cl"_smiles;
+    REQUIRE(m);
+    CHECK(Descriptors::numAtomStereoCenters(*m) == 1);
+    CHECK(Descriptors::numUnspecifiedAtomStereoCenters(*m) == 0);
+    MolOps::sanitizeMol(*m);
+    CHECK(Descriptors::numAtomStereoCenters(*m) == 1);
+  }
+  SECTION("expanded") {
+    auto m = "C[C@H](F)C(O)Cl"_smiles;
+    REQUIRE(m);
+    CHECK(Descriptors::numAtomStereoCenters(*m) == 2);
+    CHECK(Descriptors::numUnspecifiedAtomStereoCenters(*m) == 1);
+  }
 }
\ No newline at end of file
diff --git a/Code/GraphMol/Descriptors/test.cpp b/Code/GraphMol/Descriptors/test.cpp
index 361a77f083f..18de6fac347 100644
--- a/Code/GraphMol/Descriptors/test.cpp
+++ b/Code/GraphMol/Descriptors/test.cpp
@@ -1993,16 +1993,14 @@ void testStereoCounting() {
   names.emplace_back("NumUnspecifiedAtomStereoCenters");
   Properties prop(names);

-  try {
-    prop.computeProperties(*m);
-    TEST_ASSERT(0);  // didn't catch exception
-  } catch (ValueErrorException &) {
-    BOOST_LOG(rdErrorLog) << "---Caught stereo value error---" << std::endl;
-  }
+  // the stereo perception is done automatically:
+  std::vector<double> res = prop.computeProperties(*m);
+  TEST_ASSERT(res[0] == 1);
+  TEST_ASSERT(res[1] == 1);

   delete m;
   m = SmilesToMol("NC(C)(F)C(=O)O", sanitize);
-  std::vector<double> res = prop.computeProperties(*m);
+  res = prop.computeProperties(*m);
   TEST_ASSERT(res[0] == 1);
   TEST_ASSERT(res[1] == 1);
   delete m;


Fixes #7699 (rdkit/rdkit#7904)

Stereo groups in product templates of reactions are now propagated to the product molecules.

If the atoms involved in those stereo groups are present in any stereo groups in the reactant molecules, they will be removed from those stereogroups when they are propagated to the products.

Carry over enhanced stereochemistry groups from reaction product templates to products

**Is your feature request related to a problem? Please describe.**
The problem is discussed in https://github.com/rdkit/rdkit/discussions/7660
Basically, for reactions, while stereo groups from reactants are carried over after https://github.com/rdkit/rdkit/issues/2366, this does not happen for stereogroups in product templates, yet.

**Describe the solution you'd like**
I would like to see stereogroups from the product template to be copied over to the actual reaction products in the core RDKit code for reactions.

**Describe alternatives you've considered**
In the discussion linked above, a workaround is shown, which involves copying the stereogroups manually with the help of reaction-realted atom props annotated during reaction application.

**Additional context**
Copying over the example from the discussion.

```python
from rdkit import Chem
from rdkit.Chem import rdChemReactions

input = Chem.MolFromMolBlock(""""
  MJ231601

  5  4  0  0  1  0  0  0  0  0999 V2000
   -0.8258    1.4955    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.8258    0.6705    0.0000 F   0  0  0  0  0  0  0  0  0  0  0  0
   -1.6508    1.4955    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
   -0.8258    2.3205    0.0000 Cl  0  0  0  0  0  0  0  0  0  0  0  0
   -0.0008    1.4955    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
  1  2  1  0  0  0  0
  1  4  1  0  0  0  0
  1  3  1  0  0  0  0
  1  5  1  0  0  0  0
M  END
""")

# rxn molblock with enhanced stereochemistry
rxn = """$RXN V3000

      Mrv2401  072420241107

M  V30 COUNTS 1 1
M  V30 BEGIN REACTANT
M  V30 BEGIN CTAB
M  V30 COUNTS 5 4 0 0 1
M  V30 BEGIN ATOM
M  V30 1 C -1.5417 2.7917 0 1
M  V30 2 F -1.5417 1.2517 0 4
M  V30 3 C -3.0817 2.7917 0 3
M  V30 4 Cl -1.5417 4.3317 0 2
M  V30 5 C -0.0017 2.7917 0 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 1 2
M  V30 2 1 1 4
M  V30 3 1 1 3
M  V30 4 1 1 5
M  V30 END BOND
M  V30 END CTAB
M  V30 END REACTANT
M  V30 BEGIN PRODUCT
M  V30 BEGIN CTAB
M  V30 COUNTS 5 4 0 0 0
M  V30 BEGIN ATOM
M  V30 1 C 9.2083 2.9167 0 1
M  V30 2 C 7.6683 2.9167 0 3
M  V30 3 F 9.2083 1.3767 0 4
M  V30 4 Cl 9.2083 4.4567 0 2
M  V30 5 I 10.7483 2.9167 0 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 1 2
M  V30 2 1 1 3
M  V30 3 1 1 4
M  V30 4 1 1 5 CFG=1
M  V30 END BOND
M  V30 BEGIN COLLECTION
M  V30 MDLV30/STERAC1 ATOMS=(1 1)
M  V30 END COLLECTION
M  V30 END CTAB
M  V30 END PRODUCT
M  END
"""

rxn = rdChemReactions.ReactionFromRxnBlock(rxn)
rxn.Initialize()
p = rxn.RunReactants([input])
for prod in p:
    sgs = prod[0].GetStereoGroups()
    if len(sgs) <= 0:
        print("Stereochemistry missing :(")
```

```
Stereochemistry missing :(
Stereochemistry missing :(
```

diff --git a/Code/GraphMol/ChemReactions/ReactionRunner.cpp b/Code/GraphMol/ChemReactions/ReactionRunner.cpp
index 19fc31fc14f..26639feb67a 100644
--- a/Code/GraphMol/ChemReactions/ReactionRunner.cpp
+++ b/Code/GraphMol/ChemReactions/ReactionRunner.cpp
@@ -1255,15 +1255,14 @@ void copyEnhancedStereoGroups(const ROMol &reactant, RWMOL_SPTR product,
   for (const auto &sg : reactant.getStereoGroups()) {
     std::vector<Atom *> atoms;
     std::vector<Bond *> bonds;
-    for (auto &&reactantAtom : sg.getAtoms()) {
+    for (const auto &reactantAtom : sg.getAtoms()) {
       auto productAtoms = mapping.reactProdAtomMap.find(reactantAtom->getIdx());
       if (productAtoms == mapping.reactProdAtomMap.end()) {
         continue;
       }

-      for (auto &&productAtomIdx : productAtoms->second) {
+      for (auto &productAtomIdx : productAtoms->second) {
         auto productAtom = product->getAtomWithIdx(productAtomIdx);
-
         // If chirality destroyed by the reaction, skip the atom
         if (productAtom->getChiralTag() == Atom::CHI_UNSPECIFIED) {
           continue;
@@ -1409,6 +1408,76 @@ void addReactantAtomsAndBonds(const ChemicalReaction &rxn, RWMOL_SPTR product,
   delete (mapping);
 }  // end of addReactantAtomsAndBonds

+namespace {
+void copyTemplateStereoGroupsToMol(const ROMol &templateMol,
+                                   RWMOL_SPTR product) {
+  const auto &stereoGroups = templateMol.getStereoGroups();
+  if (stereoGroups.empty()) {
+    return;
+  }
+
+  boost::dynamic_bitset<> atomsInTemplateStereoGroups(product->getNumAtoms());
+  std::vector<StereoGroup> newStereoGroups;
+  for (const auto &sg : stereoGroups) {
+    bool keepIt = true;
+    std::vector<Atom *> atoms;
+    for (const auto &atom : sg.getAtoms()) {
+      if (auto mapNum = atom->getAtomMapNum()) {
+        for (auto productAtom : product->atoms()) {
+          int oldMapNum = 0;
+          if (productAtom->getPropIfPresent(common_properties::reactionMapNum,
+                                            oldMapNum) &&
+              oldMapNum == mapNum) {
+            atoms.push_back(productAtom);
+            atomsInTemplateStereoGroups.set(productAtom->getIdx());
+          }
+        }
+      } else {
+        keepIt = false;
+        break;
+      }
+    }
+    if (keepIt && !atoms.empty()) {
+      std::vector<Bond *> bonds;
+      newStereoGroups.emplace_back(sg.getGroupType(), std::move(atoms),
+                                   std::move(bonds), sg.getReadId());
+    }
+  }
+  if (!newStereoGroups.empty()) {
+    // remove any stereo groups that are already present in the product (these
+    // were copied over from the reactant in copyEnhancedStereoGroups()) and
+    // that overlap with the added ones
+    for (const auto &productSG : product->getStereoGroups()) {
+      unsigned int nOverlappingAtoms = 0;
+      for (const auto atom : productSG.getAtoms()) {
+        if (atomsInTemplateStereoGroups[atom->getIdx()]) {
+          ++nOverlappingAtoms;
+        }
+      }
+      if (!nOverlappingAtoms) {
+        // no overlapping atoms, we can just keep the stereogroup.
+        newStereoGroups.push_back(productSG);
+      } else if (nOverlappingAtoms < productSG.getAtoms().size()) {
+        // some of the atoms in the stereo group are not already there
+        // in the product, we need to split the stereo group
+        std::vector<Atom *> newAtoms;
+        for (const auto atom : productSG.getAtoms()) {
+          if (!atomsInTemplateStereoGroups[atom->getIdx()]) {
+            newAtoms.push_back(atom);
+          }
+        }
+        std::vector<Bond *> newBonds;
+        newStereoGroups.emplace_back(productSG.getGroupType(),
+                                     std::move(newAtoms), std::move(newBonds),
+                                     productSG.getReadId());
+      }
+      // else: all atoms in the stereo group are already there, we can skip it
+    }
+    product->setStereoGroups(std::move(newStereoGroups));
+  }
+}
+}  // namespace
+
 MOL_SPTR_VECT
 generateOneProductSet(const ChemicalReaction &rxn,
                       const MOL_SPTR_VECT &reactants,
@@ -1469,6 +1538,11 @@ generateOneProductSet(const ChemicalReaction &rxn,
       MolOps::setDoubleBondNeighborDirections(*product);
     }

+    // if the product template has stereo groups, copy them over now
+    if (!(*pTemplIt)->getStereoGroups().empty()) {
+      copyTemplateStereoGroupsToMol(**pTemplIt, product);
+    }
+
     res[prodId] = product;
     ++prodId;
   }

diff --git a/Code/GraphMol/ChemReactions/catch_tests.cpp b/Code/GraphMol/ChemReactions/catch_tests.cpp
index 4bcb71143d8..d1d00dbdbb4 100644
--- a/Code/GraphMol/ChemReactions/catch_tests.cpp
+++ b/Code/GraphMol/ChemReactions/catch_tests.cpp
@@ -1983,3 +1983,111 @@ M  END)RXN";
     CHECK(rxn2.getReactants()[0]->getAtomWithIdx(0)->hasProp("molFileValue"));
   }
 }
+
+TEST_CASE("Github #7669: propate stereo groups from product templates") {
+  auto rxnBlock = R"RXN($RXN V3000
+
+      Mrv2401  072420241107
+
+M  V30 COUNTS 1 1
+M  V30 BEGIN REACTANT
+M  V30 BEGIN CTAB
+M  V30 COUNTS 5 4 0 0 1
+M  V30 BEGIN ATOM
+M  V30 1 C -1.5417 2.7917 0 1
+M  V30 2 F -1.5417 1.2517 0 4
+M  V30 3 C -3.0817 2.7917 0 3
+M  V30 4 Cl -1.5417 4.3317 0 2
+M  V30 5 C -0.0017 2.7917 0 0
+M  V30 END ATOM
+M  V30 BEGIN BOND
+M  V30 1 1 1 2
+M  V30 2 1 1 4
+M  V30 3 1 1 3
+M  V30 4 1 1 5
+M  V30 END BOND
+M  V30 END CTAB
+M  V30 END REACTANT
+M  V30 BEGIN PRODUCT
+M  V30 BEGIN CTAB
+M  V30 COUNTS 5 4 0 0 0
+M  V30 BEGIN ATOM
+M  V30 1 C 9.2083 2.9167 0 1
+M  V30 2 C 7.6683 2.9167 0 3
+M  V30 3 F 9.2083 1.3767 0 4
+M  V30 4 Cl 9.2083 4.4567 0 2
+M  V30 5 I 10.7483 2.9167 0 0
+M  V30 END ATOM
+M  V30 BEGIN BOND
+M  V30 1 1 1 2
+M  V30 2 1 1 3
+M  V30 3 1 1 4
+M  V30 4 1 1 5 CFG=1
+M  V30 END BOND
+M  V30 BEGIN COLLECTION
+M  V30 MDLV30/STERAC1 ATOMS=(1 1)
+M  V30 END COLLECTION
+M  V30 END CTAB
+M  V30 END PRODUCT
+M  END)RXN";
+  auto rxn = v2::ReactionParser::ReactionFromRxnBlock(rxnBlock);
+  REQUIRE(rxn);
+  CHECK(rxn->getProducts()[0]->getStereoGroups().size() == 1);
+  rxn->initReactantMatchers();
+  SECTION("as reported") {
+    auto mol = R"CTAB(
+  MJ231601
+
+  5  4  0  0  1  0  0  0  0  0999 V2000
+   -0.8258    1.4955    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
+   -0.8258    0.6705    0.0000 F   0  0  0  0  0  0  0  0  0  0  0  0
+   -1.6508    1.4955    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
+   -0.8258    2.3205    0.0000 Cl  0  0  0  0  0  0  0  0  0  0  0  0
+   -0.0008    1.4955    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0
+  1  2  1  0  0  0  0
+  1  4  1  0  0  0  0
+  1  3  1  0  0  0  0
+  1  5  1  0  0  0  0
+M  END)CTAB"_ctab;
+    REQUIRE(mol);
+    ROMOL_SPTR reactant(std::move(mol));
+    auto ps = rxn->runReactant(reactant, 0);
+    REQUIRE(ps.size() == 2);
+    REQUIRE(ps[0].size() == 1);
+
+    CHECK(ps[0][0]->getStereoGroups().size() == 1);
+    CHECK(ps[0][0]->getStereoGroups()[0].getGroupType() ==
+          StereoGroupType::STEREO_AND);
+    CHECK(ps[1][0]->getStereoGroups().size() == 1);
+    CHECK(ps[0][0]->getStereoGroups()[0].getGroupType() ==
+          StereoGroupType::STEREO_AND);
+
+    auto sg = StereoGroup(StereoGroupType::STEREO_OR,
+                          {reactant->getAtomWithIdx(0)}, {});
+    reactant->setStereoGroups({sg});
+    ps = rxn->runReactant(reactant, 0);
+    REQUIRE(ps.size() == 2);
+    REQUIRE(ps[0].size() == 1);
+    CHECK(ps[0][0]->getStereoGroups().size() == 1);
+    CHECK(ps[0][0]->getStereoGroups()[0].getGroupType() ==
+          StereoGroupType::STEREO_AND);
+    CHECK(ps[1][0]->getStereoGroups().size() == 1);
+    CHECK(ps[0][0]->getStereoGroups()[0].getGroupType() ==
+          StereoGroupType::STEREO_AND);
+  }
+
+  SECTION(
+      "stereogroups in product template remove atoms from stereogroups in reactant") {
+    auto mol = "C[C@](F)(Cl)C[C@](C)(O)Br |o1:1,5|"_smiles;
+    ROMOL_SPTR reactant(std::move(mol));
+    auto ps = rxn->runReactant(reactant, 0);
+    REQUIRE(ps.size() == 2);
+    REQUIRE(ps[0].size() == 1);
+    CHECK(ps[0][0]->getStereoGroups().size() == 1);
+    CHECK(ps[0][0]->getStereoGroups()[0].getGroupType() ==
+          StereoGroupType::STEREO_AND);
+    CHECK(ps[1][0]->getStereoGroups().size() == 2);
+    CHECK(ps[0][0]->getStereoGroups()[0].getGroupType() ==
+          StereoGroupType::STEREO_AND);
+  }
+}
\ No newline at end of file
diff --git a/Code/GraphMol/ChemReactions/testData/github7669_1.rxn b/Code/GraphMol/ChemReactions/testData/github7669_1.rxn
new file mode 100644
index 00000000000..32c84cf12ae
--- /dev/null
+++ b/Code/GraphMol/ChemReactions/testData/github7669_1.rxn
@@ -0,0 +1,90 @@
+$RXN V3000
+
+      Mrv2401  100720240457
+
+M  V30 COUNTS 1 1
+M  V30 BEGIN REACTANT
+M  V30 BEGIN CTAB
+M  V30 COUNTS 16 17 0 0 0
+M  V30 BEGIN ATOM
+M  V30 1 C -17.5833 8.8333 0 1
+M  V30 2 C -16.2497 9.6033 0 2
+M  V30 3 C -18.917 9.6033 0 3
+M  V30 4 C -20.2507 8.8333 0 4
+M  V30 5 C -20.2507 7.2933 0 5
+M  V30 6 C -18.917 6.5233 0 6
+M  V30 7 C -17.5833 7.2933 0 7
+M  V30 8 C -14.916 11.9134 0 8
+M  V30 9 C -16.2497 11.1434 0 9
+M  V30 10 C -14.916 8.8333 0 10
+M  V30 11 C -13.5823 9.6033 0 11
+M  V30 12 C -13.5823 11.1434 0 12
+M  V30 13 C -18.917 11.1433 0 13
+M  V30 14 F -16.2496 6.5233 0 14
+M  V30 15 F -14.916 7.2933 0 15
+M  V30 16 O -14.916 13.4534 0 16
+M  V30 END ATOM
+M  V30 BEGIN BOND
+M  V30 1 1 1 2
+M  V30 2 1 7 1
+M  V30 3 2 3 1
+M  V30 4 2 9 2
+M  V30 5 1 3 4
+M  V30 6 1 3 13
+M  V30 7 2 4 5
+M  V30 8 1 5 6
+M  V30 9 2 6 7
+M  V30 10 1 7 14
+M  V30 11 1 8 9
+M  V30 12 2 8 12
+M  V30 13 1 8 16
+M  V30 14 2 10 11
+M  V30 15 1 10 15
+M  V30 16 1 11 12
+M  V30 17 1 2 10
+M  V30 END BOND
+M  V30 END CTAB
+M  V30 END REACTANT
+M  V30 BEGIN PRODUCT
+M  V30 BEGIN CTAB
+M  V30 COUNTS 16 17 0 0 0
+M  V30 BEGIN ATOM
+M  V30 1 C -4.7085 10.1437 0 1
+M  V30 2 C -3.3749 10.9137 0 2
+M  V30 3 C -6.0422 10.9137 0 3
+M  V30 4 C -7.3759 10.1437 0 4
+M  V30 5 C -7.3759 8.6037 0 5
+M  V30 6 C -6.0422 7.8337 0 6
+M  V30 7 C -4.7085 8.6037 0 7
+M  V30 8 C -2.0412 13.2238 0 8
+M  V30 9 C -3.3749 12.4538 0 9
+M  V30 10 C -2.0412 10.1437 0 10
+M  V30 11 C -0.7075 10.9137 0 11
+M  V30 12 C -0.7075 12.4538 0 12
+M  V30 13 C -6.0422 12.4537 0 13
+M  V30 14 F -3.3748 7.8337 0 14
+M  V30 15 F -2.0412 8.6037 0 15
+M  V30 16 N -2.0412 14.7638 0 16
+M  V30 END ATOM
+M  V30 BEGIN BOND
+M  V30 1 1 1 2
+M  V30 2 1 7 1
+M  V30 3 2 3 1
+M  V30 4 2 9 2
+M  V30 5 1 3 4
+M  V30 6 1 3 13
+M  V30 7 2 4 5
+M  V30 8 1 5 6
+M  V30 9 2 6 7
+M  V30 10 1 7 14
+M  V30 11 1 8 9
+M  V30 12 2 8 12
+M  V30 13 1 8 16
+M  V30 14 2 10 11
+M  V30 15 1 10 15
+M  V30 16 1 11 12
+M  V30 17 1 2 10 CFG=1
+M  V30 END BOND
+M  V30 END CTAB
+M  V30 END PRODUCT
+M  END


Fixes #8162 (rdkit/rdkit#8164)

Straightforward fix to make only conjugated double and single bonds match aromatic bonds

Cyanamide incorrectly matches methyl-imidazole when aromaticMatchesConjugated is set

**Describe the bug**

Given the MOL 3000 query (cyanamide) and smiles target (methyl-imidazole) below an incorrect match is reported when `aromaticMatchesConjugated = True`.  Interestingly, this does not happen using the equivalent SMARTS pattern.

The behavior is observed in the 2024.09.04 Python release and also on C# wrappers built on 1/10/25

**To Reproduce**

```python
# %%
import rdkit
from rdkit import Chem
from rdkit.Chem import AllChem, Draw
from rdkit.Chem.Draw import IPythonConsole
from rdkit.Chem.rdchem import SubstructMatchParameters
IPythonConsole.drawOptions.addAtomIndices = True
# %%
rdkit.__version__
# %%
block = """ACS Document 1996
  ChemDraw01092510212D

  0  0  0     0  0              0 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 4 3 0 0 0
M  V30 BEGIN ATOM
M  V30 1 N 0.357236 0.412500 0.000000 0
M  V30 2 C 1.071707 0.000000 0.000000 0
M  V30 3 C -0.357236 0.000000 0.000000 0
M  V30 4 N -1.071707 -0.412500 0.000000 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 1 2
M  V30 2 1 1 3
M  V30 3 3 3 4
M  V30 END BOND
M  V30 END CTAB
M  END
"""
query = Chem.MolFromMolBlock(block)
query
# %%
smi = 'CN1C=NC=C1'
mol = Chem.MolFromSmiles(smi)
mol
# %%
params = SubstructMatchParameters()
params.aromaticMatchesConjugated = True
mol.GetSubstructMatch(query, params)
# %%
smarts = "N(C)C#N"
query2 =  Chem.MolFromSmarts(smarts)
query2
# %%
mol.GetSubstructMatch(query2, params)
```

Which gives a match between the mol block and the smiles structure

**Expected behavior**
Expect both substructure matches to be empty

**Screenshots**

![image](https://github.com/user-attachments/assets/d9adf90a-ac66-4c2b-ab4e-920171581d33)

**Configuration (please complete the following information):**
 - RDKit version:  2024.09.04
 - OS: Windows
 - Python version (if relevant):
 - Are you using conda? yes
 - If you are using conda, which channel did you install the rdkit from? conda-forge
 - If you are not using conda: how did you install the RDKit?

diff --git a/Code/GraphMol/Substruct/SubstructUtils.cpp b/Code/GraphMol/Substruct/SubstructUtils.cpp
index af6d52bb255..a7aebbbb2d8 100644
--- a/Code/GraphMol/Substruct/SubstructUtils.cpp
+++ b/Code/GraphMol/Substruct/SubstructUtils.cpp
@@ -168,6 +168,12 @@ bool bondCompat(const Bond *b1, const Bond *b2,
   PRECONDITION(b1, "bad bond");
   PRECONDITION(b2, "bad bond");
   bool res;
+
+  auto isConjugatedSingleOrDoubleBond([](const Bond *bond) {
+    return bond->getIsConjugated() && (bond->getBondType() == Bond::SINGLE ||
+                                       bond->getBondType() == Bond::DOUBLE);
+  });
+
   if (ps.useQueryQueryMatches && b1->hasQuery() && b2->hasQuery()) {
     res = static_cast<const QueryBond *>(b1)->QueryMatch(
         static_cast<const QueryBond *>(b2));
@@ -175,8 +181,10 @@ bool bondCompat(const Bond *b1, const Bond *b2,
              !b2->hasQuery() &&
              ((b1->getBondType() == Bond::AROMATIC &&
                b2->getBondType() == Bond::AROMATIC) ||
-              (b1->getBondType() == Bond::AROMATIC && b2->getIsConjugated()) ||
-              (b2->getBondType() == Bond::AROMATIC && b1->getIsConjugated()))) {
+              (b1->getBondType() == Bond::AROMATIC &&
+               isConjugatedSingleOrDoubleBond(b2)) ||
+              (b2->getBondType() == Bond::AROMATIC &&
+               isConjugatedSingleOrDoubleBond(b1)))) {
     res = true;
   } else {
     res = b1->Match(b2);

diff --git a/Code/GraphMol/Substruct/catch_tests.cpp b/Code/GraphMol/Substruct/catch_tests.cpp
index ce4a070ae73..0c007677a52 100644
--- a/Code/GraphMol/Substruct/catch_tests.cpp
+++ b/Code/GraphMol/Substruct/catch_tests.cpp
@@ -19,6 +19,7 @@
 #include <GraphMol/SmilesParse/SmilesParse.h>
 #include <GraphMol/SmilesParse/SmilesWrite.h>
 #include <GraphMol/Substruct/SubstructMatch.h>
+#include <GraphMol/FileParsers/FileParsers.h>
 #include <GraphMol/QueryOps.h>
 #include <GraphMol/MolPickler.h>

@@ -777,11 +778,66 @@ TEST_CASE("specified query matches unspecified atom") {
     CHECK(SubstructMatch(*m3, *q, ps).empty());

     ps.specifiedStereoQueryMatchesUnspecified = true;
-    std::cerr << "m1" << std::endl;
     CHECK(SubstructMatch(*m1, *q, ps).size() == 1);
-    std::cerr << "m2" << std::endl;
     CHECK(SubstructMatch(*m2, *q, ps).size() == 1);
-    std::cerr << "m3" << std::endl;
     CHECK(SubstructMatch(*m3, *q, ps).empty());
   }
+}
+
+TEST_CASE(
+    "Github 8162: conjugated triple bonds match aromatic bonds with aromaticMatchesConjugated") {
+  SECTION("as reported") {
+    auto qry = R"CTAB(ACS Document 1996
+  ChemDraw01092510212D
+
+  0  0  0     0  0              0 V3000
+M  V30 BEGIN CTAB
+M  V30 COUNTS 4 3 0 0 0
+M  V30 BEGIN ATOM
+M  V30 1 N 0.357236 0.412500 0.000000 0
+M  V30 2 C 1.071707 0.000000 0.000000 0
+M  V30 3 C -0.357236 0.000000 0.000000 0
+M  V30 4 N -1.071707 -0.412500 0.000000 0
+M  V30 END ATOM
+M  V30 BEGIN BOND
+M  V30 1 1 1 2
+M  V30 2 1 1 3
+M  V30 3 3 3 4
+M  V30 END BOND
+M  V30 END CTAB
+M  END)CTAB"_ctab;
+    REQUIRE(qry);
+    auto mol = "CN1C=NC=C1"_smiles;
+    REQUIRE(mol);
+    SubstructMatchParameters ps;
+    ps.aromaticMatchesConjugated = true;
+    CHECK(SubstructMatch(*mol, *qry, ps).empty());
+  }
+
+  SECTION("details") {
+    auto m_triple = "C#N"_smiles;
+    REQUIRE(m_triple);
+    m_triple->getBondWithIdx(0)->setIsConjugated(true);
+    auto m_double = "C=N"_smiles;
+    REQUIRE(m_double);
+    m_double->getBondWithIdx(0)->setIsConjugated(true);
+    auto m_single = "CN"_smiles;
+    REQUIRE(m_single);
+    m_single->getBondWithIdx(0)->setIsConjugated(true);
+    auto m_aromatic = "CN"_smiles;
+    REQUIRE(m_aromatic);
+    m_aromatic->getBondWithIdx(0)->setBondType(Bond::AROMATIC);
+    m_aromatic->getBondWithIdx(0)->setIsAromatic(true);
+    m_aromatic->getBondWithIdx(0)->setIsConjugated(true);
+
+    SubstructMatchParameters ps;
+    ps.aromaticMatchesConjugated = true;
+    CHECK(SubstructMatch(*m_triple, *m_aromatic, ps).empty());
+    CHECK(SubstructMatch(*m_aromatic, *m_triple, ps).empty());
+
+    CHECK(SubstructMatch(*m_double, *m_aromatic, ps).size() == 1);
+    CHECK(SubstructMatch(*m_aromatic, *m_double, ps).size() == 1);
+    CHECK(SubstructMatch(*m_single, *m_aromatic, ps).size() == 1);
+    CHECK(SubstructMatch(*m_aromatic, *m_single, ps).size() == 1);
+  }
 }
\ No newline at end of file


improve handling of esters, amides, etc. in the v2 tautomer hash (rdkit/rdkit#8173)

The current version of the tautomer hash includes bonds in the tautomer zone that shouldn't be for amides, esters, carboxylates, and related structures. One simple example of this is acetic acid, where all of the atoms are in the tautomeric zone:
```
In [7]: rdMolHash.MolHash(Chem.MolFromSmiles('CC(=O)O'),rdMolHash.HashFunction.HetAtomTautomerv2)
Out[7]: '[C]:[C](:[O]):[O]_4_0'
```

The solution here is to mark some bonds as not being eligible for tautomerism.
The set of bonds in this PR are the first bond in each of these SMARTS:
```
      "[C;!$(C-C(=[NH])-[NH2])]-[C;!$(C(-C)(=[NH])-[NH2])](=[O,N,S])-[O,N,S]",  //< one side of the "amide", with an ugly exclusion for amidine
      "[C;!$(C=[O,N,S])]-[O,N,S]-C=[O,N,S]",  //< the other side
      "[OH0,SH0]-C=[O,N,S]",                  //< "esters" and "carboxyls"
      "[C]-[c](:[o,n,s]):[o,n,s]",  //< a limited version of handling this for
                                    //aromatic systems
```

While working on this, I made heavy use of the great "Tautomerizer" tool from the NCI in order to get a sense of what types of bonds are likely to be involved in tautomerism:
https://cactus.nci.nih.gov/tautomerizer/
(NOTE: this does not mean that anyone should expect similar results from the tools! That wasn't the goal)

This fixes #8090, along with a bunch of other weirdness; hopefully it doesn't introduce too much new weirdness.

The results are definitely different from what you'd get with the current version of the code for many molecules.

**Describe the bug**
HetAtomTautomerv2 detects tautomerism at carboxylates. I'm pretty confident that this is a side-effect of allowing keto-enol tautomerism... But I do feel that carboxylates are a special case and should be excluded.

**To Reproduce**
```
from rdkit import Chem
from rdkit.Chem import rdMolHash

smi = 'O=C(O)CCC'
mol = Chem.MolFromSmiles(smi)
print(rdMolHash.MolHash(mol, rdMolHash.HashFunction.HetAtomTautomer))
print(rdMolHash.MolHash(mol, rdMolHash.HashFunction.HetAtomTautomerv2))
```

**Expected behavior**
I'd expect the neighboring carbon to be excluded from the carboxylate tautomeric region. I think the desired tautomerhash would be `[CH3]-[CH2]-[C2]-[C](:[O]):[O]_1_0`.

**Screenshots**
<img width="625" alt="tautomerhashv2bug" src="https://github.com/user-attachments/assets/8886577b-b758-4f54-b602-d28ba520629c" />

**Configuration (please complete the following information):**
 - RDKit version: Fall 2025
 - Are you using conda? no
 - 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? Schrödinger custom install

**Additional context**
I'm using this in the RegistrationHash. This causes a collapse of chiral carboxylic acids.

diff --git a/Code/GraphMol/MolHash/CMakeLists.txt b/Code/GraphMol/MolHash/CMakeLists.txt
index b4ce6020d2a..7afb7899187 100644
--- a/Code/GraphMol/MolHash/CMakeLists.txt
+++ b/Code/GraphMol/MolHash/CMakeLists.txt
@@ -1,7 +1,7 @@

 rdkit_library(MolHash
               hashfunctions.cpp normalize.cpp
-              LINK_LIBRARIES Depictor GraphMol)
+              LINK_LIBRARIES SubstructMatch Depictor GraphMol)
 target_compile_definitions(MolHash PRIVATE RDKIT_MOLHASH_BUILD)

 rdkit_headers(MolHash.h nmmolhash.h
diff --git a/Code/GraphMol/MolHash/hashfunctions.cpp b/Code/GraphMol/MolHash/hashfunctions.cpp
index a6acf248f1c..c687eb6402f 100644
--- a/Code/GraphMol/MolHash/hashfunctions.cpp
+++ b/Code/GraphMol/MolHash/hashfunctions.cpp
@@ -21,6 +21,8 @@
 #include <GraphMol/RDKitBase.h>
 #include <GraphMol/RDKitQueries.h>
 #include <GraphMol/SmilesParse/SmilesWrite.h>
+#include <GraphMol/SmilesParse/SmilesParse.h>
+#include <GraphMol/Substruct/SubstructMatch.h>

 #include "nmmolhash.h"
 #include "mf.h"
@@ -378,14 +380,50 @@ std::string MesomerHash(RWMol *mol, bool netq, bool useCXSmiles,
   return result;
 }

+namespace details {
+
+constexpr std::uint64_t bondFlagCarboxyl =
+    1;  //*< bond involved in in carboxyl, amide, etc.
+std::vector<std::uint64_t> getBondFlags(const ROMol &mol) {
+  // FIX: oversimplified, but should work for now
+  static std::vector<std::string> patterns{
+      "[C;!$(C-C(=[NH])-[NH2])]-[C;!$(C(-C)(=[NH])-[NH2])](=[O,N,S])-[O,N,S]",  //< one side of the "amide", with an ugly exclusion for amidine
+      "[C;!$(C=[O,N,S])]-[O,N,S]-C=[O,N,S]",  //< the other side
+      "[OH0,SH0]-C=[O,N,S]",                  //< "esters" and "carboxyls"
+      "[C]-[c](:[o,n,s]):[o,n,s]",  //< a limited version of handling this for
+                                    // aromatic systems
+  };
+  static std::vector<std::unique_ptr<RDKit::RWMol>> queries;
+  if (queries.empty()) {
+    for (const auto &pattern : patterns) {
+      queries.emplace_back(SmartsToMol(pattern));
+    }
+  }
+
+  std::vector<std::uint64_t> bondFlags(mol.getNumBonds(), 0);
+  for (const auto &qry : queries) {
+    auto matches = SubstructMatch(mol, *qry);
+    for (const auto &match : matches) {
+      const auto bnd =
+          mol.getBondBetweenAtoms(match[0].second, match[1].second);
+      bondFlags[bnd->getIdx()] |= bondFlagCarboxyl;
+    }
+  }
+  return bondFlags;
+}
+
+}  // namespace details
+
 namespace {
 // candidate atoms are either unsaturated or have implicit Hs
 // NOTE that being aromatic is not sufficient. The molecule
 //  Cn1cccc1 is a good example of this:
 //    - it should not be a tautomeric system
 //    - the N is not unsaturated, but it is aromatic
-bool isCandidateAtom(const Atom *aptr) {
-  return aptr->getTotalNumHs() || queryAtomUnsaturated(aptr);
+bool isCandidateAtom(const Atom *aptr,
+                     const std::vector<std::uint64_t> &atomFlags) {
+  return !atomFlags[aptr->getIdx()] &&
+         (aptr->getTotalNumHs() || queryAtomUnsaturated(aptr));
 }

 // atomic number > 1, not carbon
@@ -403,18 +441,26 @@ bool isUnsaturatedBond(const Bond *bptr) {
 }

 // potential tautomeric bonds are unsaturated and both atoms are candidates
-bool isPossibleTautomericBond(const Bond *bptr) {
-  return isUnsaturatedBond(bptr) && isCandidateAtom(bptr->getBeginAtom()) &&
-         isCandidateAtom(bptr->getEndAtom());
+bool isPossibleTautomericBond(const Bond *bptr,
+                              const std::vector<std::uint64_t> &atomFlags,
+                              const std::vector<std::uint64_t> &bondFlags) {
+  return !bondFlags[bptr->getIdx()] && isUnsaturatedBond(bptr) &&
+         isCandidateAtom(bptr->getBeginAtom(), atomFlags) &&
+         isCandidateAtom(bptr->getEndAtom(), atomFlags);
 }

 // a bond is a possible starting bond if it involves a candidate hetereoatom
 // (definition above) and an unsaturated atom
-bool isPossibleStartingBond(const Bond *bptr) {
+bool isPossibleStartingBond(const Bond *bptr,
+                            const std::vector<std::uint64_t> &atomFlags,
+                            const std::vector<std::uint64_t> &bondFlags) {
+  if (bondFlags[bptr->getIdx()]) {
+    return false;
+  }
   auto heteroBeg = isHeteroAtom(bptr->getBeginAtom()) &&
-                   isCandidateAtom(bptr->getBeginAtom());
-  auto heteroEnd =
-      isHeteroAtom(bptr->getEndAtom()) && isCandidateAtom(bptr->getEndAtom());
+                   isCandidateAtom(bptr->getBeginAtom(), atomFlags);
+  auto heteroEnd = isHeteroAtom(bptr->getEndAtom()) &&
+                   isCandidateAtom(bptr->getEndAtom(), atomFlags);
   // at least one atom has to be an eligible heteroatom:
   if (!heteroBeg && !heteroEnd) {
     return false;
@@ -425,7 +471,8 @@ bool isPossibleStartingBond(const Bond *bptr) {
   auto unsatBeg = queryAtomUnsaturated(bptr->getBeginAtom());
   auto unsatEnd = queryAtomUnsaturated(bptr->getEndAtom());

-  // both we need a heteroatom on one side and an unsaturated atom on the other:
+  // both we need a heteroatom on one side and an unsaturated atom on the
+  // other:
   if (!((heteroBeg && unsatEnd) || (heteroEnd && unsatBeg))) {
     return false;
   }
@@ -448,11 +495,14 @@ bool hasStartBond(const Atom *aptr, const boost::dynamic_bitset<> &startBonds) {
 // doesn't have a start bond
 bool skipNeighborBond(const Atom *atom, const Atom *otherAtom,
                       const Bond *nbrBond,
-                      const boost::dynamic_bitset<> &startBonds) {
-  return (
-      (!isCandidateAtom(otherAtom) && !hasStartBond(otherAtom, startBonds)) ||
-      (!isUnsaturatedBond(nbrBond) && !nbrBond->getIsConjugated() &&
-       !hasStartBond(atom, startBonds)));
+                      const boost::dynamic_bitset<> &startBonds,
+                      const std::vector<std::uint64_t> &atomFlags,
+                      const std::vector<std::uint64_t> &bondFlags) {
+  return (bondFlags[nbrBond->getIdx()] ||
+          ((!isCandidateAtom(otherAtom, atomFlags) &&
+            !hasStartBond(otherAtom, startBonds)) ||
+           (!isUnsaturatedBond(nbrBond) && !nbrBond->getIsConjugated() &&
+            !hasStartBond(atom, startBonds))));
 }
 }  // namespace

@@ -463,12 +513,18 @@ std::string TautomerHashv2(RWMol *mol, bool proto, bool useCXSmiles,
   unsigned int hcount = 0;
   int charge = 0;

+  // we aren't current doing anything with atomFlags, but we have added them in
+  // analogy to the bondFlags as a kind of future proofing.
+  std::vector<std::uint64_t> atomFlags(mol->getNumAtoms(), 0);
+  auto bondFlags = details::getBondFlags(*mol);
+
   boost::dynamic_bitset<> bondsToModify(mol->getNumBonds());
   boost::dynamic_bitset<> bondsConsidered(mol->getNumBonds());

   boost::dynamic_bitset<> startBonds(mol->getNumBonds());
   for (const auto bnd : mol->bonds()) {
-    startBonds.set(bnd->getIdx(), isPossibleStartingBond(bnd));
+    startBonds.set(bnd->getIdx(),
+                   isPossibleStartingBond(bnd, atomFlags, bondFlags));
   }
 #ifdef VERBOSE_HASH
   std::cerr << " START BONDS: " << startBonds << std::endl;
@@ -526,8 +582,10 @@ std::string TautomerHashv2(RWMol *mol, bool proto, bool useCXSmiles,
         }

         // if both bonds are not eligible, then we can skip this neighbor
-        if (skipNeighborBond(atm, oatom, nbrBond, startBonds) &&
-            skipNeighborBond(oatom, atm, nbrBond, startBonds)) {
+        if (skipNeighborBond(atm, oatom, nbrBond, startBonds, atomFlags,
+                             bondFlags) &&
+            skipNeighborBond(oatom, atm, nbrBond, startBonds, atomFlags,
+                             bondFlags)) {
           continue;
         }

@@ -591,8 +649,10 @@ std::string TautomerHashv2(RWMol *mol, bool proto, bool useCXSmiles,
                     << hasStartBond(atm, startBonds) << std::endl;
 #endif
           if (bondsConsidered[nbrBnd->getIdx()] ||
-              (skipNeighborBond(atm, oatom, nbrBnd, startBonds) &&
-               skipNeighborBond(oatom, atm, nbrBnd, startBonds))) {
+              (skipNeighborBond(atm, oatom, nbrBnd, startBonds, atomFlags,
+                                bondFlags) &&
+               skipNeighborBond(oatom, atm, nbrBnd, startBonds, atomFlags,
+                                bondFlags))) {
             // we won't add this bond for further traversal, but if both atoms
             // are already in this system, then we should add the bond to the
             // system


Simple fix to allow aromatic CIS/TRANS SMARTS to match molecules (rdkit/rdkit#8192)

Fixes #7944

This converts smarts / and \ to match single or aromatic bonds with the appropriate bond direction.

**Describe the bug**
rxn.RunReactants mismatch directional bonds in a heterocycle ring when two aromatic atoms are connected by directional bonds.

**To Reproduce**
from rdkit import Chem
from rdkit.Chem import rdChemReactions

`rxn = rdChemReactions.ReactionFromSmarts("[O:1]=[c:2]1/[c:3](=[C:4]/[c:5]2:[c:10]:[c:9]:[c:8]:[c:7]:[c:6]:2):[s:11]:[c:12]2:[n:13]:1-[N:14]-[C:15]-[N:20]-[N:21]=2>>[S:11]=[C:12]1-[N:13]-[N:14]-[C:15]-[N:20]-[N:21]-1.[O]=[C:4]-[c:5]1:[c:10]:[c:9]:[c:8]:[c:7]:[c:6]:1.[C]-[C]-[O]-[C:2](=[O:1])-[C:3]-[Cl]")`

`reacts = [Chem.MolFromSmiles('[O:1]=[c:2]1/[c:3](=[CH:4]/[c:5]2[cH:6][cH:7][cH:8][cH:9][cH:10]2)[s:11][c:12]2[n:13]1[NH:14][C:15]1([CH2:16][CH2:17][CH2:18][CH2:19]1)[NH:20][N:21]=2')]`

`products = rxn.RunReactants(reacts)`
`len(products)`
 0

`rxn.GetReactants()[0].GetBondWithIdx(1).GetBondType()`
 rdkit.Chem.rdchem.BondType.SINGLE
`reacts[0].GetBondWithIdx(1).GetBondType()`
 rdkit.Chem.rdchem.BondType.AROMATIC

**Expected behavior**
The rxn is supposed to work for reactants and yield products. Bond 2-3 is the same directional bond by aromatic atoms ([c:2]1/[c:3]) for both rxn and reacts.

**Configuration (please complete the following information):**
 - RDKit version: 2024.03.4
 - OS: Windows
 - Python version (if relevant): 3.12.4
 - Are you using conda? No
 - 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? pip install

diff --git a/Code/GraphMol/SmilesParse/SmartsWrite.cpp b/Code/GraphMol/SmilesParse/SmartsWrite.cpp
index 77065da7a96..6446a99a103 100644
--- a/Code/GraphMol/SmilesParse/SmartsWrite.cpp
+++ b/Code/GraphMol/SmilesParse/SmartsWrite.cpp
@@ -410,7 +410,19 @@ std::string getBondSmartsSimple(const Bond *bond,
   } else if (descrip == "BondInRing") {
     res += "@";
   } else if (descrip == "SingleOrAromaticBond") {
-    // don't need to do anything here... :-)
+    auto dir = bond->getBondDir();
+    switch(dir) {
+      case Bond::ENDDOWNRIGHT: {
+        res += "\\";
+        break;
+      }
+      case Bond::ENDUPRIGHT: {
+        res += "/";
+        break;
+      }
+      default:
+        break;
+    }
   } else if (descrip == "SingleOrDoubleBond") {
     res += "-,=";
   } else if (descrip == "DoubleOrAromaticBond") {
@@ -1040,4 +1052,4 @@ std::string MolFragmentToCXSmarts(const ROMol &mol,
   return res;
 }

-}  // namespace RDKit
\ No newline at end of file
+}  // namespace RDKit
diff --git a/Code/GraphMol/SmilesParse/lex.yysmarts.cpp.cmake b/Code/GraphMol/SmilesParse/lex.yysmarts.cpp.cmake
index bec2ec0a604..c6ff0e64ef6 100644
--- a/Code/GraphMol/SmilesParse/lex.yysmarts.cpp.cmake
+++ b/Code/GraphMol/SmilesParse/lex.yysmarts.cpp.cmake
@@ -1733,6 +1733,7 @@ YY_RULE_SETUP
 #line 362 "smarts.ll"
 { yylval->bond = new QueryBond(Bond::SINGLE);
 	yylval->bond->setBondDir(Bond::ENDDOWNRIGHT);
+	yylval->bond->setQuery(makeSingleOrAromaticBondQuery());
 	return BOND_TOKEN;  }
 	YY_BREAK
 case 153:
@@ -1740,6 +1741,7 @@ YY_RULE_SETUP
 #line 366 "smarts.ll"
 { yylval->bond = new QueryBond(Bond::SINGLE);
 	yylval->bond->setBondDir(Bond::ENDUPRIGHT);
+	yylval->bond->setQuery(makeSingleOrAromaticBondQuery());
 	return BOND_TOKEN;  }
 	YY_BREAK
 case 154:
diff --git a/Code/GraphMol/SmilesParse/smarts.ll b/Code/GraphMol/SmilesParse/smarts.ll
index 5de37432b14..874a5a8a43b 100644
--- a/Code/GraphMol/SmilesParse/smarts.ll
+++ b/Code/GraphMol/SmilesParse/smarts.ll
@@ -360,10 +360,12 @@ A			{

 [\\]{1,2}    { yylval->bond = new QueryBond(Bond::SINGLE);
 	yylval->bond->setBondDir(Bond::ENDDOWNRIGHT);
+	yylval->bond->setQuery(makeSingleOrAromaticBondQuery());
 	return BOND_TOKEN;  }

 [\/]    { yylval->bond = new QueryBond(Bond::SINGLE);
 	yylval->bond->setBondDir(Bond::ENDUPRIGHT);
+	yylval->bond->setQuery(makeSingleOrAromaticBondQuery());
 	return BOND_TOKEN;  }

 \-\> {

diff --git a/Code/GraphMol/SmilesParse/smatest.cpp b/Code/GraphMol/SmilesParse/smatest.cpp
index 379e77978e7..54c4098869f 100644
--- a/Code/GraphMol/SmilesParse/smatest.cpp
+++ b/Code/GraphMol/SmilesParse/smatest.cpp
@@ -1167,6 +1167,16 @@ void testSmartsStereochem() {
   _checkMatches("C/C=C/C", "C/C=C/C", 1, 4);
   _checkMatches("C/C=C/C", "C\\C=C\\C", 1, 4);
   _checkMatches("C/C=C/C", "C/C=C\\C", 1, 4);
+
+  // directional bonds are set to be a direction \ /
+  //  and a query - SingleOrAromatic, make sure that this
+  //  is their current representation
+  auto m1 = "C/C=C\\C"_smarts;
+  TEST_ASSERT(m1->getBondWithIdx(0)->hasQuery());
+  TEST_ASSERT(m1->getBondWithIdx(0)->getQuery()->getDescription() == "SingleOrAromaticBond");
+
+  auto m2 = "O=c1/c(=C/c2ccccc2)sc2n1-N-C-N-N=2"_smarts;
+  TEST_ASSERT(MolToSmarts(*m2) == "O=c1/c(=C/c2ccccc2)sc2n1-N-C-N-N=2");

   BOOST_LOG(rdInfoLog) << "\tdone" << std::endl;
 }
diff --git a/Code/GraphMol/Substruct/catch_tests.cpp b/Code/GraphMol/Substruct/catch_tests.cpp
index 0c007677a52..cbf014fded4 100644
--- a/Code/GraphMol/Substruct/catch_tests.cpp
+++ b/Code/GraphMol/Substruct/catch_tests.cpp
@@ -840,4 +840,18 @@ M  END)CTAB"_ctab;
     CHECK(SubstructMatch(*m_single, *m_aromatic, ps).size() == 1);
     CHECK(SubstructMatch(*m_aromatic, *m_single, ps).size() == 1);
   }
-}
\ No newline at end of file
+}
+
+TEST_CASE("Github #7295", "CIS/TRANS in aromatic ring") {
+    SECTION("Smart CIS/TRANS bonds should match single or aromatic"){
+      SubstructMatchParameters ps;
+
+      auto query = "[O:1]=[c:2]1/[c:3](=[C:4]/[c:5]2:[c:10]:[c:9]:[c:8]:[c:7]:[c:6]:2):[s:11]:[c:12]2:[n:13]:1-[N:14]-[C:15]-[N:20]-[N:21]=2"_smarts;
+      auto mol = "[O:1]=[c:2]1/[c:3](=[CH:4]/[c:5]2[cH:6][cH:7][cH:8][cH:9][cH:10]2)[s:11][c:12]2[n:13]1[NH:14][C:15]1([CH2:16][CH2:17][CH2:18][CH2:19]1)[NH:20][N:21]=2"_smiles;
+      CHECK(SubstructMatch(*mol, *query, ps).size() == 1);
+      ps.useChirality = true;
+      CHECK(SubstructMatch(*mol, *query, ps).size() == 1);
+      auto mol2 = "[O:1]=[c:2]1\\[c:3](=[CH:4]/[c:5]2[cH:6][cH:7][cH:8][cH:9][cH:10]2)[s:11][c:12]2[n:13]1[NH:14][C:15]1([CH2:16][CH2:17][CH2:18][CH2:19]1)[NH:20][N:21]=2"_smiles;
+      CHECK(SubstructMatch(*mol2, *query, ps).size() == 0);
+    }
+}


Fixes #7983 (rdkit/rdkit#8342)

This is an alternate (and hopefully correct) approach to the fix earlier proposed in #8242

We fix this by moving the call to `cleanupAtropisomerStereoGroups()` into `assignStereochemistry()`.

This also adds some more tests.

More explanation coming.

Stereogroup lost when importing chiral sulfoxide from v3000 molfile

Hello,

We came across the following issue.

When I import a compound containing a chiral sulfoxide with the corresponding stereogroup, the behavior is different when I import from cxsmiles vs when I import from v3000 molfile.

```
from rdkit import Chem
smilesParserParams = Chem.SmilesParserParams()
smilesParserParams.allowCXSMILES = True

source_smi = 'Cc1ccc(NC(=O)OCc2ccccc2[S@](C)=O)cc1 |a:16|'
mol: Chem.Mol = Chem.MolFromSmiles(source_smi, smilesParserParams)
mol.Debug()

```
Result:
...
	16 16 S chg: 0  deg: 3 exp: 4 imp: 0 hyb: SP3 chi: CCW nbrs:[15 17 18]
Stereo Groups:
	0 ABS rId: 0 wId: 0 atoms: { 16 }

```
m3k = '''
  Mrv2317 02032512242D

  0  0  0     0  0            999 V3000
M  V30 BEGIN CTAB
M  V30 COUNTS 21 22 0 0 0
M  V30 BEGIN ATOM
M  V30 1 C -4.001 -2.31 0 0
M  V30 2 C -2.6674 -3.08 0 0
M  V30 3 C -1.3337 -2.31 0 0
M  V30 4 C 0 -3.08 0 0
M  V30 5 C 0 -4.6198 0 0
M  V30 6 N 1.3337 -5.3898 0 0
M  V30 7 C 2.6674 -4.6198 0 0
M  V30 8 O 2.6674 -3.08 0 0
M  V30 9 O 4.001 -5.3898 0 0
M  V30 10 C 5.3345 -4.6198 0 0
M  V30 11 C 6.6682 -5.3898 0 0
M  V30 12 C 8.0019 -4.6198 0 0
M  V30 13 C 9.3356 -5.3898 0 0
M  V30 14 C 9.3356 -6.9298 0 0
M  V30 15 C 8.0019 -7.6998 0 0
M  V30 16 C 6.6682 -6.9298 0 0
M  V30 17 S 5.3345 -7.6998 0 0 CFG=2
M  V30 18 C 4.001 -6.9298 0 0
M  V30 19 O 5.3345 -9.2398 0 0
M  V30 20 C -1.3337 -5.3898 0 0
M  V30 21 C -2.6674 -4.6198 0 0
M  V30 END ATOM
M  V30 BEGIN BOND
M  V30 1 1 1 2
M  V30 2 4 2 3
M  V30 3 4 3 4
M  V30 4 4 4 5
M  V30 5 1 5 6
M  V30 6 1 6 7
M  V30 7 2 7 8
M  V30 8 1 7 9
M  V30 9 1 9 10
M  V30 10 1 10 11
M  V30 11 4 11 12
M  V30 12 4 12 13
M  V30 13 4 13 14
M  V30 14 4 14 15
M  V30 15 4 15 16
M  V30 16 4 11 16
M  V30 17 1 17 16
M  V30 18 2 17 19
M  V30 19 4 5 20
M  V30 20 4 20 21
M  V30 21 4 2 21
M  V30 22 1 17 18 CFG=1
M  V30 END BOND
M  V30 BEGIN COLLECTION
M  V30 MDLV30/STEABS ATOMS=(1 17)
M  V30 END COLLECTION
M  V30 END CTAB
M  END
'''
m3 = Chem.MolFromMolBlock(m3k)
m3.Debug()
```
Result:
	16 16 S chg: 0  deg: 3 exp: 4 imp: 0 hyb: SP3 chi: CW nbrs:[15 18 17]

But no stereogroup included.

This seems specific to sulfoxides.

The issue can be reproduced in:
RDKit:2024.03.5
RDKit:2024.09.2

**Configuration (please complete the following information):**

 - OS: windows 11
 - Python version (if relevant): 3.11
 - Are you using conda? Yes
 - If you are using conda, which channel did you install the rdkit from? Conda-forge
 - If you are not using conda: how did you install the RDKit?

diff --git a/Code/GraphMol/Atropisomers.cpp b/Code/GraphMol/Atropisomers.cpp
index 0b23d7f1ed6..61ef68129a5 100644
--- a/Code/GraphMol/Atropisomers.cpp
+++ b/Code/GraphMol/Atropisomers.cpp
@@ -283,7 +283,7 @@ std::pair<bool, Bond::BondDir> getBondDir(
   return {true, Bond::BondDir::NONE};
 }

-bool DetectAtropisomerChiralityOneBond(Bond *bond, ROMol &mol,
+void DetectAtropisomerChiralityOneBond(Bond *bond, ROMol &mol,
                                        const Conformer *conf) {
   // the approach is this:
   // we will view the system along the line from the potential atropisomer
@@ -307,7 +307,7 @@ bool DetectAtropisomerChiralityOneBond(Bond *bond, ROMol &mol,
   // one vector for each end - each one - should end up with 1 or 2 entries
   AtropAtomAndBondVec atomAndBondVecs[2];
   if (!getAtropisomerAtomsAndBonds(bond, atomAndBondVecs, mol)) {
-    return false;  // not an atropisomer
+    return;  // not an atropisomer
   }

   // make sure we do not have wiggle bonds
@@ -315,7 +315,7 @@ bool DetectAtropisomerChiralityOneBond(Bond *bond, ROMol &mol,
   for (auto atomAndBondVec : atomAndBondVecs) {
     for (auto endBond : atomAndBondVec.second) {
       if (endBond->getBondDir() == Bond::UNKNOWN) {
-        return false;  // not an atropisomer
+        return;  // not an atropisomer
       }
     }
   }
@@ -341,19 +341,19 @@ bool DetectAtropisomerChiralityOneBond(Bond *bond, ROMol &mol,
     std::pair<bool, Bond::BondDir> bond1DirResult;
     bond1DirResult = getBondDir(bond, atomAndBondVecs[0]);
     if (!bond1DirResult.first) {
-      return false;
+      return;
     }
     std::pair<bool, Bond::BondDir> bond2DirResult;
     bond2DirResult = getBondDir(bond, atomAndBondVecs[1]);
     if (!bond2DirResult.first) {
-      return false;
+      return;
     }
     if (bond1DirResult.second == bond2DirResult.second) {
       BOOST_LOG(rdWarningLog)
           << "inconsistent bond wedging for an atropisomer.  Atoms are: "
           << bond->getBeginAtomIdx() << " " << bond->getEndAtomIdx()
           << std::endl;
-      return false;
+      return;
     }

     if (bond1DirResult.second == Bond::BEGINWEDGE ||
@@ -364,7 +364,7 @@ bool DetectAtropisomerChiralityOneBond(Bond *bond, ROMol &mol,
       bond->setStereo(Bond::BondStereo::STEREOATROPCW);
     }

-    return true;
+    return;
   }

   // create a frame of reference that has its X-axis along the atrop bond
@@ -375,7 +375,7 @@ bool DetectAtropisomerChiralityOneBond(Bond *bond, ROMol &mol,
     BOOST_LOG(rdWarningLog)
         << "Failed to get a frame of reference along an atropisomer bond - atoms are: "
         << bond->getBeginAtomIdx() << " " << bond->getEndAtomIdx() << std::endl;
-    return false;
+    return;
   }
   RDGeom::Point3D bondVecs[2];  // one bond vector from each end of the
                                 // potential atropisomer bond
@@ -395,12 +395,12 @@ bool DetectAtropisomerChiralityOneBond(Bond *bond, ROMol &mol,

       bondDirResult = getBondDir(bond, atomAndBondVecs[bondAtomIndex]);
       if (!bondDirResult.first) {
-        return false;
+        return;
       }

       if (!getAtropIsomerEndVect(atomAndBondVecs[bondAtomIndex], yAxis, zAxis,
                                  conf, bondVecs[bondAtomIndex])) {
-        return false;
+        return;
       }

       if (bondDirResult.second == Bond::BEGINWEDGE) {
@@ -453,7 +453,7 @@ bool DetectAtropisomerChiralityOneBond(Bond *bond, ROMol &mol,
               << "Both bonds on one end of an atropisomer are on the same side - atoms are: "
               << bond->getBeginAtomIdx() << " " << bond->getEndAtomIdx()
               << std::endl;
-          return false;
+          return;
         }
       }

@@ -462,7 +462,7 @@ bool DetectAtropisomerChiralityOneBond(Bond *bond, ROMol &mol,
             << "Failed to find a bond on one end of an atropisomer that is NOT co-linear - atoms are: "
             << bond->getBeginAtomIdx() << " " << bond->getEndAtomIdx()
             << std::endl;
-        return false;
+        return;
       }
     }
   }
@@ -477,10 +477,8 @@ bool DetectAtropisomerChiralityOneBond(Bond *bond, ROMol &mol,
     BOOST_LOG(rdWarningLog)
         << "The 2 defining bonds for an atropisomer are co-planar - atoms are: "
         << bond->getBeginAtomIdx() << " " << bond->getEndAtomIdx() << std::endl;
-    return false;
+    return;
   }
-
-  return true;
 }

 void cleanupAtropisomerStereoGroups(ROMol &mol) {
@@ -537,7 +535,6 @@ void detectAtropisomerChirality(ROMol &mol, const Conformer *conf) {
     }
   }

-  bool foundAtrop = false;
   for (auto bondToTry : bondsToTry) {
     if (bondToTry->getBeginAtom()->needsUpdatePropertyCache()) {
       bondToTry->getBeginAtom()->updatePropertyCache(false);
@@ -554,13 +551,7 @@ void detectAtropisomerChirality(ROMol &mol, const Conformer *conf) {
       continue;
     }

-    if (DetectAtropisomerChiralityOneBond(bondToTry, mol, conf)) {
-      foundAtrop = true;
-    }
-  }
-
-  if (foundAtrop) {
-    cleanupAtropisomerStereoGroups(mol);
+    DetectAtropisomerChiralityOneBond(bondToTry, mol, conf);
   }
 }
 void getAllAtomIdsForStereoGroup(
diff --git a/Code/GraphMol/Atropisomers.h b/Code/GraphMol/Atropisomers.h
index 86c8d5442dd..2d61c4ad9f8 100644
--- a/Code/GraphMol/Atropisomers.h
+++ b/Code/GraphMol/Atropisomers.h
@@ -1,5 +1,5 @@
 //
-//  Copyright (C) 2004-203 Tad hurst/CDD and other RDKit contributors
+//  Copyright (C) 2004-2025 Tad Hurst/CDD and other RDKit contributors
 //
 //   @@ All Rights Reserved @@
 //  This file is part of the RDKit.
@@ -37,6 +37,8 @@ RDKIT_GRAPHMOL_EXPORT void getAllAtomIdsForStereoGroup(
     std::vector<unsigned int> &atomIds,
     const std::map<int, std::unique_ptr<RDKit::Chirality::WedgeInfoBase>>
         &wedgeBonds);
+
+RDKIT_GRAPHMOL_EXPORT void cleanupAtropisomerStereoGroups(ROMol &mol);
 }  // namespace Atropisomers
 }  // namespace RDKit
 #endif
diff --git a/Code/GraphMol/Chirality.cpp b/Code/GraphMol/Chirality.cpp
index c32f551caaa..091ab815383 100644
--- a/Code/GraphMol/Chirality.cpp
+++ b/Code/GraphMol/Chirality.cpp
@@ -2402,6 +2402,7 @@ void legacyStereoPerception(ROMol &mol, bool cleanIt,
         }
       }
     }
+    bool foundAtropisomer = false;
     for (auto bond : mol.bonds()) {
       // wedged bonds to atoms that have no stereochem
       // should be removed. (github issue 87)
@@ -2411,15 +2412,16 @@ void legacyStereoPerception(ROMol &mol, bool cleanIt,
           bond->getEndAtom()->getChiralTag() == Atom::CHI_UNSPECIFIED) {
         // see if there is an atropisomer bond connected to this bond

-        bool hasAtropisomer = false;
+        bool atomHasAtropisomer = false;
         for (auto nbond : mol.atomBonds(bond->getBeginAtom())) {
           if (nbond->getStereo() == Bond::STEREOATROPCCW ||
               nbond->getStereo() == Bond::STEREOATROPCW) {
-            hasAtropisomer = true;
+            atomHasAtropisomer = true;
+            foundAtropisomer = true;
             break;
           }
         }
-        if (!hasAtropisomer) {
+        if (!atomHasAtropisomer) {
           bond->setBondDir(Bond::NONE);
         }
       }
@@ -2459,6 +2461,9 @@ void legacyStereoPerception(ROMol &mol, bool cleanIt,
         }
       }
     }
+    if (foundAtropisomer || Atropisomers::doesMolHaveAtropisomers(mol)) {
+      Atropisomers::cleanupAtropisomerStereoGroups(mol);
+    }
     Chirality::cleanupStereoGroups(mol);
   }
 }
@@ -2544,6 +2549,7 @@ void stereoPerception(ROMol &mol, bool cleanIt,
   // populate double bond stereo info:
   updateDoubleBondStereo(mol, sinfo, cleanIt);
   if (cleanIt) {
+    Atropisomers::cleanupAtropisomerStereoGroups(mol);
     Chirality::cleanupStereoGroups(mol);
   }
 }
@@ -2914,9 +2920,9 @@ void findPotentialStereoBonds(ROMol &mol, bool cleanIt) {
             if (begAtomNeighbors.size() > 0 && endAtomNeighbors.size() > 0) {
               if ((begAtomNeighbors.size() == 2) &&
                   (endAtomNeighbors.size() == 2)) {
-// if both of the atoms have 2 neighbors (other than the one
-// connected
-// by the double bond) and ....
+                // if both of the atoms have 2 neighbors (other than the one
+                // connected
+                // by the double bond) and ....
                 if ((ranks[begAtomNeighbors[0]] !=
                      ranks[begAtomNeighbors[1]]) &&
                     (ranks[endAtomNeighbors[0]] !=
diff --git a/Code/GraphMol/FileParsers/CMakeLists.txt b/Code/GraphMol/FileParsers/CMakeLists.txt
index 3258e3bc304..6a809d8e118 100644
--- a/Code/GraphMol/FileParsers/CMakeLists.txt
+++ b/Code/GraphMol/FileParsers/CMakeLists.txt
@@ -96,6 +96,11 @@ rdkit_catch_test(v2MolSuppliers v2_suppliers_catch.cpp
 rdkit_catch_test(v2FileParsersCatchTest v2_file_parsers_catch.cpp
     LINK_LIBRARIES FileParsers)

+rdkit_catch_test(atropisomersCatch atropisomers_catch.cpp
+    LINK_LIBRARIES FileParsers)
+
+
+
 if(RDK_TEST_MULTITHREADED AND RDK_BUILD_THREADSAFE_SSS)
 rdkit_catch_test(multithreadedSupplierCatchTest multithreaded_supplier_catch.cpp
     LINK_LIBRARIES FileParsers)
diff --git a/Code/GraphMol/FileParsers/MolFileParser.cpp b/Code/GraphMol/FileParsers/MolFileParser.cpp
index 39355176278..fb741d70b89 100644
--- a/Code/GraphMol/FileParsers/MolFileParser.cpp
+++ b/Code/GraphMol/FileParsers/MolFileParser.cpp
@@ -3351,7 +3351,9 @@ void finishMolProcessing(
   const Conformer &conf = res->getConformer();
   if (chiralityPossible || conf.is3D()) {
     if (!conf.is3D()) {
-      DetectAtomStereoChemistry(*res, &conf);
+      bool replaceExistingTags = true;
+      MolOps::assignChiralTypesFromBondDirs(*res, conf.getId(),
+                                            replaceExistingTags);
     } else {
       res->updatePropertyCache(false);
       MolOps::assignChiralTypesFrom3D(*res, conf.getId(), true);
diff --git a/Code/GraphMol/FileParsers/atropisomers_catch.cpp b/Code/GraphMol/FileParsers/atropisomers_catch.cpp
new file mode 100644
index 00000000000..03dfe14cb18
--- /dev/null
+++ b/Code/GraphMol/FileParsers/atropisomers_catch.cpp
@@ -0,0 +1,129 @@
+//
+//  Copyright (C) 2025 Greg Landrum and other RDKit contributors
+//   @@ All Rights Reserved @@
+//  This file is part of the RDKit.
+//  The contents are covered by the terms of the BSD license
+//  which is included in the file license.txt, found at the root
+//  of the RDKit source tree.
+//
+
+#include <string>
+
+#include "RDGeneral/test.h"
+#include <catch2/catch_all.hpp>
+#include <GraphMol/RDKitBase.h>
+#include <GraphMol/Chirality.h>
+#include <GraphMol/FileParsers/FileParsers.h>
+#include <GraphMol/FileParsers/MolSupplier.h>
+#include <GraphMol/SmilesParse/SmilesWrite.h>
+#include <GraphMol/test_fixtures.h>
+
+using namespace RDKit;
+
+TEST_CASE("explicit tests") {
+  // making some of the other atropisomer tests explicitly test what we expect
+  std::string rdbase = getenv("RDBASE");
+  REQUIRE(rdbase != "");
+  SECTION("basics") {
+    std::string file =
+        rdbase +
+        "/Code/GraphMol/FileParsers/test_data/atropisomers/AtropManyChirals.sdf";
+    auto mol = v2::FileParsers::MolFromMolFile(file);
+    REQUIRE(mol);
+    CHECK(mol->getBondWithIdx(7)->getStereo() ==
+          Bond::BondStereo::STEREOATROPCCW);
+  }
+  SECTION("macrocycle") {
+    std::string file =
+        rdbase +
+        "/Code/GraphMol/FileParsers/test_data/atropisomers/macrocycle-5-meta-Cl-ortho-hash.sdf";
+    auto mol = v2::FileParsers::MolFromMolFile(file);
+    REQUIRE(mol);
+    CHECK(mol->getBondWithIdx(15)->getStereo() ==
+          Bond::BondStereo::STEREOATROPCW);
+  }
+  SECTION("with stereo groups and other chiral centers") {
+    REQUIRE(rdbase != "");
+    std::string file =
+        rdbase +
+        "/Code/GraphMol/FileParsers/test_data/atropisomers/AtropManyChiralsEnhanced.sdf";
+    auto mol = v2::FileParsers::MolFromMolFile(file);
+    REQUIRE(mol);
+    CHECK(mol->getBondWithIdx(7)->getStereo() ==
+          Bond::BondStereo::STEREOATROPCCW);
+    CHECK(mol->getAtomWithIdx(6)->getChiralTag() ==
+          Atom::ChiralType::CHI_UNSPECIFIED);
+    CHECK(mol->getAtomWithIdx(9)->getChiralTag() !=
+          Atom::ChiralType::CHI_UNSPECIFIED);
+    CHECK(mol->getAtomWithIdx(13)->getChiralTag() !=
+          Atom::ChiralType::CHI_UNSPECIFIED);
+    CHECK(mol->getAtomWithIdx(16)->getChiralTag() !=
+          Atom::ChiralType::CHI_UNSPECIFIED);
+    CHECK(mol->getAtomWithIdx(10)->getChiralTag() !=
+          Atom::ChiralType::CHI_UNSPECIFIED);
+
+    REQUIRE(mol->getStereoGroups().size() == 2);
+    {
+      const auto &sg = mol->getStereoGroups()[0];
+      CHECK(sg.getAtoms().size() == 2);
+      CHECK(sg.getBonds().size() == 1);
+    }
+    {
+      const auto &sg = mol->getStereoGroups()[1];
+      CHECK(sg.getAtoms().size() == 2);
+      CHECK(sg.getBonds().size() == 0);
+    }
+  }
+}
+
+TEST_CASE("atropisomerism and dependent chirality") {
+  SECTION("chirality allows stereoisomers") {
+    {
+      auto m = "C=C(C)C1=CC([C@H](C)O)=CC([C@@H](C)O)=C1 |wD:3.14|"_smiles;
+      REQUIRE(m);
+      CHECK(m->getBondWithIdx(2)->getStereo() ==
+            Bond::BondStereo::STEREOATROPCW);
+    }
+#if 0
+    // FIX: this test does not work, this is Github #8340
+    {
+      // the two stereocenters are identical, we should not have atropisomerism
+      auto m = "C=C(C)C1=CC([C@H](C)O)=CC([C@H](C)O)=C1 |wD:3.14|"_smiles;
+      REQUIRE(m);
+      CHECK(m->getBondWithIdx(2)->getStereo() == Bond::BondStereo::STEREONONE);
+    }
+#endif
+  }
+
+  SECTION("atropisomers allow chirality") {
+#if 0
+    // FIX: this test does not work
+    {
+      // here the atropisomeric bonds have opposite stereochemistry, so the
+      // central atom should be chiral, this is Github #8341
+      auto m =
+          "C=C(C1C=C(C)C=CC=1)[C@H](C)C(=C)C1C=C(C)C=CC=1 |wD:2.2,wD:13.14|"_smiles;
+      REQUIRE(m);
+      CHECK(m->getBondWithIdx(1)->getStereo() ==
+            Bond::BondStereo::STEREOATROPCCW);
+      CHECK(m->getBondWithIdx(12)->getStereo() ==
+            Bond::BondStereo::STEREOATROPCCW);
+      CHECK(m->getAtomWithIdx(9)->getChiralTag() !=
+            Atom::ChiralType::CHI_UNSPECIFIED);
+    }
+#endif
+    {
+      // here the atropisomeric bonds have the same stereochemistry, so the
+      // central atom should not be chiral
+      auto m =
+          "C=C(C1C=C(C)C=CC=1)[C@H](C)C(=C)C1C=C(C)C=CC=1 |wD:2.2,wU:13.14|"_smiles;
+      REQUIRE(m);
+      CHECK(m->getBondWithIdx(1)->getStereo() ==
+            Bond::BondStereo::STEREOATROPCCW);
+      CHECK(m->getBondWithIdx(12)->getStereo() ==
+            Bond::BondStereo::STEREOATROPCW);
+      CHECK(m->getAtomWithIdx(9)->getChiralTag() ==
+            Atom::ChiralType::CHI_UNSPECIFIED);
+    }
+  }
+}
diff --git a/Code/GraphMol/MolOps.h b/Code/GraphMol/MolOps.h
index c6252488494..60984810f8c 100644
--- a/Code/GraphMol/MolOps.h
+++ b/Code/GraphMol/MolOps.h
@@ -1085,8 +1085,7 @@ RDKIT_GRAPHMOL_EXPORT void assignChiralTypesFrom3D(
 RDKIT_GRAPHMOL_EXPORT void assignStereochemistryFrom3D(
     ROMol &mol, int confId = -1, bool replaceExistingTags = true);

-//! \brief Use bond directions to assign ChiralTypes to a molecule's atoms and
-//! stereo flags to its bonds
+//! \brief Use bond directions to assign ChiralTypes to a molecule's atoms
 /*!

   \param mol                  the molecule of interest
diff --git a/Code/GraphMol/catch_chirality.cpp b/Code/GraphMol/catch_chirality.cpp
index 3abd59ecb0d..0dc5eb3f597 100644
--- a/Code/GraphMol/catch_chirality.cpp
+++ b/Code/GraphMol/catch_chirality.cpp
@@ -5999,4 +5999,132 @@ TEST_CASE(
     CHECK(m->getAtomWithIdx(0)->hasProp(common_properties::_CIPCode) == false);
     CHECK(m->getBondWithIdx(0)->hasProp(common_properties::_CIPCode) == false);
   }
+}
+
+TEST_CASE("Github issue #7983: stereogroup lost on chiral sulfoxide") {
+  SECTION("basics") {
+    auto m = R"CTAB(
+  Mrv2317 02032512242D
+
+  0  0  0     0  0            999 V3000
+M  V30 BEGIN CTAB
+M  V30 COUNTS 21 22 0 0 0
+M  V30 BEGIN ATOM
+M  V30 1 C -4.001 -2.31 0 0
+M  V30 2 C -2.6674 -3.08 0 0
+M  V30 3 C -1.3337 -2.31 0 0
+M  V30 4 C 0 -3.08 0 0
+M  V30 5 C 0 -4.6198 0 0
+M  V30 6 N 1.3337 -5.3898 0 0
+M  V30 7 C 2.6674 -4.6198 0 0
+M  V30 8 O 2.6674 -3.08 0 0
+M  V30 9 O 4.001 -5.3898 0 0
+M  V30 10 C 5.3345 -4.6198 0 0
+M  V30 11 C 6.6682 -5.3898 0 0
+M  V30 12 C 8.0019 -4.6198 0 0
+M  V30 13 C 9.3356 -5.3898 0 0
+M  V30 14 C 9.3356 -6.9298 0 0
+M  V30 15 C 8.0019 -7.6998 0 0
+M  V30 16 C 6.6682 -6.9298 0 0
+M  V30 17 S 5.3345 -7.6998 0 0 CFG=2
+M  V30 18 C 4.001 -6.9298 0 0
+M  V30 19 O 5.3345 -9.2398 0 0
+M  V30 20 C -1.3337 -5.3898 0 0
+M  V30 21 C -2.6674 -4.6198 0 0
+M  V30 END ATOM
+M  V30 BEGIN BOND
+M  V30 1 1 1 2
+M  V30 2 4 2 3
+M  V30 3 4 3 4
+M  V30 4 4 4 5
+M  V30 5 1 5 6
+M  V30 6 1 6 7
+M  V30 7 2 7 8
+M  V30 8 1 7 9
+M  V30 9 1 9 10
+M  V30 10 1 10 11
+M  V30 11 4 11 12
+M  V30 12 4 12 13
+M  V30 13 4 13 14
+M  V30 14 4 14 15
+M  V30 15 4 15 16
+M  V30 16 4 11 16
+M  V30 17 1 17 16
+M  V30 18 2 17 19
+M  V30 19 4 5 20
+M  V30 20 4 20 21
+M  V30 21 4 2 21
+M  V30 22 1 17 18 CFG=1
+M  V30 END BOND
+M  V30 BEGIN COLLECTION
+M  V30 MDLV30/STEABS ATOMS=(1 17)
+M  V30 END COLLECTION
+M  V30 END CTAB
+M  END)CTAB"_ctab;
+    REQUIRE(m);
+    auto &sgs = m->getStereoGroups();
+    REQUIRE(sgs.size() == 1);
+    CHECK(sgs[0].getGroupType() == StereoGroupType::STEREO_ABSOLUTE);
+    CHECK(sgs[0].getAtoms().size() == 1);
+    CHECK(sgs[0].getAtoms().at(0)->getIdx() == 16);
+  }
+  SECTION("making sure sulfoxides do not trigger atropisomerism") {
+    auto m = R"CTAB(
+     RDKit          2D
+
+  0  0  0  0  0  0  0  0  0  0999 V3000
+M  V30 BEGIN CTAB
+M  V30 COUNTS 10 10 0 0 0
+M  V30 BEGIN ATOM
+M  V30 1 C 1.892852 4.651432 0.000000 0
+M  V30 2 C 3.199931 3.915535 0.000000 0
+M  V30 3 C 3.216171 2.415622 0.000000 0
+M  V30 4 C 1.925330 1.651602 0.000000 0
+M  V30 5 C 0.618251 2.387492 0.000000 0
+M  V30 6 C 0.602011 3.887408 0.000000 0
+M  V30 7 S -0.705072 4.623298 0.000000 0
+M  V30 8 C -0.672591 1.623472 0.000000 0
+M  V30 9 O -0.721314 6.123210 0.000000 0
+M  V30 10 C -1.995913 3.859276 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 CFG=1
+M  V30 7 1 6 7
+M  V30 8 1 5 8
+M  V30 9 2 7 9
+M  V30 10 1 7 10 CFG=1
+M  V30 END BOND
+M  V30 BEGIN COLLECTION
+M  V30 MDLV30/STEABS ATOMS=(1 7)
+M  V30 END COLLECTION
+M  V30 END CTAB
+M  END
+$$$$
+)CTAB"_ctab;
+    REQUIRE(m);
+    CHECK(m->getAtomWithIdx(6)->getChiralTag() !=
+          Atom::ChiralType::CHI_UNSPECIFIED);
+    CHECK(m->getBondBetweenAtoms(5, 6)->getStereo() ==
+          Bond::BondStereo::STEREONONE);
+  }
+  SECTION("examples from #8323") {
+    std::string pathName = getenv("RDBASE");
+    pathName += "/Code/GraphMol/test_data/Github8323.sdf";
+    SDMolSupplier suppl(pathName);
+    while (!suppl.atEnd()) {
+      auto mol = suppl.next();
+      REQUIRE(mol);
+      auto &sgs = mol->getStereoGroups();
+      REQUIRE(sgs.size() == 1);
+      REQUIRE(mol->hasProp("StereoGroupOnAtom"));
+      auto aid = std::stoul(mol->getProp<std::string>("StereoGroupOnAtom"));
+      CHECK(sgs[0].getAtoms().size() == 1);
+      CHECK(sgs[0].getAtoms().at(0)->getIdx() == aid);
+    }
+  }
 }
\ No newline at end of file

diff --git a/Code/GraphMol/FileParsers/test_data/atropisomers/AtropManyChiralsEnhanced.sdf.expected.sdf b/Code/GraphMol/FileParsers/test_data/atropisomers/AtropManyChiralsEnhanced.sdf.expected.sdf
index 2bd3de0b0bd..6f329a3ca47 100644
--- a/Code/GraphMol/FileParsers/test_data/atropisomers/AtropManyChiralsEnhanced.sdf.expected.sdf
+++ b/Code/GraphMol/FileParsers/test_data/atropisomers/AtropManyChiralsEnhanced.sdf.expected.sdf
@@ -49,7 +49,7 @@ M  V30 19 1 17 19
 M  V30 20 2 6 20
 M  V30 END BOND
 M  V30 BEGIN COLLECTION
-M  V30 MDLV30/STEREL1 ATOMS=(3 10 14 7)
+M  V30 MDLV30/STEREL1 ATOMS=(3 7 10 14)
 M  V30 MDLV30/STERAC1 ATOMS=(2 11 17)
 M  V30 END COLLECTION
 M  V30 END CTAB
diff --git a/Code/GraphMol/FileParsers/test_data/atropisomers/AtropManyChiralsEnhanced2.sdf.expected.sdf b/Code/GraphMol/FileParsers/test_data/atropisomers/AtropManyChiralsEnhanced2.sdf.expected.sdf
index a3c275f5b70..146d0a2411e 100644
--- a/Code/GraphMol/FileParsers/test_data/atropisomers/AtropManyChiralsEnhanced2.sdf.expected.sdf
+++ b/Code/GraphMol/FileParsers/test_data/atropisomers/AtropManyChiralsEnhanced2.sdf.expected.sdf
@@ -49,7 +49,7 @@ M  V30 19 1 17 19
 M  V30 20 2 6 20
 M  V30 END BOND
 M  V30 BEGIN COLLECTION
-M  V30 MDLV30/STEABS ATOMS=(3 10 14 7)
+M  V30 MDLV30/STEABS ATOMS=(3 7 10 14)
 M  V30 MDLV30/STERAC1 ATOMS=(2 11 17)
 M  V30 END COLLECTION
 M  V30 END CTAB
diff --git a/Code/GraphMol/MarvinParse/test_data/AtropEnhancedStereo.mrv.nosan.expected.mrv b/Code/GraphMol/MarvinParse/test_data/AtropEnhancedStereo.mrv.nosan.expected.mrv
index c3518dc67a8..8d8ce27eab6 100644
--- a/Code/GraphMol/MarvinParse/test_data/AtropEnhancedStereo.mrv.nosan.expected.mrv
+++ b/Code/GraphMol/MarvinParse/test_data/AtropEnhancedStereo.mrv.nosan.expected.mrv
@@ -1,2 +1,2 @@
 <?xml version="1.0" encoding="utf-8"?>
-<cml xmlns="http://www.chemaxon.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.chemaxon.com http://www.chemaxon.com/marvin/schema/mrvSchema_20_20_0.xsd"><MDocument><MChemicalStruct><molecule molID="m1"><atomArray><atom id="a1" elementType="C" x2="-1.39593" y2="-1.93907"/><atom id="a2" elementType="C" x2="-2.72947" y2="-2.70907"/><atom id="a3" elementType="C" x2="-2.72947" y2="-4.24926"/><atom id="a4" elementType="C" x2="-1.39593" y2="-5.01926"/><atom id="a5" elementType="C" x2="-0.0621933" y2="-4.24926"/><atom id="a6" elementType="C" x2="-0.0621933" y2="-2.70907"/><atom id="a7" elementType="C" x2="-1.39597" y2="-0.399073" mrvStereoGroup="or1"/><atom id="a8" elementType="N" x2="1.27148" y2="-1.93906"/><atom id="a9" elementType="C" x2="-4.06317" y2="-1.9391"/><atom id="a10" elementType="C" x2="-0.0624033" y2="0.370886"/><atom id="a11" elementType="C" x2="-0.0623567" y2="1.91107"/><atom id="a12" elementType="C" x2="-1.39588" y2="2.68111"/><atom id="a13" elementType="C" x2="-2.72964" y2="1.91115"/><atom id="a14" elementType="C" x2="-2.72968" y2="0.370967"/><atom id="a15" elementType="O" x2="1.27127" y2="-0.399126"/><atom id="a16" elementType="C" x2="-4.06338" y2="-0.399004"/></atomArray><bondArray><bond id="b1" atomRefs2="a1 a2" order="2"/><bond id="b2" atomRefs2="a2 a3" order="1"/><bond id="b3" atomRefs2="a3 a4" order="2"/><bond id="b4" atomRefs2="a4 a5" order="1"/><bond id="b5" atomRefs2="a5 a6" order="2"/><bond id="b6" atomRefs2="a6 a1" order="1"/><bond id="b7" atomRefs2="a1 a7" order="1"/><bond id="b8" atomRefs2="a2 a9" order="1"/><bond id="b9" atomRefs2="a10 a11" order="1"/><bond id="b10" atomRefs2="a11 a12" order="2"/><bond id="b11" atomRefs2="a12 a13" order="1"/><bond id="b12" atomRefs2="a13 a14" order="2"/><bond id="b13" atomRefs2="a7 a10" order="2"/><bond id="b14" atomRefs2="a7 a14" order="1"><bondStereo>W</bondStereo></bond><bond id="b15" atomRefs2="a14 a16" order="1"/><bond id="b16" atomRefs2="a10 a15" order="1"/><bond id="b17" atomRefs2="a6 a8" order="1"/></bondArray></molecule></MChemicalStruct></MDocument></cml>
\ No newline at end of file
+<cml xmlns="http://www.chemaxon.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.chemaxon.com http://www.chemaxon.com/marvin/schema/mrvSchema_20_20_0.xsd"><MDocument><MChemicalStruct><molecule molID="m1"><atomArray><atom id="a1" elementType="C" x2="-1.39593" y2="-1.93907" mrvStereoGroup="or1"/><atom id="a2" elementType="C" x2="-2.72947" y2="-2.70907"/><atom id="a3" elementType="C" x2="-2.72947" y2="-4.24926"/><atom id="a4" elementType="C" x2="-1.39593" y2="-5.01926"/><atom id="a5" elementType="C" x2="-0.0621933" y2="-4.24926"/><atom id="a6" elementType="C" x2="-0.0621933" y2="-2.70907"/><atom id="a7" elementType="C" x2="-1.39597" y2="-0.399073"/><atom id="a8" elementType="N" x2="1.27148" y2="-1.93906"/><atom id="a9" elementType="C" x2="-4.06317" y2="-1.9391"/><atom id="a10" elementType="C" x2="-0.0624033" y2="0.370886"/><atom id="a11" elementType="C" x2="-0.0623567" y2="1.91107"/><atom id="a12" elementType="C" x2="-1.39588" y2="2.68111"/><atom id="a13" elementType="C" x2="-2.72964" y2="1.91115"/><atom id="a14" elementType="C" x2="-2.72968" y2="0.370967"/><atom id="a15" elementType="O" x2="1.27127" y2="-0.399126"/><atom id="a16" elementType="C" x2="-4.06338" y2="-0.399004"/></atomArray><bondArray><bond id="b1" atomRefs2="a1 a2" order="2"/><bond id="b2" atomRefs2="a2 a3" order="1"/><bond id="b3" atomRefs2="a3 a4" order="2"/><bond id="b4" atomRefs2="a4 a5" order="1"/><bond id="b5" atomRefs2="a5 a6" order="2"/><bond id="b6" atomRefs2="a6 a1" order="1"/><bond id="b7" atomRefs2="a1 a7" order="1"/><bond id="b8" atomRefs2="a2 a9" order="1"/><bond id="b9" atomRefs2="a10 a11" order="1"/><bond id="b10" atomRefs2="a11 a12" order="2"/><bond id="b11" atomRefs2="a12 a13" order="1"/><bond id="b12" atomRefs2="a13 a14" order="2"/><bond id="b13" atomRefs2="a7 a10" order="2"/><bond id="b14" atomRefs2="a7 a14" order="1"><bondStereo>W</bondStereo></bond><bond id="b15" atomRefs2="a14 a16" order="1"/><bond id="b16" atomRefs2="a10 a15" order="1"/><bond id="b17" atomRefs2="a6 a8" order="1"/></bondArray></molecule></MChemicalStruct></MDocument></cml>
\ No newline at end of file
diff --git a/Code/GraphMol/MarvinParse/test_data/AtropEnhancedStereo.mrv.nosan.expected.sdf b/Code/GraphMol/MarvinParse/test_data/AtropEnhancedStereo.mrv.nosan.expected.sdf
index dac8923f3c9..b68f5788152 100644
--- a/Code/GraphMol/MarvinParse/test_data/AtropEnhancedStereo.mrv.nosan.expected.sdf
+++ b/Code/GraphMol/MarvinParse/test_data/AtropEnhancedStereo.mrv.nosan.expected.sdf
@@ -42,7 +42,7 @@ M  V30 16 1 10 15
 M  V30 17 1 6 8
 M  V30 END BOND
 M  V30 BEGIN COLLECTION
-M  V30 MDLV30/STEREL1 ATOMS=(1 7)
+M  V30 MDLV30/STEREL1 ATOMS=(1 1)
 M  V30 END COLLECTION
 M  V30 END CTAB
 M  END
diff --git a/Code/GraphMol/MarvinParse/test_data/AtropManyChiralsEnhanced.mrv.nosan.expected.sdf b/Code/GraphMol/MarvinParse/test_data/AtropManyChiralsEnhanced.mrv.nosan.expected.sdf
index 426a4a19dd8..16b9d65271a 100644
--- a/Code/GraphMol/MarvinParse/test_data/AtropManyChiralsEnhanced.mrv.nosan.expected.sdf
+++ b/Code/GraphMol/MarvinParse/test_data/AtropManyChiralsEnhanced.mrv.nosan.expected.sdf
@@ -49,7 +49,7 @@ M  V30 19 1 18 19 CFG=1
 M  V30 20 1 18 20
 M  V30 END BOND
 M  V30 BEGIN COLLECTION
-M  V30 MDLV30/STEREL1 ATOMS=(3 11 15 8)
+M  V30 MDLV30/STEREL1 ATOMS=(3 8 11 15)
 M  V30 MDLV30/STERAC1 ATOMS=(2 12 18)
 M  V30 END COLLECTION
 M  V30 END CTAB
diff --git a/Code/GraphMol/MarvinParse/test_data/AtropManyChiralsEnhanced2.mrv.nosan.expected.sdf b/Code/GraphMol/MarvinParse/test_data/AtropManyChiralsEnhanced2.mrv.nosan.expected.sdf
index 30d76c1bd8c..8fd896c62da 100644
--- a/Code/GraphMol/MarvinParse/test_data/AtropManyChiralsEnhanced2.mrv.nosan.expected.sdf
+++ b/Code/GraphMol/MarvinParse/test_data/AtropManyChiralsEnhanced2.mrv.nosan.expected.sdf
@@ -49,7 +49,7 @@ M  V30 19 1 18 19 CFG=1
 M  V30 20 1 18 20
 M  V30 END BOND
 M  V30 BEGIN COLLECTION
-M  V30 MDLV30/STEABS ATOMS=(3 11 15 8)
+M  V30 MDLV30/STEABS ATOMS=(3 8 11 15)
 M  V30 MDLV30/STERAC1 ATOMS=(2 12 18)
 M  V30 END COLLECTION
 M  V30 END CTAB
diff --git a/Code/GraphMol/MarvinParse/test_data/AtropManyChiralsEnhanced3.mrv.nosan.expected.sdf b/Code/GraphMol/MarvinParse/test_data/AtropManyChiralsEnhanced3.mrv.nosan.expected.sdf
index c87bac9d130..52e876af68d 100644
--- a/Code/GraphMol/MarvinParse/test_data/AtropManyChiralsEnhanced3.mrv.nosan.expected.sdf
+++ b/Code/GraphMol/MarvinParse/test_data/AtropManyChiralsEnhanced3.mrv.nosan.expected.sdf
@@ -49,7 +49,7 @@ M  V30 19 1 18 19 CFG=1
 M  V30 20 1 18 20
 M  V30 END BOND
 M  V30 BEGIN COLLECTION
-M  V30 MDLV30/STERAC1 ATOMS=(3 11 15 8)
+M  V30 MDLV30/STERAC1 ATOMS=(3 8 11 15)
 M  V30 MDLV30/STERAC2 ATOMS=(2 12 18)
 M  V30 END COLLECTION
 M  V30 END CTAB
diff --git a/Code/GraphMol/MarvinParse/test_data/AtropManyChiralsEnhanced4.mrv.nosan.expected.mrv b/Code/GraphMol/MarvinParse/test_data/AtropManyChiralsEnhanced4.mrv.nosan.expected.mrv
index f8f99c1afec..85c5ba7b198 100644
--- a/Code/GraphMol/MarvinParse/test_data/AtropManyChiralsEnhanced4.mrv.nosan.expected.mrv
+++ b/Code/GraphMol/MarvinParse/test_data/AtropManyChiralsEnhanced4.mrv.nosan.expected.mrv
@@ -1,2 +1,2 @@
 <?xml version="1.0" encoding="utf-8"?>
-<cml xmlns="http://www.chemaxon.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.chemaxon.com http://www.chemaxon.com/marvin/schema/mrvSchema_20_20_0.xsd"><MDocument><MChemicalStruct><molecule molID="m1"><atomArray><atom id="a1" elementType="O" x2="2.8625" y2="1.0561"/><atom id="a2" elementType="C" x2="2.2921" y2="-0.5174"/><atom id="a3" elementType="C" x2="3.4688" y2="-1.0018"/><atom id="a4" elementType="C" x2="3.6019" y2="-2.4682"/><atom id="a5" elementType="N" x2="2.3988" y2="-3.3169"/><atom id="a6" elementType="C" x2="1.0621" y2="-2.6992"/><atom id="a7" elementType="O" x2="0.1062" y2="-3.9298"/><atom id="a8" elementType="N" x2="0.9288" y2="-1.2328" mrvStereoGroup="or1"/><atom id="a9" elementType="C" x2="-0.5563" y2="-0.6124" mrvStereoGroup="or1"/><atom id="a10" elementType="C" x2="-1.5367" y2="-1.8"/><atom id="a11" elementType="C" x2="-0.8947" y2="0.9647" mrvStereoGroup="or1"/><atom id="a12" elementType="C" x2="-0.8778" y2="2.5678" mrvStereoGroup="and1"/><atom id="a13" elementType="C" x2="-0.1398" y2="3.9298"/><atom id="a14" elementType="Br" x2="-1.9563" y2="3.6407"/><atom id="a15" elementType="C" x2="0.5974" y2="1.3136" mrvStereoGroup="or1"/><atom id="a16" elementType="F" x2="1.1268" y2="-0.0778"/><atom id="a17" elementType="C" x2="1.9954" y2="1.9251"/><atom id="a18" elementType="C" x2="-2.4534" y2="0.7177" mrvStereoGroup="and1"/><atom id="a19" elementType="Cl" x2="-3.6019" y2="1.737"/><atom id="a20" elementType="C" x2="-3.4495" y2="-0.4655"/></atomArray><bondArray><bond id="b1" atomRefs2="a1 a2" order="2"/><bond id="b2" atomRefs2="a2 a3" order="1"/><bond id="b3" atomRefs2="a3 a4" order="2"/><bond id="b4" atomRefs2="a4 a5" order="1"/><bond id="b5" atomRefs2="a5 a6" order="1"/><bond id="b6" atomRefs2="a6 a7" order="2"/><bond id="b7" atomRefs2="a6 a8" order="1"/><bond id="b8" atomRefs2="a8 a2" order="1"><bondStereo>W</bondStereo></bond><bond id="b9" atomRefs2="a8 a9" order="1"/><bond id="b10" atomRefs2="a9 a10" order="2"/><bond id="b11" atomRefs2="a9 a11" order="1"><bondStereo>H</bondStereo></bond><bond id="b12" atomRefs2="a11 a12" order="1"/><bond id="b13" atomRefs2="a12 a13" order="1"/><bond id="b14" atomRefs2="a12 a14" order="1"><bondStereo>W</bondStereo></bond><bond id="b15" atomRefs2="a11 a15" order="1"><bondStereo>W</bondStereo></bond><bond id="b16" atomRefs2="a15 a16" order="1"><bondStereo>H</bondStereo></bond><bond id="b17" atomRefs2="a15 a17" order="1"/><bond id="b18" atomRefs2="a11 a18" order="1"/><bond id="b19" atomRefs2="a18 a19" order="1"><bondStereo>W</bondStereo></bond><bond id="b20" atomRefs2="a18 a20" order="1"/></bondArray></molecule></MChemicalStruct></MDocument></cml>
\ No newline at end of file
+<cml xmlns="http://www.chemaxon.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.chemaxon.com http://www.chemaxon.com/marvin/schema/mrvSchema_20_20_0.xsd"><MDocument><MChemicalStruct><molecule molID="m1"><atomArray><atom id="a1" elementType="O" x2="2.8625" y2="1.0561"/><atom id="a2" elementType="C" x2="2.2921" y2="-0.5174"/><atom id="a3" elementType="C" x2="3.4688" y2="-1.0018"/><atom id="a4" elementType="C" x2="3.6019" y2="-2.4682"/><atom id="a5" elementType="N" x2="2.3988" y2="-3.3169"/><atom id="a6" elementType="C" x2="1.0621" y2="-2.6992"/><atom id="a7" elementType="O" x2="0.1062" y2="-3.9298"/><atom id="a8" elementType="N" x2="0.9288" y2="-1.2328" mrvStereoGroup="or1"/><atom id="a9" elementType="C" x2="-0.5563" y2="-0.6124"/><atom id="a10" elementType="C" x2="-1.5367" y2="-1.8"/><atom id="a11" elementType="C" x2="-0.8947" y2="0.9647" mrvStereoGroup="or1"/><atom id="a12" elementType="C" x2="-0.8778" y2="2.5678" mrvStereoGroup="and1"/><atom id="a13" elementType="C" x2="-0.1398" y2="3.9298"/><atom id="a14" elementType="Br" x2="-1.9563" y2="3.6407"/><atom id="a15" elementType="C" x2="0.5974" y2="1.3136" mrvStereoGroup="or1"/><atom id="a16" elementType="F" x2="1.1268" y2="-0.0778"/><atom id="a17" elementType="C" x2="1.9954" y2="1.9251"/><atom id="a18" elementType="C" x2="-2.4534" y2="0.7177" mrvStereoGroup="and1"/><atom id="a19" elementType="Cl" x2="-3.6019" y2="1.737"/><atom id="a20" elementType="C" x2="-3.4495" y2="-0.4655"/></atomArray><bondArray><bond id="b1" atomRefs2="a1 a2" order="2"/><bond id="b2" atomRefs2="a2 a3" order="1"/><bond id="b3" atomRefs2="a3 a4" order="2"/><bond id="b4" atomRefs2="a4 a5" order="1"/><bond id="b5" atomRefs2="a5 a6" order="1"/><bond id="b6" atomRefs2="a6 a7" order="2"/><bond id="b7" atomRefs2="a6 a8" order="1"/><bond id="b8" atomRefs2="a8 a2" order="1"><bondStereo>W</bondStereo></bond><bond id="b9" atomRefs2="a8 a9" order="1"/><bond id="b10" atomRefs2="a9 a10" order="2"/><bond id="b11" atomRefs2="a9 a11" order="1"><bondStereo>H</bondStereo></bond><bond id="b12" atomRefs2="a11 a12" order="1"/><bond id="b13" atomRefs2="a12 a13" order="1"/><bond id="b14" atomRefs2="a12 a14" order="1"><bondStereo>W</bondStereo></bond><bond id="b15" atomRefs2="a11 a15" order="1"><bondStereo>W</bondStereo></bond><bond id="b16" atomRefs2="a15 a16" order="1"><bondStereo>H</bondStereo></bond><bond id="b17" atomRefs2="a15 a17" order="1"/><bond id="b18" atomRefs2="a11 a18" order="1"/><bond id="b19" atomRefs2="a18 a19" order="1"><bondStereo>W</bondStereo></bond><bond id="b20" atomRefs2="a18 a20" order="1"/></bondArray></molecule></MChemicalStruct></MDocument></cml>
\ No newline at end of file
diff --git a/Code/GraphMol/MarvinParse/test_data/AtropManyChiralsEnhanced4.mrv.nosan.expected.sdf b/Code/GraphMol/MarvinParse/test_data/AtropManyChiralsEnhanced4.mrv.nosan.expected.sdf
index a601cb2f275..83121a8b6c8 100644
--- a/Code/GraphMol/MarvinParse/test_data/AtropManyChiralsEnhanced4.mrv.nosan.expected.sdf
+++ b/Code/GraphMol/MarvinParse/test_data/AtropManyChiralsEnhanced4.mrv.nosan.expected.sdf
@@ -49,7 +49,7 @@ M  V30 19 1 18 19 CFG=1
 M  V30 20 1 18 20
 M  V30 END BOND
 M  V30 BEGIN COLLECTION
-M  V30 MDLV30/STEREL1 ATOMS=(4 11 15 8 9)
+M  V30 MDLV30/STEREL1 ATOMS=(3 8 11 15)
 M  V30 MDLV30/STERAC1 ATOMS=(2 12 18)
 M  V30 END COLLECTION
 M  V30 END CTAB
diff --git a/Code/GraphMol/test_data/Github8323.sdf b/Code/GraphMol/test_data/Github8323.sdf
new file mode 100644
index 00000000000..20da45c6094
--- /dev/null
+++ b/Code/GraphMol/test_data/Github8323.sdf
@@ -0,0 +1,152 @@
+
+  ACCLDraw03052509552D
+
+  0  0  0     0  0            999 V3000
+M  V30 BEGIN CTAB
+M  V30 COUNTS 9 9 0 0 1
+M  V30 BEGIN ATOM
+M  V30 1 S 15.9078 -7.0163 0 0 CFG=2
+M  V30 2 C 15.3149 -5.994 0 0
+M  V30 3 O 15.319 -8.0401 0 0
+M  V30 4 C 17.0876 -7.0147 0 0
+M  V30 5 C 18.8552 -5.9903 0 0
+M  V30 6 C 17.6773 -5.9908 0 0
+M  V30 7 C 19.4475 -7.013 0 0
+M  V30 8 C 18.863 -8.033 0 0
+M  V30 9 C 17.6823 -8.0414 0 0
+M  V30 END ATOM
+M  V30 BEGIN BOND
+M  V30 1 1 1 2 CFG=1
+M  V30 2 2 1 3
+M  V30 3 1 1 4
+M  V30 4 1 5 6
+M  V30 5 2 6 4
+M  V30 6 2 5 7
+M  V30 7 1 7 8
+M  V30 8 2 8 9
+M  V30 9 1 4 9
+M  V30 END BOND
+M  V30 BEGIN COLLECTION
+M  V30 MDLV30/STEABS ATOMS=(1 1)
+M  V30 END COLLECTION
+M  V30 END CTAB
+M  END
+>  <StereoGroupOnAtom>
+0
+
+$$$$
+
+  ACCLDraw03052509542D
+
+  0  0  0     0  0            999 V3000
+M  V30 BEGIN CTAB
+M  V30 COUNTS 9 9 0 0 0
+M  V30 BEGIN ATOM
+M  V30 1 C 15.3149 -5.994 0 0
+M  V30 2 S 15.9078 -7.0163 0 0 CFG=2
+M  V30 3 O 15.319 -8.0401 0 0
+M  V30 4 C 17.0876 -7.0147 0 0
+M  V30 5 C 18.8552 -5.9903 0 0
+M  V30 6 C 17.6773 -5.9908 0 0
+M  V30 7 C 19.4475 -7.013 0 0
+M  V30 8 C 18.863 -8.033 0 0
+M  V30 9 C 17.6823 -8.0414 0 0
+M  V30 END ATOM
+M  V30 BEGIN BOND
+M  V30 1 1 2 1 CFG=1
+M  V30 2 2 2 3
+M  V30 3 1 4 2
+M  V30 4 1 5 6
+M  V30 5 2 6 4
+M  V30 6 2 7 5
+M  V30 7 1 8 7
+M  V30 8 2 9 8
+M  V30 9 1 4 9
+M  V30 END BOND
+M  V30 BEGIN COLLECTION
+M  V30 MDLV30/STERAC1 ATOMS=(1 2)
+M  V30 END COLLECTION
+M  V30 END CTAB
+M  END
+>  <StereoGroupOnAtom>
+1
+
+$$$$
+
+  ACCLDraw03052510142D
+
+  0  0  0     0  0            999 V3000
+M  V30 BEGIN CTAB
+M  V30 COUNTS 10 10 0 0 1
+M  V30 BEGIN ATOM
+M  V30 1 C 15.2801 -6.3814 0 0
+M  V30 2 C 14.0994 -6.3853 0 0
+M  V30 3 C 13.5125 -7.4099 0 0
+M  V30 4 C 14.1063 -8.4305 0 0
+M  V30 5 C 15.287 -8.4265 0 0
+M  V30 6 C 15.8741 -7.4019 0 0
+M  V30 7 C 17.0554 -7.4003 0 0
+M  V30 8 S 17.6481 -8.4223 0 0 CFG=2
+M  V30 9 O 17.0595 -9.4458 0 0
+M  V30 10 C 18.8275 -8.4207 0 0
+M  V30 END ATOM
+M  V30 BEGIN BOND
+M  V30 1 2 2 1
+M  V30 2 1 3 2
+M  V30 3 2 4 3
+M  V30 4 1 5 4
+M  V30 5 1 1 6
+M  V30 6 2 6 5
+M  V30 7 1 7 6
+M  V30 8 1 8 7 CFG=1
+M  V30 9 2 8 9
+M  V30 10 1 10 8
+M  V30 END BOND
+M  V30 BEGIN COLLECTION
+M  V30 MDLV30/STEABS ATOMS=(1 8)
+M  V30 END COLLECTION
+M  V30 END CTAB
+M  END
+>  <StereoGroupOnAtom>
+7
+
+$$$$
+
+  ACCLDraw03052510142D
+
+  0  0  0     0  0            999 V3000
+M  V30 BEGIN CTAB
+M  V30 COUNTS 10 10 0 0 0
+M  V30 BEGIN ATOM
+M  V30 1 C 15.2801 -6.3814 0 0
+M  V30 2 C 14.0994 -6.3853 0 0
+M  V30 3 C 13.5125 -7.4099 0 0
+M  V30 4 C 14.1063 -8.4305 0 0
+M  V30 5 C 15.287 -8.4265 0 0
+M  V30 6 C 15.8741 -7.4019 0 0
+M  V30 7 C 17.0554 -7.4003 0 0
+M  V30 8 S 17.6481 -8.4223 0 0 CFG=2
+M  V30 9 O 17.0595 -9.4458 0 0
+M  V30 10 C 18.8275 -8.4207 0 0
+M  V30 END ATOM
+M  V30 BEGIN BOND
+M  V30 1 2 2 1
+M  V30 2 1 3 2
+M  V30 3 2 4 3
+M  V30 4 1 5 4
+M  V30 5 1 1 6
+M  V30 6 2 6 5
+M  V30 7 1 7 6
+M  V30 8 1 8 7 CFG=1
+M  V30 9 2 8 9
+M  V30 10 1 10 8
+M  V30 END BOND
+M  V30 BEGIN COLLECTION
+M  V30 MDLV30/STERAC1 ATOMS=(1 8)
+M  V30 END COLLECTION
+M  V30 END CTAB
+M  END
+>  <StereoGroupOnAtom>
+7
+
+$$$$
\ No newline at end of file