Handle query atoms and bonds in SWIG wrappers (#7431)

* Query atom information in swig

* Atom query swig wrapping working

* SWIG wrapper for queries

* Add match to QueryAtom and QueryBond

* CShart test

* Added Java test

* Replace var with type for Java test

* Apply suggestions from code review

Co-authored-by: Paolo Tosco <paolo.tosco.mail@gmail.com>

* Update Code/JavaWrappers/Queries.i

Co-authored-by: Paolo Tosco <paolo.tosco.mail@gmail.com>

* Apply suggestions from code review

Co-authored-by: Paolo Tosco <paolo.tosco.mail@gmail.com>

* Get SWIG build working again

* Remove trailing whitespace from Queries.i

* Update Queries.i to use shared_ptr

* small simplification

* remove boost::make_iterator from ROMol.i

* further simplification

---------

Co-authored-by: Paolo Tosco <paolo.tosco.mail@gmail.com>
Co-authored-by: ptosco <paolo.tosco@novartis.com>
This commit is contained in:
Gareth Jones
2024-05-22 23:18:49 -06:00
committed by GitHub
parent db952786b6
commit 930c7d6345
12 changed files with 893 additions and 20 deletions

View File

@@ -47,9 +47,29 @@
#include <GraphMol/MolTransforms/MolTransforms.h>
#include <Geometry/point.h>
#include <GraphMol/MolOps.h>
#include <GraphMol/SmilesParse/SmilesWrite.h>
#include <GraphMol/SmilesParse/SmartsWrite.h>
// 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<RDKit::Bond*>;
%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<const RDKit::QueryAtom *>(($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;
}
}

View File

@@ -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 <GraphMol/Bond.h>

287
Code/JavaWrappers/Queries.i Normal file
View File

@@ -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 <boost_shared_ptr.i>
%shared_ptr(RDKit::QueryAtom)
%shared_ptr(RDKit::QueryBond)
// Adapted from the Python Wrappers
%{
#include <GraphMol/RDKitBase.h>
#include <GraphMol/RDKitQueries.h>
#include <RDGeneral/types.h>
%}
%include <GraphMol/RDKitBase.h>;
%include <GraphMol/RDKitQueries.h>;
%include <RDGeneral/types.h>;
/*
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<RDKit::QueryAtom> funcname ## EqualsQueryAtom(type val, bool negate=false) {
boost::shared_ptr<RDKit::QueryAtom> res(new RDKit::QueryAtom());
res->setQuery(RDKit:: ## func(val));
if (negate) {
res->getQuery()->setNegation(true);
}
return res;
}
boost::shared_ptr<RDKit::QueryAtom> funcname ## LessQueryAtom(type val, bool negate=false) {
boost::shared_ptr<RDKit::QueryAtom> res(new RDKit::QueryAtom());
res->setQuery(
RDKit:: ## func <RDKit::ATOM_GREATER_QUERY>(val, std::string( # funcname "Less")));
if (negate) {
res->getQuery()->setNegation(true);
}
return res;
}
boost::shared_ptr<RDKit::QueryAtom> funcname ## GreaterQueryAtom(type val, bool negate=false) {
boost::shared_ptr<RDKit::QueryAtom> res(new RDKit::QueryAtom());
res->setQuery(
RDKit:: ## func <RDKit::ATOM_LESS_QUERY>(val, std::string(# funcname "Greater")));
if (negate) {
res->getQuery()->setNegation(true);
}
return res;
}
%}
%enddef
%define QAFUNC2(funcname, func, type)
%inline %{
boost::shared_ptr<RDKit::QueryAtom> funcname(bool negate=false) {
boost::shared_ptr<RDKit::QueryAtom> 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 <class Ob, class Ret, class T>
boost::shared_ptr<Ret> PropQuery(const std::string &propname, const T &v, bool negate) {
boost::shared_ptr<Ret> res(new Ret());
res->setQuery(RDKit::makePropQuery<Ob, T>(propname, v));
if (negate) {
res->getQuery()->setNegation(true);
}
return res;
}
template <class Ob, class Ret, class T>
boost::shared_ptr<Ret> PropQueryWithTol(const std::string &propname, const T &v, bool negate,
const T &tol = T()) {
boost::shared_ptr<Ret> res(new Ret());
res->setQuery(RDKit::makePropQuery<Ob, T>(propname, v, tol));
if (negate) {
res->getQuery()->setNegation(true);
}
return res;
}
template <class Ob, class Ret>
boost::shared_ptr<Ret> PropQueryWithTol(const std::string &propname, const ExplicitBitVect &v,
bool negate=false, float tol = 0.0) {
boost::shared_ptr<Ret> res(new Ret());
res->setQuery(RDKit::makePropQuery<Ob>(propname, v, tol));
if (negate) {
res->getQuery()->setNegation(true);
}
return res;
}
boost::shared_ptr<RDKit::QueryAtom> HasPropQueryAtom(const std::string &propname, bool negate=false) {
boost::shared_ptr<RDKit::QueryAtom> res(new RDKit::QueryAtom());
res->setQuery(RDKit::makeHasPropQuery<RDKit::Atom>(propname));
if (negate) {
res->getQuery()->setNegation(true);
}
return res;
}
boost::shared_ptr<RDKit::QueryAtom> HasIntPropWithValueQueryAtom(const std::string &propname, int val, bool negate=false) {
return PropQuery<RDKit::Atom, RDKit::QueryAtom, int>(propname, val, negate);
}
boost::shared_ptr<RDKit::QueryAtom> HasBoolPropWithValueQueryAtom(const std::string &propname, bool val, bool negate=false) {
return PropQuery<RDKit::Atom, RDKit::QueryAtom, bool>(propname, val, negate);
}
boost::shared_ptr<RDKit::QueryAtom> HasStringPropWithValueQueryAtom(const std::string &propname, const std::string &val, bool negate=false) {
return PropQuery<RDKit::Atom, RDKit::QueryAtom, std::string>(propname, val, negate);
}
boost::shared_ptr<RDKit::QueryAtom> HasDoublePropWithValueQueryAtom(const std::string &propname, double val, bool negate=false, double tol=0) {
return PropQueryWithTol<RDKit::Atom, RDKit::QueryAtom, double>(propname, val, negate, tol);
}
boost::shared_ptr<RDKit::QueryAtom> HasBitVectPropWithValueQueryAtom(const std::string &propname, const ExplicitBitVect &val, bool negate=false, float tol=0) {
return PropQueryWithTol<RDKit::Atom, RDKit::QueryAtom>(propname, val, negate, tol);
}
boost::shared_ptr<RDKit::QueryBond> HasPropQueryBond(const std::string &propname, bool negate=false) {
boost::shared_ptr<RDKit::QueryBond> res(new RDKit::QueryBond());
res->setQuery(RDKit::makeHasPropQuery<RDKit::Bond>(propname));
if (negate) {
res->getQuery()->setNegation(true);
}
return res;
}
boost::shared_ptr<RDKit::QueryBond> HasIntPropWithValueQueryBond(const std::string &propname, int val, bool negate=false) {
return PropQuery<RDKit::Bond, RDKit::QueryBond, int>(propname, val, negate);
}
boost::shared_ptr<RDKit::QueryBond> HasBoolPropWithValueQueryBond(const std::string &propname, bool val, bool negate=false) {
return PropQuery<RDKit::Bond, RDKit::QueryBond, bool>(propname, val, negate);
}
boost::shared_ptr<RDKit::QueryBond> HasStringPropWithValueQueryBond(const std::string &propname, const std::string &val, bool negate=false) {
return PropQuery<RDKit::Bond, RDKit::QueryBond, std::string>(propname, val, negate);
}
boost::shared_ptr<RDKit::QueryBond> HasDoublePropWithValueQueryBond(const std::string &propname, double val, bool negate=false, double tol=0) {
return PropQueryWithTol<RDKit::Bond, RDKit::QueryBond, double>(propname, val, negate, tol);
}
%}
boost::shared_ptr<RDKit::QueryAtom> HasPropQueryAtom(const std::string &propname, bool negate=false);
boost::shared_ptr<RDKit::QueryBond> HasPropQueryBond(const std::string &propname, bool negate=false);
boost::shared_ptr<RDKit::QueryAtom> HasIntPropWithValueQueryAtom(const std::string &propname, int val, bool negate=false);
boost::shared_ptr<RDKit::QueryAtom> HasBoolPropWithValueQueryAtom(const std::string &propname, bool val, bool negate=false);
boost::shared_ptr<RDKit::QueryAtom> HasStringPropWithValueQueryAtom(const std::string &propname, const std::string &val, bool negate=false);
boost::shared_ptr<RDKit::QueryAtom> HasDoublePropWithValueQueryAtom(const std::string &propname, double val, bool negate=false, double tol=0);
boost::shared_ptr<RDKit::QueryAtom> HasBitVectPropWithValueQueryAtom(const std::string &propname, const ExplicitBitVect &val, bool negate=false, float tol=0);
boost::shared_ptr<RDKit::QueryBond> HasPropQueryBond(const std::string &propname, bool negate=false);
boost::shared_ptr<RDKit::QueryBond> HasIntPropWithValueQueryBond(const std::string &propname, int val, bool negate=false);
boost::shared_ptr<RDKit::QueryBond> HasBoolPropWithValueQueryBond(const std::string &propname, bool val, bool negate=false);
boost::shared_ptr<RDKit::QueryBond> HasStringPropWithValueQueryBond(const std::string &propname, const std::string &val, bool negate=false);
boost::shared_ptr<RDKit::QueryBond> 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());
}
}
}

View File

@@ -35,6 +35,14 @@
#include <GraphMol/QueryAtom.h>
%}
%ignore RDKit::QueryAtom::expandQuery;
%ignore RDKit::QueryAtom::setQuery;
%include <GraphMol/QueryAtom.h>
%extend RDKit::QueryAtom {
bool MatchAtom(RDKit::Atom const *what) {
return ($self)->Match(what);
}
}

View File

@@ -35,6 +35,15 @@
#include <GraphMol/QueryBond.h>
%}
%ignore RDKit::QueryBond::expandQuery;
%ignore RDKit::QueryBond::setQuery;
%include <GraphMol/QueryBond.h>
%extend RDKit::QueryBond {
bool MatchBond(RDKit::Bond const *what) {
return ($self)->Match(what);
}
}

View File

@@ -38,5 +38,8 @@
%include <RDGeneral/RDProps.h>
/* For the time being, assume all properties will be strings */
%template(setProp) RDKit::RDProps::setProp<std::string>;
%template(setIntProp) RDKit::RDProps::setProp<int>;
%template(setBoolProp) RDKit::RDProps::setProp<bool>;
%template(setDoubleProp) RDKit::RDProps::setProp<double>;

View File

@@ -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<RDKit::Atom*> *getAtoms() {
int c = ($self)->getNumAtoms();
std::vector<RDKit::Atom*> *atoms = new std::vector<RDKit::Atom*>;
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<RDKit::Atom*>(atoms.begin(), atoms.end());
}
std::vector<RDKit::Bond*> *getBonds() {
auto bonds = ($self)->bonds();
return new std::vector<RDKit::Bond*>(bonds.begin(), bonds.end());
}
std::vector<RDKit::Atom*> *getAtomNeighbors(RDKit::Atom *at) {
std::vector<RDKit::Atom*> *atoms = new std::vector<RDKit::Atom*>;
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<RDKit::Atom*>(atomNbrs.begin(), atomNbrs.end());
}
std::vector<RDKit::Bond*> *getAtomBonds(RDKit::Atom *at) {
std::vector<RDKit::Bond*> *bonds = new std::vector<RDKit::Bond*>;
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<RDKit::Bond*>(bondNbrs.begin(), bondNbrs.end());
}
/* From MolPickler.h */

View File

@@ -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

View File

@@ -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<uint> 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<uint> { 2U, 4U }, matches);
qa.ExpandQuery(RDKFuncs.AtomNumEqualsQueryAtom(6, true));
matches = GetAtomIdxsMatchingQuery(m, qa);
Assert.Equal(new List<uint> { 4U }, matches);
qa = RDKFuncs.ExplicitDegreeEqualsQueryAtom(3);
qa.ExpandQuery(RDKFuncs.AtomNumEqualsQueryAtom(6, true),
CompositeQueryType.COMPOSITE_OR);
matches = GetAtomIdxsMatchingQuery(m, qa);
Assert.Equal(new List<uint> { 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<uint> { 1U, 2U }, matches);
qa = RDKFuncs.ExplicitDegreeGreaterQueryAtom(2);
matches = GetAtomIdxsMatchingQuery(m, qa);
Assert.Equal(new List<uint> { 2U, 4U }, matches);
qa = RDKFuncs.ExplicitDegreeLessQueryAtom(2);
matches = GetAtomIdxsMatchingQuery(m, qa);
Assert.Equal(new List<uint> { 3U, 6U }, matches);
m = RWMol.MolFromSmiles("N[CH][CH]");
qa = RDKFuncs.NumRadicalElectronsGreaterQueryAtom(0);
matches = GetAtomIdxsMatchingQuery(m, qa);
Assert.Equal(new List<uint> { 1U, 2U }, matches);
qa = RDKFuncs.NumRadicalElectronsGreaterQueryAtom(1);
matches = GetAtomIdxsMatchingQuery(m, qa);
Assert.Equal(new List<uint> { 2U }, matches);
m = RWMol.MolFromSmiles("F[C@H](Cl)C");
qa = RDKFuncs.HasChiralTagQueryAtom();
matches = GetAtomIdxsMatchingQuery(m, qa);
Assert.Equal(new List<uint> { 1U }, matches);
qa = RDKFuncs.MissingChiralTagQueryAtom();
Assert.Equal(new List<uint> { 1U }, matches);
m = RWMol.MolFromSmiles("F[CH](Cl)C");
qa = RDKFuncs.HasChiralTagQueryAtom();
matches = GetAtomIdxsMatchingQuery(m, qa);
Assert.Equal(new List<uint> { }, matches);
qa = RDKFuncs.MissingChiralTagQueryAtom();
matches = GetAtomIdxsMatchingQuery(m, qa);
Assert.Equal(new List<uint> { 1U }, matches);
m = RWMol.MolFromSmiles("CNCON");
qa = RDKFuncs.NumHeteroatomNeighborsEqualsQueryAtom(2);
matches = GetAtomIdxsMatchingQuery(m, qa);
Assert.Equal(new List<uint> { 2U }, matches);
qa = RDKFuncs.NumHeteroatomNeighborsGreaterQueryAtom(0);
matches = GetAtomIdxsMatchingQuery(m, qa);
Assert.Equal(new List<uint> { 0U, 2U, 3U, 4U }, matches);
m = RWMol.MolFromSmiles("CC12CCN(CC1)C2");
qa = RDKFuncs.IsBridgeheadQueryAtom();
matches = GetAtomIdxsMatchingQuery(m, qa);
Assert.Equal(new List<uint> { 1U, 4U }, matches);
m = RWMol.MolFromSmiles("OCCOC");
qa = RDKFuncs.NonHydrogenDegreeEqualsQueryAtom(2);
matches = GetAtomIdxsMatchingQuery(m, qa);
Assert.Equal(new List<uint> { 1U, 2U, 3U }, matches);
}
private IList<uint> 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<uint> { 1U }, matches);
qb = RDKFuncs.HasIntPropWithValueQueryBond("bar",2);
matches = GetBondIdxsMatchingQuery(m, qb);
Assert.Equal(new List<uint> { 2U }, matches);
qb = RDKFuncs.HasBoolPropWithValueQueryBond("baz",true);
matches = GetBondIdxsMatchingQuery(m, qb);
Assert.Equal(new List<uint> { 3U }, matches);
qb = RDKFuncs.HasBoolPropWithValueQueryBond("baz",false);
matches = GetBondIdxsMatchingQuery(m, qb);
Assert.Equal(new List<uint> { 4U }, matches);
qb = RDKFuncs.HasStringPropWithValueQueryBond("boo","hoo");
matches = GetBondIdxsMatchingQuery(m, qb);
Assert.Equal(new List<uint> { 5U }, matches);
qb = RDKFuncs.HasStringPropWithValueQueryBond("boo","-urns");
matches = GetBondIdxsMatchingQuery(m, qb);
Assert.Equal(new List<uint> { 6U }, matches);
qb = RDKFuncs.HasDoublePropWithValueQueryBond("boot",1.0);
matches = GetBondIdxsMatchingQuery(m, qb);
Assert.Equal(new List<uint> { 7U }, matches);
qb = RDKFuncs.HasDoublePropWithValueQueryBond("boot",4.0);
matches = GetBondIdxsMatchingQuery(m, qb);
Assert.Equal(new List<uint> { 8U }, matches);
qb = RDKFuncs.HasDoublePropWithValueQueryBond("boot",1.0, false, 3.0);
matches = GetBondIdxsMatchingQuery(m, qb);
Assert.Equal(new List<uint> { 7U, 8U }, matches);
qb = RDKFuncs.HasIntPropWithValueQueryBond("number",4);
matches = GetBondIdxsMatchingQuery(m, qb);
Assert.Equal(new List<uint> { 10U }, matches);
}
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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<Integer> indices = new ArrayList<Integer>();
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<arr.length; i++) {
arr[i] = indices.get(i);
}
return arr;
}
@Test
public void testQueryAtoms() {
RWMol m = RWMol.MolFromSmiles("c1nc(C)n(CC)c1");
QueryAtom qa = RDKFuncs.ExplicitDegreeEqualsQueryAtom(3);
int[] matches = GetAtomIdxsMatchingQuery(m, qa);
assertArrayEquals(new int[] {2, 4}, matches);
qa.ExpandQuery(RDKFuncs.AtomNumEqualsQueryAtom(6, true));
matches = GetAtomIdxsMatchingQuery(m, qa);
assertArrayEquals(new int[] {4}, matches);
qa = RDKFuncs.ExplicitDegreeEqualsQueryAtom(3);
qa.ExpandQuery(RDKFuncs.AtomNumEqualsQueryAtom(6, true),
CompositeQueryType.COMPOSITE_OR);
matches = GetAtomIdxsMatchingQuery(m, qa);
assertArrayEquals(new int[] {1, 2, 4}, matches);
qa = RDKFuncs.ExplicitDegreeEqualsQueryAtom(3);
qa.ExpandQuery(RDKFuncs.AtomNumEqualsQueryAtom(6, true),
CompositeQueryType.COMPOSITE_XOR);
matches = GetAtomIdxsMatchingQuery(m, qa);
assertArrayEquals(new int[] {1, 2}, matches);
qa = RDKFuncs.ExplicitDegreeGreaterQueryAtom(2);
matches = GetAtomIdxsMatchingQuery(m, qa);
assertArrayEquals(new int[] {2, 4}, matches);
qa = RDKFuncs.ExplicitDegreeLessQueryAtom(2);
matches = GetAtomIdxsMatchingQuery(m, qa);
assertArrayEquals(new int[] {3, 6}, matches);
m = RWMol.MolFromSmiles("N[CH][CH]");
qa = RDKFuncs.NumRadicalElectronsGreaterQueryAtom(0);
matches = GetAtomIdxsMatchingQuery(m, qa);
assertArrayEquals(new int[] {1, 2}, matches);
qa = RDKFuncs.NumRadicalElectronsGreaterQueryAtom(1);
matches = GetAtomIdxsMatchingQuery(m, qa);
assertArrayEquals(new int[] {2}, matches);
m = RWMol.MolFromSmiles("F[C@H](Cl)C");
qa = RDKFuncs.HasChiralTagQueryAtom();
matches = GetAtomIdxsMatchingQuery(m, qa);
assertArrayEquals(new int[] {1}, matches);
qa = RDKFuncs.MissingChiralTagQueryAtom();
assertArrayEquals(new int[] {1}, matches);
m = RWMol.MolFromSmiles("F[CH](Cl)C");
qa = RDKFuncs.HasChiralTagQueryAtom();
matches = GetAtomIdxsMatchingQuery(m, qa);
assertArrayEquals(new int[] {}, matches);
qa = RDKFuncs.MissingChiralTagQueryAtom();
matches = GetAtomIdxsMatchingQuery(m, qa);
assertArrayEquals(new int[] {1}, matches);
m = RWMol.MolFromSmiles("CNCON");
qa = RDKFuncs.NumHeteroatomNeighborsEqualsQueryAtom(2);
matches = GetAtomIdxsMatchingQuery(m, qa);
assertArrayEquals(new int[] {2}, matches);
qa = RDKFuncs.NumHeteroatomNeighborsGreaterQueryAtom(0);
matches = GetAtomIdxsMatchingQuery(m, qa);
assertArrayEquals(new int[] {0, 2, 3, 4}, matches);
m = RWMol.MolFromSmiles("CC12CCN(CC1)C2");
qa = RDKFuncs.IsBridgeheadQueryAtom();
matches = GetAtomIdxsMatchingQuery(m, qa);
assertArrayEquals(new int[] {1, 4}, matches);
m = RWMol.MolFromSmiles("OCCOC");
qa = RDKFuncs.NonHydrogenDegreeEqualsQueryAtom(2);
matches = GetAtomIdxsMatchingQuery(m, qa);
assertArrayEquals(new int[] {1, 2, 3}, matches);
}
private int[] GetBondIdxsMatchingQuery(ROMol mol, QueryBond qa) {
List<Integer> indices = new ArrayList<Integer>();
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<arr.length; i++) {
arr[i] = indices.get(i);
}
return arr;
}
@Test
public void TestBondPropQueries() {
RWMol m = RWMol.MolFromSmiles("CCCCCCCCCCCCCC");
Bond_Vect bonds = m.getBonds();
bonds.get(0).setProp("hah", "hah");
bonds.get(1).setIntProp("bar", 1);
bonds.get(2).setIntProp("bar", 2);
bonds.get(3).setBoolProp("baz", true);
bonds.get(4).setBoolProp("baz", false);
bonds.get(5).setProp("boo", "hoo");
bonds.get(6).setProp("boo", "-urns");
bonds.get(7).setDoubleProp("boot", 1.0);
bonds.get(8).setDoubleProp("boot", 4.0);
bonds.get(9).setDoubleProp("number", 4.0);
bonds.get(10).setIntProp("number", 4);
QueryBond qb = RDKFuncs.HasIntPropWithValueQueryBond("bar", 1);
int[] matches = GetBondIdxsMatchingQuery(m, qb);
assertArrayEquals(new int[] {1}, matches);
qb = RDKFuncs.HasIntPropWithValueQueryBond("bar", 2);
matches = GetBondIdxsMatchingQuery(m, qb);
assertArrayEquals(new int[] {2}, matches);
qb = RDKFuncs.HasBoolPropWithValueQueryBond("baz", true);
matches = GetBondIdxsMatchingQuery(m, qb);
assertArrayEquals(new int[] {3}, matches);
qb = RDKFuncs.HasBoolPropWithValueQueryBond("baz", false);
matches = GetBondIdxsMatchingQuery(m, qb);
assertArrayEquals(new int[] {4}, matches);
qb = RDKFuncs.HasStringPropWithValueQueryBond("boo", "hoo");
matches = GetBondIdxsMatchingQuery(m, qb);
assertArrayEquals(new int[] {5}, matches);
qb = RDKFuncs.HasStringPropWithValueQueryBond("boo", "-urns");
matches = GetBondIdxsMatchingQuery(m, qb);
assertArrayEquals(new int[] {6}, matches);
qb = RDKFuncs.HasDoublePropWithValueQueryBond("boot", 1.0);
matches = GetBondIdxsMatchingQuery(m, qb);
assertArrayEquals(new int[] {7}, matches);
qb = RDKFuncs.HasDoublePropWithValueQueryBond("boot", 4.0);
matches = GetBondIdxsMatchingQuery(m, qb);
assertArrayEquals(new int[] {8}, matches);
qb = RDKFuncs.HasDoublePropWithValueQueryBond("boot", 1.0, false, 3.0);
matches = GetBondIdxsMatchingQuery(m, qb);
assertArrayEquals(new int[] {7, 8}, matches);
qb = RDKFuncs.HasIntPropWithValueQueryBond("number", 4);
matches = GetBondIdxsMatchingQuery(m, qb);
assertArrayEquals(new int[] {10}, matches);
}
public static void main(String args[]) {
org.junit.runner.JUnitCore.main("org.RDKit.RascalMCESTest");
}
}