Repository: rdkit/rdkit — pull request #8164 (cpp)
Title: Fixes #8162

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

Resolved issues:
title: Cyanamide incorrectly matches methyl-imidazole when aromaticMatchesConjugated is set
body: 

**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?



Hints:

Fix patch:
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);


Test patch:
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