Skip to content

Commit

Permalink
allow specified chiral features to SSS match unspecified features (rd…
Browse files Browse the repository at this point in the history
  • Loading branch information
greglandrum authored Dec 18, 2024
1 parent 74fc77f commit e77d4e3
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 9 deletions.
17 changes: 14 additions & 3 deletions Code/GraphMol/Substruct/SubstructMatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,9 @@ bool MolMatchFinalCheckFunctor::operator()(const std::uint32_t q_c[],
}
const Atom *mAt = d_mol.getAtomWithIdx(m_c[i]);
if (!detail::hasChiralLabel(mAt)) {
if (d_params.specifiedStereoQueryMatchesUnspecified) {
continue;
}
return false;
}
if (qAt->getDegree() > mAt->getDegree()) {
Expand Down Expand Up @@ -327,10 +330,15 @@ bool MolMatchFinalCheckFunctor::operator()(const std::uint32_t q_c[],
const Bond *mBnd = d_mol.getBondBetweenAtoms(
q_to_mol[qBnd->getBeginAtomIdx()], q_to_mol[qBnd->getEndAtomIdx()]);
CHECK_INVARIANT(mBnd, "Matching bond not found");
if (mBnd->getBondType() != Bond::DOUBLE ||
qBnd->getStereo() <= Bond::STEREOANY) {
if (mBnd->getBondType() != Bond::DOUBLE) {
continue;
}

if (!d_params.specifiedStereoQueryMatchesUnspecified &&
mBnd->getStereo() <= Bond::STEREOANY) {
return false;
}

// don't think this can actually happen, but check to be sure:
if (mBnd->getStereoAtoms().size() != 2) {
continue;
Expand Down Expand Up @@ -386,14 +394,16 @@ class AtomLabelFunctor {
AtomLabelFunctor(const ROMol &query, const ROMol &mol,
const SubstructMatchParameters &ps)
: d_query(query), d_mol(mol), d_params(ps) {};

bool operator()(unsigned int i, unsigned int j) const {
bool res = false;
if (d_params.useChirality) {
const Atom *qAt = d_query.getAtomWithIdx(i);
if (qAt->getChiralTag() == Atom::CHI_TETRAHEDRAL_CW ||
qAt->getChiralTag() == Atom::CHI_TETRAHEDRAL_CCW) {
const Atom *mAt = d_mol.getAtomWithIdx(j);
if (mAt->getChiralTag() != Atom::CHI_TETRAHEDRAL_CW &&
if (!d_params.specifiedStereoQueryMatchesUnspecified &&
mAt->getChiralTag() != Atom::CHI_TETRAHEDRAL_CW &&
mAt->getChiralTag() != Atom::CHI_TETRAHEDRAL_CCW) {
return false;
}
Expand Down Expand Up @@ -421,6 +431,7 @@ class BondLabelFunctor {
qBnd->getStereo() > Bond::STEREOANY) {
const Bond *mBnd = d_mol[j];
if (mBnd->getBondType() == Bond::DOUBLE &&
!d_params.specifiedStereoQueryMatchesUnspecified &&
mBnd->getStereo() <= Bond::STEREOANY) {
return false;
}
Expand Down
5 changes: 4 additions & 1 deletion Code/GraphMol/Substruct/SubstructMatch.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ struct RDKIT_SUBSTRUCTMATCH_EXPORT SubstructMatchParameters {
//!< match
unsigned int maxRecursiveMatches =
1000; //!< maximum number of matches that the recursive substructure
//!< matching should return
//!< matching should return
bool specifiedStereoQueryMatchesUnspecified =
false; //!< If set, query atoms and bonds with specified stereochemistry
//!< will match atoms and bonds with unspecified stereochemistry
SubstructMatchParameters() {}
};

Expand Down
2 changes: 2 additions & 0 deletions Code/GraphMol/Substruct/SubstructUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ void updateSubstructMatchParamsFromJSON(SubstructMatchParameters &params,
PT_OPT_GET(maxMatches);
PT_OPT_GET(maxRecursiveMatches);
PT_OPT_GET(numThreads);
PT_OPT_GET(specifiedStereoQueryMatchesUnspecified);
}

std::string substructMatchParamsToJSON(const SubstructMatchParameters &params) {
Expand All @@ -287,6 +288,7 @@ std::string substructMatchParamsToJSON(const SubstructMatchParameters &params) {
PT_OPT_PUT(maxMatches);
PT_OPT_PUT(maxRecursiveMatches);
PT_OPT_PUT(numThreads);
PT_OPT_PUT(specifiedStereoQueryMatchesUnspecified);

std::stringstream ss;
boost::property_tree::json_parser::write_json(ss, pt);
Expand Down
56 changes: 53 additions & 3 deletions Code/GraphMol/Substruct/catch_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -710,7 +710,7 @@ TEST_CASE("pickling HasPropWithValue queries") {
REQUIRE(pklmol.getAtomWithIdx(0)->hasQuery());
REQUIRE(pklmol.getBondWithIdx(0)->hasQuery());
CHECK(SubstructMatch(*target, pklmol, ps).size() == 1);
// make sure we are idempotent in pickling
// make sure we are idempotent in pickling
CHECK(SubstructMatch(*target, *mol, ps).size() == 1);
}
{
Expand All @@ -720,7 +720,7 @@ TEST_CASE("pickling HasPropWithValue queries") {
REQUIRE(pklmol.getAtomWithIdx(0)->hasQuery());
REQUIRE(pklmol.getBondWithIdx(0)->hasQuery());
CHECK(SubstructMatch(*target, pklmol, ps).size() == 0);
// make sure we are idempotent in pickling
// make sure we are idempotent in pickling
CHECK(SubstructMatch(*target, mol2, ps).size() == 0);
}
{
Expand All @@ -730,8 +730,58 @@ TEST_CASE("pickling HasPropWithValue queries") {
REQUIRE(pklmol.getAtomWithIdx(0)->hasQuery());
REQUIRE(pklmol.getBondWithIdx(0)->hasQuery());
CHECK(SubstructMatch(*target, pklmol, ps).size() == 0);
// make sure we are idempotent in pickling
// make sure we are idempotent in pickling
CHECK(SubstructMatch(*target, mol3, ps).size() == 0);
}
}
}

TEST_CASE("specified query matches unspecified atom") {
SECTION("atom basics") {
auto q = "F[C@](Cl)(Br)C"_smarts;
REQUIRE(q);

auto m1 = "F[C@](Cl)(Br)C"_smiles;
REQUIRE(m1);
auto m2 = "FC(Cl)(Br)C"_smiles;
REQUIRE(m2);
auto m3 = "F[C@@](Cl)(Br)C"_smiles;
REQUIRE(m3);

SubstructMatchParameters ps;
ps.useChirality = true;
CHECK(SubstructMatch(*m1, *q, ps).size() == 1);
CHECK(SubstructMatch(*m2, *q, ps).empty());
CHECK(SubstructMatch(*m3, *q, ps).empty());

ps.specifiedStereoQueryMatchesUnspecified = true;
CHECK(SubstructMatch(*m1, *q, ps).size() == 1);
CHECK(SubstructMatch(*m2, *q, ps).size() == 1);
CHECK(SubstructMatch(*m3, *q, ps).empty());
}
SECTION("bond basics") {
auto q = "F/C=C/Br"_smarts;
REQUIRE(q);

auto m1 = "F/C=C/Br"_smiles;
REQUIRE(m1);
auto m2 = "FC=CBr"_smiles;
REQUIRE(m2);
auto m3 = "F/C=C\\Br"_smiles;
REQUIRE(m3);

SubstructMatchParameters ps;
ps.useChirality = true;
CHECK(SubstructMatch(*m1, *q, ps).size() == 1);
CHECK(SubstructMatch(*m2, *q, ps).empty());
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());
}
}
9 changes: 7 additions & 2 deletions Code/GraphMol/Wrap/Mol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -334,12 +334,17 @@ struct mol_wrapper {
"0 selects the number of concurrent threads supported by the"
"hardware. negative values are added to the number of concurrent"
"threads supported by the hardware.")
.def_readwrite(
"bondProperties", &RDKit::SubstructMatchParameters::bondProperties,
"bond properties that must be equivalent in order to match.")
.def_readwrite(
"atomProperties", &RDKit::SubstructMatchParameters::atomProperties,
"atom properties that must be equivalent in order to match.")
.def_readwrite(
"bondProperties", &RDKit::SubstructMatchParameters::bondProperties,
"bond properties that must be equivalent in order to match.")
"specifiedStereoQueryMatchesUnspecified",
&RDKit::SubstructMatchParameters::
specifiedStereoQueryMatchesUnspecified,
"If set, query atoms and bonds with specified stereochemistry will match atoms and bonds with unspecified stereochemistry.")
.def("setExtraFinalCheck", setSubstructMatchFinalCheck,
python::with_custodian_and_ward<1, 2>(),
python::args("self", "func"),
Expand Down

0 comments on commit e77d4e3

Please sign in to comment.