Task: Fixes #8405 (rdkit/rdkit#8406)

org: rdkit
repo: rdkit
pr_number: 8406
issue_title: Fixes #8405
issue_body: There's another special case that we need to cover. This adds it, along with some documentation.
resolved_issues: number: 8405
title: Some tautomer pairs generating different HetAtomTautomerv2 hashes
body: I mentioned this issue in https://github.com/rdkit/rdkit/pull/8368.

Since that PR was merged, these two pairs of known tautomers generate different hashes:

```
In [5]: from rdkit import  Chem
   ...: from rdkit.Chem import rdMolHash

In [6]: pair1 = ("OC1=CC(C)N=C(C1)O", "O=C1CC(C)NC(=O)C1")

In [7]: pair2 = (r'Cc2cc(C)nc(N/C(N)=N\c1ccc(Cl)c(C(F)(F)F)c1)n2', r'Cc2cc(C)nc(/N=C(N)/Nc1ccc(Cl)c(C(F)(F)F)c1)n2')

In [8]: # we'd expect both tautomers to produce the same hash, which should be deduplicated by the set
   ...: set(rdMolHash.MolHash(Chem.MolFromSmiles(s), rdMolHash.HashFunction.HetAtomTautomerv2) for s in pair1)
Out[8]: 
{'[CH3]-[CH]1-[C]:[C](:[O]):[C]-[C](:[O]):[N]-1_5_0',
 '[CH3]-[C]1:[N]:[C](:[O])-[C]:[C](:[O]):[C]-1_6_0'}

In [9]: # these should also produce a single hash
   ...: set(rdMolHash.MolHash(Chem.MolFromSmiles(s), rdMolHash.HashFunction.HetAtomTautomerv2) for s in pair2)
Out[9]: 
{'[C]:[C]1:[C]:[C](:[C]):[N]:[C](:[N]-[C](-[NH2]):[N]:[C]2:[C]:[C]:[C](-[Cl]):[C](-[C](-[F])(-[F])-[F]):[C]:2):[N]:1_11_0',
 '[C]:[C]1:[C]:[C](:[C]):[N]:[C](:[N]:[C](-[NH2])-[N]:[C]2:[C]:[C]:[C](-[Cl]):[C](-[C](-[F])(-[F])-[F]):[C]:2):[N]:1_11_0'}
```

Also:
```
In [3]: rdMolHash.MolHash(Chem.MolFromSmiles('O=C1CC(C)NC(=O)C1'),rdMolHash.HashFunction.HetAtomTautomerv2)
Out[3]: '[CH3]-[CH]1-[C]:[C](:[O]):[C]-[C](:[O]):[N]-1_5_0'

In [4]: rdMolHash.MolHash(Chem.MolFromSmiles('OC1=CC(C)N=C(C1)O'),rdMolHash.HashFunction.HetAtomTautomerv2)
Out[4]: '[CH3]-[C]1:[N]:[C](:[O])-[C]:[C](:[O]):[C]-1_6_0'

In [5]: rdMolHash.MolHash(Chem.MolFromSmiles('O=C1CC(C)N=C(C1)O'),rdMolHash.HashFunction.HetAtomTautomerv2)
Out[5]: '[CH3]-[CH]1-[C]:[C](:[O]):[C]-[C](:[O]):[N]-1_5_0'

In [6]: _5 == _3
Out[6]: True

In [7]: rdMolHash.MolHash(Chem.MolFromSmiles('OC1=CC(C)NC(C1)=O'),rdMolHash.HashFunction.HetAtomTautomerv2)
Out[7]: '[CH3]-[CH]1-[C]:[C](:[O]):[C]-[C](:[O]):[N]-1_5_0'

In [8]: _7 == _3
Out[8]: True
```


Reference solution:
fix_patch: diff --git a/Code/GraphMol/MolHash/hashfunctions.cpp b/Code/GraphMol/MolHash/hashfunctions.cpp
index f27e02cceeb..43a2209b4b3 100644
--- a/Code/GraphMol/MolHash/hashfunctions.cpp
+++ b/Code/GraphMol/MolHash/hashfunctions.cpp
@@ -395,7 +395,7 @@ std::vector<std::uint64_t> getBondFlags(const ROMol &mol) {
       "[C]-[c](:[o,n,s]):[o,n,s]",  //< a limited version of handling this for
                                     // aromatic systems
       "*[SD4](=*)=*",               // sulfates, sulfonyls, etc.
-      "*=[SD4](=*)*",               // the other side
+      "*=[SD4;$(S(=*)=*)](*)*",     // the other side
       "*-[H0]=,#[C,N]=,#*",         // isocyanates, azides, et al
       "*[#7+]-[O-]",                // nitro groups and aromatic n-oxides
       "[O,S;H]-P=O",                // phosphoric acid, etc.
@@ -757,6 +757,73 @@ std::string TautomerHashv2(RWMol *mol, bool proto, bool useCXSmiles,
 #endif
     }
   }
+
+  /*  Another situation we need to correct for is the following:
+
+   These two tautomers should be recognized as equivalent:
+
+      O8          O9
+      ||          |
+   C1-C2-C3-C4-N5=C6-C7
+
+      O8          O9
+      |           |
+   C1-C2=C3-C4-N5=C6-C7
+
+   In the first case neither the N5-C4 nor the C3-C4 bond is recognized as being
+   tautomeric since C4 does not have two conjugated neighbors, in the second
+   case the N5-C4 bond is recognized since C4 does have two conjugated
+   neighbors.
+
+
+   In order to fix this and other similar cases we need to check the neighboring
+   atoms of each start bond and check to see they are connected to other atoms
+   which are involved in bonds which are either in a tautomeric system already
+   (i.e. bondsToModify is set for them) or are start bonds.
+
+   So in the case of the first tautomer above, here we're going to check N5-C4,
+   and recognize that C4 has a neighbor (C3) with an adjacent tautomeric bond
+   (C3-C2). So we'll flag N5-C4 as being part of the tautomeric system.
+
+  */
+  for (const auto bptr : mol->bonds()) {
+    // If this is not a possible starting bond,
+    // then skip it
+    if (!startBonds[bptr->getIdx()]) {
+      continue;
+    }
+
+    for (const auto atm :
+         std::vector<const Atom *>{bptr->getBeginAtom(), bptr->getEndAtom()}) {
+      for (const auto nbrBond : mol->atomBonds(atm)) {
+        if (nbrBond == bptr || bondsToModify[nbrBond->getIdx()]) {
+          continue;
+        }
+        const auto oatom = nbrBond->getOtherAtom(atm);
+        if (!oatom->getTotalNumHs()) {
+          continue;
+        }
+        unsigned int numModifiedNeighbors = 0;
+        for (const auto nbr : mol->atomNeighbors(oatom)) {
+          if (nbr == atm) {
+            continue;
+          }
+          for (const auto nbnd : mol->atomBonds(nbr)) {
+            if (bondsToModify[nbnd->getIdx()] || startBonds[nbnd->getIdx()]) {
+              ++numModifiedNeighbors;
+              break;
+            }
+          }
+          if (numModifiedNeighbors) {
+            break;
+          }
+        }
+        if (numModifiedNeighbors) {
+          bondsToModify.set(nbrBond->getIdx());
+        }
+      }
+    }
+  }
 #ifdef VERBOSE_HASH
   std::cerr << "FINAL: " << bondsToModify << std::endl;
 #endif