Implements #8222 adds react_idx (index of reactant) to product atoms (#8231)

* Implements #8222 adds react_idx (index of reactant) to product atoms

* Remove react_idx from mol enumerator products

* Response to review, add test to ensure product only atoms aren't tagged

* Reenable tests
This commit is contained in:
Brian Kelley
2025-02-06 11:07:55 -05:00
committed by greg landrum
parent a73ae0a1ff
commit 492e150997
5 changed files with 55 additions and 10 deletions

View File

@@ -819,7 +819,8 @@ void checkProductChirality(Atom::ChiralType reactantChirality,
void setReactantAtomPropertiesToProduct(Atom *productAtom,
const Atom &reactantAtom,
bool setImplicitProperties) {
bool setImplicitProperties,
unsigned int reactantId) {
// which properties need to be set from the reactant?
if (productAtom->getAtomicNum() <= 0 ||
productAtom->hasProp(common_properties::_MolFileAtomQuery)) {
@@ -838,8 +839,6 @@ void setReactantAtomPropertiesToProduct(Atom *productAtom,
if (productAtom->hasProp(common_properties::_MolFileRLabel)) {
productAtom->clearProp(common_properties::_MolFileRLabel);
}
productAtom->setProp<unsigned int>(common_properties::reactantAtomIdx,
reactantAtom.getIdx());
productAtom->setProp(WAS_DUMMY, true);
} else {
// remove bookkeeping labels (if present)
@@ -849,6 +848,8 @@ void setReactantAtomPropertiesToProduct(Atom *productAtom,
}
productAtom->setProp<unsigned int>(common_properties::reactantAtomIdx,
reactantAtom.getIdx());
productAtom->setProp<unsigned int>(common_properties::reactantIdx,
reactantId);
if (setImplicitProperties) {
updateImplicitAtomProperties(productAtom, &reactantAtom);
}
@@ -905,7 +906,7 @@ void addMissingProductBonds(const Bond &origB, RWMOL_SPTR product,
void addMissingProductAtom(const Atom &reactAtom, unsigned reactNeighborIdx,
unsigned prodNeighborIdx, RWMOL_SPTR product,
const ROMol &reactant,
ReactantProductAtomMapping *mapping) {
ReactantProductAtomMapping *mapping, unsigned int reactantId) {
Atom *newAtom = nullptr;
if (!reactAtom.hasQuery()) {
newAtom = new Atom(reactAtom);
@@ -915,6 +916,8 @@ void addMissingProductAtom(const Atom &reactAtom, unsigned reactNeighborIdx,
unsigned reactAtomIdx = reactAtom.getIdx();
newAtom->setProp<unsigned int>(common_properties::reactantAtomIdx,
reactAtomIdx);
newAtom->setProp<unsigned int>(common_properties::reactantIdx,
reactantId);
unsigned productIdx = product->addAtom(newAtom, false, true);
mapping->reactProdAtomMap[reactAtomIdx].push_back(productIdx);
mapping->prodReactAtomMap[productIdx] = reactAtomIdx;
@@ -939,7 +942,7 @@ void addReactantNeighborsToProduct(
const ROMol &reactant, const Atom &reactantAtom, RWMOL_SPTR product,
boost::dynamic_bitset<> &visitedAtoms,
std::vector<const Atom *> &chiralAtomsToCheck,
ReactantProductAtomMapping *mapping) {
ReactantProductAtomMapping *mapping, unsigned int reactantId) {
std::list<const Atom *> atomStack;
atomStack.push_back(&reactantAtom);
@@ -1048,7 +1051,7 @@ void addReactantNeighborsToProduct(
const Atom *neighbor = reactant.getAtomWithIdx(*nbrIdx);
for (unsigned int i : lReactantAtomProductIndex) {
addMissingProductAtom(*neighbor, lreactIdx, i, product, reactant,
mapping);
mapping, reactantId);
}
// update the stack:
atomStack.push_back(neighbor);
@@ -1323,7 +1326,8 @@ void addReactantAtomsAndBonds(const ChemicalReaction &rxn, RWMOL_SPTR product,
const ROMOL_SPTR reactantSptr,
const MatchVectType &match,
const ROMOL_SPTR reactantTemplate,
Conformer *productConf) {
Conformer *productConf,
unsigned int reactantId) {
// start by looping over all matches and marking the reactant atoms that
// have already been "added" by virtue of being in the product. We'll also
// mark "skipped" atoms: those that are in the match, but not in this
@@ -1363,7 +1367,7 @@ void addReactantAtomsAndBonds(const ChemicalReaction &rxn, RWMOL_SPTR product,
unsigned productAtomIdx = mapping->reactProdAtomMap[reactantAtomIdx][i];
Atom *productAtom = product->getAtomWithIdx(productAtomIdx);
setReactantAtomPropertiesToProduct(productAtom, *reactantAtom,
rxn.getImplicitPropertiesFlag());
rxn.getImplicitPropertiesFlag(), reactantId);
if (reactantAtom->hasQuery()) {
// finally: if the reactant atom is a query we should copy over the
// query information. We need to replace the atom to do this
@@ -1375,7 +1379,7 @@ void addReactantAtomsAndBonds(const ChemicalReaction &rxn, RWMOL_SPTR product,
}
// now traverse:
addReactantNeighborsToProduct(*reactant, *reactantAtom, product,
visitedAtoms, chiralAtomsToCheck, mapping);
visitedAtoms, chiralAtomsToCheck, mapping, reactantId);
// now that we've added all the reactant's neighbors, check to see if
// it is chiral in the reactant but is not in the reaction. If so
@@ -1525,7 +1529,7 @@ generateOneProductSet(const ChemicalReaction &rxn,
for (auto iter = rxn.beginReactantTemplates();
iter != rxn.endReactantTemplates(); ++iter, reactantId++) {
addReactantAtomsAndBonds(rxn, product, reactants.at(reactantId),
reactantsMatch.at(reactantId), *iter, conf);
reactantsMatch.at(reactantId), *iter, conf, reactantId);
}
if (doConfs) {

View File

@@ -2228,3 +2228,41 @@ TEST_CASE("Structural fingerprints values") {
CHECK(TanimotoSimilarity(*fp1, *fp2) == 0.4);
}
}
TEST_CASE(
"Github #6015: react_idx property") {
SECTION("Ensure that atoms are marked with their reactant idx") {
std::unique_ptr<ChemicalReaction> rxn{
RxnSmartsToChemicalReaction("[C:1][O:2].[N:3][S:4]>>[C:1][O:2][N:3][S:4]C")};
REQUIRE(rxn);
rxn->initReactantMatchers();
ROMOL_SPTR mol1(SmilesToMol("CO"));
ROMOL_SPTR mol2(SmilesToMol("NSP"));
std::vector<ROMOL_SPTR> reactants{mol1, mol2};
REQUIRE(reactants.size() == 2);
REQUIRE(reactants[0]);
REQUIRE(reactants[1]);
auto products = rxn->runReactants(reactants);
REQUIRE(products.size() == 1);
CHECK(products[0][0]->getAtomWithIdx(0)->getProp<unsigned int>("react_idx") == 0);
CHECK(products[0][0]->getAtomWithIdx(0)->getProp<unsigned int>("react_atom_idx") == 0);
CHECK(products[0][0]->getAtomWithIdx(1)->getProp<unsigned int>("react_idx") == 0);
CHECK(products[0][0]->getAtomWithIdx(1)->getProp<unsigned int>("react_atom_idx") == 1);
CHECK(products[0][0]->getAtomWithIdx(2)->getProp<unsigned int>("react_idx") == 1);
CHECK(products[0][0]->getAtomWithIdx(2)->getProp<unsigned int>("react_atom_idx") == 0);
CHECK(products[0][0]->getAtomWithIdx(3)->getProp<unsigned int>("react_idx") == 1);
CHECK(products[0][0]->getAtomWithIdx(3)->getProp<unsigned int>("react_atom_idx") == 1);
CHECK(products[0][0]->getAtomWithIdx(4)->hasProp("react_atom_idx") == false);
CHECK(products[0][0]->getAtomWithIdx(4)->hasProp("react_idx") == false);
CHECK(products[0][0]->getAtomWithIdx(5)->getProp<unsigned int>("react_idx") == 1);
CHECK(products[0][0]->getAtomWithIdx(5)->getProp<unsigned int>("react_atom_idx") == 2);
}
}

View File

@@ -70,6 +70,7 @@ void clearReactionProps(ROMol &mol) {
mol.clearComputedProps(includeRings);
for (auto atom : mol.atoms()) {
atom->clearProp(common_properties::reactantAtomIdx);
atom->clearProp(common_properties::reactantIdx);
atom->clearProp("was_dummy");
atom->clearProp(common_properties::reactionMapNum);
}

View File

@@ -58,6 +58,7 @@ const std::string _QueryMass = "_QueryMass";
const std::string _ReactionDegreeChanged = "_ReactionDegreeChanged";
const std::string reactantAtomIdx = "react_atom_idx";
const std::string reactionMapNum = "old_mapno";
const std::string reactantIdx = "react_idx";
const std::string _RingClosures = "_RingClosures";
const std::string _SLN_s = "_SLN_s";

View File

@@ -220,6 +220,7 @@ RDKIT_RDGENERAL_EXPORT extern const std::string _rgroupTargetAtoms;
RDKIT_RDGENERAL_EXPORT extern const std::string _rgroupTargetBonds;
RDKIT_RDGENERAL_EXPORT extern const std::string reactantAtomIdx;
RDKIT_RDGENERAL_EXPORT extern const std::string reactionMapNum;
RDKIT_RDGENERAL_EXPORT extern const std::string reactantIdx;
// SLN
RDKIT_RDGENERAL_EXPORT extern const std::string