1 /**********************************************************************
2 graphsym.cpp - Symmetry detection algorithm
3
4 Copyright (C) 2009-2010 by Tim Vandermeersch
5 Copyright (C) 2005-2006, eMolecules, Inc. (www.emolecules.com)
6
7 This file is part of the Open Babel project.
8 For more information, see <http://openbabel.org/>
9
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation version 2 of the License.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 02110-1301, USA.
23 **********************************************************************/
24
25 #include <openbabel/graphsym.h>
26 #include <openbabel/babelconfig.h>
27 #include <openbabel/mol.h>
28 #include <openbabel/atom.h>
29 #include <openbabel/bond.h>
30 #include <openbabel/ring.h>
31 #include <openbabel/obiter.h>
32 #include <openbabel/generic.h>
33
34 #include <openbabel/stereo/cistrans.h>
35 #include <openbabel/stereo/tetrahedral.h>
36 #include <openbabel/elements.h>
37
38 #include <iterator> // std::istream_iterator
39 #include <cassert>
40 #include <algorithm>
41 #include <cmath>
42 #include <limits>
43
44 #include "stereo/stereoutil.h"
45
46 using namespace std;
47
48
49 // debug function
50 template<typename T>
print_vector(const std::string & label,const std::vector<T> & v)51 void print_vector(const std::string &label, const std::vector<T> &v)
52 {
53 std::cout << label << ": ";
54 for (std::size_t i = 0; i < v.size(); ++i)
55 if (v[i] < 10)
56 std::cout << " " << v[i] << " ";
57 else
58 std::cout << v[i] << " ";
59
60 std::cout << endl;
61 }
62
63 // debug function
print_sym_classes(const std::string & label,const std::vector<std::pair<OpenBabel::OBAtom *,unsigned int>> & atom_sym_classes)64 void print_sym_classes(const std::string &label, const std::vector<std::pair<OpenBabel::OBAtom*, unsigned int> > &atom_sym_classes)
65 {
66 cout << label << ": ";
67 for (unsigned int i = 0; i < atom_sym_classes.size(); i++)
68 cout << atom_sym_classes[i].second << " ";
69 cout << endl;
70 }
71
72 namespace OpenBabel {
73
74 class OBGraphSymPrivate
75 {
76 public:
77 OBBitVec _frag_atoms;
78 OBMol* _pmol;
79 std::vector<unsigned int> _canonLabels;
80 OBStereoUnitSet _stereoUnits;
81
82 unsigned int GetHvyDegree(OBAtom *atom);
83 unsigned int GetHvyBondSum(OBAtom *atom);
84 void FindRingAtoms(OBBitVec &ring_atoms);
85 void CreateNewClassVector(std::vector<std::pair<OBAtom*,unsigned int> > &vp1,
86 std::vector<std::pair<OBAtom*,unsigned int> > &vp2);
87 static void CreateNewClassVector(OBMol *mol, std::vector<std::pair<OBAtom*,unsigned int> > &vp1,
88 std::vector<std::pair<OBAtom*,unsigned int> > &vp2);
89 void GetGIVector(std::vector<unsigned int> &vid);
90 bool GetGTDVector(std::vector<int> >d);
91 static void CountAndRenumberClasses(std::vector<std::pair<OBAtom*,unsigned int> > &vp, unsigned int &count);
92 int ExtendInvariants(std::vector<std::pair<OBAtom*, unsigned int> > &symmetry_classes);
93 int CalculateSymmetry(std::vector<unsigned int> &symmetry_classes);
94 int Iterate(std::vector<unsigned int> &symmetry_classes);
95 void CanonicalLabels(const std::vector<unsigned int> &symmetry_classes, std::vector<unsigned int> &canon_labels, int maxSeconds);
96 };
97
OBGraphSym(OBMol * pmol,const OBBitVec * frag_atoms)98 OBGraphSym::OBGraphSym(OBMol* pmol, const OBBitVec* frag_atoms) : d(new OBGraphSymPrivate)
99 {
100 d->_pmol = pmol;
101 if (frag_atoms) {
102 d->_frag_atoms = *frag_atoms;
103 } else {
104 d->_frag_atoms.Resize(d->_pmol->NumAtoms());
105 FOR_ATOMS_OF_MOL(a, d->_pmol)
106 d->_frag_atoms.SetBitOn(a->GetIdx());
107 }
108 }
109
~OBGraphSym()110 OBGraphSym::~OBGraphSym()
111 {
112 delete d;
113 }
114
115 const unsigned int OBGraphSym::NoSymmetryClass = 0x7FFFFFFF;
116
117 /**
118 * Functions for use by the sort() method of a vector.
119 */
CompareUnsigned(const unsigned int & a,const unsigned int & b)120 inline bool CompareUnsigned(const unsigned int &a,const unsigned int &b)
121 {
122 return(a<b);
123 }
124
ComparePairFirst(const std::pair<OBAtom *,unsigned int> & a,const std::pair<OBAtom *,unsigned int> & b)125 inline bool ComparePairFirst(const std::pair<OBAtom*,unsigned int> &a,const std::pair<OBAtom*,unsigned int> &b)
126 {
127 return(a.first->GetIdx() < b.first->GetIdx());
128 }
129
ComparePairSecond(const std::pair<OBAtom *,unsigned int> & a,const std::pair<OBAtom *,unsigned int> & b)130 inline bool ComparePairSecond(const std::pair<OBAtom*,unsigned int> &a,const std::pair<OBAtom*,unsigned int> &b)
131 {
132 return(a.second < b.second);
133 }
134
135
136 /**
137 * Like OBAtom::GetHvyDegree(): Counts the number non-hydrogen
138 * neighbors, but doesn't count atoms not in the fragment.
139 */
GetHvyDegree(OBAtom * atom)140 unsigned int OBGraphSymPrivate::GetHvyDegree(OBAtom *atom)
141 {
142 unsigned int count = 0;
143 OBBond *bond;
144 OBAtom *nbr;
145
146 vector<OBBond*>::iterator bi;
147 for (bond = atom->BeginBond(bi); bond; bond = atom->NextBond(bi)) {
148 nbr = bond->GetNbrAtom(atom);
149 if (_frag_atoms.BitIsSet(nbr->GetIdx()) && nbr->GetAtomicNum() != OBElements::Hydrogen)
150 count++;
151 }
152
153 return(count);
154 }
155
156 /**
157 * Sums the bond order over the bonds from this atom to other atoms
158 * in the fragment. Single = 1, double = 2, triple = 3, aromatic = 1.6,
159 * but sum is rounded to nearest integer.
160 *
161 * This is used for fragment symmetry perception instead of the "implicit
162 * valence" used by the standard OpenBabel symmetry perception. It
163 * has the same effect, but we don't have to worry about hydrogen counts,
164 * EXCEPT for aromatic N, where the difference between n and [nH] is
165 * critical.
166 */
GetHvyBondSum(OBAtom * atom)167 unsigned int OBGraphSymPrivate::GetHvyBondSum(OBAtom *atom)
168 {
169 float count = 0.0f;
170 OBBond *bond;
171 OBAtom *nbr;
172
173 vector<OBBond*>::iterator bi;
174 for (bond = atom->BeginBond(bi); bond; bond = atom->NextBond(bi)) {
175 nbr = bond->GetNbrAtom(atom);
176 if (_frag_atoms.BitIsSet(nbr->GetIdx()) && nbr->GetAtomicNum() != OBElements::Hydrogen) {
177 if (bond->IsAromatic())
178 count += 1.6f;
179 else
180 count += (float)bond->GetBondOrder();
181 }
182 }
183 if (atom->GetAtomicNum() == 7 && atom->IsAromatic() && atom->GetTotalDegree() == 3) {
184 count += 1.0f; // [nH] - add another bond
185 }
186 return(int(count + 0.5)); // round to nearest int
187 }
188
189 /**
190 * Calculates the graph theoretical distance of each atom.
191 * Vector is indexed from zero.
192 *
193 * NOTE: "Indexed from zero" means it's one off from the atom->GetIdx()
194 * that's used to index atoms inside the molecule!
195 *
196 * NOTE: This function is hard to decipher, and seems to be misnamed.
197 * A "distance" should be be between two atoms, but there's more here
198 * than that. It seems to be doing a breadth-first search to find the
199 * most-distant atom from each atom, and reporting the number of steps
200 * (which happens to be the graph-theoretical distance) to that atom.
201 * The name "Graph Theoretical Distance" is thus misleading.
202 */
GetGTDVector(vector<int> & gtd)203 bool OBGraphSymPrivate::GetGTDVector(vector<int> >d)
204 {
205 gtd.clear();
206 gtd.resize(_pmol->NumAtoms());
207
208 int gtdcount, natom;
209 OBBitVec used, curr, next;
210 OBAtom *atom, *atom1;
211 OBBond *bond;
212 vector<OBNodeBase*>::iterator ai;
213 vector<OBBond*>::iterator j;
214
215 next.Clear();
216
217 for (atom = _pmol->BeginAtom(ai); atom; atom = _pmol->NextAtom(ai)) {
218
219 int idx = atom->GetIdx();
220 if (!_frag_atoms.BitIsSet(idx)) { // Not in this fragment?
221 gtd[idx-1] = OBGraphSym::NoSymmetryClass;
222 continue;
223 }
224
225 gtdcount = 0;
226 used.Clear();curr.Clear();
227 used.SetBitOn(idx);
228 curr.SetBitOn(idx);
229
230 while (!curr.IsEmpty()) {
231 next.Clear();
232 for (natom = curr.NextBit(-1);natom != curr.EndBit();natom = curr.NextBit(natom)) {
233 atom1 = _pmol->GetAtom(natom);
234 if (!_frag_atoms.BitIsSet(atom1->GetIdx()))
235 continue;
236 for (bond = atom1->BeginBond(j);bond;bond = atom1->NextBond(j)) {
237 int nbr_idx = bond->GetNbrAtomIdx(atom1);
238 if ( _frag_atoms.BitIsSet(nbr_idx)
239 && !used.BitIsSet(nbr_idx)
240 && !curr.BitIsSet(nbr_idx)
241 && bond->GetNbrAtom(atom1)->GetAtomicNum() != OBElements::Hydrogen)
242 next.SetBitOn(nbr_idx);
243 }
244 }
245 used |= next;
246 curr = next;
247 gtdcount++;
248 }
249 gtd[idx-1] = gtdcount;
250 }
251
252 return(true);
253 }
254
255 /**
256 * Finds all atoms that are part of a ring in the current fragment.
257 * We start with the whole molecule's rings, and eliminate any that
258 * have atoms not in the subset. For the rings that are left, mark
259 * each atom of the ring as a ring atom.
260 *
261 * @return A bit vector where TRUE means it's a ring atom.
262 */
FindRingAtoms(OBBitVec & ring_atoms)263 void OBGraphSymPrivate::FindRingAtoms(OBBitVec &ring_atoms)
264 {
265 vector<OBRing*> sssRings;
266 vector<OBRing*>::iterator ri;
267
268 ring_atoms.Resize(_pmol->NumAtoms());
269 ring_atoms.Clear();
270
271 sssRings = _pmol->GetSSSR();
272 for (ri = sssRings.begin(); ri != sssRings.end(); ++ri) {
273 OBRing *ring = *ri;
274 OBBitVec bvtmp = _frag_atoms & ring->_pathset; // intersection: fragment and ring
275 if (bvtmp == ring->_pathset) // all ring atoms in fragment?
276 ring_atoms |= ring->_pathset; // yes - add this ring's atoms
277 }
278 }
279
280 /**
281 * Calculates a set of graph invariant indexes using the graph theoretical
282 * distance, number of connected heavy atoms, aromatic boolean, ring
283 * boolean, atomic number, and summation of bond orders connected to the
284 * atom.
285 *
286 * We have to recalculate which atoms are in rings by taking the fragment's
287 * atoms into account when we generate the graph invarients.
288 *
289 * Vector is indexed from zero (not one, like atom->GetIdx()).
290 *
291 * NOTE: This may need to be extended to include the bond-invariant properties,
292 * particularly the size of all rings the bond is in (from a SSSR).
293 */
GetGIVector(vector<unsigned int> & vid)294 void OBGraphSymPrivate::GetGIVector(vector<unsigned int> &vid)
295 {
296 // Prepare the vector...
297 vid.clear();
298 vid.resize(_pmol->NumAtoms());
299
300 // The "graph theoretical distance" for each atom (see comments in the function)
301 vector<int> v;
302 GetGTDVector(v);
303
304 // Compute the ring atoms for this particular fragment (set of atoms)
305 OBBitVec ring_atoms;
306 FindRingAtoms(ring_atoms);
307
308 int i;
309 OBAtom *atom;
310 vector<OBNodeBase*>::iterator ai;
311 for (i=0, atom = _pmol->BeginAtom(ai); atom; atom = _pmol->NextAtom(ai)) {
312 // vid[i] = 0;
313 vid[i] = OBGraphSym::NoSymmetryClass;
314 if (_frag_atoms.BitIsSet(atom->GetIdx())) {
315 vid[i] =
316 v[i] // 10 bits: graph-theoretical distance
317 | (GetHvyDegree(atom) <<10) // 4 bits: heavy valence
318 | (((atom->IsAromatic()) ? 1 : 0) <<14) // 1 bit: aromaticity
319 | (((ring_atoms.BitIsSet(atom->GetIdx())) ? 1 : 0)<<15) // 1 bit: ring atom
320 | (atom->GetAtomicNum() <<16) // 7 bits: atomic number
321 | (GetHvyBondSum(atom) <<23) // 4 bits: heavy bond sum
322 | ((7 + atom->GetFormalCharge()) <<27); // 4 bits: formal charge
323 }
324 i++;
325 }
326 }
327
328 /**
329 * Creates a new vector of symmetry classes based on an existing
330 * vector. (Helper routine to GetGIDVector.) On return, vp2 will
331 * have newly-extended connectivity sums, but the numbers (the class
332 * IDs) are very large.
333 *
334 * (Comments by CJ) This appears to compute the "extended connectivity
335 * sums" similar to those described by Weininger, Morgan, etc. It uses
336 * vp1 as its starting point (the current connectivity sums), and puts
337 * the new sums in vp2. Note that vp1 is modified along the way.
338 *
339 * Note that, per Weininger's warning, this assumes the initial class
340 * ID's are less than 100, which is a BAD assumption, e.g. OCC...CCN
341 * would have more than 100 symmetry classes if the chain is more than
342 * 98 carbons long. Should change this to use Weininger's product of
343 * corresponding primes.
344 */
CreateNewClassVector(std::vector<std::pair<OBAtom *,unsigned int>> & vp1,std::vector<std::pair<OBAtom *,unsigned int>> & vp2)345 void OBGraphSymPrivate::CreateNewClassVector(std::vector<std::pair<OBAtom*,unsigned int> > &vp1,
346 std::vector<std::pair<OBAtom*,unsigned int> > &vp2)
347 {
348 int m,id;
349 OBAtom *atom, *nbr;
350 vector<OBBond*>::iterator nbr_iter;
351 vector<unsigned int>::iterator k;
352 vector<pair<OBAtom*,unsigned int> >::iterator vp_iter;
353
354 #if DEBUG2
355 cout << "CreateNewClassVector: START\n";
356 //print_vector_pairs(" ", vp1);
357 #endif
358
359 // There may be fewer atoms than in the whole molecule, so we can't
360 // index the vp1 array by atom->GetIdx(). Instead, create a quick
361 // mapping vector of idx-to-index for vp1.
362 vector<int> idx2index(_pmol->NumAtoms() + 1, -1); // natoms + 1
363 int index = 0;
364 for (vp_iter = vp1.begin(); vp_iter != vp1.end(); ++vp_iter) {
365 int idx = vp_iter->first->GetIdx();
366 idx2index[idx] = index++;
367 }
368
369 // vp2 will hold the newly-extended symmetry classes
370 vp2.resize(vp1.size());
371 vp2.clear();
372
373 // Loop over original atoms.
374 // Create a new extended varient for each atom. Get its neighbors' class ID's,
375 // sort them into ascending order, and create a sum of (c0 + c1*10^2 + c2*10^4 + ...)
376 // which becomes the new class ID (where c0 is the current classID).
377
378 for (vp_iter = vp1.begin(); vp_iter != vp1.end(); ++vp_iter) {
379 atom = vp_iter->first;
380 id = vp_iter->second;
381 vector<unsigned int> vtmp;
382 for (nbr = atom->BeginNbrAtom(nbr_iter); nbr; nbr = atom->NextNbrAtom(nbr_iter)) {
383 int idx = nbr->GetIdx();
384 if (_frag_atoms.BitIsSet(idx))
385 vtmp.push_back(vp1[idx2index[idx]].second);
386 }
387
388 sort(vtmp.begin(),vtmp.end(),CompareUnsigned);
389 for (m = 100, k = vtmp.begin(); k != vtmp.end(); ++k, m*=100)
390 id += *k * m;
391 vp2.push_back(pair<OBAtom*,unsigned int> (atom, id));
392 }
393 #if DEBUG2
394 cout << "CreateNewClassVector: FINISH\n";
395 //print_vector_pairs(" ", vp2);
396 #endif
397 }
398
CreateNewClassVector(OBMol * mol,std::vector<std::pair<OBAtom *,unsigned int>> & vp1,std::vector<std::pair<OBAtom *,unsigned int>> & vp2)399 void OBGraphSymPrivate::CreateNewClassVector(OBMol *mol, std::vector<std::pair<OBAtom*,unsigned int> > &vp1,
400 std::vector<std::pair<OBAtom*,unsigned int> > &vp2)
401 {
402 int m,id;
403 OBAtom *atom, *nbr;
404 vector<OBBond*>::iterator nbr_iter;
405 vector<unsigned int>::iterator k;
406 vector<pair<OBAtom*,unsigned int> >::iterator vp_iter;
407
408 #if DEBUG2
409 cout << "CreateNewClassVector: START\n";
410 //print_vector_pairs(" ", vp1);
411 #endif
412
413 // There may be fewer atoms than in the whole molecule, so we can't
414 // index the vp1 array by atom->GetIdx(). Instead, create a quick
415 // mapping vector of idx-to-index for vp1.
416 vector<int> idx2index(mol->NumAtoms() + 1, -1); // natoms + 1
417 int index = 0;
418 for (vp_iter = vp1.begin(); vp_iter != vp1.end(); ++vp_iter) {
419 int idx = vp_iter->first->GetIdx();
420 idx2index[idx] = index++;
421 }
422
423 // vp2 will hold the newly-extended symmetry classes
424 vp2.resize(vp1.size());
425 vp2.clear();
426
427 // Loop over original atoms.
428 // Create a new extended varient for each atom. Get its neighbors' class ID's,
429 // sort them into ascending order, and create a sum of (c0 + c1*10^2 + c2*10^4 + ...)
430 // which becomes the new class ID (where c0 is the current classID).
431
432 for (vp_iter = vp1.begin(); vp_iter != vp1.end(); ++vp_iter) {
433 atom = vp_iter->first;
434 id = vp_iter->second;
435 vector<unsigned int> vtmp;
436 for (nbr = atom->BeginNbrAtom(nbr_iter); nbr; nbr = atom->NextNbrAtom(nbr_iter)) {
437 int idx = nbr->GetIdx();
438 vtmp.push_back(vp1[idx2index[idx]].second);
439 }
440
441 sort(vtmp.begin(),vtmp.end(),CompareUnsigned);
442 for (m = 100, k = vtmp.begin(); k != vtmp.end(); ++k, m*=100)
443 id += *k * m;
444 vp2.push_back(pair<OBAtom*,unsigned int> (atom, id));
445 }
446 #if DEBUG2
447 cout << "CreateNewClassVector: FINISH\n";
448 //print_vector_pairs(" ", vp2);
449 #endif
450
451 }
452
453 /**
454 * Counts the number of unique symmetry classes in a list.
455 *
456 * (NOTE: CJ -- It also appears to MODIFY the list. It sorts it in order
457 * of class ID, then renumbers the ID's zero through N-1. See the comments
458 * in CreateNewClassVector() about how it returns very large numbers for the
459 * class IDs it creates. These are replaced by lower, sequential numbers here.)
460 */
CountAndRenumberClasses(std::vector<std::pair<OBAtom *,unsigned int>> & vp,unsigned int & count)461 void OBGraphSymPrivate::CountAndRenumberClasses(std::vector<std::pair<OBAtom*,unsigned int> > &vp,
462 unsigned int &count)
463 {
464 count = 1;
465 vector<pair<OBAtom*,unsigned int> >::iterator k;
466
467 sort(vp.begin(), vp.end(), ComparePairSecond);
468 k = vp.begin();
469 if (k != vp.end()) {
470 unsigned int id = k->second;
471 if (id) {
472 k->second = 1;
473 ++k;
474 for (;k != vp.end(); ++k) {
475 if (k->second != id) {
476 id = k->second;
477 k->second = ++count;
478 } else {
479 k->second = count;
480 }
481 }
482 }
483 }
484 }
485
486 /**
487 * This is the core of symmetry analysis. Starting with a set of
488 * classes on each atom, it "spreads" them using a sum-of-invariants
489 * of each atom's class and its neighbors' classes. This iterates
490 * until a stable solution is found (further spreading doesn't
491 * change the answer).
492 *
493 * @return The number of distinct symmetry classes found.
494 */
ExtendInvariants(std::vector<std::pair<OBAtom *,unsigned int>> & symmetry_classes)495 int OBGraphSymPrivate::ExtendInvariants(std::vector<std::pair<OBAtom*, unsigned int> > &symmetry_classes)
496 {
497 unsigned int nclasses1, nclasses2;
498 vector<pair<OBAtom*,unsigned int> > tmp_classes;
499
500 // How many classes are we starting with? (The "renumber" part isn't relevant.)
501 CountAndRenumberClasses(symmetry_classes, nclasses1);
502
503 unsigned int nfragatoms = _frag_atoms.CountBits();
504
505 // LOOP: Do extended sum-of-invarients until no further changes are
506 // noted. (Note: This is inefficient, as it re-computes extended sums
507 // and re-sorts the entire list each time. You can save a lot of time by
508 // only recomputing and resorting within regions where there is a tie
509 // initially. But it's a lot more code.)
510
511 if (nclasses1 < nfragatoms) {
512 for (int i = 0; i < 100;i++) { //sanity check - shouldn't ever hit this number
513 CreateNewClassVector(symmetry_classes, tmp_classes);
514 CountAndRenumberClasses(tmp_classes, nclasses2);
515 symmetry_classes = tmp_classes;
516 if (nclasses1 == nclasses2) break;
517 nclasses1 = nclasses2;
518 }
519 }
520
521 CreateNewClassVector(symmetry_classes, tmp_classes);
522 CountAndRenumberClasses(tmp_classes, nclasses2);
523
524
525 if (nclasses1 != nclasses2) {
526 symmetry_classes = tmp_classes;
527 return ExtendInvariants(symmetry_classes);
528 }
529
530
531 return nclasses1;
532 }
533
534 /**
535 * Calculates a set of canonical symmetry identifiers for a molecule.
536 * Atoms with the same symmetry ID are symmetrically equivalent. By
537 * "canonical", we mean it generates a repeatable labelling of the
538 * atoms, i.e. the same fragment will get the same symmetry labels in
539 * any molecule in which it occurs.
540 *
541 * Vector is indexed from zero, corresponding to (atom->GetIdx() - 1).
542 *
543 * The bit vector "_frag_atoms" specifies a fragment of the molecule,
544 * where each bit represents the presence or absence of the atom in
545 * the fragment. Symmetry is computed as though the fragment is the
546 * only part that exists.
547 */
CalculateSymmetry(std::vector<unsigned int> & atom_sym_classes)548 int OBGraphSymPrivate::CalculateSymmetry(std::vector<unsigned int> &atom_sym_classes)
549 {
550 vector<unsigned int> vgi;
551 vector<OBNodeBase*>::iterator j;
552 OBAtom *atom;
553
554 // Get vector of graph invariants. These are the starting "symmetry classes".
555 GetGIVector(vgi);
556
557
558 // Create a vector-of-pairs, associating each atom with its Class ID.
559 std::vector<std::pair<OBAtom*, unsigned int> > symmetry_classes;
560 for (atom = _pmol->BeginAtom(j); atom; atom = _pmol->NextAtom(j)) {
561 int idx = atom->GetIdx();
562 if (_frag_atoms.BitIsSet(idx))
563 symmetry_classes.push_back(pair<OBAtom*, unsigned int> (atom, vgi[idx-1]));
564 //else
565 // symmetry_classes.push_back(pair<OBAtom*, unsigned int> (atom, OBGraphSym::NoSymmetryClass));
566 }
567
568
569 // The heart of the matter: Do extended sum-of-invariants until no further
570 // changes are noted.
571 int nclasses = ExtendInvariants(symmetry_classes);
572
573 // Convert to a vector indexed by Index
574 // Atoms not in the fragment will have a value of OBGraphSym::NoSymmetryClass
575 atom_sym_classes.clear();
576 atom_sym_classes.resize(_pmol->NumAtoms(), OBGraphSym::NoSymmetryClass);
577 for (unsigned int i = 0; i < symmetry_classes.size(); ++i) {
578 atom_sym_classes[symmetry_classes.at(i).first->GetIndex()] = symmetry_classes.at(i).second;
579 }
580
581 // Store the symmetry classes in an OBPairData
582 stringstream temp;
583 vector<unsigned int>::iterator sym_iter = atom_sym_classes.begin();
584 if (sym_iter != atom_sym_classes.end())
585 temp << (*sym_iter++);
586 for (; sym_iter != atom_sym_classes.end(); ++sym_iter)
587 temp << " " << (*sym_iter);
588
589 OBPairData *symData = new OBPairData;
590 symData->SetAttribute("OpenBabel Symmetry Classes");
591 symData->SetOrigin(local); //will not show as sdf or cml property
592 symData->SetValue(temp.str());
593 _pmol->SetData(symData);
594
595 return nclasses;
596 }
597
GetSymmetry(std::vector<unsigned int> & symmetry_classes)598 int OBGraphSym::GetSymmetry(std::vector<unsigned int> &symmetry_classes)
599 {
600 ClearSymmetry(); // For the moment just recalculate the symmetry classes
601
602 // Check to see whether we have already calculated the symmetry classes
603 OBPairData *pd = dynamic_cast<OBPairData*>(d->_pmol->GetData("OpenBabel Symmetry Classes"));
604
605 int nclasses = 0;
606 if (!pd) {
607 nclasses = d->CalculateSymmetry(symmetry_classes);
608 } else {
609 istringstream iss(pd->GetValue());
610 symmetry_classes.clear();
611 copy(istream_iterator<unsigned int>(iss),
612 istream_iterator<unsigned int>(),
613 back_inserter<vector<unsigned int> >(symmetry_classes));
614 // Now find the number of unique elements
615 vector<unsigned int> copy_sym = symmetry_classes;
616 sort(copy_sym.begin(), copy_sym.end());
617 vector<unsigned int>::iterator end_pos = unique(copy_sym.begin(), copy_sym.end()); // Requires sorted elements
618 nclasses = end_pos - copy_sym.begin();
619 }
620
621 return nclasses;
622 }
623
Iterate(vector<unsigned int> & symClasses)624 int OBGraphSymPrivate::Iterate(vector<unsigned int> &symClasses)
625 {
626 // Create a vector-of-pairs, associating each atom with its Class ID.
627 vector<OBAtom*>::iterator j;
628 std::vector<std::pair<OBAtom*, unsigned int> > symmetry_classes;
629 for (OBAtom *atom = _pmol->BeginAtom(j); atom; atom = _pmol->NextAtom(j)) {
630 int idx = atom->GetIdx();
631 if (_frag_atoms.BitIsSet(idx))
632 symmetry_classes.push_back(pair<OBAtom*, unsigned int> (atom, symClasses[idx-1]));
633 }
634
635 // The heart of the matter: Do extended sum-of-invariants until no further
636 // changes are noted.
637 int nclasses = ExtendInvariants(symmetry_classes);
638
639 // Convert to a vector indexed by Index
640 // Atoms not in the fragment will have a value of OBGraphSym::NoSymmetryClass
641 symClasses.clear();
642 symClasses.resize(_pmol->NumAtoms(), OBGraphSym::NoSymmetryClass);
643 for (unsigned int i = 0; i < symmetry_classes.size(); ++i) {
644 symClasses[symmetry_classes.at(i).first->GetIndex()] = symmetry_classes.at(i).second;
645 }
646
647 return nclasses;
648 }
649
650 // Clears perceived symmetry
ClearSymmetry()651 void OBGraphSym::ClearSymmetry()
652 {
653 d->_pmol->DeleteData("OpenBabel Symmetry Classes");
654 }
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671 } // namespace OpenBabel
672
673 //! \file graphsym.cpp
674 //! \brief Handle and perceive graph symmtery for canonical numbering.
675