Repository: rdkit/rdkit — pull request #6189 (cpp)
Title: Fixes #5883

The fix here is to recognize that the expected chiral volume for three-coordinate atoms is smaller than that for four-coordinate atoms.
The corrected value here is empirical

Resolved issues:
title: Conformer Generation Fails for three-coordinate Ns with specified stereo
body: **Describe the bug**
RDKit doesn't seem to be able to generate conformers for a three-membered ring with two nitrogen atoms if stereocenters are specified.

**To Reproduce**

```python
from rdkit.Chem import AllChem

mol = AllChem.AddHs(AllChem.MolFromSmiles("N1NC1"))

c1 = AllChem.Mol(mol, quickCopy=False)
AllChem.AssignStereochemistry(c1, flagPossibleStereoCenters=False)

for isomer in AllChem.EnumerateStereoisomers(c1):
    AllChem.EmbedMolecule(isomer)
    assert isomer.GetNumConformers() == 1

c2 = AllChem.Mol(mol, quickCopy=False)
AllChem.AssignStereochemistry(c2, flagPossibleStereoCenters=True)  # flagPossibleStereoCenters

for isomer in AllChem.EnumerateStereoisomers(c2):
    AllChem.EmbedMolecule(isomer)
    assert isomer.GetNumConformers() == 1  # Fails
```

**Configuration**
 - RDKit version: 2022.09.1, py38h5acb366_1, conda-forge
 - OS: Ubuntu 20.04

Hints:

Fix patch:
diff --git a/Code/GraphMol/DistGeomHelpers/Embedder.cpp b/Code/GraphMol/DistGeomHelpers/Embedder.cpp
index d1bf33610ab..6613466a846 100644
--- a/Code/GraphMol/DistGeomHelpers/Embedder.cpp
+++ b/Code/GraphMol/DistGeomHelpers/Embedder.cpp
@@ -34,13 +34,14 @@
 #include <boost/dynamic_bitset.hpp>
 #include <iomanip>
 #include <RDGeneral/RDThreads.h>
+#include <typeinfo>
 
 #ifdef RDK_BUILD_THREADSAFE_SSS
 #include <future>
 #include <mutex>
 #endif
 
-//#define DEBUG_EMBEDDING 1
+// #define DEBUG_EMBEDDING 1
 
 #ifdef M_PI_2
 #undef M_PI_2
@@ -1016,7 +1017,13 @@ void findChiralSets(const ROMol &mol, DistGeom::VECT_CHIRALSET &chiralCenters,
         // we should at least have 3 though
         CHECK_INVARIANT(nbrs.size() >= 3, "Cannot be a chiral center");
 
+        double volLowerBound = 5.0;
+        double volUpperBound = 100.0;
+
         if (nbrs.size() < 4) {
+          // we get lower volumes if there are three neighbors,
+          //  this was github #5883
+          volLowerBound = 2.0;
           nbrs.insert(nbrs.end(), atom->getIdx());
         }
 
@@ -1024,13 +1031,15 @@ void findChiralSets(const ROMol &mol, DistGeom::VECT_CHIRALSET &chiralCenters,
         // 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], 5.0, 100.0);
+          auto *cset =
+              new DistGeom::ChiralSet(atom->getIdx(), nbrs[0], nbrs[1], nbrs[2],
+                                      nbrs[3], volLowerBound, volUpperBound);
           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], -100.0, -5.0);
