diff --git a/Code/JavaWrappers/Atom.i b/Code/JavaWrappers/Atom.i index 02e9b1bc3..2e998e96e 100644 --- a/Code/JavaWrappers/Atom.i +++ b/Code/JavaWrappers/Atom.i @@ -47,9 +47,29 @@ #include #include #include +#include +#include + +// from Python wrapper +std::string describeQueryHelper(const RDKit::Atom::QUERYATOM_QUERY *q, unsigned int depth) { + std::string res; + if (q) { + for (unsigned int i = 0; i < depth; ++i) { + res += " "; + } + res += q->getFullDescription() + "\n"; + for (const auto &child : + boost::make_iterator_range(q->beginChildren(), q->endChildren())) { + res += describeQueryHelper(child.get(), depth + 1); + } + } + return res; +} + %} %ignore RDKit::Atom::Match(const Atom *) const; +%ignore RDKit::Atom::expandQuery; %template(Bond_Vect) std::vector; %include "enums.swg" @@ -120,4 +140,27 @@ $self->setQuery(query); } + // From Python Wrapper + std::string AtomGetSmarts(bool doKekule=false, bool allHsExplicit=false, + bool isomericSmiles=true) { + std::string res; + if (($self)->hasQuery()) { + res = RDKit::SmartsWrite::GetAtomSmarts(static_cast(($self))); + } else { + // FIX: this should not be necessary + res = RDKit::SmilesWrite::GetAtomSmiles(($self), doKekule, nullptr, allHsExplicit, + isomericSmiles); + } + return res; + } + + // from Python Wrapper + std::string describeQuery() { + std::string res = ""; + res = describeQueryHelper(($self)->getQuery(), 0); + return res; + } + + + } diff --git a/Code/JavaWrappers/Bond.i b/Code/JavaWrappers/Bond.i index b6f55f7b6..07d6d0c5c 100644 --- a/Code/JavaWrappers/Bond.i +++ b/Code/JavaWrappers/Bond.i @@ -47,6 +47,8 @@ %ignore RDKit::Bond::setBeginAtom(Atom *at); %ignore RDKit::Bond::setEndAtom(Atom *at); %ignore RDKit::getTwiceBondType(const RDKit::Bond &b); +%ignore RDKit::Bond::setQuery; +%ignore RDKit::Bond::expandQuery; %include diff --git a/Code/JavaWrappers/Queries.i b/Code/JavaWrappers/Queries.i new file mode 100644 index 000000000..f56e694ef --- /dev/null +++ b/Code/JavaWrappers/Queries.i @@ -0,0 +1,287 @@ +// +// Copyright (C) 2024 Gareth Jones, Glysade LLC +// +// @@ 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 +%shared_ptr(RDKit::QueryAtom) +%shared_ptr(RDKit::QueryBond) + +// Adapted from the Python Wrappers + +%{ +#include +#include +#include +%} + +%include ; +%include ; +%include ; + +/* + NOTE: it looks like there is a typo in the below code + ATOM_GREATER_QUERY is intentionally being used for the LessQueryAtom + and ATOM_LESS_QUERY for GreaterQueryAtom in the python API. + The C++ API is internally consistent and logical, but having + AtomNumLessQueryAtom(6) return atoms where 6 is less than their atomic + number feels backwards in Python. + + */ + %define QAFUNC1(funcname, func, type) + %inline %{ + + boost::shared_ptr funcname ## EqualsQueryAtom(type val, bool negate=false) { + boost::shared_ptr res(new RDKit::QueryAtom()); + res->setQuery(RDKit:: ## func(val)); + if (negate) { + res->getQuery()->setNegation(true); + } + return res; + } + + boost::shared_ptr funcname ## LessQueryAtom(type val, bool negate=false) { + boost::shared_ptr res(new RDKit::QueryAtom()); + res->setQuery( + RDKit:: ## func (val, std::string( # funcname "Less"))); + if (negate) { + res->getQuery()->setNegation(true); + } + return res; + } + + boost::shared_ptr funcname ## GreaterQueryAtom(type val, bool negate=false) { + boost::shared_ptr res(new RDKit::QueryAtom()); + res->setQuery( + RDKit:: ## func (val, std::string(# funcname "Greater"))); + if (negate) { + res->getQuery()->setNegation(true); + } + return res; + } +%} +%enddef + +%define QAFUNC2(funcname, func, type) +%inline %{ + boost::shared_ptr funcname(bool negate=false) { + boost::shared_ptr res(new RDKit::QueryAtom()); + res->setQuery(RDKit:: ## func()); + if (negate) { + res->getQuery()->setNegation(true); + } + return res; + } +%} +%enddef + +QAFUNC1(AtomNum, makeAtomNumQuery, int); +QAFUNC1(ExplicitValence, makeAtomExplicitValenceQuery, int); +QAFUNC1(TotalValence, makeAtomTotalValenceQuery, int); +QAFUNC1(ExplicitDegree, makeAtomExplicitDegreeQuery, int); +QAFUNC1(TotalDegree, makeAtomTotalDegreeQuery, int); +QAFUNC1(NonHydrogenDegree, makeAtomNonHydrogenDegreeQuery, int); +QAFUNC1(HCount, makeAtomHCountQuery, int); +QAFUNC1(Mass, makeAtomMassQuery, int); +QAFUNC1(Isotope, makeAtomIsotopeQuery, int); +QAFUNC1(FormalCharge, makeAtomFormalChargeQuery, int); +QAFUNC1(Hybridization, makeAtomHybridizationQuery, int); +QAFUNC1(InNRings, makeAtomInNRingsQuery, int); +QAFUNC1(MinRingSize, makeAtomMinRingSizeQuery, int); +QAFUNC1(RingBondCount, makeAtomRingBondCountQuery, int); +QAFUNC1(NumRadicalElectrons, makeAtomNumRadicalElectronsQuery, int); +QAFUNC1(NumHeteroatomNeighbors, makeAtomNumHeteroatomNbrsQuery, int); +QAFUNC1(NumAliphaticHeteroatomNeighbors, + makeAtomNumAliphaticHeteroatomNbrsQuery, int); + +QAFUNC2(IsUnsaturatedQueryAtom, makeAtomUnsaturatedQuery, int); +QAFUNC2(IsAromaticQueryAtom, makeAtomAromaticQuery, int); +QAFUNC2(IsAliphaticQueryAtom, makeAtomAliphaticQuery, int); +QAFUNC2(IsInRingQueryAtom, makeAtomInRingQuery, int); +QAFUNC2(HasChiralTagQueryAtom, makeAtomHasChiralTagQuery, int); +QAFUNC2(MissingChiralTagQueryAtom, makeAtomMissingChiralTagQuery, int); +QAFUNC2(IsBridgeheadQueryAtom, makeAtomIsBridgeheadQuery, int); +QAFUNC2(AAtomQueryAtom, makeAAtomQuery, int); +QAFUNC2(AHAtomQueryAtom, makeAHAtomQuery, int); +QAFUNC2(QAtomQueryAtom, makeQAtomQuery, int); +QAFUNC2(QHAtomQueryAtom, makeQHAtomQuery, int); +QAFUNC2(XAtomQueryAtom, makeXAtomQuery, int); +QAFUNC2(XHAtomQueryAtom, makeXHAtomQuery, int); +QAFUNC2(MAtomQueryAtom, makeMAtomQuery, int); +QAFUNC2(MHAtomQueryAtom, makeMHAtomQuery, int); + +%{ + + template + boost::shared_ptr PropQuery(const std::string &propname, const T &v, bool negate) { + boost::shared_ptr res(new Ret()); + res->setQuery(RDKit::makePropQuery(propname, v)); + if (negate) { + res->getQuery()->setNegation(true); + } + return res; + } + + template + boost::shared_ptr PropQueryWithTol(const std::string &propname, const T &v, bool negate, + const T &tol = T()) { + boost::shared_ptr res(new Ret()); + res->setQuery(RDKit::makePropQuery(propname, v, tol)); + if (negate) { + res->getQuery()->setNegation(true); + } + return res; + } + + template + boost::shared_ptr PropQueryWithTol(const std::string &propname, const ExplicitBitVect &v, + bool negate=false, float tol = 0.0) { + boost::shared_ptr res(new Ret()); + res->setQuery(RDKit::makePropQuery(propname, v, tol)); + if (negate) { + res->getQuery()->setNegation(true); + } + return res; + } + + boost::shared_ptr HasPropQueryAtom(const std::string &propname, bool negate=false) { + boost::shared_ptr res(new RDKit::QueryAtom()); + res->setQuery(RDKit::makeHasPropQuery(propname)); + if (negate) { + res->getQuery()->setNegation(true); + } + return res; + } + + boost::shared_ptr HasIntPropWithValueQueryAtom(const std::string &propname, int val, bool negate=false) { + return PropQuery(propname, val, negate); + } + + boost::shared_ptr HasBoolPropWithValueQueryAtom(const std::string &propname, bool val, bool negate=false) { + return PropQuery(propname, val, negate); + } + + boost::shared_ptr HasStringPropWithValueQueryAtom(const std::string &propname, const std::string &val, bool negate=false) { + return PropQuery(propname, val, negate); + } + + boost::shared_ptr HasDoublePropWithValueQueryAtom(const std::string &propname, double val, bool negate=false, double tol=0) { + return PropQueryWithTol(propname, val, negate, tol); + } + + boost::shared_ptr HasBitVectPropWithValueQueryAtom(const std::string &propname, const ExplicitBitVect &val, bool negate=false, float tol=0) { + return PropQueryWithTol(propname, val, negate, tol); + } + + boost::shared_ptr HasPropQueryBond(const std::string &propname, bool negate=false) { + boost::shared_ptr res(new RDKit::QueryBond()); + res->setQuery(RDKit::makeHasPropQuery(propname)); + if (negate) { + res->getQuery()->setNegation(true); + } + return res; + } + + boost::shared_ptr HasIntPropWithValueQueryBond(const std::string &propname, int val, bool negate=false) { + return PropQuery(propname, val, negate); + } + + boost::shared_ptr HasBoolPropWithValueQueryBond(const std::string &propname, bool val, bool negate=false) { + return PropQuery(propname, val, negate); + } + + boost::shared_ptr HasStringPropWithValueQueryBond(const std::string &propname, const std::string &val, bool negate=false) { + return PropQuery(propname, val, negate); + } + + boost::shared_ptr HasDoublePropWithValueQueryBond(const std::string &propname, double val, bool negate=false, double tol=0) { + return PropQueryWithTol(propname, val, negate, tol); + } + +%} + +boost::shared_ptr HasPropQueryAtom(const std::string &propname, bool negate=false); +boost::shared_ptr HasPropQueryBond(const std::string &propname, bool negate=false); +boost::shared_ptr HasIntPropWithValueQueryAtom(const std::string &propname, int val, bool negate=false); +boost::shared_ptr HasBoolPropWithValueQueryAtom(const std::string &propname, bool val, bool negate=false); +boost::shared_ptr HasStringPropWithValueQueryAtom(const std::string &propname, const std::string &val, bool negate=false); +boost::shared_ptr HasDoublePropWithValueQueryAtom(const std::string &propname, double val, bool negate=false, double tol=0); +boost::shared_ptr HasBitVectPropWithValueQueryAtom(const std::string &propname, const ExplicitBitVect &val, bool negate=false, float tol=0); +boost::shared_ptr HasPropQueryBond(const std::string &propname, bool negate=false); +boost::shared_ptr HasIntPropWithValueQueryBond(const std::string &propname, int val, bool negate=false); +boost::shared_ptr HasBoolPropWithValueQueryBond(const std::string &propname, bool val, bool negate=false); +boost::shared_ptr HasStringPropWithValueQueryBond(const std::string &propname, const std::string &val, bool negate=false); +boost::shared_ptr HasDoublePropWithValueQueryBond(const std::string &propname, double val, bool negate=false, double tol=0); + +%extend RDKit::QueryAtom { + void ExpandQuery(const RDKit::QueryAtom *other, Queries::CompositeQueryType how=Queries::COMPOSITE_AND, bool maintainOrder=true) { + PRECONDITION(other, "bad atoms"); + if (other->hasQuery()) { + const RDKit::QueryAtom::QUERYATOM_QUERY *qry = other->getQuery(); + ($self)->expandQuery(qry->copy(), how, maintainOrder); + } + } + + void setQuery(const RDKit::QueryAtom *other) { + PRECONDITION(other, "bad atoms"); + if (other->hasQuery()) { + ($self)->setQuery(other->getQuery()->copy()); + } + } +} + +%extend RDKit::Atom { + void ExpandQuery(const RDKit::QueryAtom *other, Queries::CompositeQueryType how=Queries::COMPOSITE_AND, bool maintainOrder=true) { + PRECONDITION(other, "bad atoms"); + if (other->hasQuery()) { + const RDKit::QueryAtom::QUERYATOM_QUERY *qry = other->getQuery(); + ($self)->expandQuery(qry->copy(), how, maintainOrder); + } + } + + void setQuery(const RDKit::QueryAtom *other) { + PRECONDITION(other, "bad atoms"); + if (other->hasQuery()) { + ($self)->setQuery(other->getQuery()->copy()); + } + } +} + +%extend RDKit::QueryBond { + void ExpandQuery(const RDKit::QueryBond *other, Queries::CompositeQueryType how=Queries::COMPOSITE_AND, bool maintainOrder=true) { + PRECONDITION(other, "bad bonds"); + if (other->hasQuery()) { + const RDKit::QueryBond::QUERYBOND_QUERY *qry = other->getQuery(); + ($self)->expandQuery(qry->copy(), how, maintainOrder); + } + } + + void SetQuery(const RDKit::QueryBond *other) { + PRECONDITION(other, "bad bonds"); + if (other->hasQuery()) { + ($self)->setQuery(other->getQuery()->copy()); + } + } +} + +%extend RDKit::Bond { + void ExpandQuery(const RDKit::QueryBond *other, Queries::CompositeQueryType how=Queries::COMPOSITE_AND, bool maintainOrder=true) { + PRECONDITION(other, "bad bonds"); + if (other->hasQuery()) { + const RDKit::QueryBond::QUERYBOND_QUERY *qry = other->getQuery(); + ($self)->expandQuery(qry->copy(), how, maintainOrder); + } + } + + void SetQuery(const RDKit::QueryBond *other) { + PRECONDITION(other, "bad bonds"); + if (other->hasQuery()) { + ($self)->setQuery(other->getQuery()->copy()); + } + } +} diff --git a/Code/JavaWrappers/QueryAtom.i b/Code/JavaWrappers/QueryAtom.i index 7eab10280..e291cb115 100644 --- a/Code/JavaWrappers/QueryAtom.i +++ b/Code/JavaWrappers/QueryAtom.i @@ -35,6 +35,14 @@ #include %} - +%ignore RDKit::QueryAtom::expandQuery; +%ignore RDKit::QueryAtom::setQuery; %include + +%extend RDKit::QueryAtom { + bool MatchAtom(RDKit::Atom const *what) { + return ($self)->Match(what); + } +} + diff --git a/Code/JavaWrappers/QueryBond.i b/Code/JavaWrappers/QueryBond.i index 724620106..ae10ddfa0 100644 --- a/Code/JavaWrappers/QueryBond.i +++ b/Code/JavaWrappers/QueryBond.i @@ -35,6 +35,15 @@ #include %} - +%ignore RDKit::QueryBond::expandQuery; +%ignore RDKit::QueryBond::setQuery; %include + +%extend RDKit::QueryBond { + bool MatchBond(RDKit::Bond const *what) { + return ($self)->Match(what); + } +} + + diff --git a/Code/JavaWrappers/RDProps.i b/Code/JavaWrappers/RDProps.i index 557b33c6e..c167aaa4c 100644 --- a/Code/JavaWrappers/RDProps.i +++ b/Code/JavaWrappers/RDProps.i @@ -38,5 +38,8 @@ %include -/* For the time being, assume all properties will be strings */ %template(setProp) RDKit::RDProps::setProp; +%template(setIntProp) RDKit::RDProps::setProp; +%template(setBoolProp) RDKit::RDProps::setProp; +%template(setDoubleProp) RDKit::RDProps::setProp; + diff --git a/Code/JavaWrappers/ROMol.i b/Code/JavaWrappers/ROMol.i index 6a14a49ff..6c523dd1f 100644 --- a/Code/JavaWrappers/ROMol.i +++ b/Code/JavaWrappers/ROMol.i @@ -198,6 +198,7 @@ %newobject replaceSidechains; %newobject deleteSubstructs; %newobject getAtoms; +%newobject getBonds; %newobject getAtomNeighbors; %newobject getAtomBonds; @@ -563,29 +564,23 @@ void setAllowNontetrahedralChirality(bool); /* From Python wrappers -- implied functionality */ std::vector *getAtoms() { - int c = ($self)->getNumAtoms(); - std::vector *atoms = new std::vector; - for (int i = 0; i < c; i++) { - RDKit::Atom* a = ($self)->getAtomWithIdx(i); - atoms->push_back(a); - } - return atoms; + auto atoms = ($self)->atoms(); + return new std::vector(atoms.begin(), atoms.end()); + } + + std::vector *getBonds() { + auto bonds = ($self)->bonds(); + return new std::vector(bonds.begin(), bonds.end()); } std::vector *getAtomNeighbors(RDKit::Atom *at) { - std::vector *atoms = new std::vector; - for(const auto &nbri : boost::make_iterator_range(($self)->getAtomNeighbors(at))){ - atoms->push_back((*($self))[nbri]); - } - return atoms; + auto atomNbrs = ($self)->atomNeighbors(at); + return new std::vector(atomNbrs.begin(), atomNbrs.end()); } std::vector *getAtomBonds(RDKit::Atom *at) { - std::vector *bonds = new std::vector; - for(const auto &nbri : boost::make_iterator_range(($self)->getAtomBonds(at))){ - bonds->push_back((*($self))[nbri]); - } - return bonds; + auto bondNbrs = ($self)->atomBonds(at); + return new std::vector(bondNbrs.begin(), bondNbrs.end()); } /* From MolPickler.h */ diff --git a/Code/JavaWrappers/csharp_wrapper/GraphMolCSharp.i b/Code/JavaWrappers/csharp_wrapper/GraphMolCSharp.i index 14ad6a92b..264e3ce69 100644 --- a/Code/JavaWrappers/csharp_wrapper/GraphMolCSharp.i +++ b/Code/JavaWrappers/csharp_wrapper/GraphMolCSharp.i @@ -257,6 +257,7 @@ typedef unsigned long long int uintmax_t; %include "../Streams.i" %include "../GeneralizedSubstruct.i" %include "../RascalMCES.i" +%include "../Queries.i" // Create a class to throw various sorts of errors for testing. Required for unit tests in ErrorHandlingTests.java diff --git a/Code/JavaWrappers/csharp_wrapper/RdkitTests/QueryAtomTest.cs b/Code/JavaWrappers/csharp_wrapper/RdkitTests/QueryAtomTest.cs new file mode 100644 index 000000000..45754c2a3 --- /dev/null +++ b/Code/JavaWrappers/csharp_wrapper/RdkitTests/QueryAtomTest.cs @@ -0,0 +1,252 @@ +// +// Copyright (C) 2020 Gareth Jones, Glysade LLC +// +// @@ 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. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.PortableExecutable; +using GraphMolWrap; +using Xunit; + +namespace RdkitTests +{ + public class QueryAtomTest + { + [Fact] + public void TestChemdrawBlock() + { + // smiles c1[nH]ccc1 + // Chemdraw will convert explicit hydrogens in aromatic compounds to query hydrogen count + var block = @" + ChemDraw05012418152D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 5 5 0 0 0 +M V30 BEGIN ATOM +M V30 1 C -0.652854 -0.050688 0.000000 0 +M V30 2 C -0.240354 0.663783 0.000000 0 +M V30 3 C 0.566618 0.492256 0.000000 0 +M V30 4 C 0.652854 -0.328225 0.000000 0 +M V30 5 N -0.100821 -0.663783 0.000000 0 HCOUNT=1 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 4 1 2 +M V30 2 4 2 3 +M V30 3 4 3 4 +M V30 4 4 4 5 +M V30 5 4 1 5 +M V30 END BOND +M V30 END CTAB +M END +"; + var mol = RWMol.MolFromMolBlock(block, false); + Assert.NotNull(mol); + Assert.Equal(5U, mol.getNumAtoms()); + var queryAtom = mol.getAtomWithIdx(4); + Assert.True(queryAtom.hasQuery()); + var smarts = queryAtom.AtomGetSmarts(); + var expected = "[#7&h{1-}]"; + Assert.Equal(expected, smarts); + var description = queryAtom.describeQuery(); + expected = "AtomAnd\n AtomAtomicNum 7 = val\n less_AtomImplicitHCount 1 <= \n"; + Assert.Equal(expected, description); + + var molSmiles = mol.MolToSmiles(); + var molSmarts = RDKFuncs.MolToSmarts(mol); + + var atom = new Atom(7); + atom.setNumExplicitHs(1); + mol.replaceAtom(4, atom); + mol.clearComputedProps(true); + mol.sanitizeMol(); + + atom = mol.getAtomWithIdx(4); + Assert.Equal(7, atom.getAtomicNum()); + Assert.Equal(1U, atom.getNumExplicitHs()); + molSmiles = mol.MolToSmiles(); + Assert.Equal("c1cc[nH]c1", molSmiles); + } + + [Fact] + public void TestReplaceQuerySimple() + { + var query = RWMol.MolFromSmiles("c1[nH]ccc1"); + var nitrogen = query.getAtomWithIdx(2); + var imp = nitrogen.getNumImplicitHs(); + Assert.Equal(1U, imp); + var queryAtom = RDKFuncs.replaceAtomWithQueryAtom(query, nitrogen); + queryAtom.ExpandQuery(RDKFuncs.ExplicitDegreeEqualsQueryAtom(3)); + var mol1 = RWMol.MolFromSmiles("Cc1[nH]ccc1"); + Assert.True(mol1.hasSubstructMatch(query)); + var mol2 = RWMol.MolFromSmiles("c1[nH]ccc1"); + Assert.False(mol2.hasSubstructMatch(query)); + } + + [Fact] + public void TestExpandQuerySimple() + { + var query = RWMol.MolFromSmarts("c1nccc1"); + var mol1 = RWMol.MolFromSmiles("c1[nH]ccc1"); + Assert.True(mol1.hasSubstructMatch(query)); + var atom = query.getAtomWithIdx(0); + Assert.True(atom.hasQuery()); + var queryAtom = RDKFuncs.ExplicitDegreeEqualsQueryAtom(3); + atom.ExpandQuery(queryAtom, CompositeQueryType.COMPOSITE_AND); + Assert.False(mol1.hasSubstructMatch(query)); + var mol2 = RWMol.MolFromSmiles("Cc1[nH]ccc1"); + Assert.True(mol2.hasSubstructMatch(query)); + } + + + // adapted from Python tests + private IList GetAtomIdxsMatchingQuery(ROMol mol, QueryAtom qa) + { + return mol.getAtoms().Where(qa.MatchAtom).Select(a => a.getIdx()).ToList(); + } + + [Fact] + public void TestQueryAtoms() + { + var m = RWMol.MolFromSmiles("c1nc(C)n(CC)c1"); + var qa = RDKFuncs.ExplicitDegreeEqualsQueryAtom(3); + var matches = GetAtomIdxsMatchingQuery(m, qa); + Assert.Equal(new List { 2U, 4U }, matches); + + qa.ExpandQuery(RDKFuncs.AtomNumEqualsQueryAtom(6, true)); + matches = GetAtomIdxsMatchingQuery(m, qa); + Assert.Equal(new List { 4U }, matches); + + qa = RDKFuncs.ExplicitDegreeEqualsQueryAtom(3); + qa.ExpandQuery(RDKFuncs.AtomNumEqualsQueryAtom(6, true), + CompositeQueryType.COMPOSITE_OR); + matches = GetAtomIdxsMatchingQuery(m, qa); + Assert.Equal(new List { 1U, 2U, 4U }, matches); + + qa = RDKFuncs.ExplicitDegreeEqualsQueryAtom(3); + qa.ExpandQuery(RDKFuncs.AtomNumEqualsQueryAtom(6, true), + CompositeQueryType.COMPOSITE_XOR); + matches = GetAtomIdxsMatchingQuery(m, qa); + Assert.Equal(new List { 1U, 2U }, matches); + + qa = RDKFuncs.ExplicitDegreeGreaterQueryAtom(2); + matches = GetAtomIdxsMatchingQuery(m, qa); + Assert.Equal(new List { 2U, 4U }, matches); + + qa = RDKFuncs.ExplicitDegreeLessQueryAtom(2); + matches = GetAtomIdxsMatchingQuery(m, qa); + Assert.Equal(new List { 3U, 6U }, matches); + + m = RWMol.MolFromSmiles("N[CH][CH]"); + qa = RDKFuncs.NumRadicalElectronsGreaterQueryAtom(0); + matches = GetAtomIdxsMatchingQuery(m, qa); + Assert.Equal(new List { 1U, 2U }, matches); + qa = RDKFuncs.NumRadicalElectronsGreaterQueryAtom(1); + matches = GetAtomIdxsMatchingQuery(m, qa); + Assert.Equal(new List { 2U }, matches); + + m = RWMol.MolFromSmiles("F[C@H](Cl)C"); + qa = RDKFuncs.HasChiralTagQueryAtom(); + matches = GetAtomIdxsMatchingQuery(m, qa); + Assert.Equal(new List { 1U }, matches); + qa = RDKFuncs.MissingChiralTagQueryAtom(); + Assert.Equal(new List { 1U }, matches); + + m = RWMol.MolFromSmiles("F[CH](Cl)C"); + qa = RDKFuncs.HasChiralTagQueryAtom(); + matches = GetAtomIdxsMatchingQuery(m, qa); + Assert.Equal(new List { }, matches); + qa = RDKFuncs.MissingChiralTagQueryAtom(); + matches = GetAtomIdxsMatchingQuery(m, qa); + Assert.Equal(new List { 1U }, matches); + + m = RWMol.MolFromSmiles("CNCON"); + qa = RDKFuncs.NumHeteroatomNeighborsEqualsQueryAtom(2); + matches = GetAtomIdxsMatchingQuery(m, qa); + Assert.Equal(new List { 2U }, matches); + qa = RDKFuncs.NumHeteroatomNeighborsGreaterQueryAtom(0); + matches = GetAtomIdxsMatchingQuery(m, qa); + Assert.Equal(new List { 0U, 2U, 3U, 4U }, matches); + + m = RWMol.MolFromSmiles("CC12CCN(CC1)C2"); + qa = RDKFuncs.IsBridgeheadQueryAtom(); + matches = GetAtomIdxsMatchingQuery(m, qa); + Assert.Equal(new List { 1U, 4U }, matches); + + m = RWMol.MolFromSmiles("OCCOC"); + qa = RDKFuncs.NonHydrogenDegreeEqualsQueryAtom(2); + matches = GetAtomIdxsMatchingQuery(m, qa); + Assert.Equal(new List { 1U, 2U, 3U }, matches); + } + private IList GetBondIdxsMatchingQuery(ROMol mol, QueryBond qb) + { + return mol.getBonds().Where(qb.MatchBond).Select(a => a.getIdx()).ToList(); + } + + + [Fact] + public void TestBondPropQueries() + { + var m = RWMol.MolFromSmiles("CCCCCCCCCCCCCC"); + var bonds = m.getBonds(); + bonds[0].setProp("hah", "hah"); + bonds[1].setIntProp("bar", 1); + bonds[2].setIntProp("bar", 2); + bonds[3].setBoolProp("baz", true); + bonds[4].setBoolProp("baz", false); + bonds[5].setProp("boo", "hoo"); + bonds[6].setProp("boo", "-urns"); + bonds[7].setDoubleProp("boot", 1.0); + bonds[8].setDoubleProp("boot", 4.0); + bonds[9].setDoubleProp("number", 4.0); + bonds[10].setIntProp("number", 4); + + var qb = RDKFuncs.HasIntPropWithValueQueryBond("bar",1); + var matches = GetBondIdxsMatchingQuery(m, qb); + Assert.Equal(new List { 1U }, matches); + + qb = RDKFuncs.HasIntPropWithValueQueryBond("bar",2); + matches = GetBondIdxsMatchingQuery(m, qb); + Assert.Equal(new List { 2U }, matches); + + qb = RDKFuncs.HasBoolPropWithValueQueryBond("baz",true); + matches = GetBondIdxsMatchingQuery(m, qb); + Assert.Equal(new List { 3U }, matches); + + qb = RDKFuncs.HasBoolPropWithValueQueryBond("baz",false); + matches = GetBondIdxsMatchingQuery(m, qb); + Assert.Equal(new List { 4U }, matches); + + qb = RDKFuncs.HasStringPropWithValueQueryBond("boo","hoo"); + matches = GetBondIdxsMatchingQuery(m, qb); + Assert.Equal(new List { 5U }, matches); + + qb = RDKFuncs.HasStringPropWithValueQueryBond("boo","-urns"); + matches = GetBondIdxsMatchingQuery(m, qb); + Assert.Equal(new List { 6U }, matches); + + qb = RDKFuncs.HasDoublePropWithValueQueryBond("boot",1.0); + matches = GetBondIdxsMatchingQuery(m, qb); + Assert.Equal(new List { 7U }, matches); + + qb = RDKFuncs.HasDoublePropWithValueQueryBond("boot",4.0); + matches = GetBondIdxsMatchingQuery(m, qb); + Assert.Equal(new List { 8U }, matches); + + qb = RDKFuncs.HasDoublePropWithValueQueryBond("boot",1.0, false, 3.0); + matches = GetBondIdxsMatchingQuery(m, qb); + Assert.Equal(new List { 7U, 8U }, matches); + + qb = RDKFuncs.HasIntPropWithValueQueryBond("number",4); + matches = GetBondIdxsMatchingQuery(m, qb); + Assert.Equal(new List { 10U }, matches); + } + } +} \ No newline at end of file diff --git a/Code/JavaWrappers/gmwrapper/CMakeLists.txt b/Code/JavaWrappers/gmwrapper/CMakeLists.txt index ef50db449..cbc6b6f72 100644 --- a/Code/JavaWrappers/gmwrapper/CMakeLists.txt +++ b/Code/JavaWrappers/gmwrapper/CMakeLists.txt @@ -307,6 +307,10 @@ ADD_TEST(JavaRascalMCES java -Djava.library.path=${CMAKE_CURRENT_SOURCE_DIR} -cp "${JUNIT_JAR}${PATH_SEP}${CMAKE_JAVA_TEST_OUTDIR}${PATH_SEP}${CMAKE_CURRENT_SOURCE_DIR}/org.RDKit.jar" org.RDKit.RascalMCESTest) +ADD_TEST(JavaQueryAtomTest + java -Djava.library.path=${CMAKE_CURRENT_SOURCE_DIR} + -cp "${JUNIT_JAR}${PATH_SEP}${CMAKE_JAVA_TEST_OUTDIR}${PATH_SEP}${CMAKE_CURRENT_SOURCE_DIR}/org.RDKit.jar" + org.RDKit.QueryAtomTest) if(RDK_BUILD_AVALON_SUPPORT) ADD_TEST(JavaAvalonTests diff --git a/Code/JavaWrappers/gmwrapper/GraphMolJava.i b/Code/JavaWrappers/gmwrapper/GraphMolJava.i index 0a930086f..e5fcb27a1 100644 --- a/Code/JavaWrappers/gmwrapper/GraphMolJava.i +++ b/Code/JavaWrappers/gmwrapper/GraphMolJava.i @@ -226,6 +226,7 @@ typedef unsigned long long int uintmax_t; %include "../Streams.i" %include "../GeneralizedSubstruct.i" %include "../RascalMCES.i" +%include "../Queries.i" // Create a class to throw various sorts of errors for testing. Required for unit tests in ErrorHandlingTests.java #ifdef INCLUDE_ERROR_GENERATOR diff --git a/Code/JavaWrappers/gmwrapper/src-test/org/RDKit/QueryAtomTest.java b/Code/JavaWrappers/gmwrapper/src-test/org/RDKit/QueryAtomTest.java new file mode 100644 index 000000000..95bfe0522 --- /dev/null +++ b/Code/JavaWrappers/gmwrapper/src-test/org/RDKit/QueryAtomTest.java @@ -0,0 +1,268 @@ +// Copyright (C) 2020 Gareth Jones, Glysade LLC +// +// @@ 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. +// + +package org.RDKit; + +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.*; + +public class QueryAtomTest extends GraphMolTest { + + @Test + public void testChemdrawBlock() { + // smiles c1[nH]ccc1 + // Chemdraw will convert explicit hydrogens in aromatic compounds to query hydrogen count + String block = String.join("\n", + "ChemDraw05012418152D", + "", + "0 0 0 0 0 0 V3000", + "M V30 BEGIN CTAB", + "M V30 COUNTS 5 5 0 0 0", + "M V30 BEGIN ATOM", + "M V30 1 C - 0.652854 - 0.050688 0.000000 0", + "M V30 2 C - 0.240354 0.663783 0.000000 0", + "M V30 3 C 0.566618 0.492256 0.000000 0", + "M V30 4 C 0.652854 - 0.328225 0.000000 0", + "M V30 5 N - 0.100821 - 0.663783 0.000000 0 HCOUNT = 1", + "M V30 END ATOM", + "M V30 BEGIN BOND", + "M V30 1 4 1 2", + "M V30 2 4 2 3", + "M V30 3 4 3 4", + "M V30 4 4 4 5", + "M V30 5 4 1 5", + "M V30 END BOND", + "M V30 END CTAB", + "M END", + ""); + RWMol mol = RWMol.MolFromMolBlock(block, false); + assertNotNull(mol); + assertEquals(5, mol.getNumAtoms()); + Atom queryAtom = mol.getAtomWithIdx(4); + assertTrue(queryAtom.hasQuery()); + String smarts = queryAtom.AtomGetSmarts(); + String expected = "[#7&h{1-}]"; + assertEquals(expected, smarts); + String description = queryAtom.describeQuery(); + expected = "AtomAnd\n AtomAtomicNum 7 = val\n less_AtomImplicitHCount 1 <= \n"; + assertEquals(expected, description); + + String molSmiles = mol.MolToSmiles(); + + Atom atom = new Atom(7); + atom.setNumExplicitHs(1); + mol.replaceAtom(4, atom); + mol.clearComputedProps(true); + mol.sanitizeMol(); + + atom = mol.getAtomWithIdx(4); + assertEquals(7, atom.getAtomicNum()); + assertEquals(1, atom.getNumExplicitHs()); + molSmiles = mol.MolToSmiles(); + assertEquals("c1cc[nH]c1", molSmiles); + } + + @Test + public void testReplaceQuerySimple() { + RWMol query = RWMol.MolFromSmiles("c1[nH]ccc1"); + Atom nitrogen = query.getAtomWithIdx(2); + long imp = nitrogen.getNumImplicitHs(); + assertEquals(1, imp); + Atom queryAtom = RDKFuncs.replaceAtomWithQueryAtom(query, nitrogen); + queryAtom.ExpandQuery(RDKFuncs.ExplicitDegreeEqualsQueryAtom(3)); + RWMol mol1 = RWMol.MolFromSmiles("Cc1[nH]ccc1"); + assertTrue(mol1.hasSubstructMatch(query)); + RWMol mol2 = RWMol.MolFromSmiles("c1[nH]ccc1"); + assertFalse(mol2.hasSubstructMatch(query)); + } + + @Test + public void testExpandQuerySimple() { + RWMol query = RWMol.MolFromSmarts("c1nccc1"); + RWMol mol1 = RWMol.MolFromSmiles("c1[nH]ccc1"); + assertTrue(mol1.hasSubstructMatch(query)); + Atom atom = query.getAtomWithIdx(0); + assertTrue(atom.hasQuery()); + QueryAtom queryAtom = RDKFuncs.ExplicitDegreeEqualsQueryAtom(3); + atom.ExpandQuery(queryAtom, CompositeQueryType.COMPOSITE_AND); + assertFalse(mol1.hasSubstructMatch(query)); + RWMol mol2 = RWMol.MolFromSmiles("Cc1[nH]ccc1"); + assertTrue(mol2.hasSubstructMatch(query)); + } + + // adapted from Python tests + + private int[] GetAtomIdxsMatchingQuery(ROMol mol, QueryAtom qa) { + List indices = new ArrayList(); + for (int i =0; i< mol.getNumAtoms(); i++) { + Atom atom = mol.getAtomWithIdx(i); + if (qa.MatchAtom(atom)) { + indices.add(i); + } + } + int[] arr = new int[indices.size()]; + for (int i=0; i indices = new ArrayList(); + for (int i =0; i< mol.getNumBonds(); i++) { + Bond atom = mol.getBondWithIdx(i); + if (qa.MatchBond(atom)) { + indices.add(i); + } + } + int[] arr = new int[indices.size()]; + for (int i=0; i