mirror of
https://github.com/rdkit/rdkit.git
synced 2026-06-04 21:54:27 +08:00
* Partially resolve a problem with implicit Hs ending up in SMARTS * update doctest * document in release notes
909 lines
32 KiB
C++
909 lines
32 KiB
C++
//
|
|
// Copyright (C) 2020-2021 Greg Landrum and other RDKit contributors
|
|
//
|
|
// @@ All Rights Reserved @@
|
|
// This file is part of the RDKit.
|
|
// The contents are covered by the terms of the BSD license
|
|
// which is included in the file license.txt, found at the root
|
|
// of the RDKit source tree.
|
|
//
|
|
|
|
#include <catch2/catch_all.hpp>
|
|
|
|
#include <utility>
|
|
|
|
#include <GraphMol/RDKitBase.h>
|
|
#include <GraphMol/RDKitQueries.h>
|
|
#include <GraphMol/FileParsers/FileParsers.h>
|
|
#include <GraphMol/SmilesParse/SmilesParse.h>
|
|
#include <GraphMol/SmilesParse/SmilesWrite.h>
|
|
#include <GraphMol/SmilesParse/SmartsWrite.h>
|
|
#include <GraphMol/Substruct/SubstructMatch.h>
|
|
#include <GraphMol/test_fixtures.h>
|
|
|
|
using namespace RDKit;
|
|
|
|
using matchCase = std::tuple<std::string, std::string, bool, bool>;
|
|
|
|
class _IsSubstructOf
|
|
: public Catch::Matchers::MatcherBase<const std::string &> {
|
|
ROMol const *m_query;
|
|
std::string m_description;
|
|
SubstructMatchParameters m_ps;
|
|
|
|
public:
|
|
_IsSubstructOf(const ROMol &m, std::string description)
|
|
: m_query(&m), m_description(std::move(description)) {}
|
|
|
|
_IsSubstructOf(const ROMol &m, std::string description,
|
|
SubstructMatchParameters ps)
|
|
: m_query(&m),
|
|
m_description(std::move(description)),
|
|
m_ps(std::move(ps)) {}
|
|
|
|
bool match(const std::string &smiles) const override {
|
|
std::unique_ptr<ROMol> mol(SmilesToMol(smiles));
|
|
return !SubstructMatch(*mol, *m_query, m_ps).empty();
|
|
}
|
|
|
|
std::string describe() const override {
|
|
std::ostringstream ss;
|
|
ss << "is not a substructure of " << m_description;
|
|
return ss.str();
|
|
}
|
|
};
|
|
|
|
static _IsSubstructOf IsSubstructOf(const ROMol &m, const std::string &smarts,
|
|
const SubstructMatchParameters &ps) {
|
|
return _IsSubstructOf(m, smarts, ps);
|
|
}
|
|
|
|
static _IsSubstructOf IsSubstructOf(const ROMol &m, const std::string &smarts) {
|
|
return _IsSubstructOf(m, smarts);
|
|
}
|
|
|
|
TEST_CASE("handling of bondStereoCare in adjustQueryProperties") {
|
|
auto useLegacy = GENERATE(true, false);
|
|
UseLegacyStereoPerceptionFixture fx(useLegacy);
|
|
SECTION("fully specified") {
|
|
auto mol = R"CTAB(basic test
|
|
Mrv1810 01292006422D
|
|
|
|
0 0 0 0 0 999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 4 3 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C -7.0316 2.0632 0 0 STBOX=1
|
|
M V30 2 C -5.6979 2.8332 0 0 STBOX=1
|
|
M V30 3 O -4.3642 2.0632 0 0
|
|
M V30 4 F -8.3653 2.8332 0 0
|
|
M V30 END ATOM
|
|
M V30 BEGIN BOND
|
|
M V30 1 1 2 3
|
|
M V30 2 1 1 4
|
|
M V30 3 2 1 2 STBOX=1
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END
|
|
)CTAB"_ctab;
|
|
REQUIRE(mol);
|
|
REQUIRE(mol->getBondBetweenAtoms(0, 1));
|
|
if (useLegacy) {
|
|
CHECK(mol->getBondBetweenAtoms(0, 1)->getStereo() ==
|
|
Bond::BondStereo::STEREOE);
|
|
} else {
|
|
CHECK(mol->getBondBetweenAtoms(0, 1)->getStereo() ==
|
|
Bond::BondStereo::STEREOTRANS);
|
|
}
|
|
MolOps::AdjustQueryParameters ps;
|
|
ps.useStereoCareForBonds = true;
|
|
MolOps::adjustQueryProperties(*mol, &ps);
|
|
if (useLegacy) {
|
|
CHECK(mol->getBondBetweenAtoms(0, 1)->getStereo() ==
|
|
Bond::BondStereo::STEREOE);
|
|
} else {
|
|
CHECK(mol->getBondBetweenAtoms(0, 1)->getStereo() ==
|
|
Bond::BondStereo::STEREOTRANS);
|
|
}
|
|
}
|
|
SECTION("fully unspecified") {
|
|
auto mol = R"CTAB(basic test
|
|
Mrv1810 01292006422D
|
|
|
|
0 0 0 0 0 999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 4 3 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C -7.0316 2.0632 0 0
|
|
M V30 2 C -5.6979 2.8332 0 0
|
|
M V30 3 O -4.3642 2.0632 0 0
|
|
M V30 4 F -8.3653 2.8332 0 0
|
|
M V30 END ATOM
|
|
M V30 BEGIN BOND
|
|
M V30 1 1 2 3
|
|
M V30 2 1 1 4
|
|
M V30 3 2 1 2
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END
|
|
)CTAB"_ctab;
|
|
REQUIRE(mol);
|
|
REQUIRE(mol->getBondBetweenAtoms(0, 1));
|
|
if (useLegacy) {
|
|
CHECK(mol->getBondBetweenAtoms(0, 1)->getStereo() ==
|
|
Bond::BondStereo::STEREOE);
|
|
} else {
|
|
CHECK(mol->getBondBetweenAtoms(0, 1)->getStereo() ==
|
|
Bond::BondStereo::STEREOTRANS);
|
|
}
|
|
MolOps::AdjustQueryParameters ps;
|
|
ps.useStereoCareForBonds = true;
|
|
MolOps::adjustQueryProperties(*mol, &ps);
|
|
CHECK(mol->getBondBetweenAtoms(0, 1)->getStereo() ==
|
|
Bond::BondStereo::STEREONONE);
|
|
}
|
|
SECTION("partially unspecified") {
|
|
std::vector<std::string> mbs = {R"CTAB(keep
|
|
Mrv1810 01292006422D
|
|
|
|
0 0 0 0 0 999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 4 3 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C -7.0316 2.0632 0 0 STBOX=1
|
|
M V30 2 C -5.6979 2.8332 0 0 STBOX=1
|
|
M V30 3 O -4.3642 2.0632 0 0
|
|
M V30 4 F -8.3653 2.8332 0 0
|
|
M V30 END ATOM
|
|
M V30 BEGIN BOND
|
|
M V30 1 1 2 3
|
|
M V30 2 1 1 4
|
|
M V30 3 2 1 2
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END
|
|
)CTAB",
|
|
R"CTAB(keep
|
|
Mrv1810 01292006422D
|
|
|
|
0 0 0 0 0 999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 4 3 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C -7.0316 2.0632 0 0
|
|
M V30 2 C -5.6979 2.8332 0 0
|
|
M V30 3 O -4.3642 2.0632 0 0
|
|
M V30 4 F -8.3653 2.8332 0 0
|
|
M V30 END ATOM
|
|
M V30 BEGIN BOND
|
|
M V30 1 1 2 3
|
|
M V30 2 1 1 4
|
|
M V30 3 2 1 2 STBOX=1
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END
|
|
)CTAB",
|
|
R"CTAB(remove
|
|
Mrv1810 01292006422D
|
|
|
|
0 0 0 0 0 999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 4 3 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C -7.0316 2.0632 0 0
|
|
M V30 2 C -5.6979 2.8332 0 0
|
|
M V30 3 O -4.3642 2.0632 0 0
|
|
M V30 4 F -8.3653 2.8332 0 0
|
|
M V30 END ATOM
|
|
M V30 BEGIN BOND
|
|
M V30 1 1 2 3
|
|
M V30 2 1 1 4
|
|
M V30 3 2 1 2 STBOX=0
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END
|
|
)CTAB",
|
|
R"CTAB(remove
|
|
Mrv1810 01292006422D
|
|
|
|
0 0 0 0 0 999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 4 3 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C -7.0316 2.0632 0 0
|
|
M V30 2 C -5.6979 2.8332 0 0 STBOX=1
|
|
M V30 3 O -4.3642 2.0632 0 0
|
|
M V30 4 F -8.3653 2.8332 0 0
|
|
M V30 END ATOM
|
|
M V30 BEGIN BOND
|
|
M V30 1 1 2 3
|
|
M V30 2 1 1 4
|
|
M V30 3 2 1 2
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END
|
|
)CTAB",
|
|
R"CTAB(remove
|
|
Mrv1810 01292006422D
|
|
|
|
0 0 0 0 0 999 V3000
|
|
M V30 BEGIN CTAB
|
|
M V30 COUNTS 4 3 0 0 0
|
|
M V30 BEGIN ATOM
|
|
M V30 1 C -7.0316 2.0632 0 0 STBOX=1
|
|
M V30 2 C -5.6979 2.8332 0 0
|
|
M V30 3 O -4.3642 2.0632 0 0
|
|
M V30 4 F -8.3653 2.8332 0 0
|
|
M V30 END ATOM
|
|
M V30 BEGIN BOND
|
|
M V30 1 1 2 3
|
|
M V30 2 1 1 4
|
|
M V30 3 2 1 2
|
|
M V30 END BOND
|
|
M V30 END CTAB
|
|
M END
|
|
)CTAB"};
|
|
for (const auto &mb : mbs) {
|
|
std::unique_ptr<RWMol> mol{MolBlockToMol(mb)};
|
|
REQUIRE(mol);
|
|
REQUIRE(mol->getBondBetweenAtoms(0, 1));
|
|
if (useLegacy) {
|
|
CHECK(mol->getBondBetweenAtoms(0, 1)->getStereo() ==
|
|
Bond::BondStereo::STEREOE);
|
|
} else {
|
|
CHECK(mol->getBondBetweenAtoms(0, 1)->getStereo() ==
|
|
Bond::BondStereo::STEREOTRANS);
|
|
}
|
|
MolOps::AdjustQueryParameters ps;
|
|
ps.useStereoCareForBonds = true;
|
|
MolOps::adjustQueryProperties(*mol, &ps);
|
|
if (mol->getProp<std::string>(common_properties::_Name) == "keep") {
|
|
if (useLegacy) {
|
|
CHECK(mol->getBondBetweenAtoms(0, 1)->getStereo() ==
|
|
Bond::BondStereo::STEREOE);
|
|
} else {
|
|
CHECK(mol->getBondBetweenAtoms(0, 1)->getStereo() ==
|
|
Bond::BondStereo::STEREOTRANS);
|
|
}
|
|
} else {
|
|
CHECK(mol->getBondBetweenAtoms(0, 1)->getStereo() ==
|
|
Bond::BondStereo::STEREONONE);
|
|
}
|
|
}
|
|
}
|
|
SECTION("V2000") {
|
|
auto mol = R"CTAB(basic test
|
|
Mrv1810 01292015042D
|
|
|
|
4 3 0 0 0 0 999 V2000
|
|
-3.7669 1.1053 0.0000 C 0 0 0 0 1 0 0 0 0 0 0 0
|
|
-3.0524 1.5178 0.0000 C 0 0 0 0 1 0 0 0 0 0 0 0
|
|
-2.3380 1.1053 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
|
|
-4.4814 1.5178 0.0000 F 0 0 0 0 0 0 0 0 0 0 0 0
|
|
2 3 1 0 0 0 0
|
|
1 4 1 0 0 0 0
|
|
1 2 2 0 0 0 0
|
|
M END
|
|
)CTAB"_ctab;
|
|
REQUIRE(mol);
|
|
CHECK(mol->getAtomWithIdx(0)->hasProp(common_properties::molStereoCare));
|
|
CHECK(mol->getAtomWithIdx(1)->hasProp(common_properties::molStereoCare));
|
|
REQUIRE(mol->getBondBetweenAtoms(0, 1));
|
|
if (useLegacy) {
|
|
CHECK(mol->getBondBetweenAtoms(0, 1)->getStereo() ==
|
|
Bond::BondStereo::STEREOE);
|
|
} else {
|
|
CHECK(mol->getBondBetweenAtoms(0, 1)->getStereo() ==
|
|
Bond::BondStereo::STEREOTRANS);
|
|
}
|
|
// property added by the CTAB parser:
|
|
CHECK(mol->getBondBetweenAtoms(0, 1)->hasProp(
|
|
common_properties::molStereoCare));
|
|
MolOps::AdjustQueryParameters ps;
|
|
ps.useStereoCareForBonds = true;
|
|
MolOps::adjustQueryProperties(*mol, &ps);
|
|
if (useLegacy) {
|
|
CHECK(mol->getBondBetweenAtoms(0, 1)->getStereo() ==
|
|
Bond::BondStereo::STEREOE);
|
|
} else {
|
|
CHECK(mol->getBondBetweenAtoms(0, 1)->getStereo() ==
|
|
Bond::BondStereo::STEREOTRANS);
|
|
}
|
|
}
|
|
SECTION("molecule from SMILES") {
|
|
auto mol = "C/C=C/C"_smiles;
|
|
REQUIRE(mol);
|
|
REQUIRE(mol->getBondBetweenAtoms(2, 1));
|
|
if (useLegacy) {
|
|
CHECK(mol->getBondBetweenAtoms(2, 1)->getStereo() ==
|
|
Bond::BondStereo::STEREOE);
|
|
} else {
|
|
CHECK(mol->getBondBetweenAtoms(2, 1)->getStereo() ==
|
|
Bond::BondStereo::STEREOTRANS);
|
|
}
|
|
MolOps::AdjustQueryParameters ps;
|
|
ps.useStereoCareForBonds = true;
|
|
// since stereoCare is not set on the bond from SMILES,
|
|
// stereochem will be removed:
|
|
{
|
|
RWMol molcp(*mol);
|
|
MolOps::adjustQueryProperties(molcp, &ps);
|
|
CHECK(molcp.getBondBetweenAtoms(2, 1)->getStereo() ==
|
|
Bond::BondStereo::STEREONONE);
|
|
}
|
|
// but we can preserve it by setting the property:
|
|
{
|
|
RWMol molcp(*mol);
|
|
molcp.getBondBetweenAtoms(2, 1)->setProp(common_properties::molStereoCare,
|
|
1);
|
|
MolOps::adjustQueryProperties(molcp, &ps);
|
|
if (useLegacy) {
|
|
CHECK(molcp.getBondBetweenAtoms(2, 1)->getStereo() ==
|
|
Bond::BondStereo::STEREOE);
|
|
} else {
|
|
CHECK(molcp.getBondBetweenAtoms(2, 1)->getStereo() ==
|
|
Bond::BondStereo::STEREOTRANS);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("adjustQueryParameters from JSON") {
|
|
SECTION("basics") {
|
|
MolOps::AdjustQueryParameters ps;
|
|
CHECK(ps.makeAtomsGeneric == false);
|
|
CHECK(ps.makeBondsGeneric == false);
|
|
CHECK(ps.makeBondsGenericFlags == MolOps::ADJUST_IGNORENONE);
|
|
|
|
std::string json = R"JSON({"makeAtomsGeneric":true})JSON";
|
|
MolOps::parseAdjustQueryParametersFromJSON(ps, json);
|
|
|
|
CHECK(ps.makeAtomsGeneric == true);
|
|
CHECK(ps.makeBondsGeneric == false);
|
|
// the parsing updates the parameters, it doesn't replace them:
|
|
|
|
json = R"JSON({"makeBondsGeneric":true,
|
|
"makeBondsGenericFlags":"IGNOREDUMMIES|IGNORECHAINS"})JSON";
|
|
MolOps::parseAdjustQueryParametersFromJSON(ps, json);
|
|
|
|
CHECK(ps.makeAtomsGeneric == true);
|
|
CHECK(ps.makeBondsGeneric == true);
|
|
CHECK(ps.makeBondsGenericFlags ==
|
|
(MolOps::ADJUST_IGNOREDUMMIES | MolOps::ADJUST_IGNORECHAINS));
|
|
}
|
|
SECTION("useStereoCare") {
|
|
MolOps::AdjustQueryParameters ps;
|
|
CHECK(ps.useStereoCareForBonds == false);
|
|
|
|
std::string json = R"JSON({"useStereoCareForBonds":true})JSON";
|
|
MolOps::parseAdjustQueryParametersFromJSON(ps, json);
|
|
CHECK(ps.useStereoCareForBonds == true);
|
|
json = R"JSON({"useStereoCareForBonds":false})JSON";
|
|
MolOps::parseAdjustQueryParametersFromJSON(ps, json);
|
|
CHECK(ps.useStereoCareForBonds == false);
|
|
}
|
|
SECTION("adjustHeavyDegreeFlags") {
|
|
MolOps::AdjustQueryParameters ps;
|
|
CHECK(ps.adjustHeavyDegreeFlags != MolOps::ADJUST_IGNORENONE);
|
|
std::string json = R"JSON({"adjustHeavyDegreeFlags":"IGNORENONE"})JSON";
|
|
MolOps::parseAdjustQueryParametersFromJSON(ps, json);
|
|
CHECK(ps.adjustHeavyDegreeFlags == MolOps::ADJUST_IGNORENONE);
|
|
}
|
|
SECTION("adjustRingCountFlags") {
|
|
MolOps::AdjustQueryParameters ps;
|
|
CHECK(ps.adjustRingCountFlags != MolOps::ADJUST_IGNORERINGS);
|
|
std::string json = R"JSON({"adjustRingCountFlags":"IGNORERINGS"})JSON";
|
|
MolOps::parseAdjustQueryParametersFromJSON(ps, json);
|
|
CHECK(ps.adjustRingCountFlags == MolOps::ADJUST_IGNORERINGS);
|
|
}
|
|
SECTION("makeAtomsGenericFlags") {
|
|
MolOps::AdjustQueryParameters ps;
|
|
CHECK(ps.makeAtomsGenericFlags != MolOps::ADJUST_IGNOREALL);
|
|
std::string json = R"JSON({"makeAtomsGenericFlags":"IGNOREALL"})JSON";
|
|
MolOps::parseAdjustQueryParametersFromJSON(ps, json);
|
|
CHECK(ps.makeAtomsGenericFlags == MolOps::ADJUST_IGNOREALL);
|
|
}
|
|
SECTION("adjustRingChainFlags") {
|
|
MolOps::AdjustQueryParameters ps;
|
|
CHECK(ps.adjustRingChainFlags != MolOps::ADJUST_IGNORENONDUMMIES);
|
|
std::string json = R"JSON({"adjustRingChainFlags":"IGNORENONDUMMIES"})JSON";
|
|
MolOps::parseAdjustQueryParametersFromJSON(ps, json);
|
|
CHECK(ps.adjustRingChainFlags == MolOps::ADJUST_IGNORENONDUMMIES);
|
|
}
|
|
SECTION("bogus contents") {
|
|
MolOps::AdjustQueryParameters ps;
|
|
CHECK(ps.adjustDegree == true);
|
|
CHECK(ps.adjustDegreeFlags ==
|
|
(MolOps::ADJUST_IGNOREDUMMIES | MolOps::ADJUST_IGNORECHAINS));
|
|
|
|
std::string json = R"JSON({"bogosity":true})JSON";
|
|
MolOps::parseAdjustQueryParametersFromJSON(ps, json);
|
|
CHECK(ps.adjustDegree == true);
|
|
|
|
json = R"JSON({"adjustDegree":"foo"})JSON";
|
|
MolOps::parseAdjustQueryParametersFromJSON(ps, json);
|
|
CHECK(ps.adjustDegree == true);
|
|
|
|
json = R"JSON({"adjustDegreeFlags":"IGNORENONE|bogus"})JSON";
|
|
CHECK_THROWS_AS(MolOps::parseAdjustQueryParametersFromJSON(ps, json),
|
|
ValueErrorException);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("MDL five-rings") {
|
|
MolOps::AdjustQueryParameters ps =
|
|
MolOps::AdjustQueryParameters::noAdjustments();
|
|
ps.setMDLFiveRingAromaticity = true;
|
|
SECTION("query details") {
|
|
using extuple = std::tuple<std::string, std::string, std::string>;
|
|
// clang-format off
|
|
std::vector<extuple> examples = {
|
|
// no queries, no change
|
|
extuple{"adjustqueryprops_MDLfivering_1.mol","[#7]1:[#6]:[#6]:[#6]:[#6]:1",""},
|
|
// Q atom, no change
|
|
extuple{"adjustqueryprops_MDLfivering_2.mol","[!#6&!#1]1:[#6]:[#6]:[#6]:[#6]:1",""},
|
|
// A atom, this one changes
|
|
extuple{"adjustqueryprops_MDLfivering_3.mol","[!#1]1:[#6]:[#6]:[#6]:[#6]:1","[!#1]1-,:[#6]=,:[#6]-,:[#6]=,:[#6]-,:1"},
|
|
// NOTE that this is not technically correct according to the documentation, but if we make the bridging bond
|
|
// aromatic then it won't match azulene in a normal RDKit molecule, which is certainly not the intent of this.
|
|
extuple{"adjustqueryprops_MDLfivering_4.mol","[#6]12:[#6]:[#6]:[#6]:[#6]-1:[#6]:[#6]:[#6]:[#6]:[#6]:2",""},
|
|
};
|
|
// clang-format on
|
|
for (auto tpl : examples) {
|
|
if (std::get<2>(tpl).empty()) {
|
|
std::get<2>(tpl) = std::get<1>(tpl);
|
|
}
|
|
auto fname = std::get<0>(tpl);
|
|
std::string pathName = getenv("RDBASE");
|
|
pathName += "/Code/GraphMol/test_data/";
|
|
std::unique_ptr<RWMol> qry(MolFileToMol(pathName + fname));
|
|
REQUIRE(qry);
|
|
{
|
|
RWMol cp(*qry);
|
|
CHECK(std::get<1>(tpl) == MolToSmarts(cp));
|
|
MolOps::adjustQueryProperties(cp, &ps);
|
|
CHECK(std::get<2>(tpl) == MolToSmarts(cp));
|
|
}
|
|
{ // make sure ring-finding happens
|
|
RWMol cp(*qry);
|
|
cp.getRingInfo()->reset();
|
|
CHECK(std::get<1>(tpl) == MolToSmarts(cp));
|
|
MolOps::adjustQueryProperties(cp, &ps);
|
|
CHECK(std::get<2>(tpl) == MolToSmarts(cp));
|
|
}
|
|
}
|
|
}
|
|
SECTION("edge cases") {
|
|
SmilesParserParams smiles_ps;
|
|
smiles_ps.sanitize = false;
|
|
{
|
|
std::unique_ptr<RWMol> qry{SmilesToMol("*1:c:c:c:c:1", smiles_ps)};
|
|
REQUIRE(qry);
|
|
std::unique_ptr<QueryAtom> qat(new QueryAtom(0));
|
|
qat->setQuery(makeAAtomQuery());
|
|
qat->setIsAromatic(true);
|
|
qry->replaceAtom(0, qat.get());
|
|
MolOps::adjustQueryProperties(*qry, &ps);
|
|
CHECK(MolToSmarts(*qry) == "[!#1]1-,:[#6]=,:[#6]-,:[#6]=,:[#6]-,:1");
|
|
}
|
|
{ // ring not fully aromatic
|
|
std::unique_ptr<RWMol> qry{SmilesToMol("*1:c:C-c:c:1", smiles_ps)};
|
|
REQUIRE(qry);
|
|
std::unique_ptr<QueryAtom> qat(new QueryAtom(0));
|
|
qat->setQuery(makeAAtomQuery());
|
|
qat->setIsAromatic(true);
|
|
qry->replaceAtom(0, qat.get());
|
|
MolOps::adjustQueryProperties(*qry, &ps);
|
|
CHECK(MolToSmarts(*qry) == "[!#1]1:[#6]:[#6]-[#6]:[#6]:1");
|
|
}
|
|
{ // ring has additional dummy
|
|
std::unique_ptr<RWMol> qry{SmilesToMol("*1:c:*:c:c:1", smiles_ps)};
|
|
REQUIRE(qry);
|
|
std::unique_ptr<QueryAtom> qat(new QueryAtom(0));
|
|
qat->setQuery(makeAAtomQuery());
|
|
qat->setIsAromatic(true);
|
|
qry->replaceAtom(0, qat.get());
|
|
MolOps::adjustQueryProperties(*qry, &ps);
|
|
CHECK(MolToSmarts(*qry) == "[!#1]1:[#6]:[#0]:[#6]:[#6]:1");
|
|
}
|
|
{ // query bond in ring
|
|
std::unique_ptr<RWMol> qry{SmilesToMol("*1:c:c:c:c:1", smiles_ps)};
|
|
REQUIRE(qry);
|
|
std::unique_ptr<QueryAtom> qat(new QueryAtom(0));
|
|
qat->setQuery(makeAAtomQuery());
|
|
qat->setIsAromatic(true);
|
|
qry->replaceAtom(0, qat.get());
|
|
std::unique_ptr<QueryBond> qbnd(new QueryBond());
|
|
qbnd->setBondType(Bond::BondType::SINGLE);
|
|
qbnd->setQuery(makeBondOrderEqualsQuery(Bond::BondType::SINGLE));
|
|
qry->replaceBond(0, qbnd.get());
|
|
MolOps::adjustQueryProperties(*qry, &ps);
|
|
CHECK(MolToSmarts(*qry) == "[!#1]1-[#6]:[#6]:[#6]:[#6]:1");
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("conjugated five-rings") {
|
|
MolOps::AdjustQueryParameters ps =
|
|
MolOps::AdjustQueryParameters::noAdjustments();
|
|
ps.adjustConjugatedFiveRings = true;
|
|
SECTION("matching") {
|
|
// clang-format off
|
|
std::vector<matchCase> examples = {
|
|
// 1,3 cyclopentadiene
|
|
matchCase{"C1=CCC=C1","adjustqueryprops_fivering_1.mol",false,true},
|
|
matchCase{"C1=CCC=C1","adjustqueryprops_fivering_2.mol",false,true},
|
|
matchCase{"C1=CCC=C1","adjustqueryprops_fivering_3.mol",true,true},
|
|
matchCase{"C1=CCC=C1","adjustqueryprops_fivering_4.mol",false,false},
|
|
matchCase{"C1=CCC=C1","adjustqueryprops_fivering_5.mol",false,false},
|
|
matchCase{"C1=CCC=C1","adjustqueryprops_fivering_6.mol",false,false},
|
|
// pyrrole
|
|
matchCase{"C1=CNC=C1","adjustqueryprops_fivering_1.mol",true,true},
|
|
matchCase{"C1=CNC=C1","adjustqueryprops_fivering_2.mol",true,true},
|
|
matchCase{"C1=CNC=C1","adjustqueryprops_fivering_3.mol",false,false},
|
|
matchCase{"C1=CNC=C1","adjustqueryprops_fivering_4.mol",false,false},
|
|
matchCase{"C1=CNC=C1","adjustqueryprops_fivering_5.mol",false,false},
|
|
matchCase{"C1=CNC=C1","adjustqueryprops_fivering_6.mol",false,false},
|
|
// thiophene
|
|
matchCase{"C1=CSC=C1","adjustqueryprops_fivering_1.mol",false,false},
|
|
matchCase{"C1=CSC=C1","adjustqueryprops_fivering_2.mol",true,true},
|
|
matchCase{"C1=CSC=C1","adjustqueryprops_fivering_3.mol",false,false},
|
|
matchCase{"C1=CSC=C1","adjustqueryprops_fivering_4.mol",true,true},
|
|
matchCase{"C1=CSC=C1","adjustqueryprops_fivering_5.mol",false,false},
|
|
matchCase{"C1=CSC=C1","adjustqueryprops_fivering_6.mol",true,true},
|
|
// thiophene oxide
|
|
matchCase{"C1=CS(=O)C=C1","adjustqueryprops_fivering_1.mol",false,false},
|
|
matchCase{"C1=CS(=O)C=C1","adjustqueryprops_fivering_2.mol",false,true},
|
|
matchCase{"C1=CS(=O)C=C1","adjustqueryprops_fivering_3.mol",false,false},
|
|
matchCase{"C1=CS(=O)C=C1","adjustqueryprops_fivering_4.mol",false,true},
|
|
matchCase{"C1=CS(=O)C=C1","adjustqueryprops_fivering_5.mol",false,false},
|
|
matchCase{"C1=CS(=O)C=C1","adjustqueryprops_fivering_6.mol",true,true},
|
|
// furan
|
|
matchCase{"C1=COC=C1","adjustqueryprops_fivering_1.mol",true,true},
|
|
matchCase{"C1=COC=C1","adjustqueryprops_fivering_2.mol",true,true},
|
|
matchCase{"C1=COC=C1","adjustqueryprops_fivering_3.mol",false,false},
|
|
matchCase{"C1=COC=C1","adjustqueryprops_fivering_4.mol",false,false},
|
|
matchCase{"C1=COC=C1","adjustqueryprops_fivering_5.mol",false,false},
|
|
matchCase{"C1=COC=C1","adjustqueryprops_fivering_6.mol",false,false},
|
|
};
|
|
// clang-format on
|
|
for (const auto &tpl : examples) {
|
|
auto fname = std::get<1>(tpl);
|
|
std::string pathName = getenv("RDBASE");
|
|
pathName += "/Code/GraphMol/test_data/";
|
|
std::unique_ptr<RWMol> qry(MolFileToMol(pathName + fname));
|
|
REQUIRE(qry);
|
|
if (std::get<2>(tpl)) {
|
|
CHECK_THAT(std::get<0>(tpl), IsSubstructOf(*qry, fname));
|
|
} else {
|
|
CHECK_THAT(std::get<0>(tpl), !IsSubstructOf(*qry, fname));
|
|
}
|
|
MolOps::adjustQueryProperties(*qry, &ps);
|
|
if (std::get<3>(tpl)) {
|
|
CHECK_THAT(std::get<0>(tpl), IsSubstructOf(*qry, fname));
|
|
} else {
|
|
CHECK_THAT(std::get<0>(tpl), !IsSubstructOf(*qry, fname));
|
|
}
|
|
}
|
|
}
|
|
SECTION("query details") {
|
|
auto fname = "adjustqueryprops_fivering_2.mol";
|
|
std::string pathName = getenv("RDBASE");
|
|
pathName += "/Code/GraphMol/test_data/";
|
|
std::unique_ptr<RWMol> qry(MolFileToMol(pathName + fname));
|
|
REQUIRE(qry);
|
|
auto smarts = MolToSmarts(*qry);
|
|
CHECK(smarts == "[!#1]1:[#6]:[#6]:[#6]:[#6]:1");
|
|
MolOps::adjustQueryProperties(*qry, &ps);
|
|
smarts = MolToSmarts(*qry);
|
|
CHECK(smarts == "[!#1]1-,=,:[#6]-,=,:[#6]-,=,:[#6]-,=,:[#6]-,=,:1");
|
|
}
|
|
SECTION("some edge cases") {
|
|
{
|
|
auto qry = "C1=COCC1"_smiles;
|
|
auto smarts = MolToSmarts(*qry);
|
|
CHECK(smarts == "[#6]1=[#6]-[#8]-[#6]-[#6]-1");
|
|
MolOps::adjustQueryProperties(*qry, &ps);
|
|
CHECK(MolToSmarts(*qry) == smarts);
|
|
}
|
|
{
|
|
auto qry = "C1=CCC=C1"_smiles;
|
|
auto smarts = MolToSmarts(*qry);
|
|
CHECK(smarts == "[#6]1=[#6]-[#6]-[#6]=[#6]-1");
|
|
MolOps::adjustQueryProperties(*qry, &ps);
|
|
CHECK(MolToSmarts(*qry) ==
|
|
"[#6]1-,=,:[#6]-,=,:[#6]-,=,:[#6]-,=,:[#6]-,=,:1");
|
|
}
|
|
{
|
|
// conjugation (not bond order)
|
|
auto qry = "C1=COOO1"_smiles;
|
|
auto smarts = MolToSmarts(*qry);
|
|
CHECK(smarts == "[#6]1=[#6]-[#8]-[#8]-[#8]-1");
|
|
MolOps::adjustQueryProperties(*qry, &ps);
|
|
CHECK(MolToSmarts(*qry) ==
|
|
"[#6]1-,=,:[#6]-,=,:[#8]-,=,:[#8]-,=,:[#8]-,=,:1");
|
|
}
|
|
{
|
|
// conjugation (not bond order)
|
|
auto qry = "O=C1C(=O)C(=O)C(=O)C1=O"_smiles;
|
|
auto smarts = MolToSmarts(*qry);
|
|
CHECK(smarts ==
|
|
"[#8]=[#6]1-[#6](=[#8])-[#6](=[#8])-[#6](=[#8])-[#6]-1=[#8]");
|
|
MolOps::adjustQueryProperties(*qry, &ps);
|
|
CHECK(MolToSmarts(*qry) ==
|
|
"[#8]=[#6]1-,=,:[#6](=[#8])-,=,:[#6](=[#8])-,=,:[#6](=[#8])-,=,:[#"
|
|
"6]-,=,:1=[#8]");
|
|
}
|
|
}
|
|
SECTION("edge cases: ring finding") {
|
|
// test that ring finding happens:
|
|
SmilesParserParams smiles_ps;
|
|
smiles_ps.sanitize = false;
|
|
std::unique_ptr<RWMol> qry{SmilesToMol("O1C=CC=C1", smiles_ps)};
|
|
REQUIRE(qry);
|
|
qry->updatePropertyCache();
|
|
MolOps::setConjugation(*qry);
|
|
qry->getRingInfo()->reset();
|
|
CHECK(!qry->getRingInfo()->isInitialized());
|
|
MolOps::adjustQueryProperties(*qry, &ps);
|
|
CHECK(qry->getRingInfo()->isInitialized());
|
|
CHECK(MolToSmarts(*qry) ==
|
|
"[#8]1-,=,:[#6]-,=,:[#6]-,=,:[#6]-,=,:[#6]-,=,:1");
|
|
}
|
|
SECTION("edge cases: ignore larger rings") {
|
|
SmilesParserParams smiles_ps;
|
|
smiles_ps.sanitize = false;
|
|
std::unique_ptr<RWMol> qry{SmilesToMol("N1=CC=CC=C1", smiles_ps)};
|
|
REQUIRE(qry);
|
|
qry->updatePropertyCache();
|
|
MolOps::setConjugation(*qry);
|
|
MolOps::adjustQueryProperties(*qry, &ps);
|
|
CHECK(MolToSmarts(*qry) == "[#7]1=[#6]-[#6]=[#6]-[#6]=[#6]-1");
|
|
}
|
|
}
|
|
|
|
TEST_CASE("single bonds to degree-one neighbors") {
|
|
MolOps::AdjustQueryParameters ps =
|
|
MolOps::AdjustQueryParameters::noAdjustments();
|
|
ps.adjustSingleBondsToDegreeOneNeighbors = true;
|
|
SECTION("matching") {
|
|
// clang-format off
|
|
std::vector<matchCase> examples = {
|
|
matchCase{"C2CCCc1c2nncc1","Cc1cnncc1",true,true},
|
|
matchCase{"C2CCCc1c2nncc1","CCc1cnncc1",true,true},
|
|
matchCase{"C2CCC(C)c1c2nncc1","CCc1cnncc1",true,true},
|
|
matchCase{"c2cccc1c2nncc1","Cc1cnncc1",false,true},
|
|
matchCase{"c2cccc1c2nncc1","CCc1cnncc1",false,false},
|
|
matchCase{"c2ccc(C)c1c2nncc1","CCc1cnncc1",false,false},
|
|
|
|
matchCase{"C2CCCc1[nH]ccc12","Cc1[nH]ccc1",true,true},
|
|
matchCase{"C2CCCc1[nH]ccc12","CCc1[nH]ccc1",true,true},
|
|
matchCase{"c2cccc1[nH]ccc12","Cc1[nH]ccc1",false,true},
|
|
matchCase{"c2cccc1[nH]ccc12","CCc1[nH]ccc1",false,false},
|
|
};
|
|
// clang-format on
|
|
for (const auto &tpl : examples) {
|
|
auto smi = std::get<1>(tpl);
|
|
std::unique_ptr<RWMol> qry(SmilesToMol(smi));
|
|
REQUIRE(qry);
|
|
if (std::get<2>(tpl)) {
|
|
CHECK_THAT(std::get<0>(tpl), IsSubstructOf(*qry, smi));
|
|
} else {
|
|
CHECK_THAT(std::get<0>(tpl), !IsSubstructOf(*qry, smi));
|
|
}
|
|
{
|
|
RWMol cp(*qry);
|
|
MolOps::adjustQueryProperties(cp, &ps);
|
|
if (std::get<3>(tpl)) {
|
|
CHECK_THAT(std::get<0>(tpl), IsSubstructOf(cp, smi));
|
|
} else {
|
|
CHECK_THAT(std::get<0>(tpl), !IsSubstructOf(cp, smi));
|
|
}
|
|
}
|
|
{ // make sure ring-finding happens
|
|
RWMol cp(*qry);
|
|
cp.getRingInfo()->reset();
|
|
MolOps::adjustQueryProperties(cp, &ps);
|
|
if (std::get<3>(tpl)) {
|
|
CHECK_THAT(std::get<0>(tpl), IsSubstructOf(cp, smi));
|
|
} else {
|
|
CHECK_THAT(std::get<0>(tpl), !IsSubstructOf(cp, smi));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("single bonds to aromatic neighbors") {
|
|
MolOps::AdjustQueryParameters ps =
|
|
MolOps::AdjustQueryParameters::noAdjustments();
|
|
ps.adjustSingleBondsBetweenAromaticAtoms = true;
|
|
SECTION("matching") {
|
|
// clang-format off
|
|
std::vector<matchCase> examples = {
|
|
matchCase{"c1ncccc1-c1cnncc1","c1ncccc1-c1cnncc1",true,true},
|
|
matchCase{"C1=CC2=C(C=CC3=C2C=NN=C3)N=C1","c1ncccc1-c1cnncc1",false,true},
|
|
matchCase{"C1CC2=C(C=CC=N2)C2=C1C=NN=C2","c1ncccc1-c1cnncc1",true,true},
|
|
matchCase{"C1CC2=NN=CC3=C2C2=C(C=C3)N=CC=C12","c1ncccc1-c1cnncc1",false,true},
|
|
// confirm that we don't modify ring bonds
|
|
matchCase{"C1=CC2=C(C=CC3=C2C=NN=C3)N=C1","C1CC2=C(C=CC=N2)C2=C1C=NN=C2",false,false},
|
|
matchCase{"C1CC2=C(C=CC=N2)C2=C1C=NN=C2","C1CC2=C(C=CC=N2)C2=C1C=NN=C2",true,true},
|
|
matchCase{"C1CC2=C3C(C=CC4=NN=CC1=C34)=CC=N2","C1CC2=C(C=CC=N2)C2=C1C=NN=C2",false,true}, // was github #3325
|
|
};
|
|
// clang-format on
|
|
for (const auto &tpl : examples) {
|
|
auto smi = std::get<1>(tpl);
|
|
std::unique_ptr<RWMol> qry(SmilesToMol(smi));
|
|
REQUIRE(qry);
|
|
if (std::get<2>(tpl)) {
|
|
CHECK_THAT(std::get<0>(tpl), IsSubstructOf(*qry, smi));
|
|
} else {
|
|
CHECK_THAT(std::get<0>(tpl), !IsSubstructOf(*qry, smi));
|
|
}
|
|
MolOps::adjustQueryProperties(*qry, &ps);
|
|
if (std::get<3>(tpl)) {
|
|
CHECK_THAT(std::get<0>(tpl), IsSubstructOf(*qry, smi));
|
|
} else {
|
|
CHECK_THAT(std::get<0>(tpl), !IsSubstructOf(*qry, smi));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"github #3388: Information about charges and isotopes lost when calling "
|
|
"AdjustQueryProperties") {
|
|
MolOps::AdjustQueryParameters ps =
|
|
MolOps::AdjustQueryParameters::noAdjustments();
|
|
ps.adjustDegree = true;
|
|
ps.adjustDegreeFlags = MolOps::AdjustQueryWhichFlags::ADJUST_IGNORENONE;
|
|
SECTION("basics") {
|
|
auto mol = "[13CH3]C[O-]"_smiles;
|
|
REQUIRE(mol);
|
|
MolOps::adjustQueryProperties(*mol, &ps);
|
|
auto sma = MolToSmarts(*mol);
|
|
CHECK(sma == "[#6&13*&D1]-[#6&D2]-[#8&-&D1]");
|
|
}
|
|
SECTION("root cause") {
|
|
auto mol = "[13CH2-]C[O-]"_smiles;
|
|
REQUIRE(mol);
|
|
QueryAtom atm(*mol->getAtomWithIdx(0));
|
|
auto sma = SmartsWrite::GetAtomSmarts(&atm);
|
|
CHECK(sma == "[#6&13*&-]");
|
|
}
|
|
SECTION("root cause2") {
|
|
// since we don't have a way to query for number of radical electrons in
|
|
// SMARTS, we need to check that a different way:
|
|
auto mol = "[CH2]C[O-]"_smiles;
|
|
REQUIRE(mol);
|
|
QueryAtom atm(*mol->getAtomWithIdx(0));
|
|
auto descr = describeQuery(&atm);
|
|
CHECK(descr.find("AtomNumRadicalElectrons 1 = val") != std::string::npos);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("makeAtomsGeneric") {
|
|
SECTION("ignore mapped atoms") {
|
|
MolOps::AdjustQueryParameters ps =
|
|
MolOps::AdjustQueryParameters::noAdjustments();
|
|
ps.makeAtomsGeneric = true;
|
|
ps.makeAtomsGenericFlags =
|
|
MolOps::AdjustQueryWhichFlags::ADJUST_IGNOREMAPPED;
|
|
auto m = "C[CH3:1]"_smiles;
|
|
REQUIRE(m);
|
|
MolOps::adjustQueryProperties(*m, &ps);
|
|
CHECK(MolToSmarts(*m) == "*-[#6:1]");
|
|
}
|
|
}
|
|
|
|
TEST_CASE("other edges") {
|
|
SECTION("adjustHeavyDegree") {
|
|
MolOps::AdjustQueryParameters ps =
|
|
MolOps::AdjustQueryParameters::noAdjustments();
|
|
ps.adjustHeavyDegree = true;
|
|
ps.adjustHeavyDegreeFlags = MolOps::ADJUST_IGNOREMAPPED;
|
|
auto m = "C[CH3:1]"_smiles;
|
|
MolOps::adjustQueryProperties(*m, &ps);
|
|
CHECK(m->getAtomWithIdx(0)->hasQuery());
|
|
CHECK(describeQuery(m->getAtomWithIdx(0)).find("AtomHeavyAtomDegree") !=
|
|
std::string::npos);
|
|
CHECK(!m->getAtomWithIdx(1)->hasQuery());
|
|
}
|
|
SECTION("adjustRingCount") {
|
|
MolOps::AdjustQueryParameters ps =
|
|
MolOps::AdjustQueryParameters::noAdjustments();
|
|
ps.adjustRingCount = true;
|
|
ps.adjustRingCountFlags = MolOps::ADJUST_IGNOREMAPPED;
|
|
auto m = "C[CH3:1]"_smiles;
|
|
MolOps::adjustQueryProperties(*m, &ps);
|
|
CHECK(m->getAtomWithIdx(0)->hasQuery());
|
|
CHECK(describeQuery(m->getAtomWithIdx(0)).find("AtomInNRings") !=
|
|
std::string::npos);
|
|
CHECK(!m->getAtomWithIdx(1)->hasQuery());
|
|
}
|
|
SECTION("adjustRingCount") {
|
|
MolOps::AdjustQueryParameters ps =
|
|
MolOps::AdjustQueryParameters::noAdjustments();
|
|
ps.adjustRingChain = true;
|
|
ps.adjustRingChainFlags = MolOps::ADJUST_IGNOREMAPPED;
|
|
auto m = "C[CH3:1]"_smiles;
|
|
MolOps::adjustQueryProperties(*m, &ps);
|
|
CHECK(m->getAtomWithIdx(0)->hasQuery());
|
|
CHECK(describeQuery(m->getAtomWithIdx(0)).find("AtomInRing") !=
|
|
std::string::npos);
|
|
CHECK(!m->getAtomWithIdx(1)->hasQuery());
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Github #4789: AdjustQueryProperties() is inconsistent when "
|
|
"adjustConjugatedFiveRings is set") {
|
|
SECTION("degree one neighbors") {
|
|
auto mol = "C1=CC=CC1C"_smiles;
|
|
REQUIRE(mol);
|
|
MolOps::AdjustQueryParameters ps =
|
|
MolOps::AdjustQueryParameters::noAdjustments();
|
|
ps.adjustConjugatedFiveRings = true;
|
|
ps.adjustSingleBondsToDegreeOneNeighbors = true;
|
|
MolOps::adjustQueryProperties(*mol, &ps);
|
|
CHECK(MolToSmarts(*mol) ==
|
|
"[#6]1-,=,:[#6]-,=,:[#6]-,=,:[#6]-,=,:[#6]-,=,:1[#6]");
|
|
}
|
|
SECTION("between aromatic atoms") {
|
|
auto mol = "C1=CC=CC1C1=CC=CC1"_smiles;
|
|
REQUIRE(mol);
|
|
MolOps::AdjustQueryParameters ps =
|
|
MolOps::AdjustQueryParameters::noAdjustments();
|
|
ps.adjustConjugatedFiveRings = true;
|
|
ps.adjustSingleBondsBetweenAromaticAtoms = true;
|
|
MolOps::adjustQueryProperties(*mol, &ps);
|
|
// clang-format off
|
|
CHECK(MolToSmarts(*mol) ==
|
|
"[#6]1-,=,:[#6]-,=,:[#6]-,=,:[#6]-,=,:[#6]-,=,:1[#6]1-,=,:[#6]-,=,:[#6]-,=,:[#6]-,=,:[#6]-,=,:1");
|
|
// clang-format on
|
|
}
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Github #8256: PR #8192 breaks AdjustQueryProperties / aromatizeIfPossible for some molecules") {
|
|
SECTION("between aromatic atoms") {
|
|
auto mol =
|
|
R"SMARTS([#6]1=[#6]-[#6]=[#6]-[#6]=[#6]-1/[#7](=[#7]\[#8])-*)SMARTS"_smarts;
|
|
REQUIRE(mol);
|
|
auto ps = MolOps::AdjustQueryParameters::noAdjustments();
|
|
ps.aromatizeIfPossible = true;
|
|
MolOps::adjustQueryProperties(*mol, &ps);
|
|
CHECK(MolToSmiles(*mol) == "*/N(=N/O)c1ccccc1");
|
|
}
|
|
}
|
|
|
|
TEST_CASE("makeAtomsGeneric and other operations") {
|
|
SECTION("makeAtomsGeneric and adjustDegree") {
|
|
MolOps::AdjustQueryParameters ps =
|
|
MolOps::AdjustQueryParameters::noAdjustments();
|
|
ps.makeAtomsGeneric = true;
|
|
ps.adjustDegree = true;
|
|
ps.adjustDegreeFlags = MolOps::ADJUST_IGNOREDUMMIES;
|
|
auto q = "CN"_smiles;
|
|
REQUIRE(q);
|
|
MolOps::adjustQueryProperties(*q, &ps);
|
|
std::vector<std::pair<std::string, bool>> examples = {
|
|
{"CC", true},
|
|
{"CN", true},
|
|
{"CCC", false},
|
|
{"CNC", false},
|
|
};
|
|
for (const auto &tpl : examples) {
|
|
auto smi = tpl.first;
|
|
auto shouldMatch = tpl.second;
|
|
INFO(smi);
|
|
auto m = v2::SmilesParse::MolFromSmiles(smi);
|
|
REQUIRE(m);
|
|
if (shouldMatch) {
|
|
CHECK(!SubstructMatch(*m, *q).empty());
|
|
} else {
|
|
CHECK(SubstructMatch(*m, *q).empty());
|
|
}
|
|
}
|
|
}
|
|
} |