Coverage Report

Created: 2026-01-10 06:44

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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.