/src/openbabel/src/fingerprints/finger2.cpp
Line | Count | Source |
1 | | /********************************************************************** |
2 | | finger2.cpp: fingerprint2 definition and implementation. |
3 | | |
4 | | Copyright (C) 2005 Chris Morley |
5 | | |
6 | | This file is part of the Open Babel project. |
7 | | For more information, see <http://openbabel.org/> |
8 | | |
9 | | This program is free software; you can redistribute it and/or modify |
10 | | it under the terms of the GNU General Public License as published by |
11 | | the Free Software Foundation version 2 of the License. |
12 | | |
13 | | This program is distributed in the hope that it will be useful, |
14 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | | GNU General Public License for more details. |
17 | | ***********************************************************************/ |
18 | | |
19 | | #include <openbabel/babelconfig.h> |
20 | | #include <openbabel/oberror.h> |
21 | | #include <openbabel/mol.h> |
22 | | #include <openbabel/atom.h> |
23 | | #include <openbabel/bond.h> |
24 | | #include <openbabel/fingerprint.h> |
25 | | #include <set> |
26 | | #include <vector> |
27 | | #include <algorithm> |
28 | | #include <openbabel/elements.h> |
29 | | |
30 | | using namespace std; |
31 | | namespace OpenBabel |
32 | | { |
33 | | /// \brief Fingerprint based on linear fragments up to 7 atoms ID="FP2" |
34 | | class fingerprint2 : public OBFingerprint |
35 | | { |
36 | | public: |
37 | | fingerprint2(const char* ID, bool IsDefault=false) |
38 | 2 | : OBFingerprint(ID, IsDefault), _flags(0){}; |
39 | | |
40 | | const char* Description() override |
41 | 0 | { return "Indexes linear fragments up to 7 atoms." |
42 | 0 | "\n1021 bits.\n" |
43 | 0 | "Similar to Daylight fingerprints\n" |
44 | 0 | "A molecule structure is analysed to identify linear fragments of length\n" |
45 | 0 | "from one to Max_Fragment_Size = 7 atoms but single atom fragments of C,N,and O\n" |
46 | 0 | "are ignored. A fragment is terminated when the atoms form a ring.\n" |
47 | 0 | "For each of these fragments the atoms, bonding and whether they constitute\n" |
48 | 0 | "a complete ring is recorded and saved in a std::set, so that there is\n" |
49 | 0 | "only one of each fragment type. Chemically identical versions, i.e. ones with\n" |
50 | 0 | "the atoms listed in reverse order and rings listed starting at different\n" |
51 | 0 | "atoms, are identified and only a single canonical fragment is retained\n" |
52 | 0 | "Each remaining fragment is assigned a hash number from 0 to 1020 which is\n" |
53 | 0 | "used to set a bit in a 1024 bit vector.\n" |
54 | 0 | "For further details see:\n" |
55 | 0 | "http://baoilleach.blogspot.co.uk/2012/01/visualising-fragments-in-path-based.html \n" |
56 | 0 | ;} |
57 | | |
58 | | //Calculates the fingerprint |
59 | | bool GetFingerprint(OBBase* pOb, vector<unsigned int>&fp, int nbits=0) override; |
60 | | |
61 | | /// \returns fragment info unless SetFlags(OBFingerprint::FPT_NOINFO) has been called before GetFingerprint() called. |
62 | | /** Structure of a fragment (vector<int>) |
63 | | For a complete ring: last atom bonded to first atom |
64 | | bo(0)(n), atno(1), bo(1)(2), atno(2), bo(2)(3),...atno(n) |
65 | | For the rest, even when stopped by encountering atoms already visited |
66 | | 0 , atno(1), bo(1)(2), atno(2), bo(2)(3),...atno(n) |
67 | | **/ |
68 | | std::string DescribeBits(const std::vector<unsigned int> /*fp*/, bool /*bSet*/ = true) override |
69 | 0 | { return _ss.str(); } |
70 | | |
71 | 0 | unsigned int Flags() override { return _flags; } |
72 | 0 | void SetFlags(unsigned int f) override { _flags=f; } |
73 | | |
74 | | private: |
75 | | typedef std::set<std::vector<int> > Fset; |
76 | | typedef std::set<std::vector<int> >::iterator SetItr; |
77 | | |
78 | | void getFragments(std::vector<int> levels, std::vector<int> curfrag, |
79 | | int level, OBAtom* patom, OBBond* pbond); |
80 | | void DoReverses(); |
81 | | void DoRings(); |
82 | | |
83 | | unsigned int CalcHash(const std::vector<int>& frag); |
84 | | void PrintFpt(const std::vector<int>& f, int hash=0); |
85 | | |
86 | | Fset fragset; |
87 | | Fset ringset; |
88 | | stringstream _ss; |
89 | | unsigned int _flags; |
90 | | |
91 | | }; |
92 | | |
93 | | //*********************************************** |
94 | | //Make a global instance |
95 | | fingerprint2 thefingerprint2("FP2",true); |
96 | | //*********************************************** |
97 | | |
98 | | /*! class fingerprint2 |
99 | | Similar to Fabien Fontain's fingerprint class, with a slightly improved |
100 | | algorithm, but re-written using STL which makes it shorter. |
101 | | */ |
102 | | |
103 | | bool fingerprint2::GetFingerprint(OBBase* pOb, vector<unsigned int>&fp, int nbits) |
104 | 2.06k | { |
105 | 2.06k | OBMol* pmol = dynamic_cast<OBMol*>(pOb); |
106 | 2.06k | if(!pmol) return false; |
107 | 2.06k | fp.resize(1024/Getbitsperint()); |
108 | 2.06k | fragset.clear();//needed because now only one instance of fp class |
109 | 2.06k | ringset.clear(); |
110 | | |
111 | | //identify fragments starting at every atom |
112 | 2.06k | OBAtom *patom; |
113 | 2.06k | vector<OBNodeBase*>::iterator i; |
114 | 2.06k | for (patom = pmol->BeginAtom(i);patom;patom = pmol->NextAtom(i)) |
115 | 0 | { |
116 | 0 | if(patom->GetAtomicNum() == OBElements::Hydrogen) continue; |
117 | 0 | vector<int> curfrag; |
118 | 0 | vector<int> levels(pmol->NumAtoms()); |
119 | 0 | getFragments(levels, curfrag, 1, patom, nullptr); |
120 | 0 | } |
121 | | |
122 | | // TRACE("%s %d frags before; ",pmol->GetTitle(),fragset.size()); |
123 | | |
124 | | //Ensure that each chemically identical fragment is present only in a single |
125 | 2.06k | DoRings(); |
126 | 2.06k | DoReverses(); |
127 | | |
128 | 2.06k | SetItr itr; |
129 | 2.06k | _ss.str(""); |
130 | 2.06k | for(itr=fragset.begin();itr!=fragset.end();++itr) |
131 | 0 | { |
132 | | //Use hash of fragment to set a bit in the fingerprint |
133 | 0 | int hash = CalcHash(*itr); |
134 | 0 | SetBit(fp,hash); |
135 | 0 | if(!(Flags() & FPT_NOINFO)) |
136 | 0 | PrintFpt(*itr,hash); |
137 | 0 | } |
138 | 2.06k | if(nbits) |
139 | 0 | Fold(fp, nbits); |
140 | | |
141 | | // TRACE("%d after\n",fragset.size()); |
142 | 2.06k | return true; |
143 | 2.06k | } |
144 | | |
145 | | ////////////////////////////////////////////////////////// |
146 | | void fingerprint2::getFragments(vector<int> levels, vector<int> curfrag, |
147 | | int level, OBAtom* patom, OBBond* pbond) |
148 | 0 | { |
149 | | //Recursive routine to analyse schemical structure and populate fragset and ringset |
150 | | //Hydrogens,charges(except dative bonds), spinMultiplicity ignored |
151 | 0 | const int Max_Fragment_Size = 7; |
152 | 0 | int bo=0; |
153 | 0 | if(pbond) |
154 | 0 | { |
155 | 0 | bo = pbond->IsAromatic() ? 5 : pbond->GetBondOrder(); |
156 | | |
157 | | // OBAtom* pprevat = pbond->GetNbrAtom(patom); |
158 | | // if(patom->GetFormalCharge() && (patom->GetFormalCharge() == -pprevat->GetFormalCharge())) |
159 | | // ++bo; //coordinate (dative) bond eg C[N+]([O-])=O is seen as CN(=O)=O |
160 | 0 | } |
161 | 0 | curfrag.push_back(bo); |
162 | 0 | curfrag.push_back(patom->GetAtomicNum()); |
163 | 0 | levels[patom->GetIdx()-1] = level; |
164 | |
|
165 | 0 | vector<OBBond*>::iterator itr; |
166 | 0 | OBBond *pnewbond; |
167 | | // PrintFpt(curfrag,(int)patom); |
168 | 0 | for (pnewbond = patom->BeginBond(itr);pnewbond;pnewbond = patom->NextBond(itr)) |
169 | 0 | { |
170 | 0 | if(pnewbond==pbond) continue; //don't retrace steps |
171 | 0 | OBAtom* pnxtat = pnewbond->GetNbrAtom(patom); |
172 | 0 | if(pnxtat->GetAtomicNum() == OBElements::Hydrogen) continue; |
173 | | |
174 | 0 | int atlevel = levels[pnxtat->GetIdx()-1]; |
175 | 0 | if(atlevel) //ring |
176 | 0 | { |
177 | 0 | if(atlevel==1) |
178 | 0 | { |
179 | | //If complete ring (last bond is back to starting atom) add bond at front |
180 | | //and save in ringset |
181 | 0 | curfrag[0] = pnewbond->IsAromatic() ? 5 : pnewbond->GetBondOrder(); |
182 | 0 | ringset.insert(curfrag); |
183 | 0 | curfrag[0] = 0; |
184 | 0 | } |
185 | 0 | } |
186 | 0 | else //no ring |
187 | 0 | { |
188 | 0 | if(level<Max_Fragment_Size) |
189 | 0 | { |
190 | | // TRACE("level=%d size=%d %p frag[0]=%p\n",level, curfrag.size(),&curfrag, &(curfrag[0])); |
191 | | //Do the next atom; levels, curfrag are passed by value and hence copied |
192 | 0 | getFragments(levels, curfrag, level+1, pnxtat, pnewbond); |
193 | 0 | } |
194 | 0 | } |
195 | 0 | } |
196 | | |
197 | | //do not save C,N,O single atom fragments |
198 | 0 | if(curfrag[0]==0 && |
199 | 0 | (level>1 || patom->GetAtomicNum()>8 || patom->GetAtomicNum()<6)) |
200 | 0 | { |
201 | 0 | fragset.insert(curfrag); //curfrag ignored if an identical fragment already present |
202 | | // PrintFpt(curfrag,level); |
203 | 0 | } |
204 | 0 | } |
205 | | |
206 | | /////////////////////////////////////////////////// |
207 | | void fingerprint2::DoReverses() |
208 | 2.06k | { |
209 | 2.06k | SetItr itr; |
210 | 2.06k | for(itr=fragset.begin();itr!=fragset.end();) |
211 | 0 | { |
212 | | //Reverse the order of the atoms, add the smallest fragment and remove the larger |
213 | 0 | SetItr titr = itr++; //Ensure have valid next iterator in case current one is erased |
214 | 0 | vector<int> t1(*titr); //temporary copy |
215 | 0 | reverse(t1.begin()+1, t1.end()); //(leave 0 at front alone) |
216 | 0 | if(t1!=*titr) |
217 | 0 | { |
218 | | //Add the larger fragment and delete the smaller |
219 | 0 | if(t1>*titr) |
220 | 0 | { |
221 | 0 | fragset.erase(titr); |
222 | 0 | fragset.insert(t1); |
223 | 0 | } |
224 | 0 | else |
225 | 0 | fragset.erase(t1); |
226 | 0 | } |
227 | 0 | } |
228 | 2.06k | } |
229 | | /////////////////////////////////////////////////// |
230 | | void fingerprint2::DoRings() |
231 | 2.06k | { |
232 | | //For each complete ring fragment, find its largest chemically identical representation |
233 | | //by rotating and reversing, and insert into the main set of fragments |
234 | 2.06k | SetItr itr; |
235 | 2.06k | for(itr=ringset.begin();itr!=ringset.end();++itr) |
236 | 0 | { |
237 | 0 | vector<int> t1(*itr); //temporary copy |
238 | 0 | vector<int> maxring(*itr); //the current largest vector |
239 | 0 | unsigned int i; |
240 | 0 | for(i=0;i<t1.size()/2;++i) |
241 | 0 | { |
242 | | //rotate atoms in ring |
243 | 0 | rotate(t1.begin(),t1.begin()+2,t1.end()); |
244 | 0 | if(t1>maxring) |
245 | 0 | maxring=t1; |
246 | | |
247 | | //reverse the direction around ring |
248 | 0 | vector<int> t2(t1); |
249 | 0 | reverse(t2.begin()+1, t2.end()); |
250 | 0 | if(t2>maxring) |
251 | 0 | maxring=t2; |
252 | 0 | } |
253 | 0 | fragset.insert(maxring); |
254 | | //PrintFpt(maxring,0); |
255 | 0 | } |
256 | 2.06k | } |
257 | | |
258 | | ////////////////////////////////////////////////////////// |
259 | | unsigned int fingerprint2::CalcHash(const vector<int>& frag) |
260 | 0 | { |
261 | | //Something like... whole of fragment treated as a binary number modulus 1021 |
262 | 0 | const int MODINT = 108; //2^32 % 1021 |
263 | 0 | unsigned int hash=0; |
264 | 0 | for(unsigned i=0;i<frag.size();++i) |
265 | 0 | hash= (hash*MODINT + (frag[i] % 1021)) % 1021; |
266 | 0 | return hash; |
267 | 0 | } |
268 | | |
269 | | void fingerprint2::PrintFpt(const vector<int>& f, int hash) |
270 | 0 | { |
271 | 0 | unsigned int i; |
272 | 0 | for(i=0;i<f.size();++i) |
273 | 0 | _ss << f[i] << " "; |
274 | 0 | _ss << "<" << hash << ">" << endl; |
275 | 0 | } |
276 | | |
277 | | } //namespace OpenBabel |
278 | | |
279 | | //! \file finger2.cpp |
280 | | //! \brief fingerprint2 definition and implementation |