Repository: rdkit/rdkit — pull request #7657 (cpp)
Title: Fixes #7364

make sure we only use the H-stripped molecule in the BCUT calculation

Resolved issues:
title: Pre-condition violations for BCUT descriptor calculations
body: **Describe the bug**
I receive pre-condition violations for every molecule I've tested when calculating BCUT descriptors. Our standardization code adds H's to each molecule first. The violations print out in my notebook multiple times per molecule and (sometimes multiple) per descriptor, resulting in thousands of outputs and eventually crashing my notebook. The descriptors that cause it are:
```
('BCUT2D_MWHI',
 'BCUT2D_MWLOW',
 'BCUT2D_CHGHI',
 'BCUT2D_CHGLO',
 'BCUT2D_LOGPHI',
 'BCUT2D_LOGPLOW',
 'BCUT2D_MRHI',
 'BCUT2D_MRLOW')
```
And the violation repeatedly reads:

```
****
Pre-condition Violation
bad result vector size
Violation occurred on line 40 in file /project/build/temp.linux-aarch64-cpython-39/rdkit/Code/GraphMol/Descriptors/Crippen.cpp
Failed Expression: logpContribs.size() == mol.getNumAtoms() && mrContribs.size() == mol.getNumAtoms()
----------
Stacktrace:
----------
****
```
It also seems to be somewhat dependent on first calling AddHs and/or passing the descriptors in an iterable vs a single descriptor, although I don't know why that's the case.

**To Reproduce**
```
smiles='C#CCC(C(=O)c1ccc(C)cc1)N1CCCC1'

from rdkit import Chem
mol=Chem.MolFromSmiles(smiles)
mol=Chem.AddHs(mol)

from rdkit.Chem import Descriptors
from rdkit.ML.Descriptors import MoleculeDescriptors
calc = MoleculeDescriptors.MolecularDescriptorCalculator([x[0] for x in Descriptors._descList])

desc_names = calc.GetDescriptorNames()

calc=MoleculeDescriptors.MolecularDescriptorCalculator(['BCUT2D_MWHI'])
descriptors = calc.CalcDescriptors(mol)
```
**Expected behavior**
I'd like the warnings not to appear and not crash my notebook. 

**Screenshots**
<img width="808" alt="image" src="https://github.com/rdkit/rdkit/assets/64031328/44e91a75-a5ce-4af1-8ac2-20d842dcaf71">

**Configuration (please complete the following information):**
 - RDKit version: 2023.9.5 (It happens for 2023.9.X but not 2023.3.3)
 - OS: [e.g. Ubuntu 20.04] Apple M2 Max running Ubuntu Lunar 23.04 ARM64 on a VM.
 - Python version (if relevant):
 Python 3.9.17 (main, Aug 21 2023, 10:36:40) 
 [GCC 12.3.0] on linux
 - Are you using conda? No
 - If you are using conda, which channel did you install the rdkit from? NA
 - If you are not using conda: how did you install the RDKit? `pip install rdkit==2023.9.5`

**Additional context**
I believe it is NOT ARM-specific since my colleagues see the same issue on regular Linux/Ubuntu with the 2023.9.X versions.

Hints:

Fix patch:
diff --git a/Code/GraphMol/Descriptors/BCUT.cpp b/Code/GraphMol/Descriptors/BCUT.cpp
index 35ec90b9627..8b160c21095 100644
--- a/Code/GraphMol/Descriptors/BCUT.cpp
+++ b/Code/GraphMol/Descriptors/BCUT.cpp
@@ -68,8 +68,8 @@ std::unique_ptr<Eigen::MatrixXd> make_burden(const ROMol &m) {
 
 std::pair<double, double> BCUT2D(std::unique_ptr<Eigen::MatrixXd> &burden,
                                  const std::vector<double> &atom_props) {
-  if(atom_props.empty()) {
-    return std::pair<double,double>(0.0,0.0);
+  if (atom_props.empty()) {
+    return std::pair<double, double>(0.0, 0.0);
   }
 
   for (unsigned int i = 0; i < atom_props.size(); ++i) {
@@ -107,16 +107,15 @@ std::pair<double, double> BCUT2D(const ROMol &m,
 }
 
 std::vector<double> BCUT2D(const ROMol &m) {
-  unsigned int num_atoms = m.getNumAtoms();
-  if(num_atoms == 0) {
-      return std::vector<double>(NUM_BCUTS, 0.0);
+  if (!m.getNumAtoms()) {
+    return std::vector<double>(NUM_BCUTS, 0.0);
   }
-
-  std::unique_ptr<ROMol> mol(MolOps::removeHs(m));
+  std::unique_ptr<ROMol> mol(MolOps::removeAllHs(m));
+  unsigned int numAtoms = mol->getNumAtoms();
   std::vector<double> masses;
   std::vector<double> charges;
-  masses.reserve(num_atoms);
-  charges.reserve(num_atoms);
+  masses.reserve(numAtoms);
+  charges.reserve(numAtoms);
 
   RDKit::computeGasteigerCharges(*mol, 12, true);
   for (auto &atom : mol->atoms()) {
@@ -125,13 +124,12 @@ std::vector<double> BCUT2D(const ROMol &m) {
         atom->getProp<double>(common_properties::_GasteigerCharge));
   }
 
-  std::vector<double> slogp(num_atoms, 0.0);
-  std::vector<double> cmr(num_atoms, 0.0);
+  std::vector<double> slogp(numAtoms, 0.0);
+  std::vector<double> cmr(numAtoms, 0.0);
   getCrippenAtomContribs(*mol, slogp, cmr);
-
   // polarizability? - need model
   // slogp?  sasa?
-  auto burden = make_burden(m);
+  auto burden = make_burden(*mol);
   auto atom_bcut = BCUT2D(burden, masses);
   auto gasteiger = BCUT2D(burden, charges);
   auto logp = BCUT2D(burden, slogp);


Test patch:
diff --git a/Code/GraphMol/Descriptors/catch_tests.cpp b/Code/GraphMol/Descriptors/catch_tests.cpp
index 8c512bc732c..bcac0b2dd7c 100644
--- a/Code/GraphMol/Descriptors/catch_tests.cpp
+++ b/Code/GraphMol/Descriptors/catch_tests.cpp
@@ -20,6 +20,7 @@
 #include <GraphMol/Descriptors/OxidationNumbers.h>
 #include <GraphMol/Descriptors/PMI.h>
 #include <GraphMol/Descriptors/DCLV.h>
+#include <GraphMol/Descriptors/BCUT.h>
 
 using namespace RDKit;
 
@@ -605,3 +606,16 @@ TEST_CASE("DCLV") {
     CHECK(dclv.getVDWVolume() == Catch::Approx(139.97).epsilon(0.05));
   }
 }
+
+TEST_CASE("Github #7364: BCUT descriptors failing for moleucles with Hs") {
+  SECTION("as reported") {
+    auto m = "CCOC#CCC(C(=O)c1ccc(C)cc1)N1CCCC1"_smiles;
+    REQUIRE(m);
+    RWMol m2 = *m;
+    MolOps::addHs(m2);
+    auto ref = Descriptors::BCUT2D(*m);
+    auto val = Descriptors::BCUT2D(m2);
+    CHECK(ref.size() == val.size());
+    CHECK(ref == val);
+  }
+}
\ No newline at end of file