Repository: rdkit/rdkit — pull request #5077 (cpp)
Title: Fixes #5008: Add H and X specification to P tautomerization rules

The change is simple: make sure that the P involved in the tautomer shifts only has three neighbors and doesn't have an H (this is to avoid hypervalent P)


Resolved issues:
title: Bad tautomers produced for phosphorous compounds
body: **Describe the bug**
rdMolStandardize.TautomerEnumerator().Enumerate() gives tautomers that chemically are not possible and rdMolStandardize.TautomerEnumerator().Canonicalize() assigns the chemically non-plausible tautomer as the canonical tautomer.

**To Reproduce**
```
from rdkit import Chem
from rdkit.Chem.MolStandardize import rdMolStandardize

lig = Chem.MolFromSmiles("NP(=O)(O)N(CCCl)CCCl")
canonicalizer = rdMolStandardize.TautomerEnumerator()
tautomers = canonicalizer.Enumerate(lig)

"""
Tautomers generated:
Tautomer 1: N=P(O)(O)N(CCCl)CCCl
Tautomer 2: N=[PH](=O)(O)N(CCCl)CCCl
Tautomer 3: NP(=O)(O)N(CCCl)CCCl
Tautomer 4: N[PH](=O)(=O)N(CCCl)CCCl
"""

canonical_tautomer = canonicalizer.Canonicalize(lig)

# Canonical tautomer: N[PH](=O)(=O)N(CCCl)CCCl
```

**Expected behavior**
Tautomers 2 and 4 should not be returned and tautomer 4 should not be picked as the canonical tautomer.


**Configuration:**
 - RDKit version: 2021.09.4
 - OS: CentOS Linux 7, GNOME Version 3.28.2
 - Python version (if relevant): 3.9.7
 - Are you using conda?: Yes
 - If you are using conda, which channel did you install the rdkit from?: conda-forge


Hints:

Fix patch:
diff --git a/Code/GraphMol/MolStandardize/TautomerCatalog/tautomerTransforms.in b/Code/GraphMol/MolStandardize/TautomerCatalog/tautomerTransforms.in
index b8328122aae..ba4a030e9fb 100644
--- a/Code/GraphMol/MolStandardize/TautomerCatalog/tautomerTransforms.in
+++ b/Code/GraphMol/MolStandardize/TautomerCatalog/tautomerTransforms.in
@@ -49,7 +49,7 @@ const std::vector<
         std::make_tuple(
             std::string("1,3 heteroatom H shift"),
             std::string(
-                "[#7+0,S,O,Se,Te;!H0]-[#7X2,#6,#15]=[#7+0,#16,#8,Se,Te]"),
+                "[#7+0,S,O,Se,Te;!H0]-[#7X2,#6,#15X3H0]=[#7+0,#16,#8,Se,Te]"),
             std::string(""), std::string("")),
         std::make_tuple(
             std::string("1,5 aromatic heteroatom H shift"),
@@ -135,9 +135,9 @@ const std::vector<
                         std::string("[N+!H0]#[C-]"), std::string("#"),
                         std::string("-+")),
         std::make_tuple(std::string("phosphonic acid f"),
-                        std::string("[OH]-[PH0]"), std::string("="),
+                        std::string("[OH]-[PX3H0]"), std::string("="),
                         std::string("")),
         std::make_tuple(std::string("phosphonic acid r"),
-                        std::string("[PH]=[O]"), std::string("-"),
+                        std::string("[PX4H]=[O]"), std::string("-"),
                         std::string(""))};
 }  // namespace defaults
\ No newline at end of file


Test patch:
diff --git a/Code/GraphMol/MolStandardize/catch_tests.cpp b/Code/GraphMol/MolStandardize/catch_tests.cpp
index 2e2b157e028..3067192bdee 100644
--- a/Code/GraphMol/MolStandardize/catch_tests.cpp
+++ b/Code/GraphMol/MolStandardize/catch_tests.cpp
@@ -843,4 +843,28 @@ TEST_CASE(
     CHECK(m2->getAtomWithIdx(0)->getFormalCharge() == 2);
     CHECK(m2->getAtomWithIdx(1)->getFormalCharge() == -1);
   }
+}
+
+TEST_CASE("Github #5008: bad tautomers for phosphorous compounds") {
+  SECTION("as reported") {
+    auto m = "NP(=O)(O)N(CCCl)CCCl"_smiles;
+    REQUIRE(m);
+    MolStandardize::TautomerEnumerator tenum;
+    auto tauts = tenum.enumerate(*m);
+    CHECK(tauts.size() == 1);
+  }
+  SECTION("P which should tautomerize") {
+    auto m = "CP(O)C"_smiles;
+    REQUIRE(m);
+    MolStandardize::TautomerEnumerator tenum;
+    auto tauts = tenum.enumerate(*m);
+    CHECK(tauts.size() == 2);
+  }
+  SECTION("Canonical version") {
+    auto m = "CP(O)C"_smiles;
+    REQUIRE(m);
+    std::unique_ptr<RWMol> ct(MolStandardize::canonicalTautomer(m.get()));
+    REQUIRE(ct);
+    CHECK(MolToSmiles(*ct) == "C[PH](C)=O");
+  }
 }
\ No newline at end of file
diff --git a/Code/GraphMol/MolStandardize/test_data/tautomerTransforms.in b/Code/GraphMol/MolStandardize/test_data/tautomerTransforms.in
index 3559f73345d..74509ded661 100644
--- a/Code/GraphMol/MolStandardize/test_data/tautomerTransforms.in
+++ b/Code/GraphMol/MolStandardize/test_data/tautomerTransforms.in
@@ -9,7 +9,7 @@ special imine f	[N!H0]-[C]=[CX3R0]
 special imine r	[CX4!H0]-[c]=[n]
 1,3 aromatic heteroatom H shift f	[#7+0!H0]-[#6R1]=[O,#7X2+0]
 1,3 aromatic heteroatom H shift 	[O,#7+0;!H0]-[#6R1]=[#7+0X2]
-1,3 heteroatom H shift	[#7+0,S,O,Se,Te;!H0]-[#7X2,#6,#15]=[#7+0,#16,#8,Se,Te]
+1,3 heteroatom H shift	[#7+0,S,O,Se,Te;!H0]-[#7X2,#6,#15X3H0]=[#7+0,#16,#8,Se,Te]
 1,5 aromatic heteroatom H shift	[#7+0,#16,#8;!H0]-[#6,#7]=[#6]-[#6,#7]=[#7+0,#16,#8;H0]
 1,5 aromatic heteroatom H shift 	[#7+0,#16,#8,Se,Te;!H0]-[#6,nX2]=[#6,nX2]-[#6,#7X2]=[#7X2+0,S,O,Se,Te]
 1,5 aromatic heteroatom H shift r	[#7+0,S,O,Se,Te;!H0]-[#6,#7X2]=[#6,nX2]-[#6,nX2]=[#7+0,#16,#8,Se,Te]
@@ -33,5 +33,5 @@ formamidinesulfinic acid f	[O,N;!H0]-[C]=[S,Se,Te;v6]=[O]	=--
 formamidinesulfinic acid r	[O!H0]-[S,Se,Te;v4]-[C]=[O,N]	==-
 isocyanide f	[C-0!H0]#[N+0]	#	-+
 isocyanide r	[N+!H0]#[C-]	#	-+
-phosphonic acid f	[OH]-[PH0]	=
-phosphonic acid r	[PH]=[O]	-
+phosphonic acid f	[OH]-[PX3H0]	=
+phosphonic acid r	[PX4H]=[O]	-