+          auto *cset =
+              new DistGeom::ChiralSet(atom->getIdx(), nbrs[0], nbrs[1], nbrs[2],
+                                      nbrs[3], -volUpperBound, -volLowerBound);
           DistGeom::ChiralSetPtr cptr(cset);
           chiralCenters.push_back(cptr);
         } else {
diff --git a/ReleaseNotes.md b/ReleaseNotes.md
index 7597e8342ec..ed8dcd520ab 100644
--- a/ReleaseNotes.md
+++ b/ReleaseNotes.md
@@ -11,6 +11,8 @@
 - The canonical SMILES and CXSMILES generated for molecules with enhanced stereochemistry (stereo groups) is different than in previous releases. The enhanced stereochemistry information and the stereo groups themselves are now canonical. This does *not* affect molecules which do not have enhanced stereo and will not have any effect if you generate non-isomeric SMILES. This change also affects the output of the MolHash and RegistrationHash code when applied to molecules with enhanced stereo.
 - The doIsomericSmiles parameter in Java and C# ROMol.MolToSmiles() now defaults to true (previously it was false), thus aligning to the C++ and Python behavior.
 - Double bonds which are marked as crossed (i.e. `bond.GetBondDir() == Bond.BondDir.EITHERDOUBLE`) now have their BondStereo set to `Bond.BondStereo.STEREOANY` and the BondDir information removed by default when molecules are parsed or `AssignStereochemistry()` is called with the `cleanIt` argument set to True.
+- The conformers generated for molecules with three-coordinate chiral centers will be somewhat different due to the fix for #5883.
+
 
 ## Bug Fixes:
 


Test patch:
diff --git a/Code/GraphMol/DistGeomHelpers/Wrap/testDistGeom.py b/Code/GraphMol/DistGeomHelpers/Wrap/testDistGeom.py
index 0af5cd38b23..7cce5a0827f 100644
--- a/Code/GraphMol/DistGeomHelpers/Wrap/testDistGeom.py
+++ b/Code/GraphMol/DistGeomHelpers/Wrap/testDistGeom.py
@@ -274,7 +274,7 @@ def test6Chirality(self):
     self.assertTrue(nPos > 0)
     self.assertTrue(nNeg > 0)
 
-    tgtVol = 5.0
+    tgtVol = 3.0
     for i in range(10):
       smiles = "Cl[C@H](F)Br"
       mol = Chem.MolFromSmiles(smiles)
diff --git a/Code/GraphMol/DistGeomHelpers/catch_tests.cpp b/Code/GraphMol/DistGeomHelpers/catch_tests.cpp
index 695a2d76981..2f3bb6df834 100644
--- a/Code/GraphMol/DistGeomHelpers/catch_tests.cpp
+++ b/Code/GraphMol/DistGeomHelpers/catch_tests.cpp
@@ -589,32 +589,29 @@ TEST_CASE("double bond stereo not honored in conformer generator") {
   }
 }
 
