Coverage Report

Created: 2026-07-30 06:22

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