Repository: rdkit/rdkit — pull request #5737 (cpp)
Title: Fix hybridization of atoms with dative bonds

<!--
Thanks for contributing a pull request! 
-->
#### Reference Issue
<!-- Example: Fixes #1234 -->
Fixes #5736


#### What does this implement/fix? Explain your changes.
The hybridization of atoms from which dative bonds start is calculated wrong. Electrons are double-counted, once in `numRadicals` or `numLonePairs` and again as part of a dative bond in `deg`.
Here, I try to account for that, removing 2 radicals or one lone pair if a dative bond starts at this atom.


#### Any other comments?
This is probably not nice code, I have no experience with C++ so any feedback is welcome.


Resolved issues:
title: Wrong hybridization of atoms with dative bonds
body: **Describe the bug**
The hybridization of atoms from which a dative bond starts is wrong. When calculating `numBondsPlusLonePairs` (in `ConjugHybrid.cpp`) electrons are double counted, once in `numRadicals` or `numLonePairs` and also as part of a dative bond in `deg`.
This leads to a hybridization which is 'too high'.

**To Reproduce**
Steps to reproduce the behavior, feel free to paste a bit of Python in here.

```python
mol1 = Chem.MolFromSmiles('N1(C)C=CN(C)[C]1->[Pd@SP2]<-[C]1N(C)C=CN(C)1')
# carbene atomidx 6 and 8
print(mol1.GetAtomWithIdx(6).GetHybridization())
# >> SP3D
```
and
```python
mol2 = Chem.MolFromSmiles('P(F)(F)(F)->[Pd@SP2]<-P(F)(F)F')
# phosphine atomidx 0 and 5
print(mol2.GetAtomWithIdx(0).GetHybridization())
# >> SP3D
```


**Expected behavior**
A clear and concise description of what you expected to happen.

For the carbene atoms in `mol1` I would expect `SP2` hybridization, for the phosphor atom of the phosphine in `mol2` I would expect `SP3` hybridization.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Configuration (please complete the following information):**
 - RDKit version: 2022.09.1
 - OS: Ubuntu 22.04
 - Python version (if relevant): 3.9
 - Are you using conda? Yes
 - If you are using conda, which channel did you install the rdkit from? 
 - If you are not using conda: how did you install the RDKit?

**Additional context**
Add any other context about the problem here.


Hints:

Fix patch:
diff --git a/Code/GraphMol/ConjugHybrid.cpp b/Code/GraphMol/ConjugHybrid.cpp
index 7aa63ee4ecf..cee241bca7b 100644
--- a/Code/GraphMol/ConjugHybrid.cpp
+++ b/Code/GraphMol/ConjugHybrid.cpp
@@ -71,6 +71,22 @@ void markConjAtomBonds(Atom *at) {
   }
 }
 
+int countAtomStartsDativeBond(const Atom *at) {
+  // counts number of dative bonds starting from Atom
+  PRECONDITION(at, "bad atom");
+  long count = 0;
+  auto &mol = at->getOwningMol();
+  for (const auto &nbri : boost::make_iterator_range(mol.getAtomBonds(at))) {
+    auto bnd = mol[nbri];
+    if (bnd->getBondType() == 17) {
+      if (bnd->getBeginAtom() == at) {
+        count += 1;
+      }
+    }
+  }
+  return count;
+}
+
 int numBondsPlusLonePairs(Atom *at) {
   PRECONDITION(at, "bad atom");
   int deg = at->getTotalDegree();
@@ -96,9 +112,25 @@ int numBondsPlusLonePairs(Atom *at) {
     // about radicals:
     int numRadicals = at->getNumRadicalElectrons();
     int numLonePairs = (numFreeElectrons - numRadicals) / 2;
+    // correct for electrons in dative bond
+    int ndative = countAtomStartsDativeBond(at);
+    assert(ndative <= (numLonePairs + 2 * numRadicals));
+    for (int i = 1; i <= ndative; ++i) {
+      if (numRadicals >= 2) {
+        numRadicals -= 2;
+      } else {
+        numLonePairs -= 1;
+      }
+    }
     return deg + numLonePairs + numRadicals;
   } else {
     int numLonePairs = numFreeElectrons / 2;
+    // correct for electrons in dative bond
+    assert(ndative <= (numLonePairs + 2 * numRadicals));
+    int ndative = countAtomStartsDativeBond(at);
+    for (int i = 1; i <= ndative; ++i) {
+      numLonePairs -= 1;
+    }
     return deg + numLonePairs;
   }
 }


Test patch:
diff --git a/Code/GraphMol/MolDraw2D/catch_tests.cpp b/Code/GraphMol/MolDraw2D/catch_tests.cpp
index 3f190dfffbd..9ac4fa42f69 100644
--- a/Code/GraphMol/MolDraw2D/catch_tests.cpp
+++ b/Code/GraphMol/MolDraw2D/catch_tests.cpp
@@ -658,7 +658,7 @@ TEST_CASE("dative bonds", "[drawing][organometallics]") {
     outs.close();
     check_file_hash("testDativeBonds_1.svg");
 
-    CHECK(text.find("d='M 122.5,88.4 L 85.6,88.4' "
+    CHECK(text.find("d='M 77.7,88.0 L 113.4,88.0' "
                     "style='fill:none;fill-rule:evenodd;stroke:#0000FF") !=
           std::string::npos);
   }
diff --git a/Code/GraphMol/molopstest.cpp b/Code/GraphMol/molopstest.cpp
index deea45d33c9..8b4aa7a2ba8 100644
--- a/Code/GraphMol/molopstest.cpp
+++ b/Code/GraphMol/molopstest.cpp
@@ -3342,6 +3342,24 @@ void testHybridization() {
     delete m;
   }
 
+  {
+    RWMol *m;
+    std::string smi = "[Pd]<-[C]1N(C)CCN(C)1";
+    m = SmilesToMol(smi);
+    TEST_ASSERT(m);
+    TEST_ASSERT(m->getAtomWithIdx(1)->getHybridization() == Atom::SP2);
+    delete m;
+  }
+
+  {
+    RWMol *m;
+    std::string smi = "[Pd]<-P";
+    m = SmilesToMol(smi);
+    TEST_ASSERT(m);
+    TEST_ASSERT(m->getAtomWithIdx(1)->getHybridization() == Atom::SP3);
+    delete m;
+  }
+
   BOOST_LOG(rdInfoLog) << "Finished" << std::endl;
 }