-TEST_CASE("tracking failure causes") {
-  SECTION("basics") {
-    // auto mol = "CCNS(=O)(=O)c1ccccc1"_smiles;
-    // auto mol = "c2cccc3c2OC2=CC=CC[C@H]23"_smiles;
+TEST_CASE("tracking failure causes"){SECTION("basics"){
     auto mol =
         "C=CC1=C(N)Oc2cc1c(-c1cc(C(C)O)cc(=O)cc1C1NCC(=O)N1)c(OC)c2OC"_smiles;
-    REQUIRE(mol);
-    MolOps::addHs(*mol);
-    DGeomHelpers::EmbedParameters ps = DGeomHelpers::ETKDGv3;
-    ps.randomSeed = 0xf00d;
-    ps.trackFailures = true;
-    ps.maxIterations = 50;
-    ps.randomSeed = 42;
-    auto cid = DGeomHelpers::EmbedMolecule(*mol, ps);
-    CHECK(cid < 0);
-
-    CHECK(ps.failures[DGeomHelpers::EmbedFailureCauses::INITIAL_COORDS] > 5);
-    CHECK(ps.failures[DGeomHelpers::EmbedFailureCauses::ETK_MINIMIZATION] > 10);
-
-    auto fail_cp = ps.failures;
-    // make sure we reset the counts each time
-    cid = DGeomHelpers::EmbedMolecule(*mol, ps);
-    CHECK(ps.failures == fail_cp);
-  }
-  SECTION("chirality") {
-    auto mol = R"CTAB(
+REQUIRE(mol);
+MolOps::addHs(*mol);
+DGeomHelpers::EmbedParameters ps = DGeomHelpers::ETKDGv3;
+ps.randomSeed = 0xf00d;
+ps.trackFailures = true;
+ps.maxIterations = 50;
+ps.randomSeed = 42;
+auto cid = DGeomHelpers::EmbedMolecule(*mol, ps);
+CHECK(cid < 0);
+
+CHECK(ps.failures[DGeomHelpers::EmbedFailureCauses::INITIAL_COORDS] > 5);
+CHECK(ps.failures[DGeomHelpers::EmbedFailureCauses::ETK_MINIMIZATION] > 10);
+
+auto fail_cp = ps.failures;
+// make sure we reset the counts each time
+cid = DGeomHelpers::EmbedMolecule(*mol, ps);
+CHECK(ps.failures == fail_cp);
+}
+SECTION("chirality") {
+  auto mol = R"CTAB(
   Ketcher  1102315302D 1   1.00000     0.00000     0
 
  10 11  0  0  1  0  0  0  0  0999 V2000
@@ -641,39 +638,74 @@ TEST_CASE("tracking failure causes") {
   4 10  1  1     0  0
 M  END
 )CTAB"_ctab;
+  REQUIRE(mol);
+  MolOps::addHs(*mol);
+  DGeomHelpers::EmbedParameters ps = DGeomHelpers::ETKDGv3;
+  ps.randomSeed = 0xf00d;
+  ps.trackFailures = true;
+  ps.maxIterations = 50;
+  auto cid = DGeomHelpers::EmbedMolecule(*mol, ps);
+  CHECK(cid < 0);
+  CHECK(ps.failures[DGeomHelpers::EmbedFailureCauses::INITIAL_COORDS] > 5);
+  CHECK(ps.failures[DGeomHelpers::EmbedFailureCauses::FINAL_CHIRAL_BOUNDS] > 5);
+}
+
+#ifdef RDK_TEST_MULTITHREADED
+SECTION("multithreaded") {
+  auto mol =
+      "C=CC1=C(N)Oc2cc1c(-c1cc(C(C)O)cc(=O)cc1C1NCC(=O)N1)c(OC)c2OC"_smiles;
+  REQUIRE(mol);
+  MolOps::addHs(*mol);
+  DGeomHelpers::EmbedParameters ps = DGeomHelpers::ETKDGv3;
+  ps.randomSeed = 0xf00d;
+  ps.trackFailures = true;
+  ps.maxIterations = 10;
+  ps.randomSeed = 42;
+  auto cids = DGeomHelpers::EmbedMultipleConfs(*mol, 20, ps);
+
+  DGeomHelpers::EmbedParameters ps2 = ps;
+  ps2.numThreads = 4;
+
+  auto cids2 = DGeomHelpers::EmbedMultipleConfs(*mol, 20, ps2);
+  CHECK(cids2 == cids);
+
+  CHECK(ps.failures == ps2.failures);
+}
+#endif
+}
+
+TEST_CASE("Github #5883: confgen failing for chiral N in a three ring") {
+  SECTION("basics1") {
+    auto mol = "N1[C@H-]C1"_smiles;
     REQUIRE(mol);
     MolOps::addHs(*mol);
+    mol->getAtomWithIdx(1)->setChiralTag(Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
     DGeomHelpers::EmbedParameters ps = DGeomHelpers::ETKDGv3;
-    ps.randomSeed = 0xf00d;
-    ps.trackFailures = true;
-    ps.maxIterations = 50;
+    ps.randomSeed = 42;
+    ps.maxIterations = 1;
     auto cid = DGeomHelpers::EmbedMolecule(*mol, ps);
-    CHECK(cid < 0);
-    CHECK(ps.failures[DGeomHelpers::EmbedFailureCauses::INITIAL_COORDS] > 5);
-    CHECK(ps.failures[DGeomHelpers::EmbedFailureCauses::FINAL_CHIRAL_BOUNDS] >
-          5);
+    CHECK(cid >= 0);
   }
-
-#ifdef RDK_TEST_MULTITHREADED
-  SECTION("multithreaded") {
-    auto mol =
-        "C=CC1=C(N)Oc2cc1c(-c1cc(C(C)O)cc(=O)cc1C1NCC(=O)N1)c(OC)c2OC"_smiles;
+  SECTION("basics2") {
+    auto mol = "N1[N@H]C1"_smiles;
     REQUIRE(mol);
     MolOps::addHs(*mol);
+    mol->getAtomWithIdx(1)->setChiralTag(Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
     DGeomHelpers::EmbedParameters ps = DGeomHelpers::ETKDGv3;
-    ps.randomSeed = 0xf00d;
-    ps.trackFailures = true;
-    ps.maxIterations = 10;
     ps.randomSeed = 42;
-    auto cids = DGeomHelpers::EmbedMultipleConfs(*mol, 20, ps);
-
-    DGeomHelpers::EmbedParameters ps2 = ps;
-    ps2.numThreads = 4;
-
-    auto cids2 = DGeomHelpers::EmbedMultipleConfs(*mol, 20, ps2);
-    CHECK(cids2 == cids);
-
-    CHECK(ps.failures == ps2.failures);
+    ps.maxIterations = 1;
+    auto cid = DGeomHelpers::EmbedMolecule(*mol, ps);
+    CHECK(cid >= 0);
+  }
+  SECTION("no ring") {
+    auto mol = "N[C@H-]C"_smiles;
+    REQUIRE(mol);
+    MolOps::addHs(*mol);
+    mol->getAtomWithIdx(1)->setChiralTag(Atom::ChiralType::CHI_TETRAHEDRAL_CCW);
+    DGeomHelpers::EmbedParameters ps = DGeomHelpers::ETKDGv3;
+    ps.randomSeed = 42;
+    ps.maxIterations = 1;
+    auto cid = DGeomHelpers::EmbedMolecule(*mol, ps);
+    CHECK(cid >= 0);
   }
-#endif
 }
\ No newline at end of file