Repository: rdkit/rdkit — pull request #5998 (cpp)
Title: Fix disconnection of magnesium acetate in covalent form.


#### Reference Issue
Fixes #5997


#### What does this implement/fix? Explain your changes.
Skips valence of -1 in metal valence list when patching charges after disconnection.

#### Any other comments?



Resolved issues:
title: Incorrect disconnection of CC(=O)O[Mg]OC(=O)C
body: **Describe the bug**
The metal disconnector gives CC(=O)[O-].CC(=O)[O-].[MgH2] when disconnecting CC(=O)O[Mg]OC(=O)C

**To Reproduce**
```
from rdkit import Chem
from rdkit.Chem.MolStandardize import rdMolStandardize

mol = Chem.MolFromSmiles("CC(=O)O[Mg]OC(=O)C")
md = rdMolStandardize.MetalDisconnector()
nm = md.Disconnect(mol)
print(Chem.MolToSmiles(nm))
```
**Expected behaviour**
I expected to get CC(=O)[O-].CC(=O)[O-].[Mg+2]

**Configuration (please complete the following information):**
 - RDKit version: 2022.09.4
 - OS: macOS Monterey 12.6.2 M1 chip
 - Python version (if relevant): 3.10.8
 - Are you using conda? mamba
 - 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?  I also see this when I build from the master branch.

**Additional context**
 I think it is because the allowed valence of -1 (any valence) was recently added to Mg's valence list to accommodate Grignard
reagents expressed in the form [CH3-]->[Mg+2]<-[Cl-].
The Ba analogue of the original input structure gives an analogous erroneous disconnected structure and also has -1 in its valence list.
I will work on a fix.



Hints:

Fix patch:
diff --git a/Code/GraphMol/MolStandardize/Metal.cpp b/Code/GraphMol/MolStandardize/Metal.cpp
index d3e2e7ee96b..974ef4f3220 100644
--- a/Code/GraphMol/MolStandardize/Metal.cpp
+++ b/Code/GraphMol/MolStandardize/Metal.cpp
@@ -21,10 +21,10 @@ using namespace std;
 using namespace RDKit;
 namespace RDKit {
 class RWMol;
+
 class ROMol;
 
 namespace MolStandardize {
-
 MetalDisconnector::MetalDisconnector()
     : metal_nof(
           SmartsToMol("[Li,Na,K,Rb,Cs,Fr,Be,Mg,Ca,Sr,Ba,Ra,Sc,Ti,V,Cr,Mn,Fe,Co,"
@@ -181,6 +181,13 @@ void MetalDisconnector::disconnect(RWMol &mol) {
     auto currentFc = a->getFormalCharge();
     const auto &valens =
         PeriodicTable::getTable()->getValenceList(a->getAtomicNum());
+    // valens should have at least -1 in it, as the atom data is currently
+    // configured, so max_element should never return valens.end().
+    auto max_valence = *std::max_element(valens.begin(), valens.end());
+    // Don't go over the maximum real valence.
+    if (max_valence != -1 && currentFc >= max_valence) {
+      continue;
+    }
     int fcAfterCut = it->second;
     if (currentFc > 0) {
       // if the original formal charge on the metal was positive, we trust it
@@ -189,6 +196,11 @@ void MetalDisconnector::disconnect(RWMol &mol) {
     }
     if (!valens.empty() && valens.front() != -1) {
       for (auto v = valens.begin(); v != valens.end(); ++v) {
+        // Some metals (e.g. Mg and Ba) have -1 as a final catchall, which
+        // is unhelpful for this.
+        if (*v == -1) {
+          continue;
+        }
         if (fcAfterCut > *v) {
           auto next = v + 1;
           if (next != valens.end() && fcAfterCut >= *v) {
@@ -211,6 +223,5 @@ void MetalDisconnector::disconnect(RWMol &mol) {
     a->updatePropertyCache();
   }
 }
-
 }  // namespace MolStandardize
 }  // namespace RDKit


Test patch:
diff --git a/Code/GraphMol/MolStandardize/test1.cpp b/Code/GraphMol/MolStandardize/test1.cpp
index de8a9a9d32a..445480ff43b 100644
--- a/Code/GraphMol/MolStandardize/test1.cpp
+++ b/Code/GraphMol/MolStandardize/test1.cpp
@@ -33,6 +33,12 @@ void testCleanup() {
     RWMOL_SPTR res(MolStandardize::cleanup(*m, params));
     TEST_ASSERT(MolToSmiles(*res) == "CCC(=O)[O-].[Na+]");
   }
+  {
+    // Github 5997
+    auto m = "CC(=O)O[Mg]OC(=O)C"_smiles;
+    auto res(MolStandardize::cleanup(*m, params));
+    TEST_ASSERT(MolToSmiles(*res) == "CC(=O)[O-].CC(=O)[O-].[Mg+2]");
+  }
 
   // Test metal ion is untouched during standardize.
   {
@@ -108,6 +114,13 @@ void testMetalDisconnector() {
 
   MolStandardize::MetalDisconnector md;
   unsigned int failedOp;
+  {
+    auto m(SmilesToMol("[O-]C(=O)C.[Mg+2][O-]C(=O)C", 0, false));
+    MolOps::sanitizeMol(*m, failedOp, MolOps::SANITIZE_CLEANUP);
+    TEST_ASSERT(m);
+    md.disconnect(*m);
+    TEST_ASSERT(MolToSmiles(*m) == "CC(=O)[O-].CC(=O)[O-].[Mg+2]");
+  }
 
   // testing overloaded function
   {
@@ -181,6 +194,14 @@ void testMetalDisconnector() {
     md.disconnect(*m);
     TEST_ASSERT(MolToSmiles(*m) == "CC(=O)[O-].[Na+]");
   }
+  // make sure that -1 as a valence is dealt with (Github 5997)
+  {
+    RWMOL_SPTR m(SmilesToMol("[O-]C(=O)C.[Mg+2][O-]C(=O)C", 0, false));
+    MolOps::sanitizeMol(*m, failedOp, MolOps::SANITIZE_CLEANUP);
+    TEST_ASSERT(m);
+    md.disconnect(*m);
+    TEST_ASSERT(MolToSmiles(*m) == "CC(=O)[O-].CC(=O)[O-].[Mg+2]");
+  }
 
   // test that badly specified dative bonds are perceived as such
   {