mirror of
https://github.com/rdkit/rdkit.git
synced 2026-06-04 21:54:27 +08:00
1063 lines
30 KiB
C++
1063 lines
30 KiB
C++
// $Id$
|
|
//
|
|
// Copyright (C) 2001-2015 Greg Landrum and Rational Discovery 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.
|
|
//
|
|
//
|
|
|
|
// std bits
|
|
#include <iostream>
|
|
|
|
// RD bits
|
|
#include <GraphMol/RDKitBase.h>
|
|
#include <GraphMol/RDKitQueries.h>
|
|
#include "SubstructMatch.h"
|
|
#include "SubstructUtils.h"
|
|
|
|
#include <GraphMol/SmilesParse/SmilesParse.h>
|
|
#include <GraphMol/FileParsers/FileParsers.h>
|
|
#include <GraphMol/FileParsers/MolSupplier.h>
|
|
|
|
using namespace RDKit;
|
|
|
|
void test1(){
|
|
std::cout << " ----------------- Test 1" << std::endl;
|
|
MatchVectType matchV;
|
|
std::vector< MatchVectType > matches;
|
|
unsigned int n;
|
|
|
|
RWMol *m,*q1;
|
|
m = new RWMol();
|
|
m->addAtom(new Atom(8));
|
|
m->addAtom(new Atom(6));
|
|
m->addAtom(new Atom(6));
|
|
m->addBond(0,1,Bond::SINGLE);
|
|
m->addBond(1,2,Bond::SINGLE);
|
|
|
|
q1 = new RWMol();
|
|
q1->addAtom(new QueryAtom(6));
|
|
q1->addAtom(new QueryAtom(6));
|
|
q1->addBond(0,1,Bond::SINGLE);
|
|
n = SubstructMatch(*m,*q1,matches,false);
|
|
CHECK_INVARIANT(n==2,"");
|
|
CHECK_INVARIANT(matches.size()==n,"");
|
|
CHECK_INVARIANT(matches[0].size()==2,"");
|
|
TEST_ASSERT(matches[0][0].first==0);
|
|
TEST_ASSERT(matches[0][0].second==1||matches[0][0].second==2);
|
|
TEST_ASSERT(matches[0][1].first==1);
|
|
TEST_ASSERT(matches[0][1].second!=matches[0][0].second);
|
|
|
|
TEST_ASSERT(matches[1][1].second==1||matches[0][1].second==2);
|
|
TEST_ASSERT(matches[1][0].first==0);
|
|
TEST_ASSERT(matches[1][0].second==1||matches[1][0].second==2);
|
|
TEST_ASSERT(matches[1][0].second!=matches[0][0].second);
|
|
TEST_ASSERT(matches[1][0].second==matches[0][1].second);
|
|
TEST_ASSERT(matches[1][1].first==1);
|
|
TEST_ASSERT(matches[1][1].second!=matches[1][0].second);
|
|
TEST_ASSERT(matches[1][1].second==matches[0][0].second);
|
|
|
|
n = SubstructMatch(*m,*q1,matches,true);
|
|
CHECK_INVARIANT(n==1,"");
|
|
CHECK_INVARIANT(matches.size()==n,"");
|
|
CHECK_INVARIANT(matches[0].size()==2,"");
|
|
TEST_ASSERT(matches[0][0].first==0);
|
|
TEST_ASSERT(matches[0][0].second==1||matches[0][0].second==2);
|
|
TEST_ASSERT(matches[0][1].first==1);
|
|
TEST_ASSERT(matches[0][1].second!=matches[0][0].second);
|
|
TEST_ASSERT(matches[0][1].second==1||matches[0][1].second==2);
|
|
|
|
CHECK_INVARIANT(SubstructMatch(*m,*q1,matchV),"");
|
|
CHECK_INVARIANT(matchV.size()==2,"");
|
|
|
|
// make sure we reset the match vectors.
|
|
// build a query we won't match:
|
|
q1->addAtom(new QueryAtom(6));
|
|
q1->addBond(1,2,Bond::SINGLE);
|
|
q1->addAtom(new QueryAtom(6));
|
|
q1->addBond(2,3,Bond::SINGLE);
|
|
|
|
TEST_ASSERT(!SubstructMatch(*m,*q1,matchV));
|
|
TEST_ASSERT(matchV.size()==0);
|
|
|
|
n = SubstructMatch(*m,*q1,matches,false);
|
|
TEST_ASSERT(n==0);
|
|
TEST_ASSERT(matches.size()==0);
|
|
|
|
std::cout << "Done\n" << std::endl;
|
|
}
|
|
|
|
void test2(){
|
|
std::cout << " ----------------- Test 2" << std::endl;
|
|
MatchVectType matchV;
|
|
std::vector< MatchVectType > matches;
|
|
unsigned int n;
|
|
|
|
RWMol *m,*q1;
|
|
m = new RWMol();
|
|
m->addAtom(new Atom(6));
|
|
m->addAtom(new Atom(6));
|
|
m->addAtom(new Atom(8));
|
|
m->addBond(0,1,Bond::SINGLE);
|
|
m->addBond(1,2,Bond::SINGLE);
|
|
|
|
q1 = new RWMol();
|
|
q1->addAtom(new QueryAtom(6));
|
|
q1->addAtom(new QueryAtom(8));
|
|
q1->addBond(0,1,Bond::SINGLE);
|
|
|
|
n = SubstructMatch(*m,*q1,matchV);
|
|
TEST_ASSERT(n);
|
|
TEST_ASSERT(matchV.size()==2);
|
|
TEST_ASSERT(matchV[0].first==0);
|
|
TEST_ASSERT(matchV[0].second==1);
|
|
TEST_ASSERT(matchV[1].first==1);
|
|
TEST_ASSERT(matchV[1].second==2);
|
|
|
|
n = SubstructMatch(*m,*q1,matches,false);
|
|
CHECK_INVARIANT(n==1,"");
|
|
CHECK_INVARIANT(matches.size()==n,"");
|
|
CHECK_INVARIANT(matches[0].size()==2,"");
|
|
n = SubstructMatch(*m,*q1,matches,true);
|
|
CHECK_INVARIANT(n==1,"");
|
|
CHECK_INVARIANT(matches.size()==n,"");
|
|
CHECK_INVARIANT(matches[0].size()==2,"");
|
|
|
|
CHECK_INVARIANT(SubstructMatch(*m,*q1,matchV),"");
|
|
CHECK_INVARIANT(matchV.size()==2,"");
|
|
|
|
delete m;
|
|
m = new RWMol();
|
|
m->addAtom(new Atom(6));
|
|
m->addAtom(new Atom(6));
|
|
m->addAtom(new Atom(8));
|
|
m->addBond(0,1,Bond::SINGLE);
|
|
m->addBond(1,2,Bond::DOUBLE);
|
|
|
|
matches.clear();
|
|
n = SubstructMatch(*m,*q1,matches,false);
|
|
CHECK_INVARIANT(n==0,"");
|
|
CHECK_INVARIANT(matches.size()==n,"");
|
|
n = SubstructMatch(*m,*q1,matches,true);
|
|
CHECK_INVARIANT(n==0,"");
|
|
CHECK_INVARIANT(matches.size()==n,"");
|
|
CHECK_INVARIANT(!SubstructMatch(*m,*q1,matchV),"");
|
|
|
|
std::cout << "Done\n" << std::endl;
|
|
|
|
}
|
|
|
|
void test3(){
|
|
std::cout << " ----------------- Test 3" << std::endl;
|
|
MatchVectType matchV;
|
|
std::vector< MatchVectType > matches;
|
|
unsigned int n;
|
|
|
|
RWMol *m,*q1;
|
|
m = new RWMol();
|
|
m->addAtom(new Atom(6));
|
|
m->addAtom(new Atom(6));
|
|
m->addAtom(new Atom(8));
|
|
m->addBond(0,1,Bond::SINGLE);
|
|
m->addBond(1,2,Bond::SINGLE);
|
|
|
|
q1 = new RWMol();
|
|
q1->addAtom(new QueryAtom(6));
|
|
q1->addAtom(new QueryAtom(8));
|
|
q1->addBond(0,1,Bond::UNSPECIFIED);
|
|
n = SubstructMatch(*m,*q1,matches,false);
|
|
CHECK_INVARIANT(n==1,"");
|
|
CHECK_INVARIANT(matches.size()==n,"");
|
|
CHECK_INVARIANT(matches[0].size()==2,"");
|
|
n = SubstructMatch(*m,*q1,matches,true);
|
|
CHECK_INVARIANT(n==1,"");
|
|
CHECK_INVARIANT(matches.size()==n,"");
|
|
CHECK_INVARIANT(matches[0].size()==2,"");
|
|
|
|
CHECK_INVARIANT(SubstructMatch(*m,*q1,matchV),"");
|
|
CHECK_INVARIANT(matchV.size()==2,"");
|
|
|
|
delete m;
|
|
m = new RWMol();
|
|
m->addAtom(new Atom(6));
|
|
m->addAtom(new Atom(6));
|
|
m->addAtom(new Atom(8));
|
|
m->addBond(0,1,Bond::SINGLE);
|
|
m->addBond(1,2,Bond::DOUBLE);
|
|
|
|
matches.clear();
|
|
n = SubstructMatch(*m,*q1,matches,false);
|
|
CHECK_INVARIANT(n==1,"");
|
|
CHECK_INVARIANT(matches.size()==n,"");
|
|
CHECK_INVARIANT(matches[0].size()==2,"");
|
|
n = SubstructMatch(*m,*q1,matches,true);
|
|
CHECK_INVARIANT(n==1,"");
|
|
CHECK_INVARIANT(matches.size()==n,"");
|
|
CHECK_INVARIANT(matches[0].size()==2,"");
|
|
|
|
CHECK_INVARIANT(SubstructMatch(*m,*q1,matchV),"");
|
|
CHECK_INVARIANT(matchV.size()==2,"");
|
|
|
|
|
|
delete q1;
|
|
q1 = new RWMol();
|
|
q1->addAtom(new QueryAtom(6));
|
|
q1->addAtom(new QueryAtom(6));
|
|
q1->addBond(0,1,Bond::UNSPECIFIED);
|
|
n = SubstructMatch(*m,*q1,matches,false);
|
|
TEST_ASSERT(n==2);
|
|
TEST_ASSERT(matches.size()==n);
|
|
TEST_ASSERT(matches[0].size()==2);
|
|
TEST_ASSERT(matches[1].size()==2);
|
|
TEST_ASSERT(matches[0][0].second!=matches[1][0].second);
|
|
TEST_ASSERT(matches[0][1].second!=matches[1][1].second);
|
|
n = SubstructMatch(*m,*q1,matches,true);
|
|
TEST_ASSERT(n==1);
|
|
TEST_ASSERT(matches.size()==n);
|
|
|
|
std::cout << "Done\n" << std::endl;
|
|
}
|
|
|
|
|
|
|
|
|
|
void test4(){
|
|
std::cout << " ----------------- Test 4" << std::endl;
|
|
MatchVectType matchV;
|
|
std::vector< MatchVectType > matches;
|
|
int n;
|
|
|
|
RWMol *m,*q1,*q2;
|
|
Atom *a6 = new Atom(6);
|
|
Atom *a8 = new Atom(8);
|
|
m = new RWMol();
|
|
m->addAtom(a6);
|
|
m->addAtom(a6);
|
|
m->addAtom(a8);
|
|
m->addAtom(a6);
|
|
m->addAtom(a6);
|
|
m->addBond(1,0,Bond::SINGLE);
|
|
m->addBond(1,2,Bond::SINGLE);
|
|
m->addBond(1,3,Bond::SINGLE);
|
|
m->addBond(2,4,Bond::SINGLE);
|
|
|
|
// this will be the recursive query
|
|
q1 = new RWMol();
|
|
q1->addAtom(new QueryAtom(6),true);
|
|
q1->addAtom(new QueryAtom(8),true);
|
|
q1->addBond(0,1,Bond::UNSPECIFIED);
|
|
|
|
// here's the main query
|
|
q2 = new RWMol();
|
|
QueryAtom *qA = new QueryAtom(6);
|
|
RecursiveStructureQuery *rsq = new RecursiveStructureQuery(q1);
|
|
qA->expandQuery(rsq,Queries::COMPOSITE_AND);
|
|
//std::cout << "post expand: " << qA->getQuery() << std::endl;
|
|
q2->addAtom(qA,true,true);
|
|
//std::cout << "mol: " << q2->getAtomWithIdx(0)->getQuery() << std::endl;
|
|
q2->addAtom(new QueryAtom(6),true,true);
|
|
q2->addBond(0,1,Bond::UNSPECIFIED);
|
|
|
|
bool found = SubstructMatch(*m,*q2,matchV);
|
|
CHECK_INVARIANT(found,"");
|
|
CHECK_INVARIANT(matchV.size()==2,"");
|
|
TEST_ASSERT(matchV[0].first==0);
|
|
TEST_ASSERT(matchV[0].second==1);
|
|
TEST_ASSERT(matchV[1].first==1);
|
|
TEST_ASSERT(matchV[1].second==0||matchV[1].second==3);
|
|
n = SubstructMatch(*m,*q2,matches,true);
|
|
TEST_ASSERT(n==2);
|
|
TEST_ASSERT(matches.size()==n);
|
|
TEST_ASSERT(matches[0].size()==2);
|
|
TEST_ASSERT(matches[1].size()==2);
|
|
TEST_ASSERT(matches[0][0].second==matches[1][0].second);
|
|
TEST_ASSERT(matches[0][1].second!=matches[1][1].second);
|
|
|
|
std::cout << "Done\n" << std::endl;
|
|
}
|
|
|
|
void test5(){
|
|
std::cout << " ----------------- Test 5" << std::endl;
|
|
MatchVectType matchV;
|
|
std::vector< MatchVectType > matches;
|
|
int n;
|
|
|
|
RWMol *m,*q1,*q2;
|
|
Atom *a6 = new Atom(6);
|
|
Atom *a8 = new Atom(8);
|
|
// CC(OC)C
|
|
m = new RWMol();
|
|
m->addAtom(a6);
|
|
m->addAtom(a6);
|
|
m->addAtom(a8);
|
|
m->addAtom(a6);
|
|
m->addAtom(a6);
|
|
m->addBond(0,1,Bond::SINGLE);
|
|
m->addBond(1,2,Bond::SINGLE);
|
|
m->addBond(1,4,Bond::SINGLE);
|
|
m->addBond(2,3,Bond::SINGLE);
|
|
|
|
// this will be the recursive query
|
|
q1 = new RWMol();
|
|
q1->addAtom(new QueryAtom(6),true);
|
|
q1->addAtom(new QueryAtom(8),true);
|
|
q1->addBond(0,1,Bond::UNSPECIFIED);
|
|
|
|
// here's the main query
|
|
q2 = new RWMol();
|
|
QueryAtom *qA = new QueryAtom();
|
|
RecursiveStructureQuery *rsq = new RecursiveStructureQuery(q1);
|
|
qA->setQuery(rsq);
|
|
q2->addAtom(qA,true,true);
|
|
q2->addAtom(new QueryAtom(6),true,true);
|
|
q2->addBond(0,1,Bond::UNSPECIFIED);
|
|
|
|
bool found = SubstructMatch(*m,*q2,matchV);
|
|
CHECK_INVARIANT(found,"");
|
|
CHECK_INVARIANT(matchV.size()==2,"");
|
|
n = SubstructMatch(*m,*q2,matches,true);
|
|
CHECK_INVARIANT(n==2,"");
|
|
CHECK_INVARIANT(matches[0].size()==2,"");
|
|
|
|
|
|
std::cout << "Done\n" << std::endl;
|
|
}
|
|
void test5QueryRoot(){
|
|
std::cout << " ----------------- Test 5 QueryRoot" << std::endl;
|
|
MatchVectType matchV;
|
|
std::vector< MatchVectType > matches;
|
|
int n;
|
|
|
|
RWMol *m,*q1,*q2;
|
|
Atom *a6 = new Atom(6);
|
|
Atom *a8 = new Atom(8);
|
|
// CC(OC)C
|
|
m = new RWMol();
|
|
m->addAtom(a6);
|
|
m->addAtom(a6);
|
|
m->addAtom(a8);
|
|
m->addAtom(a6);
|
|
m->addAtom(a6);
|
|
m->addBond(0,1,Bond::SINGLE);
|
|
m->addBond(1,2,Bond::SINGLE);
|
|
m->addBond(1,4,Bond::SINGLE);
|
|
m->addBond(2,3,Bond::SINGLE);
|
|
|
|
// this will be the recursive query
|
|
q1 = new RWMol();
|
|
q1->addAtom(new QueryAtom(8),true);
|
|
q1->addAtom(new QueryAtom(6),true);
|
|
q1->addBond(0,1,Bond::UNSPECIFIED);
|
|
q1->setProp(common_properties::_queryRootAtom,1);
|
|
|
|
// here's the main query
|
|
q2 = new RWMol();
|
|
QueryAtom *qA = new QueryAtom();
|
|
RecursiveStructureQuery *rsq = new RecursiveStructureQuery(q1);
|
|
qA->setQuery(rsq);
|
|
q2->addAtom(qA,true,true);
|
|
q2->addAtom(new QueryAtom(6),true,true);
|
|
q2->addBond(0,1,Bond::UNSPECIFIED);
|
|
|
|
bool found = SubstructMatch(*m,*q2,matchV);
|
|
CHECK_INVARIANT(found,"");
|
|
CHECK_INVARIANT(matchV.size()==2,"");
|
|
n = SubstructMatch(*m,*q2,matches,true);
|
|
CHECK_INVARIANT(n==2,"");
|
|
CHECK_INVARIANT(matches[0].size()==2,"");
|
|
|
|
|
|
std::cout << "Done\n" << std::endl;
|
|
}
|
|
|
|
void test6(){
|
|
std::cout << " ----------------- Test 6 (Issue71 related)" << std::endl;
|
|
MatchVectType matchV;
|
|
std::vector< MatchVectType > matches;
|
|
int n;
|
|
|
|
RWMol *m,*q1;
|
|
Atom *a6 = new Atom(6);
|
|
|
|
m = new RWMol();
|
|
m->addAtom(a6);
|
|
m->addAtom(a6);
|
|
m->addAtom(a6);
|
|
m->addBond(0,1,Bond::SINGLE);
|
|
m->addBond(1,2,Bond::SINGLE);
|
|
m->addBond(0,2,Bond::SINGLE);
|
|
|
|
q1 = new RWMol();
|
|
q1->addAtom(new QueryAtom(6),true);
|
|
q1->addAtom(new QueryAtom(6),true);
|
|
q1->addAtom(new QueryAtom(6),true);
|
|
q1->addBond(0,1,Bond::UNSPECIFIED);
|
|
q1->addBond(1,2,Bond::UNSPECIFIED);
|
|
|
|
bool found = SubstructMatch(*m,*q1,matchV);
|
|
CHECK_INVARIANT(found,"");
|
|
CHECK_INVARIANT(matchV.size()==3,"");
|
|
n = SubstructMatch(*m,*q1,matches,true);
|
|
CHECK_INVARIANT(n==1,"");
|
|
CHECK_INVARIANT(matches[0].size()==3,"");
|
|
|
|
// close the loop and try again (we should still match)
|
|
q1->addBond(0,2,Bond::UNSPECIFIED);
|
|
found = SubstructMatch(*m,*q1,matchV);
|
|
CHECK_INVARIANT(found,"");
|
|
CHECK_INVARIANT(matchV.size()==3,"");
|
|
n = SubstructMatch(*m,*q1,matches,true);
|
|
CHECK_INVARIANT(n==1,"");
|
|
CHECK_INVARIANT(matches[0].size()==3,"");
|
|
|
|
std::cout << "Done\n" << std::endl;
|
|
}
|
|
|
|
void test7(){
|
|
std::cout << " ----------------- Test 7 (leak check)" << std::endl;
|
|
MatchVectType matchV;
|
|
int n;
|
|
|
|
RWMol *m,*q1;
|
|
Atom *a6 = new Atom(6);
|
|
|
|
m = new RWMol();
|
|
m->addAtom(a6);
|
|
m->addAtom(a6);
|
|
m->addAtom(a6);
|
|
m->addBond(0,1,Bond::SINGLE);
|
|
m->addBond(1,2,Bond::SINGLE);
|
|
m->addBond(0,2,Bond::SINGLE);
|
|
|
|
q1 = new RWMol();
|
|
q1->addAtom(new QueryAtom(6),true);
|
|
q1->addAtom(new QueryAtom(6),true);
|
|
q1->addAtom(new QueryAtom(6),true);
|
|
q1->addBond(0,1,Bond::UNSPECIFIED);
|
|
q1->addBond(1,2,Bond::UNSPECIFIED);
|
|
|
|
|
|
bool found = SubstructMatch(*m,*q1,matchV);
|
|
CHECK_INVARIANT(found,"");
|
|
CHECK_INVARIANT(matchV.size()==3,"");
|
|
std::vector< MatchVectType > matches;
|
|
for(int i=0;i<300000;i++){
|
|
n = SubstructMatch(*m,*q1,matches,true,true);
|
|
CHECK_INVARIANT(n==1,"");
|
|
CHECK_INVARIANT(matches[0].size()==3,"");
|
|
if(! (i%500) ) std::cout << i << std::endl;
|
|
}
|
|
std::cout << "Done\n" << std::endl;
|
|
}
|
|
|
|
#ifdef CACHE_ARMOLGRAPHS
|
|
void test8(){
|
|
std::cout << " ----------------- Test 8 (molgraph cache)" << std::endl;
|
|
MatchVectType matchV;
|
|
int n;
|
|
|
|
RWMol *m,*q1;
|
|
Atom *a6 = new Atom(6);
|
|
|
|
m = new RWMol();
|
|
m->addAtom(a6);
|
|
m->addAtom(a6);
|
|
m->addAtom(a6);
|
|
m->addBond(0,1,Bond::SINGLE);
|
|
m->addBond(1,2,Bond::SINGLE);
|
|
m->addBond(0,2,Bond::SINGLE);
|
|
|
|
q1 = new RWMol();
|
|
q1->addAtom(new QueryAtom(6),true);
|
|
q1->addAtom(new QueryAtom(6),true);
|
|
q1->addAtom(new QueryAtom(6),true);
|
|
q1->addBond(0,1,Bond::UNSPECIFIED);
|
|
q1->addBond(1,2,Bond::UNSPECIFIED);
|
|
|
|
|
|
bool found = SubstructMatch(*m,*q1,matchV);
|
|
CHECK_INVARIANT(found,"");
|
|
CHECK_INVARIANT(matchV.size()==3,"");
|
|
std::vector< MatchVectType > matches;
|
|
for(int i=0;i<30000;i++){
|
|
n = SubstructMatch(*m,*q1,matches,true,true);
|
|
CHECK_INVARIANT(n==1,"");
|
|
CHECK_INVARIANT(matches[0].size()==3,"");
|
|
if(! (i%500) ) std::cout << i << std::endl;
|
|
}
|
|
std::cout << "Done\n" << std::endl;
|
|
}
|
|
#endif
|
|
|
|
void test9(){
|
|
std::cout << " ----------------- Test 9 (chiral searches)" << std::endl;
|
|
MatchVectType matchV;
|
|
std::vector< MatchVectType > matches;
|
|
int n;
|
|
|
|
RWMol *m,*q1;
|
|
Atom *a6 = new Atom(6);
|
|
|
|
m = new RWMol();
|
|
m->addAtom(a6);
|
|
m->addAtom(new Atom(6));
|
|
m->addAtom(new Atom(7));
|
|
m->addAtom(new Atom(8));
|
|
m->addAtom(new Atom(9));
|
|
m->addBond(0,1,Bond::SINGLE);
|
|
m->addBond(0,2,Bond::SINGLE);
|
|
m->addBond(0,3,Bond::SINGLE);
|
|
m->addBond(0,4,Bond::SINGLE);
|
|
m->getAtomWithIdx(0)->setChiralTag(Atom::CHI_TETRAHEDRAL_CW);
|
|
|
|
q1 = new RWMol();
|
|
q1->addAtom(a6);
|
|
q1->addAtom(new Atom(6));
|
|
q1->addAtom(new Atom(7));
|
|
q1->addAtom(new Atom(8));
|
|
q1->addAtom(new Atom(9));
|
|
q1->addBond(0,1,Bond::SINGLE);
|
|
q1->addBond(0,2,Bond::SINGLE);
|
|
q1->addBond(0,3,Bond::SINGLE);
|
|
q1->addBond(0,4,Bond::SINGLE);
|
|
q1->getAtomWithIdx(0)->setChiralTag(Atom::CHI_TETRAHEDRAL_CCW);
|
|
|
|
MolOps::sanitizeMol(*m);
|
|
MolOps::assignStereochemistry(*m);
|
|
MolOps::sanitizeMol(*q1);
|
|
MolOps::assignStereochemistry(*q1);
|
|
|
|
bool found;
|
|
// test with default options (no chirality):
|
|
found = SubstructMatch(*m,*q1,matchV);
|
|
TEST_ASSERT(found);
|
|
n = SubstructMatch(*m,*q1,matches,true);
|
|
TEST_ASSERT(n==1);
|
|
|
|
// test with chirality
|
|
found = SubstructMatch(*m,*q1,matchV,true,true);
|
|
TEST_ASSERT(!found);
|
|
n = SubstructMatch(*m,*q1,matches,true,true,true);
|
|
TEST_ASSERT(n==0);
|
|
|
|
// self matches:
|
|
found = SubstructMatch(*m,*m,matchV,true,true);
|
|
TEST_ASSERT(found);
|
|
n = SubstructMatch(*m,*m,matches,true,true,true);
|
|
TEST_ASSERT(n==1);
|
|
found = SubstructMatch(*q1,*q1,matchV,true,true);
|
|
TEST_ASSERT(found);
|
|
n = SubstructMatch(*q1,*q1,matches,true,true,true);
|
|
TEST_ASSERT(n==1);
|
|
|
|
std::cout << "Done\n" << std::endl;
|
|
}
|
|
|
|
|
|
void testRecursiveSerialNumbers(){
|
|
std::cout << " ----------------- Testing serial numbers on recursive queries" << std::endl;
|
|
MatchVectType matchV;
|
|
std::vector< MatchVectType > matches;
|
|
int n;
|
|
|
|
RWMol *m,*q1,*q2;
|
|
Atom *a6 = new Atom(6);
|
|
Atom *a8 = new Atom(8);
|
|
m = new RWMol();
|
|
m->addAtom(a6);
|
|
m->addAtom(a6);
|
|
m->addAtom(a8);
|
|
m->addAtom(a6);
|
|
m->addAtom(a6);
|
|
m->addBond(1,0,Bond::SINGLE);
|
|
m->addBond(1,2,Bond::SINGLE);
|
|
m->addBond(1,3,Bond::SINGLE);
|
|
m->addBond(2,4,Bond::SINGLE);
|
|
|
|
{
|
|
// this will be the recursive query
|
|
q1 = new RWMol();
|
|
q1->addAtom(new QueryAtom(6),true);
|
|
q1->addAtom(new QueryAtom(8),true);
|
|
q1->addBond(0,1,Bond::UNSPECIFIED);
|
|
|
|
// here's the main query
|
|
q2 = new RWMol();
|
|
QueryAtom *qA = new QueryAtom(6);
|
|
RecursiveStructureQuery *rsq = new RecursiveStructureQuery(new RWMol(*q1),1);
|
|
qA->expandQuery(rsq,Queries::COMPOSITE_AND);
|
|
//std::cout << "post expand: " << qA->getQuery() << std::endl;
|
|
q2->addAtom(qA,true,true);
|
|
//std::cout << "mol: " << q2->getAtomWithIdx(0)->getQuery() << std::endl;
|
|
q2->addAtom(new QueryAtom(8),true,true);
|
|
q2->addBond(0,1,Bond::UNSPECIFIED);
|
|
|
|
qA = new QueryAtom(6);
|
|
rsq = new RecursiveStructureQuery(new RWMol(*q1),1);
|
|
qA->expandQuery(rsq,Queries::COMPOSITE_AND);
|
|
q2->addAtom(qA,true,true);
|
|
q2->addBond(1,2,Bond::UNSPECIFIED);
|
|
|
|
bool found = SubstructMatch(*m,*q2,matchV);
|
|
CHECK_INVARIANT(found,"");
|
|
CHECK_INVARIANT(matchV.size()==3,"");
|
|
n = SubstructMatch(*m,*q2,matches,true);
|
|
TEST_ASSERT(n==1);
|
|
TEST_ASSERT(matches.size()==1);
|
|
TEST_ASSERT(matches[0].size()==3);
|
|
|
|
delete q1;
|
|
delete q2;
|
|
}
|
|
delete m;
|
|
std::cout << "Done\n" << std::endl;
|
|
}
|
|
|
|
#ifdef RDK_TEST_MULTITHREADED
|
|
#include <boost/thread.hpp>
|
|
#include <boost/dynamic_bitset.hpp>
|
|
namespace {
|
|
void runblock(const std::vector<ROMol *> &mols,const ROMol *query,
|
|
const boost::dynamic_bitset<> &hits,unsigned int count,unsigned int idx){
|
|
for(unsigned int j=0;j<100;j++){
|
|
for(unsigned int i=0;i<mols.size();++i){
|
|
if(i%count != idx) continue;
|
|
ROMol *mol = mols[i];
|
|
|
|
MatchVectType matchV;
|
|
bool found=SubstructMatch(*mol,*query,matchV);
|
|
|
|
TEST_ASSERT(found==hits[i]);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
void testMultiThread(){
|
|
BOOST_LOG(rdErrorLog) << "-------------------------------------" << std::endl;
|
|
BOOST_LOG(rdErrorLog) << " Test multithreading" << std::endl;
|
|
|
|
std::string fName = getenv("RDBASE");
|
|
fName += "/Data/NCI/first_200.props.sdf";
|
|
SDMolSupplier suppl(fName);
|
|
std::cerr<<"reading molecules"<<std::endl;
|
|
std::vector<ROMol *> mols;
|
|
while(!suppl.atEnd()&&mols.size()<100){
|
|
ROMol *mol=0;
|
|
try{
|
|
mol=suppl.next();
|
|
} catch(...){
|
|
continue;
|
|
}
|
|
if(!mol) continue;
|
|
mols.push_back(mol);
|
|
}
|
|
boost::thread_group tg;
|
|
|
|
ROMol *query=SmartsToMol("[#6;$([#6]([#6])[!#6])]");
|
|
boost::dynamic_bitset<> hits(mols.size());
|
|
for(unsigned int i=0;i<mols.size();++i){
|
|
MatchVectType matchV;
|
|
hits[i]=SubstructMatch(*mols[i],*query,matchV);
|
|
}
|
|
unsigned int count=4;
|
|
#if 1
|
|
std::cerr<<" hits: "<<hits<<std::endl;
|
|
std::cerr<<"processing"<<std::endl;
|
|
for(unsigned int i=0;i<count;++i){
|
|
std::cerr<<" launch :"<<i<<std::endl;std::cerr.flush();
|
|
tg.add_thread(new boost::thread(runblock,mols,query,hits,count,i));
|
|
}
|
|
tg.join_all();
|
|
std::cerr<<" done"<<std::endl;
|
|
delete query;
|
|
|
|
query=SmartsToMol("[#6]([#6])[!#6]");
|
|
for(unsigned int i=0;i<mols.size();++i){
|
|
MatchVectType matchV;
|
|
hits[i]=SubstructMatch(*mols[i],*query,matchV);
|
|
}
|
|
std::cerr<<" hits2: "<<hits<<std::endl;
|
|
std::cerr<<"processing2"<<std::endl;
|
|
for(unsigned int i=0;i<count;++i){
|
|
std::cerr<<" launch2 :"<<i<<std::endl;std::cerr.flush();
|
|
tg.add_thread(new boost::thread(runblock,mols,query,hits,count,i));
|
|
}
|
|
tg.join_all();
|
|
std::cerr<<" done"<<std::endl;
|
|
delete query;
|
|
#endif
|
|
|
|
std::cerr<<" preprocessing 3"<<std::endl;
|
|
query=SmartsToMol("[$([O,S]-[!$(*=O)])]");
|
|
for(unsigned int i=0;i<mols.size();++i){
|
|
MatchVectType matchV;
|
|
hits[i]=SubstructMatch(*mols[i],*query,matchV);
|
|
}
|
|
std::cerr<<" hits3: "<<hits<<std::endl;
|
|
std::cerr<<"processing3"<<std::endl;
|
|
for(unsigned int i=0;i<count;++i){
|
|
std::cerr<<" launch3 :"<<i<<std::endl;std::cerr.flush();
|
|
tg.add_thread(new boost::thread(runblock,mols,query,hits,count,i));
|
|
}
|
|
tg.join_all();
|
|
std::cerr<<" done"<<std::endl;
|
|
delete query;
|
|
|
|
|
|
for(unsigned int i=0;i<mols.size();++i) delete mols[i];
|
|
|
|
BOOST_LOG(rdErrorLog) << " done" << std::endl;
|
|
}
|
|
#else
|
|
void testMultiThread(){
|
|
}
|
|
#endif
|
|
|
|
void testChiralMatch(){
|
|
BOOST_LOG(rdErrorLog) << "-------------------------------------" << std::endl;
|
|
BOOST_LOG(rdErrorLog) << " Test chiral matching" << std::endl;
|
|
|
|
{
|
|
std::string qSmi="Cl[C@](C)(F)Br";
|
|
std::string mSmi="Cl[C@](C)(F)Br";
|
|
ROMol *query=SmilesToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
MatchVectType matchV;
|
|
bool matched=SubstructMatch(*mol,*query,matchV,true,true);
|
|
TEST_ASSERT(matched);
|
|
}
|
|
{
|
|
std::string qSmi="Cl[C@](C)(F)Br";
|
|
std::string mSmi="Cl[C@@](C)(F)Br";
|
|
ROMol *query=SmilesToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
MatchVectType matchV;
|
|
bool matched=SubstructMatch(*mol,*query,matchV,true,true);
|
|
TEST_ASSERT(!matched);
|
|
}
|
|
{
|
|
std::string qSmi="Cl[C@](C)(F)Br";
|
|
std::string mSmi="Cl[C@@](F)(C)Br";
|
|
ROMol *query=SmilesToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
MatchVectType matchV;
|
|
bool matched=SubstructMatch(*mol,*query,matchV,true,true);
|
|
TEST_ASSERT(matched);
|
|
}
|
|
{
|
|
std::string qSmi="Cl[C@](C)(F)Br";
|
|
std::string mSmi="Cl[C@](F)(C)Br";
|
|
ROMol *query=SmilesToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
MatchVectType matchV;
|
|
bool matched=SubstructMatch(*mol,*query,matchV,true,true);
|
|
TEST_ASSERT(!matched);
|
|
}
|
|
{
|
|
std::string qSmi="Cl[C@](C)(F)Br";
|
|
std::string mSmi="Cl[C@@](Br)(C)F";
|
|
ROMol *query=SmilesToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
MatchVectType matchV;
|
|
bool matched=SubstructMatch(*mol,*query,matchV,true,true);
|
|
TEST_ASSERT(!matched);
|
|
}
|
|
{
|
|
std::string qSmi="Cl[C@](C)(F)Br";
|
|
std::string mSmi="Cl[C@](Br)(C)F";
|
|
ROMol *query=SmilesToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
MatchVectType matchV;
|
|
bool matched=SubstructMatch(*mol,*query,matchV,true,true);
|
|
TEST_ASSERT(matched);
|
|
}
|
|
|
|
{
|
|
std::string qSmi="C[C@](O)(F)Br";
|
|
std::string mSmi="O[C@](F)(Br)CC[C@](O)(F)Br";
|
|
ROMol *query=SmilesToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
MatchVectType matchV;
|
|
bool matched=SubstructMatch(*mol,*query,matchV,true,true);
|
|
TEST_ASSERT(matched);
|
|
}
|
|
|
|
{
|
|
std::string qSmi="C[C@](O)(F)Br";
|
|
std::string mSmi="O[C@](F)(Br)CC[C@@](O)(F)Br";
|
|
ROMol *query=SmilesToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
MatchVectType matchV;
|
|
bool matched=SubstructMatch(*mol,*query,matchV,true,true);
|
|
TEST_ASSERT(!matched);
|
|
}
|
|
|
|
{
|
|
std::string qSmi="C[C@](O)(F)Br";
|
|
std::string mSmi="O[C@](F)(Br)CC(C[C@](O)(F)Br)C[C@](O)(F)Br";
|
|
ROMol *query=SmilesToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
std::vector< MatchVectType > matches;
|
|
int count=SubstructMatch(*mol,*query,matches,true,true,true);
|
|
TEST_ASSERT(count==2);
|
|
}
|
|
|
|
{
|
|
std::string qSmi="C[C@](O)(F)Br";
|
|
std::string mSmi="O[C@@](F)(Br)CC(C[C@](O)(F)Br)C[C@](O)(F)Br";
|
|
ROMol *query=SmilesToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
std::vector< MatchVectType > matches;
|
|
int count=SubstructMatch(*mol,*query,matches,true,true,true);
|
|
TEST_ASSERT(count==3);
|
|
}
|
|
{
|
|
std::string qSmi="C[C@](O)(F)Br";
|
|
std::string mSmi="O[C@](F)(Br)CC(C[C@@](O)(F)Br)C[C@](O)(F)Br";
|
|
ROMol *query=SmilesToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
std::vector< MatchVectType > matches;
|
|
int count=SubstructMatch(*mol,*query,matches,true,true,true);
|
|
TEST_ASSERT(count==1);
|
|
}
|
|
{
|
|
std::string qSmi="C[C@](O)(F)Br";
|
|
std::string mSmi="O[C@](F)(Br)CC(C[C@@](O)(F)Br)C[C@@](O)(F)Br";
|
|
ROMol *query=SmilesToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
std::vector< MatchVectType > matches;
|
|
//std::cerr<<"\n\n------------------------------------------\n"<<qSmi<<" "<<mSmi<<"\n"<<std::endl;
|
|
int count=SubstructMatch(*mol,*query,matches,true,true,true);
|
|
//std::cerr<<"res: "<<count<<std::endl;
|
|
TEST_ASSERT(count==0);
|
|
}
|
|
|
|
{
|
|
std::string qSmi="Cl[C@](*)(F)Br";
|
|
std::string mSmi="Cl[C@](C)(F)Br";
|
|
ROMol *query=SmartsToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
MatchVectType matchV;
|
|
bool matched=SubstructMatch(*mol,*query,matchV,true,true);
|
|
TEST_ASSERT(matched);
|
|
}
|
|
{
|
|
std::string qSmi="Cl[C@@](*)(F)Br";
|
|
std::string mSmi="Cl[C@](C)(F)Br";
|
|
ROMol *query=SmartsToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
MatchVectType matchV;
|
|
bool matched=SubstructMatch(*mol,*query,matchV,true,true);
|
|
TEST_ASSERT(!matched);
|
|
}
|
|
{
|
|
std::string qSmi="Cl[C@](*)(*)Br";
|
|
std::string mSmi="Cl[C@](C)(F)Br";
|
|
ROMol *query=SmartsToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
MatchVectType matchV;
|
|
bool matched=SubstructMatch(*mol,*query,matchV,true,true);
|
|
TEST_ASSERT(matched);
|
|
}
|
|
{
|
|
std::string qSmi="Cl[C@@](*)(*)Br";
|
|
std::string mSmi="Cl[C@](C)(F)Br";
|
|
ROMol *query=SmartsToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
MatchVectType matchV;
|
|
bool matched=SubstructMatch(*mol,*query,matchV,true,true);
|
|
TEST_ASSERT(matched);
|
|
}
|
|
{
|
|
std::string qSmi="[C@](C)(F)Br";
|
|
std::string mSmi="Cl[C@](C)(F)Br";
|
|
ROMol *query=SmartsToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
MatchVectType matchV;
|
|
bool matched=SubstructMatch(*mol,*query,matchV,true,true);
|
|
TEST_ASSERT(!matched);
|
|
}
|
|
{
|
|
std::string qSmi="[C@@](C)(F)Br";
|
|
std::string mSmi="Cl[C@](C)(F)Br";
|
|
ROMol *query=SmartsToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
MatchVectType matchV;
|
|
bool matched=SubstructMatch(*mol,*query,matchV,true,true);
|
|
TEST_ASSERT(!matched);
|
|
}
|
|
|
|
|
|
BOOST_LOG(rdErrorLog) << " done" << std::endl;
|
|
}
|
|
|
|
void testCisTransMatch(){
|
|
BOOST_LOG(rdErrorLog) << "-------------------------------------" << std::endl;
|
|
BOOST_LOG(rdErrorLog) << " Test cis/trans matching" << std::endl;
|
|
|
|
{
|
|
std::string qSmi="CC=CC";
|
|
std::string mSmi="CC=CC";
|
|
ROMol *query=SmilesToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
MatchVectType matchV;
|
|
bool matched=SubstructMatch(*mol,*query,matchV,true,true);
|
|
TEST_ASSERT(matched);
|
|
}
|
|
{
|
|
std::string qSmi="CC=CC";
|
|
std::string mSmi="C/C=C/C";
|
|
ROMol *query=SmilesToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
MatchVectType matchV;
|
|
bool matched=SubstructMatch(*mol,*query,matchV,true,true);
|
|
TEST_ASSERT(matched);
|
|
}
|
|
{
|
|
std::string qSmi="C/C=C/C";
|
|
std::string mSmi="CC=CC";
|
|
ROMol *query=SmilesToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
MatchVectType matchV;
|
|
bool matched=SubstructMatch(*mol,*query,matchV,true,true);
|
|
TEST_ASSERT(!matched);
|
|
}
|
|
{
|
|
std::string qSmi="C/C=C/C";
|
|
std::string mSmi="C/C=C\\C";
|
|
ROMol *query=SmilesToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
MatchVectType matchV;
|
|
bool matched=SubstructMatch(*mol,*query,matchV,true,true);
|
|
TEST_ASSERT(!matched);
|
|
}
|
|
{
|
|
std::string qSmi="C/C=C/C";
|
|
std::string mSmi="C/C=C/C";
|
|
ROMol *query=SmilesToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
MatchVectType matchV;
|
|
bool matched=SubstructMatch(*mol,*query,matchV,true,true);
|
|
TEST_ASSERT(matched);
|
|
}
|
|
{
|
|
std::string qSmi="C/C=C/C";
|
|
std::string mSmi="C/C=C(/F)C";
|
|
ROMol *query=SmilesToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
MatchVectType matchV;
|
|
bool matched=SubstructMatch(*mol,*query,matchV,true,true);
|
|
TEST_ASSERT(!matched);
|
|
}
|
|
{
|
|
std::string qSmi="C/C=C/C";
|
|
std::string mSmi="C/C=C(\\F)C";
|
|
ROMol *query=SmilesToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
MatchVectType matchV;
|
|
bool matched=SubstructMatch(*mol,*query,matchV,true,true);
|
|
TEST_ASSERT(matched);
|
|
}
|
|
{
|
|
std::string qSmi="C/C=C/C";
|
|
std::string mSmi="C/C(F)=C(\\F)C";
|
|
ROMol *query=SmilesToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
MatchVectType matchV;
|
|
bool matched=SubstructMatch(*mol,*query,matchV,true,true);
|
|
TEST_ASSERT(matched);
|
|
}
|
|
{
|
|
std::string qSmi="C/C=C/C";
|
|
std::string mSmi="CC(/F)=C(\\F)C";
|
|
ROMol *query=SmilesToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
MatchVectType matchV;
|
|
bool matched=SubstructMatch(*mol,*query,matchV,true,true);
|
|
TEST_ASSERT(matched);
|
|
}
|
|
{
|
|
std::string qSmi="C/C=C/C";
|
|
std::string mSmi="CC(\\F)=C(\\F)C";
|
|
ROMol *query=SmilesToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
MatchVectType matchV;
|
|
bool matched=SubstructMatch(*mol,*query,matchV,true,true);
|
|
TEST_ASSERT(!matched);
|
|
}
|
|
|
|
BOOST_LOG(rdErrorLog) << " done" << std::endl;
|
|
}
|
|
|
|
void testGitHubIssue15(){
|
|
BOOST_LOG(rdErrorLog) << "-------------------------------------" << std::endl;
|
|
BOOST_LOG(rdErrorLog) << " Test GitHub issue 15" << std::endl;
|
|
|
|
{
|
|
std::string qSmi="[R2]~[R1]~[R2]";
|
|
std::string mSmi="CCC";
|
|
ROMol *query=SmartsToMol(qSmi);
|
|
ROMol *mol = SmilesToMol(mSmi);
|
|
MatchVectType matchV;
|
|
bool matched=SubstructMatch(*mol,*query,matchV,true,true,true);
|
|
TEST_ASSERT(!matched);
|
|
}
|
|
{
|
|
std::string qSmi="[R2]~[R1]~[R2]";
|
|
std::string mSmi="CCC";
|
|
ROMol *query=SmartsToMol(qSmi);
|
|
ROMol *mol = SmartsToMol(mSmi);
|
|
MatchVectType matchV;
|
|
bool matched=SubstructMatch(*mol,*query,matchV,true,true,true);
|
|
TEST_ASSERT(!matched);
|
|
}
|
|
|
|
BOOST_LOG(rdErrorLog) << " done" << std::endl;
|
|
}
|
|
|
|
void testGitHubIssue409(){
|
|
BOOST_LOG(rdErrorLog) << "-------------------------------------" << std::endl;
|
|
BOOST_LOG(rdErrorLog) << " Test GitHub issue 409" << std::endl;
|
|
{
|
|
std::string smi="FC(F)(F)CC(F)(F)F";
|
|
ROMol *mol = SmilesToMol(smi);
|
|
std::vector< MatchVectType > matches;
|
|
unsigned int matched=SubstructMatch(*mol,*mol,matches,false,true,false,false);
|
|
TEST_ASSERT(matches.size()==72);
|
|
matched=SubstructMatch(*mol,*mol,matches,false,true,false,false,16);
|
|
TEST_ASSERT(matches.size()==16);
|
|
}
|
|
BOOST_LOG(rdErrorLog) << " done" << std::endl;
|
|
}
|
|
|
|
|
|
|
|
int main(int argc,char *argv[])
|
|
{
|
|
#if 1
|
|
test1();
|
|
test2();
|
|
test3();
|
|
test4();
|
|
test5();
|
|
test5QueryRoot();
|
|
test6();
|
|
if(argc>1 && !strcmp(argv[1],"-l"))
|
|
test7();
|
|
//test9();
|
|
testRecursiveSerialNumbers();
|
|
testMultiThread();
|
|
testChiralMatch();
|
|
testCisTransMatch();
|
|
#endif
|
|
testGitHubIssue15();
|
|
testGitHubIssue409();
|
|
return 0;
|
|
}
|
|
|
|
|