Repository: rdkit/rdkit — pull request #8417 (cpp)
Title: Fix indexing of heavy atoms in PubChemShape


#### Reference Issue
Fixes #8416 

#### What does this implement/fix? Explain your changes.
The loop was going over all atoms, but only updating coords for non-H atoms so gaps could be left in the array and values written over the end of the vector.

#### Any other comments?
I couldn't find a simple test that crashed it, so haven't added one.  However the test "Github #8096" now gives Tanimoto scores of 1.0 for shape and colour which I think is correct since the deuterium shouldn't be affecting things at all.  Previously, it caused an extra coordinate in the array which was left at its default value of 0.0, 0.0, 0.0 (atom index 4).
It is not impossible that whatever was done to fix #8096 didn't get to the root of the problem, or there were 2 problems.


Resolved issues:
title: Overwrite end of array in PubChemShape.cpp
body: 
**Describe the bug**
Core dump when passing molecule with hydrogens to PrepareConformer in PubChemShape.cpp

**To Reproduce**
I can't make a short reproducible that causes a crash.

**Expected behavior**
No crash in my code!

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Configuration (please complete the following information):**
 - RDKit version: built from master
 - OS: [e.g. Ubuntu 20.04] Ubuntu 22.04 and macOS latest
 - Python version (if relevant):
 - Are you using conda?
 - 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**
Fix imminent.

title: Tanimoto score exceeds 1 for deuterium-containing molecules in rdShapeAlign.AlignMol()
body: **Issue**  
rdShapeAlign.AlignMol() returns Tanimoto scores above 1 for deuterium-containing molecules, which is problematic because ChEMBL includes deuterated drugs, so others are also likely to encounter this issue. Tanimoto scores should always lie between 0 and 1.

**Root Cause**  
In `PubChemShape.cpp` (function `PrepareConformer`), the code indexes atom-related arrays using the full atom index `i` instead of using a dedicated counter for heavy atoms. This results in out-of-range memory writes ("memory overwrites") and corrupts data used to calculate Tanimoto scores.

**To Reproduce**
```python
from rdkit import Chem
from rdkit.Chem import rdShapeAlign, rdDistGeom

query_smi_2H = '[2H]c1ccccc1'  
query_smi    = 'c1ccccc1'      
ref_smi      = 'c1ccccc1'       

query_2H = Chem.AddHs(Chem.MolFromSmiles(query_smi_2H), addCoords=True)
query    = Chem.AddHs(Chem.MolFromSmiles(query_smi), addCoords=True)
ref_mol  = Chem.AddHs(Chem.MolFromSmiles(ref_smi), addCoords=True)


params = rdDistGeom.ETKDGv3()
params.randomSeed = 0xa100f
for mol in [query_2H, query, ref_mol]:
    rdDistGeom.EmbedMultipleConfs(mol, numConfs=1, params=params)

print("With [2H]:", rdShapeAlign.AlignMol(query_2H, ref_mol))
print("Without [2H]:", rdShapeAlign.AlignMol(query, ref_mol))
```

```
With [2H]: (1.2137843687421104, 0.8742135141493095)
Without [2H]: (0.9999984471607044, 0.9999999704411953)
```

**Expected Behavior**  
Tanimoto scores should remain within the range [0,1].

**Actual Behavior**  
Scores exceed 1 when 2H atoms are present.


**Proposed Fix**  
Introduce a separate heavy-atom index counter to prevent out-of-bound writes:

```cpp
unsigned int heavyAtomIdx = 0; // Dedicated counter for heavy atoms
if (Z > 1) {
    rad_vector[heavyAtomIdx] = vdw_radii.at(Z); // Use heavyAtomIdx for indexing
    heavyAtomIdx++;
}
```

**Environment**
* RDKit version: 2024.09.3
* Build: py313hc8d2389_0 (conda-forge)
* OS: Ubuntu 24.04.1 LTS
* Python: 3.13.1
* Conda: Yes

**Additional Info**  
After applying this fix, the Tanimoto scores remain within the expected range. The updated [PubChemShape.cpp.txt](https://github.com/user-attachments/files/18132106/PubChemShape.cpp.txt) is attached for reference.
I am not a C++ coder, so I am not fully certain this is the optimal solution, but it resolved the issue for me.



Hints:

Fix patch:
diff --git a/External/pubchem_shape/PubChemShape.cpp b/External/pubchem_shape/PubChemShape.cpp
index 014fa36767b..e25b44c188c 100644
--- a/External/pubchem_shape/PubChemShape.cpp
+++ b/External/pubchem_shape/PubChemShape.cpp
@@ -283,15 +283,16 @@ ShapeInput PrepareConformer(const ROMol &mol, int confId, bool useColors) {
   res.shift = {-ave.x, -ave.y, -ave.z};
   res.coord.resize(nAlignmentAtoms * 3);
 
-  for (unsigned i = 0; i < nAtoms; ++i) {
+  for (unsigned i = 0, j = 0; i < nAtoms; ++i) {
     // use only non-H for alignment
     if (mol.getAtomWithIdx(i)->getAtomicNum() > 1) {
       RDGeom::Point3D pos = conformer.getAtomPos(i);
       pos -= ave;
 
-      res.coord[i * 3] = pos.x;
-      res.coord[(i * 3) + 1] = pos.y;
-      res.coord[(i * 3) + 2] = pos.z;
+      res.coord[j * 3] = pos.x;
+      res.coord[(j * 3) + 1] = pos.y;
+      res.coord[(j * 3) + 2] = pos.z;
+      ++j;
     }
   }
 
diff --git a/External/pubchem_shape/PubChemShape.hpp b/External/pubchem_shape/PubChemShape.hpp
index 245dba9d0e5..a55064f9d7f 100644
--- a/External/pubchem_shape/PubChemShape.hpp
+++ b/External/pubchem_shape/PubChemShape.hpp
@@ -2,6 +2,9 @@
 #include <map>
 #include <vector>
 
+#ifndef RDKIT_PUBCHEMSHAPE_GUARD
+#define RDKIT_PUBCHEMSHAPE_GUARD
+
 //! The input for the pubchem shape alignment code
 struct RDKIT_PUBCHEMSHAPE_EXPORT ShapeInput {
   std::vector<float> coord;
@@ -66,3 +69,5 @@ RDKIT_PUBCHEMSHAPE_EXPORT std::pair<double, double> AlignMolecule(
     int refConfId = -1, int fitConfId = -1, bool useColors = true,
     double opt_param = 1.0, unsigned int max_preiters = 10u,
     unsigned int max_postiters = 30u);
+
+#endif
\ No newline at end of file


Test patch:
diff --git a/External/pubchem_shape/test.cpp b/External/pubchem_shape/test.cpp
index 9dd34e2909b..737b0941508 100644
--- a/External/pubchem_shape/test.cpp
+++ b/External/pubchem_shape/test.cpp
@@ -278,7 +278,7 @@ TEST_CASE("Github #8096") {
     REQUIRE(m2);
     std::vector<float> matrix(12, 0.0);
     auto [nbr_st, nbr_ct] = AlignMolecule(*m1, *m2, matrix);
-    CHECK_THAT(nbr_st, Catch::Matchers::WithinAbs(0.940, 0.005));
-    CHECK_THAT(nbr_ct, Catch::Matchers::WithinAbs(0.902, 0.005));
+    CHECK_THAT(nbr_st, Catch::Matchers::WithinAbs(1.0, 0.005));
+    CHECK_THAT(nbr_ct, Catch::Matchers::WithinAbs(1.0, 0.005));
   }
 }