/src/openbabel/src/isomorphism.cpp
Line | Count | Source |
1 | | #include <openbabel/mol.h> |
2 | | #include <openbabel/isomorphism.h> |
3 | | #include <openbabel/obiter.h> |
4 | | #include <openbabel/oberror.h> |
5 | | #include <openbabel/query.h> |
6 | | #include <openbabel/graphsym.h> |
7 | | #include <ctime> |
8 | | #include <cassert> |
9 | | #include <algorithm> |
10 | | |
11 | 0 | #define DEBUG 0 |
12 | | |
13 | | using namespace std; |
14 | | |
15 | | namespace OpenBabel { |
16 | | |
17 | | template<typename T> |
18 | | void print_vector(const std::string &label, const std::vector<T> &v) |
19 | | { |
20 | | std::cout << label << ": "; |
21 | | for (std::size_t i = 0; i < v.size(); ++i) |
22 | | if (v[i] < 10) |
23 | | std::cout << " " << v[i] << " "; |
24 | | else |
25 | | std::cout << v[i] << " "; |
26 | | |
27 | | std::cout << endl; |
28 | | } |
29 | | |
30 | | static const char *red = "\033[1;31m"; |
31 | | static const char *green = "\033[1;32m"; |
32 | | static const char *yellow = "\033[1;33m"; |
33 | | static const char *blue = "\033[1;34m"; |
34 | | static const char *normal = "\033[0m"; |
35 | | |
36 | | class MapAllFunctor : public OBIsomorphismMapper::Functor |
37 | | { |
38 | | private: |
39 | | OBIsomorphismMapper::Mappings &m_maps; |
40 | | std::size_t m_memory, m_maxMemory; |
41 | | public: |
42 | | MapAllFunctor(OBIsomorphismMapper::Mappings &maps, std::size_t maxMemory) |
43 | 0 | : m_maps(maps), m_memory(0), m_maxMemory(maxMemory) |
44 | 0 | { |
45 | 0 | } |
46 | | bool operator()(OBIsomorphismMapper::Mapping &map) override |
47 | 0 | { |
48 | 0 | m_maps.push_back(map); |
49 | 0 | m_memory += 2 * sizeof(unsigned int) * map.size(); |
50 | |
|
51 | 0 | if (m_memory > m_maxMemory) { |
52 | 0 | obErrorLog.ThrowError(__FUNCTION__, "memory limit exceeded...", obError); |
53 | | // stop mapping |
54 | 0 | return true; |
55 | 0 | } |
56 | | |
57 | | // continue mapping |
58 | 0 | return false; |
59 | 0 | } |
60 | | }; |
61 | | |
62 | | |
63 | | class VF2Mapper : public OBIsomorphismMapper |
64 | | { |
65 | | time_t m_startTime; |
66 | | |
67 | | public: |
68 | 0 | VF2Mapper(OBQuery *query) : OBIsomorphismMapper(query) |
69 | 0 | { |
70 | 0 | } |
71 | | |
72 | | struct Candidate { |
73 | 0 | Candidate() : queryAtom(nullptr), queriedAtom(nullptr) {} |
74 | | Candidate(OBQueryAtom *_queryAtom, OBAtom *_queriedAtom) |
75 | 0 | : queryAtom(_queryAtom), queriedAtom(_queriedAtom) {} |
76 | | |
77 | | bool operator==(const Candidate &other) |
78 | 0 | { |
79 | 0 | if (queryAtom != other.queryAtom) |
80 | 0 | return false; |
81 | 0 | if (queriedAtom != other.queriedAtom) |
82 | 0 | return false; |
83 | 0 | return true; |
84 | 0 | } |
85 | | |
86 | | OBQueryAtom *queryAtom; |
87 | | OBAtom *queriedAtom; |
88 | | }; |
89 | | |
90 | | struct State { |
91 | | State(Functor &_functor, const OBQuery *_query, const OBMol *_queried, const OBBitVec &mask) |
92 | 0 | : functor(_functor), query(_query), queried(_queried), queriedMask(mask) |
93 | 0 | { |
94 | 0 | abort = false; |
95 | 0 | mapping.resize(query->NumAtoms(), nullptr); |
96 | 0 | queryDepths.resize(query->NumAtoms(), 0); |
97 | 0 | queriedDepths.resize(queried->NumAtoms(), 0); |
98 | 0 | } |
99 | | bool abort; |
100 | | Functor &functor; |
101 | | const OBQuery *query; // the query |
102 | | const OBMol *queried; // the queried molecule |
103 | | OBBitVec queriedMask; // the queriedMask |
104 | | std::vector<unsigned int> queryPath; // the path in the query |
105 | | std::vector<unsigned int> queriedPath; // the path in the queried molecule |
106 | | |
107 | | std::vector<OBAtom*> mapping; |
108 | | |
109 | | OBBitVec queryPathBits, queriedPathBits; // the terminal sets |
110 | | std::vector<unsigned int> queryDepths, queriedDepths; // the terminal sets |
111 | | }; |
112 | | |
113 | | bool isInTerminalSet(const std::vector<unsigned int> &depths, |
114 | | const OBBitVec &path, std::size_t i) |
115 | 0 | { |
116 | 0 | if (!depths[i]) |
117 | 0 | return false; |
118 | | |
119 | 0 | if (path.BitIsSet(i)) |
120 | 0 | return false; |
121 | | |
122 | 0 | return true; |
123 | 0 | } |
124 | | |
125 | | /** |
126 | | * Check bonds around newly mapped atom. |
127 | | */ |
128 | | bool checkBonds(State &state, OBQueryAtom *queryAtom) |
129 | 0 | { |
130 | 0 | const std::vector<OBQueryBond*> &bonds = queryAtom->GetBonds(); |
131 | 0 | for (unsigned int i = 0; i < bonds.size(); ++i) { |
132 | 0 | OBQueryBond *qbond = bonds[i]; |
133 | 0 | unsigned int beginIndex = qbond->GetBeginAtom()->GetIndex(); |
134 | 0 | unsigned int endIndex = qbond->GetEndAtom()->GetIndex(); |
135 | |
|
136 | 0 | OBAtom *begin = state.mapping[beginIndex]; |
137 | 0 | OBAtom *end = state.mapping[endIndex]; |
138 | 0 | if (!begin || !end) |
139 | 0 | continue; |
140 | 0 | OBBond *bond = state.queried->GetBond(begin, end); |
141 | 0 | if (!bond) |
142 | 0 | return false; |
143 | 0 | if (!qbond->Matches(bond)) |
144 | 0 | return false; |
145 | 0 | } |
146 | 0 | return true; |
147 | 0 | } |
148 | | |
149 | | |
150 | | /** |
151 | | * Check if the current state is a full mapping of the query. |
152 | | */ |
153 | | bool checkForMap(State &state) |
154 | 0 | { |
155 | | // store the mapping if all atoms are mapped |
156 | 0 | if (state.queryPath.size() != state.query->NumAtoms()) |
157 | 0 | return false; |
158 | | |
159 | 0 | if (DEBUG) |
160 | 0 | std::cout << green << "-----------------> MATCH" << normal << std::endl; |
161 | | |
162 | | // create the map |
163 | 0 | Mapping map; |
164 | 0 | map.reserve(state.queryPath.size()); |
165 | 0 | for (unsigned int k = 0; k < state.queryPath.size(); ++k) |
166 | 0 | map.push_back(std::make_pair(state.queryPath[k], state.queriedPath[k])); |
167 | |
|
168 | 0 | return state.functor(map); |
169 | 0 | } |
170 | | |
171 | | /** |
172 | | * Match the candidate atoms and bonds. |
173 | | */ |
174 | | bool matchCandidate(State &state, OBQueryAtom *queryAtom, OBAtom *queriedAtom) |
175 | 0 | { |
176 | 0 | if (!queryAtom->Matches(queriedAtom)) |
177 | 0 | return false; |
178 | | |
179 | | // add the neighbors to the paths |
180 | 0 | state.queryPath.push_back(queryAtom->GetIndex()); |
181 | 0 | state.queriedPath.push_back(queriedAtom->GetIndex()); |
182 | | // update the terminal sets |
183 | 0 | state.queryPathBits.SetBitOn(queryAtom->GetIndex()); |
184 | 0 | state.queriedPathBits.SetBitOn(queriedAtom->GetIndex()); |
185 | | // update mapping |
186 | 0 | state.mapping[queryAtom->GetIndex()] = queriedAtom; |
187 | | |
188 | | // |
189 | | // update queryDepths |
190 | | // |
191 | 0 | if (!state.queryDepths[queryAtom->GetIndex()]) |
192 | 0 | state.queryDepths[queryAtom->GetIndex()] = state.queryPath.size(); |
193 | |
|
194 | 0 | std::vector<OBQueryAtom*> queryNbrs = queryAtom->GetNbrs(); |
195 | 0 | for (unsigned int i = 0; i < queryNbrs.size(); ++i) { |
196 | 0 | unsigned int index = queryNbrs[i]->GetIndex(); |
197 | 0 | if (!state.queryDepths[index]) |
198 | 0 | state.queryDepths[index] = state.queryPath.size(); |
199 | 0 | } |
200 | | |
201 | | // |
202 | | // update queriedDepths |
203 | | // |
204 | 0 | if (!state.queriedDepths[queriedAtom->GetIndex()]) |
205 | 0 | state.queriedDepths[queriedAtom->GetIndex()] = state.queriedPath.size(); |
206 | |
|
207 | 0 | FOR_NBORS_OF_ATOM (nbr, queriedAtom) { |
208 | 0 | unsigned int index = nbr->GetIndex(); |
209 | | // skip atoms not in the mask |
210 | 0 | if (!state.queriedMask.BitIsSet(index + 1)) |
211 | 0 | continue; |
212 | 0 | if (!state.queriedDepths[index]) |
213 | 0 | state.queriedDepths[index] = state.queriedPath.size(); |
214 | 0 | } |
215 | | |
216 | | // check if the bonds match |
217 | 0 | if (!checkBonds(state, queryAtom)) { |
218 | 0 | if (DEBUG) |
219 | 0 | cout << " bonds do not match..." << endl; |
220 | 0 | Backtrack(state); |
221 | 0 | return false; |
222 | 0 | } |
223 | | |
224 | 0 | if (DEBUG) { |
225 | 0 | cout << "FOUND: " << queryAtom->GetIndex() << " -> " << queriedAtom->GetIndex() << " " << state.queryPath.size() << endl; |
226 | 0 | cout << "queryDepths: "; |
227 | 0 | for (unsigned int i = 0; i < state.query->NumAtoms(); ++i) |
228 | 0 | cout << state.queryDepths[i] << " "; |
229 | 0 | cout <<endl; |
230 | 0 | cout << "queriedDepths: "; |
231 | 0 | for (unsigned int i = 0; i < state.queried->NumAtoms(); ++i) |
232 | 0 | cout << state.queriedDepths[i] << " "; |
233 | 0 | cout <<endl; |
234 | 0 | } |
235 | | |
236 | | // |
237 | | // Feasibility rules for the VF2 algorithm: |
238 | | // |
239 | | // size( T1(s) ) <= size( T2(s) ) |
240 | | // |
241 | | // size( N1 - M1(s) - T1(s) ) <= size( N2 - M2(s) - T2(s) ) |
242 | | // |
243 | | |
244 | | // compute T1(s) size |
245 | 0 | unsigned int numT1 = 0; |
246 | 0 | for (unsigned int i = 0; i < state.query->NumAtoms(); ++i) |
247 | 0 | if (isInTerminalSet(state.queryDepths, state.queryPathBits, i)) |
248 | 0 | numT1++; |
249 | | // compute T2(s) size |
250 | 0 | unsigned int numT2 = 0; |
251 | 0 | for (unsigned int i = 0; i < state.queried->NumAtoms(); ++i) |
252 | 0 | if (isInTerminalSet(state.queriedDepths, state.queriedPathBits, i)) |
253 | 0 | numT2++; |
254 | | |
255 | | // T1(s) > T() |
256 | 0 | if (numT1 > numT2) { |
257 | 0 | Backtrack(state); |
258 | 0 | return false; |
259 | 0 | } |
260 | | // N1 - M1(s) - T1(s) > N2 - M2(s) - T2(s) |
261 | 0 | if ((state.query->NumAtoms() - state.queryPath.size() - numT1) > (state.queried->NumAtoms() - state.queriedPath.size() - numT2)) { |
262 | 0 | Backtrack(state); |
263 | 0 | return false; |
264 | 0 | } |
265 | | |
266 | | // Check if there is a mapping found |
267 | 0 | state.abort = checkForMap(state); |
268 | |
|
269 | 0 | return true; |
270 | 0 | } |
271 | | |
272 | | |
273 | | Candidate NextCandidate(State &state, const Candidate &lastCandidate) |
274 | 0 | { |
275 | 0 | std::size_t lastQueryAtom = lastCandidate.queryAtom ? lastCandidate.queryAtom->GetIndex() : 0; |
276 | 0 | std::size_t lastQueriedAtom = lastCandidate.queriedAtom ? lastCandidate.queriedAtom->GetIndex() + 1 : 0; |
277 | |
|
278 | 0 | std::size_t querySize = state.query->NumAtoms(); |
279 | 0 | std::size_t queriedSize = state.queried->NumAtoms(); |
280 | |
|
281 | 0 | std::size_t queryTerminalSize = state.queryDepths.size() - std::count(state.queryDepths.begin(), state.queryDepths.end(), 0); |
282 | 0 | std::size_t queriedTerminalSize = state.queriedDepths.size() - std::count(state.queriedDepths.begin(), state.queriedDepths.end(), 0); |
283 | |
|
284 | 0 | std::size_t mappingSize = state.queryPath.size(); |
285 | |
|
286 | 0 | if (queryTerminalSize > mappingSize && queriedTerminalSize > mappingSize) { |
287 | 0 | while (lastQueryAtom < querySize && (state.queryPathBits.BitIsSet(lastQueryAtom) || !state.queryDepths[lastQueryAtom])) { |
288 | 0 | lastQueryAtom++; |
289 | 0 | lastQueriedAtom = 0; |
290 | 0 | } |
291 | 0 | } else { |
292 | 0 | while(lastQueryAtom < querySize && state.queryPathBits.BitIsSet(lastQueryAtom)) { |
293 | 0 | lastQueryAtom++; |
294 | 0 | lastQueriedAtom = 0; |
295 | 0 | } |
296 | 0 | } |
297 | |
|
298 | 0 | if (queryTerminalSize > mappingSize && queriedTerminalSize > mappingSize) { |
299 | 0 | while (lastQueriedAtom < queriedSize && (state.queriedPathBits.BitIsSet(lastQueriedAtom) || !state.queriedDepths[lastQueriedAtom])) |
300 | 0 | lastQueriedAtom++; |
301 | 0 | } else { |
302 | 0 | while(lastQueriedAtom < queriedSize && state.queriedPathBits[lastQueriedAtom]) |
303 | 0 | lastQueriedAtom++; |
304 | 0 | } |
305 | |
|
306 | 0 | if (lastQueryAtom < querySize && lastQueriedAtom < queriedSize) |
307 | 0 | return Candidate(state.query->GetAtoms()[lastQueryAtom], state.queried->GetAtom(lastQueriedAtom + 1)); |
308 | | |
309 | 0 | return Candidate(); |
310 | 0 | } |
311 | | |
312 | | /** |
313 | | * The depth-first isomorphism algorithm. |
314 | | */ |
315 | | void MapNext(State &state, OBQueryAtom *queryAtom, OBAtom *queriedAtom) |
316 | 0 | { |
317 | 0 | if (time(nullptr) - m_startTime > m_timeout) |
318 | 0 | return; |
319 | 0 | if (state.abort) |
320 | 0 | return; |
321 | | |
322 | 0 | Candidate candidate; |
323 | 0 | while (!state.abort) { |
324 | 0 | candidate = NextCandidate(state, candidate); |
325 | |
|
326 | 0 | if (!candidate.queryAtom) |
327 | 0 | return; |
328 | | |
329 | 0 | if (DEBUG) |
330 | 0 | cout << yellow << "candidate: " << candidate.queryAtom->GetIndex() << " -> " << candidate.queriedAtom->GetIndex() << normal << endl; |
331 | | |
332 | |
|
333 | 0 | if (matchCandidate(state, candidate.queryAtom, candidate.queriedAtom)) { |
334 | 0 | MapNext(state, candidate.queryAtom, candidate.queriedAtom); |
335 | 0 | Backtrack(state); |
336 | 0 | } |
337 | 0 | } |
338 | |
|
339 | 0 | } |
340 | | |
341 | | void Backtrack(State &state) |
342 | 0 | { |
343 | 0 | if (DEBUG) |
344 | 0 | cout << red << "backtrack... " << normal << state.queryPath.size()-1 << endl; |
345 | | // remove last atoms from the mapping |
346 | 0 | if (state.queryPath.size()) { |
347 | 0 | state.mapping[state.queryPath.back()] = nullptr; |
348 | 0 | state.queryPathBits.SetBitOff(state.queryPath.back()); |
349 | 0 | state.queryPath.pop_back(); |
350 | 0 | } |
351 | 0 | if (state.queriedPath.size()) { |
352 | 0 | state.queriedPathBits.SetBitOff(state.queriedPath.back()); |
353 | 0 | state.queriedPath.pop_back(); |
354 | 0 | } |
355 | | // restore queryDepths and queriedDepths |
356 | 0 | unsigned int depth = state.queryPath.size() + 1; |
357 | 0 | std::replace(state.queryDepths.begin(), state.queryDepths.end(), depth, static_cast<unsigned int>(0)); |
358 | 0 | std::replace(state.queriedDepths.begin(), state.queriedDepths.end(), depth, static_cast<unsigned int>(0)); // O(n) n = # vertices in the queried |
359 | 0 | } |
360 | | |
361 | | /** |
362 | | * Get the first mappings of the query on the queried molecule. |
363 | | * @param queried The queried molecule. |
364 | | * @return The mapping. |
365 | | */ |
366 | | void MapFirst(const OBMol *queried, Mapping &map, const OBBitVec &mask) override |
367 | 0 | { |
368 | 0 | class MapFirstFunctor : public Functor |
369 | 0 | { |
370 | 0 | private: |
371 | 0 | Mapping &m_map; |
372 | 0 | public: |
373 | 0 | MapFirstFunctor(Mapping &map) : m_map(map) |
374 | 0 | { |
375 | 0 | } |
376 | 0 | bool operator()(Mapping &map) override |
377 | 0 | { |
378 | 0 | m_map = map; |
379 | | // stop mapping |
380 | 0 | return true; |
381 | 0 | } |
382 | 0 | }; |
383 | |
|
384 | 0 | MapFirstFunctor functor(map); |
385 | 0 | MapGeneric(functor, queried, mask); |
386 | 0 | } |
387 | | |
388 | | /** |
389 | | * Get all unique mappings of the query on the queried molecule. |
390 | | * @param queried The queried molecule. |
391 | | * @return The unique mappings |
392 | | */ |
393 | | void MapUnique(const OBMol *queried, Mappings &maps, const OBBitVec &mask) override |
394 | 0 | { |
395 | 0 | class MapUniqueFunctor : public OBIsomorphismMapper::Functor |
396 | 0 | { |
397 | 0 | private: |
398 | 0 | OBIsomorphismMapper::Mappings &m_maps; |
399 | 0 | public: |
400 | 0 | MapUniqueFunctor(OBIsomorphismMapper::Mappings &maps) : m_maps(maps) |
401 | 0 | { |
402 | 0 | } |
403 | 0 | bool operator()(OBIsomorphismMapper::Mapping &map) override |
404 | 0 | { |
405 | | // get the values from the map |
406 | 0 | std::vector<unsigned int> values; |
407 | 0 | for (OBIsomorphismMapper::Mapping::const_iterator it = map.begin(); it != map.end(); ++it) |
408 | 0 | values.push_back(it->second); |
409 | 0 | std::sort(values.begin(), values.end()); |
410 | | // print_vector("values ", values); |
411 | |
|
412 | 0 | bool isUnique = true; |
413 | 0 | for (unsigned int k = 0; k < m_maps.size(); ++k) { |
414 | 0 | std::vector<unsigned int> kValues; |
415 | 0 | for (OBIsomorphismMapper::Mapping::iterator it = m_maps[k].begin(); it != m_maps[k].end(); ++it) |
416 | 0 | kValues.push_back(it->second); |
417 | 0 | std::sort(kValues.begin(), kValues.end()); |
418 | | |
419 | | // print_vector("kValues", kValues); |
420 | 0 | if (values == kValues) |
421 | 0 | isUnique = false; |
422 | 0 | } |
423 | |
|
424 | 0 | if (isUnique) |
425 | 0 | m_maps.push_back(map); |
426 | | |
427 | | // continue mapping |
428 | 0 | return false; |
429 | 0 | } |
430 | 0 | }; |
431 | | |
432 | |
|
433 | 0 | maps.clear(); |
434 | 0 | MapUniqueFunctor functor(maps); |
435 | 0 | MapGeneric(functor, queried, mask); |
436 | |
|
437 | 0 | if (DEBUG) |
438 | 0 | for (unsigned int i =0; i < maps.size(); ++i) { |
439 | 0 | cout << "mapping:" << endl; |
440 | 0 | for (Mapping::iterator it = maps[i].begin(); it != maps[i].end(); ++it) |
441 | 0 | cout << " " << it->first << " -> " << it->second << endl; |
442 | 0 | } |
443 | 0 | } |
444 | | |
445 | | /** |
446 | | * Get all mappings of the query on the queried molecule. Duplicates are |
447 | | * ignored but unlinke MapUnique, multiple mappings of the query on the same |
448 | | * part of the queried structure are allowed. This makes it possible to use |
449 | | * MapAll for finding the automorphism group. |
450 | | * @param queried The queried molecule. |
451 | | * @return The mappings. |
452 | | */ |
453 | | void MapAll(const OBMol *queried, Mappings &maps, const OBBitVec &mask, std::size_t maxMemory) override |
454 | 0 | { |
455 | 0 | maps.clear(); |
456 | 0 | MapAllFunctor functor(maps, maxMemory); |
457 | 0 | MapGeneric(functor, queried, mask); |
458 | |
|
459 | 0 | if (DEBUG) |
460 | 0 | for (unsigned int i =0; i < maps.size(); ++i) { |
461 | 0 | cout << "mapping:" << endl; |
462 | 0 | for (Mapping::iterator it = maps[i].begin(); it != maps[i].end(); ++it) |
463 | 0 | cout << " " << it->first << " -> " << it->second << endl; |
464 | 0 | } |
465 | |
|
466 | 0 | } |
467 | | |
468 | | void MapGeneric(Functor &functor, const OBMol *queried, const OBBitVec &mask) override |
469 | 0 | { |
470 | 0 | m_startTime = time(nullptr); |
471 | 0 | if(m_query->NumAtoms() == 0) return; |
472 | | // set all atoms to 1 if the mask is empty |
473 | 0 | OBBitVec queriedMask = mask; |
474 | 0 | if (!queriedMask.CountBits()) |
475 | 0 | for (unsigned int i = 0; i < queried->NumAtoms(); ++i) |
476 | 0 | queriedMask.SetBitOn(i + 1); |
477 | |
|
478 | 0 | OBQueryAtom *queryAtom = m_query->GetAtoms()[0]; |
479 | 0 | for (unsigned int i = 0; i < queried->NumAtoms(); ++i) { |
480 | 0 | if (!queriedMask.BitIsSet(i + 1)) { |
481 | 0 | continue; |
482 | 0 | } |
483 | 0 | State state(functor, m_query, queried, queriedMask); |
484 | 0 | OBAtom *queriedAtom = queried->GetAtom(i+1); |
485 | 0 | if (!queryAtom->Matches(queriedAtom)) { |
486 | 0 | continue; |
487 | 0 | } |
488 | 0 | if (DEBUG) |
489 | 0 | std::cout << blue << "START: 0 -> " << queriedAtom->GetIndex() << normal << std::endl; |
490 | |
|
491 | 0 | if (m_query->NumAtoms() > 1) { |
492 | 0 | if (matchCandidate(state, queryAtom, queriedAtom)) |
493 | 0 | MapNext(state, queryAtom, queriedAtom); |
494 | 0 | } else { |
495 | 0 | Mapping map; |
496 | 0 | map.push_back(std::make_pair(queryAtom->GetIndex(), queriedAtom->GetIndex())); |
497 | 0 | functor(map); |
498 | 0 | } |
499 | 0 | } |
500 | |
|
501 | 0 | if (time(nullptr) - m_startTime > m_timeout) |
502 | 0 | obErrorLog.ThrowError(__FUNCTION__, "time limit exceeded...", obError); |
503 | |
|
504 | 0 | } |
505 | | |
506 | | }; |
507 | | |
508 | 0 | OBIsomorphismMapper::OBIsomorphismMapper(OBQuery *query) : m_query(query), m_timeout(60) |
509 | 0 | { |
510 | 0 | } |
511 | | |
512 | | OBIsomorphismMapper::~OBIsomorphismMapper() |
513 | 0 | { |
514 | 0 | } |
515 | | |
516 | | OBIsomorphismMapper* OBIsomorphismMapper::GetInstance(OBQuery *query, const std::string &algorithm) |
517 | 0 | { |
518 | 0 | if (algorithm == "VF2") |
519 | 0 | return new VF2Mapper(query); |
520 | | // return VF2 mapper as default |
521 | 0 | return new VF2Mapper(query); |
522 | 0 | } |
523 | | |
524 | | |
525 | | |
526 | | |
527 | | |
528 | | |
529 | | |
530 | | class OBAutomorphismQueryAtom : public OBQueryAtom |
531 | | { |
532 | | public: |
533 | | OBAutomorphismQueryAtom(unsigned int _symClass, const std::vector<unsigned int> &_symClasses) |
534 | 0 | : OBQueryAtom(), symClass(_symClass), symClasses(_symClasses) |
535 | 0 | { |
536 | 0 | } |
537 | | |
538 | | bool Matches(const OBAtom *atom) const override |
539 | 0 | { |
540 | 0 | return (symClasses[atom->GetIndex()] == symClass); |
541 | 0 | } |
542 | | unsigned int symClass; |
543 | | std::vector<unsigned int> symClasses; |
544 | | }; |
545 | | |
546 | | bool isFerroceneBond(OBBond *bond); |
547 | | |
548 | | OBQuery* CompileAutomorphismQuery(OBMol *mol, const OBBitVec &mask, const std::vector<unsigned int> &symClasses) |
549 | 0 | { |
550 | 0 | OBQuery *query = new OBQuery; |
551 | 0 | unsigned int offset = 0; |
552 | 0 | std::vector<unsigned int> indexes; |
553 | 0 | FOR_ATOMS_OF_MOL (obatom, mol) { |
554 | 0 | indexes.push_back(obatom->GetIndex() - offset); |
555 | 0 | if (!mask.BitIsSet(obatom->GetIndex() + 1)) { |
556 | 0 | offset++; |
557 | 0 | continue; |
558 | 0 | } |
559 | 0 | query->AddAtom(new OBAutomorphismQueryAtom(symClasses[obatom->GetIndex()], symClasses)); |
560 | 0 | } |
561 | 0 | FOR_BONDS_OF_MOL (obbond, mol) { |
562 | 0 | if (isFerroceneBond(&*obbond)) |
563 | 0 | continue; |
564 | 0 | unsigned int beginIndex = obbond->GetBeginAtom()->GetIndex(); |
565 | 0 | unsigned int endIndex = obbond->GetEndAtom()->GetIndex(); |
566 | 0 | if (!mask.BitIsSet(beginIndex + 1) || !mask.BitIsSet(endIndex + 1)) |
567 | 0 | continue; |
568 | 0 | query->AddBond(new OBQueryBond(query->GetAtoms()[indexes[beginIndex]], query->GetAtoms()[indexes[endIndex]], |
569 | 0 | obbond->GetBondOrder(), obbond->IsAromatic())); |
570 | 0 | } |
571 | |
|
572 | 0 | return query; |
573 | 0 | } |
574 | | |
575 | | bool FindAutomorphisms(OBMol *mol, Automorphisms &maps, const OBBitVec &mask, std::size_t maxMemory) |
576 | 0 | { |
577 | | // set all atoms to 1 if the mask is empty |
578 | 0 | OBBitVec queriedMask = mask; |
579 | 0 | if (!queriedMask.CountBits()) |
580 | 0 | for (unsigned int i = 0; i < mol->NumAtoms(); ++i) |
581 | 0 | queriedMask.SetBitOn(i + 1); |
582 | | |
583 | | // get the symmetry classes |
584 | 0 | OBGraphSym gs(mol, &queriedMask); |
585 | 0 | std::vector<unsigned int> symClasses; |
586 | 0 | gs.GetSymmetry(symClasses); |
587 | |
|
588 | 0 | return FindAutomorphisms(mol, maps, symClasses, mask, maxMemory); |
589 | 0 | } |
590 | | |
591 | | |
592 | | bool FindAutomorphisms(OBMol *mol, Automorphisms &maps, const std::vector<unsigned int> &symClasses, const OBBitVec &mask, std::size_t maxMemory) |
593 | 0 | { |
594 | 0 | maps.clear(); |
595 | 0 | MapAllFunctor functor(maps, maxMemory); |
596 | 0 | FindAutomorphisms(functor, mol, symClasses, mask); |
597 | 0 | return maps.size(); |
598 | 0 | } |
599 | | |
600 | | OBBitVec getFragment(OBAtom *atom, const OBBitVec &mask, const std::vector<OBBond*> &metalloceneBonds = std::vector<OBBond*>()); |
601 | | |
602 | | void FindAutomorphisms(OBIsomorphismMapper::Functor &functor, OBMol *mol, |
603 | | const std::vector<unsigned int> &symClasses, const OBBitVec &mask) |
604 | 0 | { |
605 | 0 | class AutomorphismFunctor : public OBIsomorphismMapper::Functor |
606 | 0 | { |
607 | 0 | private: |
608 | 0 | OBIsomorphismMapper::Functor &m_functor; |
609 | 0 | const OBBitVec &m_fragment; |
610 | 0 | std::vector<unsigned int> m_indexes; |
611 | 0 | public: |
612 | 0 | AutomorphismFunctor(OBIsomorphismMapper::Functor &functor, const OBBitVec &fragment, unsigned int numAtoms) |
613 | 0 | : m_functor(functor), m_fragment(fragment) |
614 | 0 | { |
615 | 0 | for (unsigned int j = 0; j < numAtoms; ++j) |
616 | 0 | if (m_fragment.BitIsSet(j + 1)) |
617 | 0 | m_indexes.push_back(j); |
618 | 0 | } |
619 | 0 | bool operator()(Automorphism &map) override |
620 | 0 | { |
621 | | // convert the continuous mapping map to a mapping with gaps (considering key values) |
622 | 0 | for (Automorphism::iterator it = map.begin(); it != map.end(); ++it) |
623 | 0 | it->first = m_indexes[it->first]; |
624 | 0 | return m_functor(map); |
625 | 0 | } |
626 | 0 | }; |
627 | | |
628 | | // set all atoms to 1 if the mask is empty |
629 | 0 | OBBitVec queriedMask = mask; |
630 | 0 | if (!queriedMask.CountBits()) |
631 | 0 | for (unsigned int i = 0; i < mol->NumAtoms(); ++i) |
632 | 0 | queriedMask.SetBitOn(i + 1); |
633 | |
|
634 | 0 | if (DEBUG) |
635 | 0 | for (unsigned int i = 0; i < symClasses.size(); ++i) |
636 | 0 | cout << i << ": " << symClasses[i] << endl; |
637 | | |
638 | | // compute the connected fragments |
639 | 0 | OBBitVec visited; |
640 | 0 | std::vector<OBBitVec> fragments; |
641 | 0 | for (std::size_t i = 0; i < mol->NumAtoms(); ++i) { |
642 | 0 | if (!queriedMask.BitIsSet(i+1) || visited.BitIsSet(i+1)) |
643 | 0 | continue; |
644 | 0 | fragments.push_back(getFragment(mol->GetAtom(i+1), queriedMask)); |
645 | 0 | visited |= fragments.back(); |
646 | 0 | } |
647 | | |
648 | | // count the symmetry classes |
649 | 0 | std::vector<int> symClassCounts(symClasses.size() + 1, 0); |
650 | 0 | for (unsigned int i = 0; i < symClasses.size(); ++i) { |
651 | 0 | if (!queriedMask.BitIsSet(i + 1)) |
652 | 0 | continue; |
653 | 0 | unsigned int symClass = symClasses[i]; |
654 | 0 | symClassCounts[symClass]++; |
655 | 0 | } |
656 | |
|
657 | 0 | for (std::size_t i = 0; i < fragments.size(); ++i) { |
658 | 0 | OBQuery *query = CompileAutomorphismQuery(mol, fragments[i], symClasses); |
659 | 0 | OBIsomorphismMapper *mapper = OBIsomorphismMapper::GetInstance(query); |
660 | |
|
661 | 0 | AutomorphismFunctor autFunctor(functor, fragments[i], mol->NumAtoms()); |
662 | 0 | mapper->MapGeneric(autFunctor, mol, fragments[i]); |
663 | 0 | delete mapper; |
664 | 0 | delete query; |
665 | 0 | } |
666 | |
|
667 | 0 | } |
668 | | |
669 | | |
670 | | |
671 | | |
672 | | } |
673 | | |
674 | | /// @file isomorphism.cpp |
675 | | /// @brief OBIsomorphismMapper class for finding isomorphisms. |