Coverage Report

Created: 2026-08-13 07:16

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openbabel/src/stereo/perception.cpp
Line
Count
Source
1
/**********************************************************************
2
  perception.cpp - Stereochemistry perception
3
4
  Copyright (C) 2009-2010 by Tim Vandermeersch
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; either version 2 of the License, or
12
  (at your option) any later version.
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
26
#include <openbabel/stereo/tetrahedral.h>
27
#include <openbabel/stereo/cistrans.h>
28
#include <openbabel/mol.h>
29
#include <openbabel/atom.h>
30
#include <openbabel/bond.h>
31
#include <openbabel/ring.h>
32
#include <openbabel/obutil.h>
33
#include <openbabel/obiter.h>
34
#include <openbabel/generic.h>
35
#include <openbabel/graphsym.h>
36
#include <openbabel/math/matrix3x3.h>
37
#include <openbabel/canon.h>
38
#include <openbabel/oberror.h>
39
#include <openbabel/elements.h>
40
#include <cassert>
41
42
#include "stereoutil.h"
43
44
#include <cmath>
45
#include <limits>
46
#include <set>
47
#include <iterator>
48
#include <functional>
49
50
0
#define DEBUG 0
51
0
#define DEBUG_INVERSIONS 0
52
0
#define IMPLICIT_CIS_RING_SIZE 8
53
0
#define DELTA_ANGLE_FOR_OVERLAPPING_BONDS 4.0 // In degrees
54
55
using namespace std;
56
57
// debug function
58
template<typename T>
59
void print_vector(const std::string &label, const std::vector<T> &v)
60
0
{
61
0
  std::cout << label << ": ";
62
0
  for (std::size_t i = 0; i < v.size(); ++i)
63
0
    std::cout << v[i] << " ";
64
0
  std::cout << endl;
65
0
}
Unexecuted instantiation: void print_vector<unsigned int>(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::vector<unsigned int, std::__1::allocator<unsigned int> > const&)
Unexecuted instantiation: void print_vector<unsigned long>(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::vector<unsigned long, std::__1::allocator<unsigned long> > const&)
66
67
namespace OpenBabel {
68
69
  OBAtom* findAtomWithSymmetryClass(OBAtom *atom, unsigned int symClass, const std::vector<unsigned int> &symClasses);
70
  bool containsAtLeast_1true_2para(OBAtom *ligandAtom, OBAtom *atom, const OBStereoUnitSet &units);
71
  bool containsAtLeast_2true_2paraAssemblies(OBAtom *ligandAtom, OBAtom *atom, const OBStereoUnitSet &units, const std::vector<OBBitVec> &mergedRings);
72
73
  //////////////////////////////////////////////////////////////////////////////
74
  //
75
  //  General
76
  //
77
  //////////////////////////////////////////////////////////////////////////////
78
79
  void PerceiveStereo(OBMol *mol, bool force)
80
0
  {
81
0
    switch (mol->GetDimension()) {
82
0
      case 3:
83
0
        StereoFrom3D(mol, force);
84
0
        break;
85
0
      case 2:
86
0
        StereoFrom2D(mol, nullptr, force);
87
0
        break;
88
0
      default:
89
0
        StereoFrom0D(mol);
90
0
        break;
91
0
    }
92
0
    if (obErrorLog.GetOutputLevel() >= obAuditMsg)
93
0
      obErrorLog.ThrowError(__FUNCTION__, "Ran OpenBabel::PerceiveStereo", obAuditMsg);
94
0
  }
95
96
  /**
97
   * Perform a quick check for tetrahedral stereo centers. Used by
98
   * FindStereogenicUnits to return quickly if there are no stereogenic units.
99
   */
100
  bool mayHaveTetrahedralCenter(OBMol *mol)
101
0
  {
102
0
    std::vector<OBAtom*>::iterator ia;
103
0
    for (OBAtom *atom = mol->BeginAtom(ia); atom; atom = mol->NextAtom(ia))
104
0
      if (atom->GetHyb() == 3 && atom->GetHvyDegree() >= 3) {
105
0
        return true;
106
0
      }
107
0
    return false;
108
0
  }
109
110
  /**
111
   * Perform a quick check for stereogenic bonds. Used by FindStereogenicUnits
112
   * to return quickly if there are no stereogenic units.
113
   */
114
  bool mayHaveCisTransBond(OBMol *mol)
115
0
  {
116
0
    std::vector<OBBond*>::iterator ib;
117
0
    for (OBBond *bond = mol->BeginBond(ib); bond; bond = mol->NextBond(ib))
118
0
      if (bond->GetBondOrder() == 2) {
119
0
        return true;
120
0
      }
121
0
    return false;
122
0
  }
123
124
  /**
125
   * Check if the specified atom is a potential stereogenic atom.
126
   *
127
   * Criteria:
128
   * - sp3 hybridization (or P and sp3d hybridization)
129
   * - not connected to more than 4 atoms
130
   * - at least 3 "heavy" neighbors
131
   *
132
   * Nitrogen (neutral) is treated as a special case since the barrier of inversion is
133
   * low in many cases making the atom non-stereogenic. Only bridge-head
134
   * nitrogen atoms (i.e. nitrogen has 3 neighbors in rings) will be
135
   * considered stereogenic.
136
   */
137
  bool isPotentialTetrahedral(OBAtom *atom)
138
0
  {
139
    // consider only potential steroecenters
140
0
    if ((atom->GetHyb() != 3 && !(atom->GetHyb() == 5 && atom->GetAtomicNum() == OBElements::Phosphorus))
141
0
        || atom->GetTotalDegree() > 4 || atom->GetHvyDegree() < 3 || atom->GetHvyDegree() > 4)
142
0
      return false;
143
    // skip non-chiral N
144
0
    if (atom->GetAtomicNum() == OBElements::Nitrogen && atom->GetFormalCharge()==0) {
145
0
      int nbrRingAtomCount = 0;
146
0
      FOR_NBORS_OF_ATOM (nbr, atom) {
147
0
        if (nbr->IsInRing())
148
0
          nbrRingAtomCount++;
149
0
      }
150
0
      if (nbrRingAtomCount < 3)
151
0
        return false;
152
0
    }
153
0
    if (atom->GetAtomicNum() == OBElements::Carbon) {
154
0
      if (atom->GetFormalCharge())
155
0
        return false;
156
0
      FOR_NBORS_OF_ATOM (nbr, atom) {
157
0
        if (nbr->GetAtomicNum() == 26 && nbr->GetExplicitDegree() > 7)
158
0
          return false;
159
0
      }
160
0
    }
161
162
0
    return true;
163
0
  }
164
165
  /**
166
   * Check if the specified bond is a potential stereogenic bond.
167
   *
168
   * Criteria:
169
   * - must be a double bond
170
   * - must not be in a ring
171
   * - both begin and end atom should have at least one single bond
172
   */
173
  bool isPotentialCisTrans(OBBond *bond)
174
0
  {
175
0
    if (bond->GetBondOrder() != 2)
176
0
      return false;
177
0
    if (bond->IsInRing())
178
0
      return false;
179
0
    if (!bond->GetBeginAtom()->HasSingleBond() || !bond->GetEndAtom()->HasSingleBond())
180
0
      return false;
181
0
    if (bond->GetBeginAtom()->GetHvyDegree() == 1 || bond->GetEndAtom()->GetHvyDegree() == 1)
182
0
      return false;
183
0
    if (bond->GetBeginAtom()->GetHvyDegree() > 3 || bond->GetEndAtom()->GetHvyDegree() > 3)
184
0
      return false;
185
0
    return true;
186
0
  }
187
188
189
190
191
192
193
194
195
196
197
  ////////////////////////////////////////////////////////////////////////////////
198
199
200
  /**
201
   * Check if the specified stereogenic unit is in a fragment.
202
   */
203
  bool isUnitInFragment(OBMol *mol, const OBStereoUnit &unit, const OBBitVec &fragment)
204
0
  {
205
0
    if (unit.type == OBStereo::Tetrahedral) {
206
0
      if (fragment.BitIsSet(unit.id))
207
0
        return true;
208
0
    } else if(unit.type == OBStereo::CisTrans) {
209
0
      OBBond *bond = mol->GetBondById(unit.id);
210
0
      OBAtom *begin = bond->GetBeginAtom();
211
0
      OBAtom *end = bond->GetEndAtom();
212
0
      if (fragment.BitIsSet(begin->GetId()) || fragment.BitIsSet(end->GetId()))
213
0
        return true;
214
0
    }
215
0
    return false;
216
0
  }
217
218
219
220
221
222
223
224
225
226
  //////////////////////////////////////////////////////////////////////////////////
227
228
229
230
231
232
  /**
233
   * Check if the specified atom is a tetrahedral center (i.e. there is a Tetrahedral
234
   * OBStereoUnit in units with the same id)
235
   */
236
  bool isTetrahedral(OBAtom *atom, const OBStereoUnitSet &units)
237
0
  {
238
0
    for (std::size_t i = 0; i < units.size(); ++i) {
239
0
      const OBStereoUnit &unit = units[i];
240
0
      if (unit.type != OBStereo::Tetrahedral)
241
0
        continue;
242
0
      if (unit.id == atom->GetId())
243
0
        return true;
244
0
    }
245
0
    return false;
246
0
  }
247
248
  /**
249
   * Check if the specified bond is a double bond stereocenter (i.e. there is a CisTrans
250
   * OBStereoUnit in units with the same id)
251
   */
252
  bool isCisTrans(OBBond *bond, const OBStereoUnitSet &units)
253
0
  {
254
0
    for (std::size_t i = 0; i < units.size(); ++i) {
255
0
      const OBStereoUnit &unit = units[i];
256
0
      if (unit.type != OBStereo::CisTrans)
257
0
        continue;
258
0
      if (unit.id == bond->GetId())
259
0
        return true;
260
0
    }
261
0
    return false;
262
0
  }
263
264
265
  /**
266
   * Classify the tetrahedral atom using the NeighborSymmetryClasses types.
267
   */
268
  int classifyTetrahedralNbrSymClasses(const std::vector<unsigned int> &symClasses, OBAtom *atom)
269
0
  {
270
0
    std::vector<unsigned int> nbrClasses, nbrClassesCopy, uniqueClasses;
271
0
    FOR_NBORS_OF_ATOM (nbr, atom)
272
0
      nbrClasses.push_back(symClasses.at(nbr->GetIndex()));
273
    // add an implicit ref if there are only 3 explicit
274
0
    if (nbrClasses.size() == 3)
275
0
      nbrClasses.push_back(OBStereo::ImplicitRef);
276
277
    // use some STL to work out the number of unique classes
278
0
    nbrClassesCopy = nbrClasses; // keep copy for count below
279
0
    std::sort(nbrClasses.begin(), nbrClasses.end());
280
0
    std::vector<unsigned int>::iterator endLoc = std::unique(nbrClasses.begin(), nbrClasses.end());
281
0
    std::copy(nbrClasses.begin(), endLoc, std::back_inserter(uniqueClasses));
282
283
0
    switch (uniqueClasses.size()) {
284
0
      case 4:
285
0
        return T1234; // e.g. 1 2 3 4
286
0
      case 3:
287
0
        return T1123; // e.g. 1 1 2 3
288
0
      case 2:
289
        // differentiate between T1122 and T1112
290
0
        if (std::count(nbrClassesCopy.begin(), nbrClassesCopy.end(), uniqueClasses.at(0)) == 2)
291
0
          return T1122; // e.g. 1 1 2 2
292
0
        else
293
0
          return T1112; // e.g. 1 1 1 2
294
0
      case 1:
295
0
    default:
296
0
        return T1111; // e.g. 1 1 1 1
297
0
    }
298
0
  }
299
300
  /**
301
   * Classify the cis/trans bond using the NeighborSymmetryClasses types.
302
   */
303
  int classifyCisTransNbrSymClasses(const std::vector<unsigned int> &symClasses, OBBond *doubleBond, OBAtom *atom)
304
0
  {
305
0
    std::vector<unsigned int> nbrClasses, uniqueClasses;
306
0
    FOR_NBORS_OF_ATOM (nbr, atom) {
307
0
      if (nbr->GetIdx() != doubleBond->GetNbrAtom(atom)->GetIdx())
308
0
        nbrClasses.push_back(symClasses.at(nbr->GetIndex()));
309
0
    }
310
311
0
    if (nbrClasses.size() == 1)
312
0
      nbrClasses.push_back(OBStereo::ImplicitRef);
313
314
0
    if (nbrClasses.at(0) == nbrClasses.at(1))
315
0
      return C11; // e.g. 1 1
316
0
    else
317
0
      return C12; // e.g. 1 2
318
0
  }
319
320
  /**
321
   * Merge the rings in a molecule and return the result as OBBitVec objects.
322
   * Rings are merged if they share at least one atom (e.g. bridged, spiro,
323
   * adjacent, ...).
324
   */
325
  std::vector<OBBitVec> mergeRings(OBMol *mol, const std::vector<unsigned int> &symClasses)
326
0
  {
327
0
    std::vector<OBRing*> rings = mol->GetSSSR();
328
329
0
    std::vector<OBBitVec> result;
330
0
    for (std::size_t i = 0; i < rings.size(); ++i) {
331
      // check if ring shares atom with previously found ring
332
0
      bool found = false;
333
0
      for (std::size_t j = 0; j < result.size(); ++j) {
334
0
        std::vector<unsigned int> shared;
335
        // foreach ring atom
336
0
        for (std::size_t k = 0; k < rings[i]->_path.size(); ++k) {
337
          // check if the ring atom is in the current result bitvec
338
0
          if (result[j].BitIsSet(rings[i]->_path[k])) {
339
0
            shared.push_back(rings[i]->_path[k]);
340
0
          }
341
0
        }
342
343
0
        if (shared.size() > 1) {
344
0
          found = true;
345
0
        } else if (shared.size() == 1) {
346
0
          int classification = classifyTetrahedralNbrSymClasses(symClasses, mol->GetAtom(shared[0]));
347
0
          if (classification == T1122 || classification == T1111)
348
0
            found = true;
349
0
        }
350
351
0
        if (found) {
352
          // add bits for the atoms in the ring
353
0
          for (std::size_t l = 0; l < rings[i]->_path.size(); ++l)
354
0
            result[j].SetBitOn(rings[i]->_path[l]);
355
0
          break;
356
0
        }
357
0
      }
358
359
      // add the ring as a new bitvec if it shares no atom with a previous ring
360
0
      if (!found) {
361
0
        OBBitVec r;
362
0
        for (std::size_t l = 0; l < rings[i]->_path.size(); ++l)
363
0
          r.SetBitOn(rings[i]->_path[l]);
364
0
        result.push_back(r);
365
0
      }
366
0
    }
367
368
0
    return result;
369
0
  }
370
371
  /*
372
  bool isInSameMergedRing(const std::vector<OBBitVec> &mergedRings, unsigned int idx1, unsigned int idx2)
373
  {
374
    std::vector<OBBitVec>::const_iterator bits;
375
    for (bits = mergedRings.begin(); bits != mergedRings.end(); ++bits)
376
      if ((*bits).BitIsSet( idx1 ) && (*bits).BitIsSet( idx2 ))
377
        return true;
378
    return false;
379
  }
380
  */
381
382
  /**
383
   * Helper function for getFragment below.
384
   */
385
  void addNbrs(OBBitVec &fragment, OBAtom *atom, OBAtom *skip)
386
0
  {
387
0
    FOR_NBORS_OF_ATOM (nbr, atom) {
388
      // don't pass through skip
389
0
      if (nbr->GetId() == skip->GetId())
390
0
        continue;
391
      // skip visited atoms
392
0
      if (fragment.BitIsSet(nbr->GetId()))
393
0
        continue;
394
      // add the neighbor atom to the fragment
395
0
      fragment.SetBitOn(nbr->GetId());
396
      // recurse...
397
0
      addNbrs(fragment, &*nbr, skip);
398
0
    }
399
0
  }
400
401
  /**
402
   * Create an OBBitVec objects with bets set for the fragment consisting of all
403
   * atoms for which there is a path to atom without going through skip. These
404
   * fragment bitvecs are indexed by unique id (i.e. OBAtom::GetId()).
405
   */
406
  OBBitVec getFragment(OBAtom *atom, OBAtom *skip)
407
0
  {
408
0
    OBBitVec fragment;
409
0
    fragment.SetBitOn(atom->GetId());
410
    // start the recursion
411
0
    addNbrs(fragment, atom, skip);
412
0
    return fragment;
413
0
  }
414
415
416
  struct StereoRing
417
  {
418
    struct ParaAtom
419
    {
420
      typedef OBAtom CenterType;
421
422
0
      ParaAtom(unsigned long _id, unsigned int idx) : id(_id), inIdx(idx) {}
423
0
      OBAtom* GetCenter(OBMol *mol) const { return mol->GetAtomById(id); }
424
      bool isInRing(const StereoRing &ring) const
425
0
      {
426
0
        for (std::size_t i = 0; i < ring.paraAtoms.size(); ++i)
427
0
          if (ring.paraAtoms[i].inIdx == inIdx)
428
0
            return true;
429
0
        return false;
430
0
      }
431
432
      unsigned long id;
433
      union {
434
        unsigned int inIdx, outIdx;
435
      };
436
      std::vector<OBAtom*> insideNbrs, outsideNbrs;
437
    };
438
    struct ParaBond
439
    {
440
      typedef OBBond CenterType;
441
0
      ParaBond(unsigned long _id, unsigned int _inIdx, unsigned int _outIdx) : id(_id), inIdx(_inIdx), outIdx(_outIdx) {}
442
0
      OBBond* GetCenter(OBMol *mol) const { return mol->GetBondById(id); }
443
      bool isInRing(const StereoRing &ring) const
444
0
      {
445
0
        for (std::size_t i = 0; i < ring.paraBonds.size(); ++i)
446
0
          if (ring.paraBonds[i].inIdx == inIdx)
447
0
            return true;
448
0
        return false;
449
0
      }
450
451
      unsigned long id;
452
      unsigned int inIdx, outIdx;
453
      std::vector<OBAtom*> insideNbrs, outsideNbrs;
454
    };
455
456
0
    StereoRing() : trueCount(0) {}
457
458
    std::vector<ParaAtom> paraAtoms;
459
    std::vector<ParaBond> paraBonds;
460
    unsigned int trueCount;
461
  };
462
463
  template<typename Type>
464
  bool checkLigands(const Type &currentPara, const OBStereoUnitSet &units)
465
0
  {
466
0
    if (currentPara.outsideNbrs.size() == 1) {
467
      //cout << "OK: " << __LINE__ << endl;
468
0
      return true;
469
0
    }
470
0
    OBMol *mol = currentPara.insideNbrs[0]->GetParent();
471
0
    assert(mol->GetAtom(currentPara.outIdx));
472
0
    OBBitVec ligand = getFragment(currentPara.outsideNbrs[0], mol->GetAtom(currentPara.outIdx));
473
0
    for (OBStereoUnitSet::const_iterator u2 = units.begin(); u2 != units.end(); ++u2) {
474
0
      if (isUnitInFragment(mol, *u2, ligand)) {
475
        //cout << "OK: " << __LINE__ << endl;
476
0
        return true;
477
0
      }
478
0
    }
479
    //cout << "NOT OK: " << __LINE__ << endl;
480
0
    return false;
481
0
  }
Unexecuted instantiation: bool OpenBabel::checkLigands<OpenBabel::StereoRing::ParaAtom>(OpenBabel::StereoRing::ParaAtom const&, std::__1::vector<OpenBabel::OBStereoUnit, std::__1::allocator<OpenBabel::OBStereoUnit> > const&)
Unexecuted instantiation: bool OpenBabel::checkLigands<OpenBabel::StereoRing::ParaBond>(OpenBabel::StereoRing::ParaBond const&, std::__1::vector<OpenBabel::OBStereoUnit, std::__1::allocator<OpenBabel::OBStereoUnit> > const&)
482
483
484
  template<typename Type>
485
  bool ApplyRule1(const Type &currentPara, const std::vector<unsigned int> &symmetry_classes,
486
      const std::vector<StereoRing> &rings, std::vector<bool> &visitedRings, const OBStereoUnitSet &units,
487
      std::vector<unsigned int> stereoAtoms)
488
0
  {
489
0
    bool foundRing = false;
490
0
    unsigned int idx = currentPara.inIdx;
491
492
    /*
493
    for (std::size_t i = 0; i < visitedRings.size(); ++i)
494
      if (visitedRings[i])
495
        cout << "  ";
496
    cout << "ApplyRule1(" << currentPara.inIdx << ", " << currentPara.outIdx << ", outside = " << currentPara.outsideNbrs.size() << ")" << endl;
497
    */
498
499
0
    for (std::size_t i = 0; i < rings.size(); ++i) {
500
      // skip visited rings
501
0
      if (visitedRings[i])
502
0
        continue;
503
504
      // Check if currentPara is in this ring
505
0
      if (!currentPara.isInRing(rings[i]))
506
0
        continue;
507
508
      //
509
      // A new ring containing currentPara is found
510
      //
511
0
      foundRing = true;
512
513
      // if there are one or more true stereo centers, currentPara is a stereo center
514
0
      if (rings[i].trueCount) {
515
        //cout << "OK: " << __LINE__ << endl;
516
0
        return true;
517
0
      }
518
519
      // check if there is at least one other potential atom
520
0
      for (std::size_t j = 0; j < rings[i].paraAtoms.size(); ++j) {
521
0
        const StereoRing::ParaAtom &paraAtom = rings[i].paraAtoms[j];
522
        // skip idx
523
0
        if (paraAtom.inIdx == idx)
524
0
          continue;
525
        // there is another atom already identified as stereo atom
526
0
        if (std::find(stereoAtoms.begin(), stereoAtoms.end(), paraAtom.inIdx) != stereoAtoms.end()) {
527
          //cout << "OK: " << __LINE__ << endl;
528
0
          return true;
529
0
        }
530
531
0
        if (paraAtom.outsideNbrs.size() == 1) {
532
          // only 1 ring substituent, the other is implicit H -> topologically different
533
          //cout << "OK: " << __LINE__ << endl;
534
0
          return true;
535
0
        } else {
536
0
          if (paraAtom.outsideNbrs.size() != 2)
537
0
            return false;
538
          // two ring substituents, need to check for topological difference
539
0
          if (symmetry_classes[paraAtom.outsideNbrs[0]->GetIndex()] != symmetry_classes[paraAtom.outsideNbrs[1]->GetIndex()]) {
540
            // they are different
541
            //cout << "OK: " << __LINE__ << endl;
542
0
            return true;
543
0
          } else {
544
            // they are the same and they might also be in a ring -> apply rule 1 recursive
545
0
            visitedRings[i] = true;
546
0
            if (ApplyRule1(paraAtom, symmetry_classes, rings, visitedRings, units, stereoAtoms)) {
547
              //cout << "OK: " << __LINE__ << endl;
548
0
              return true;
549
0
            }
550
0
          }
551
0
        }
552
0
      }
553
      // check if there is at least one other potential bond
554
0
      for (std::size_t j = 0; j < rings[i].paraBonds.size(); ++j) {
555
0
        const StereoRing::ParaBond &paraBond = rings[i].paraBonds[j];
556
        // skip idx
557
0
        if (paraBond.inIdx == idx)
558
0
          continue;
559
        // there is another atom already identified as stereo atom
560
0
        if (std::find(stereoAtoms.begin(), stereoAtoms.end(), paraBond.inIdx) != stereoAtoms.end()) {
561
          //cout << "OK: " << __LINE__ << endl;
562
0
          return true;
563
0
        }
564
565
0
        if (paraBond.outsideNbrs.size() == 1) {
566
          // only 1 ring substituent, the other is implicit H -> topologically different
567
          //cout << "OK: " << __LINE__ << endl;
568
0
          return true;
569
0
        } else {
570
0
          if (paraBond.outsideNbrs.size() != 2)
571
0
            continue;
572
          // two ring substituents, need to check for topological difference
573
0
          if (symmetry_classes[paraBond.outsideNbrs[0]->GetIndex()] != symmetry_classes[paraBond.outsideNbrs[1]->GetIndex()]) {
574
            // they are different
575
            //cout << "OK: " << __LINE__ << endl;
576
0
            return true;
577
0
          } else {
578
            // they are the same and they might also be in a ring -> apply rule 1 recursive
579
0
            visitedRings[i] = true;
580
0
            if (ApplyRule1(paraBond, symmetry_classes, rings, visitedRings, units, stereoAtoms)) {
581
              //cout << "OK: " << __LINE__ << endl;
582
0
              return true;
583
0
            }
584
0
          }
585
0
        }
586
0
      }
587
588
0
    }
589
590
    // if a non-visited ring was found and true was not returned -> it does not
591
    // contain any stereocenters other than idx
592
0
    if (foundRing) {
593
      //cout << "NOT OK: " << __LINE__ << endl;
594
0
      return false;
595
0
    }
596
597
    //cout << "NOT OK: " << __LINE__ << endl;
598
0
    return false;
599
0
  }
Unexecuted instantiation: bool OpenBabel::ApplyRule1<OpenBabel::StereoRing::ParaAtom>(OpenBabel::StereoRing::ParaAtom const&, std::__1::vector<unsigned int, std::__1::allocator<unsigned int> > const&, std::__1::vector<OpenBabel::StereoRing, std::__1::allocator<OpenBabel::StereoRing> > const&, std::__1::vector<bool, std::__1::allocator<bool> >&, std::__1::vector<OpenBabel::OBStereoUnit, std::__1::allocator<OpenBabel::OBStereoUnit> > const&, std::__1::vector<unsigned int, std::__1::allocator<unsigned int> >)
Unexecuted instantiation: bool OpenBabel::ApplyRule1<OpenBabel::StereoRing::ParaBond>(OpenBabel::StereoRing::ParaBond const&, std::__1::vector<unsigned int, std::__1::allocator<unsigned int> > const&, std::__1::vector<OpenBabel::StereoRing, std::__1::allocator<OpenBabel::StereoRing> > const&, std::__1::vector<bool, std::__1::allocator<bool> >&, std::__1::vector<OpenBabel::OBStereoUnit, std::__1::allocator<OpenBabel::OBStereoUnit> > const&, std::__1::vector<unsigned int, std::__1::allocator<unsigned int> >)
600
601
  void StartRule1(const std::vector<unsigned int> &symmetry_classes, const std::vector<StereoRing> &rings,
602
      OBStereoUnitSet &units, std::vector<unsigned int> &stereoAtoms)
603
0
  {
604
0
    for (std::size_t i = 0; i < rings.size(); ++i) {
605
      //cout << "Checking ring: " << i << endl;
606
607
      // tetrahedral atoms
608
0
      for (std::size_t j = 0; j < rings[i].paraAtoms.size(); ++j) {
609
0
        const StereoRing::ParaAtom &paraAtom = rings[i].paraAtoms[j];
610
        // skip the atom if it is already in stereoAtoms
611
0
        if (std::find(stereoAtoms.begin(), stereoAtoms.end(), paraAtom.inIdx) != stereoAtoms.end())
612
0
          continue;
613
614
0
        std::vector<bool> visitedRings(rings.size(), false);
615
        //visitedRings[i] = true;
616
0
        if (ApplyRule1(paraAtom, symmetry_classes, rings, visitedRings, units, stereoAtoms)) {
617
0
          bool isStereoUnit = false;
618
0
          if (paraAtom.outsideNbrs.size() == 1)
619
0
            isStereoUnit = true;
620
0
          if (paraAtom.outsideNbrs.size() == 2) {
621
0
            if (symmetry_classes[paraAtom.outsideNbrs[0]->GetIndex()] == symmetry_classes[paraAtom.outsideNbrs[1]->GetIndex()]) {
622
              // check for spiro atom
623
0
              bool isSpiro = false;
624
0
              for (std::size_t k = 0; k < rings[i].paraAtoms.size(); ++k) {
625
0
                const StereoRing::ParaAtom &paraAtom2 = rings[i].paraAtoms[k];
626
0
                if (paraAtom.inIdx == paraAtom2.outIdx && paraAtom.insideNbrs == paraAtom2.outsideNbrs) {
627
0
                  isSpiro = true;
628
0
                  if (ApplyRule1(paraAtom2, symmetry_classes, rings, visitedRings, units, stereoAtoms))
629
0
                    isStereoUnit = true;
630
0
                }
631
0
              }
632
0
              if (!isSpiro)
633
0
                isStereoUnit = checkLigands(paraAtom, units);
634
              //cout << "isStereoUnit = " << isStereoUnit << endl;
635
0
            } else {
636
0
              isStereoUnit = true;
637
0
            }
638
0
          }
639
640
0
          if (isStereoUnit) {
641
0
            stereoAtoms.push_back(paraAtom.inIdx);
642
0
            OBAtom *atom = paraAtom.insideNbrs[0]->GetParent()->GetAtomById(paraAtom.id);
643
0
            units.push_back(OBStereoUnit(OBStereo::Tetrahedral, atom->GetId(), true));
644
0
          }
645
0
        }
646
647
0
      }
648
649
      // cistrans bonds
650
0
      for (std::size_t j = 0; j < rings[i].paraBonds.size(); ++j) {
651
0
        const StereoRing::ParaBond &paraBond = rings[i].paraBonds[j];
652
        // skip the atom if it is already in stereoAtoms
653
0
        if (std::find(stereoAtoms.begin(), stereoAtoms.end(), paraBond.inIdx) != stereoAtoms.end())
654
0
          continue;
655
656
0
        std::vector<bool> visitedRings(rings.size(), false);
657
        //visitedRings[i] = true;
658
0
        if (ApplyRule1(paraBond, symmetry_classes, rings, visitedRings, units, stereoAtoms)) {
659
0
          bool isStereoUnit = false;
660
0
          if (paraBond.outsideNbrs.size() == 1)
661
0
            isStereoUnit = true;
662
0
          if (paraBond.outsideNbrs.size() == 2) {
663
0
            if (symmetry_classes[paraBond.outsideNbrs[0]->GetIndex()] == symmetry_classes[paraBond.outsideNbrs[1]->GetIndex()]) {
664
              // check for spiro bond
665
0
              bool isSpiro = false;
666
0
              for (std::size_t k = 0; k < rings[i].paraBonds.size(); ++k) {
667
0
                const StereoRing::ParaBond &paraBond2 = rings[i].paraBonds[k];
668
0
                if (paraBond.inIdx == paraBond2.outIdx && paraBond.insideNbrs == paraBond2.outsideNbrs) {
669
0
                  isSpiro = true;
670
0
                  if (ApplyRule1(paraBond2, symmetry_classes, rings, visitedRings, units, stereoAtoms))
671
0
                    isStereoUnit = true;
672
0
                }
673
0
              }
674
0
              if (!isSpiro)
675
0
                isStereoUnit = checkLigands(paraBond, units);
676
              //cout << "isStereoUnit = " << isStereoUnit << endl;
677
0
            } else {
678
0
              isStereoUnit = true;
679
0
            }
680
0
          }
681
682
0
          if (isStereoUnit) {
683
0
            stereoAtoms.push_back(paraBond.inIdx);
684
0
            stereoAtoms.push_back(paraBond.outIdx);
685
0
            OBBond *bond = paraBond.insideNbrs[0]->GetParent()->GetBondById(paraBond.id);
686
0
            units.push_back(OBStereoUnit(OBStereo::CisTrans, bond->GetId(), true));
687
0
          }
688
0
        }
689
690
0
      }
691
692
693
0
    }
694
695
0
  }
696
697
  /**
698
   * Find the stereogenic units in a molecule using a set of rules.
699
   *
700
   * This is a public function: see header for details.
701
   */
702
  OBStereoUnitSet FindStereogenicUnits(OBMol *mol, const std::vector<unsigned int> &symClasses)
703
0
  {
704
0
    OBStereoUnitSet units;
705
706
    // do quick test to see if there are any possible stereogenic units
707
0
    if (!mayHaveTetrahedralCenter(mol) && !mayHaveCisTransBond(mol))
708
0
      return units;
709
710
    // make sure we have symmetry classes for all atoms
711
0
    if (symClasses.size() != mol->NumAtoms())
712
0
      return units;
713
714
    // para-stereocenters candidates
715
0
    std::vector<unsigned int> stereoAtoms; // Tetrahedral = idx, CisTrans = begin & end idx
716
0
    std::vector<unsigned int> paraAtoms;
717
0
    std::vector<unsigned int> paraBonds;
718
719
    /**
720
     * true Tetrahedral stereocenters:
721
     * - have four different symmetry classes for the ligands to the central atom
722
     */
723
0
    bool ischiral;
724
0
    std::vector<OBAtom*>::iterator ia;
725
0
    for (OBAtom *atom = mol->BeginAtom(ia); atom; atom = mol->NextAtom(ia)) {
726
0
      if (!isPotentialTetrahedral(atom))
727
0
        continue;
728
729
      // list containing neighbor symmetry classes
730
0
      std::vector<unsigned int> tlist;
731
0
      ischiral = true;
732
733
      // check neighbors to see if this atom is stereogenic
734
0
      std::vector<OBBond*>::iterator j;
735
0
      for (OBAtom *nbr = atom->BeginNbrAtom(j); nbr; nbr = atom->NextNbrAtom(j)) {
736
        // check if we already have a neighbor with this symmetry class
737
0
        std::vector<unsigned int>::iterator k;
738
0
        for (k = tlist.begin(); k != tlist.end(); ++k)
739
0
          if (symClasses[nbr->GetIndex()] == *k) {
740
0
            ischiral = false;
741
            // if so, might still be a para-stereocenter
742
0
            paraAtoms.push_back(atom->GetIdx());
743
0
          }
744
745
0
        if (ischiral)
746
          // keep track of all neighbors, so we can detect duplicates
747
0
          tlist.push_back(symClasses[nbr->GetIndex()]);
748
0
        else
749
0
          break;
750
0
      }
751
752
0
      if (ischiral) {
753
        // true-stereocenter found
754
0
        stereoAtoms.push_back(atom->GetIdx());
755
0
        units.push_back(OBStereoUnit(OBStereo::Tetrahedral, atom->GetId()));
756
0
      }
757
0
    }
758
759
    /**
760
     * true CisTrans stereocenters:
761
     * - each terminal has two different symmetry classes for it's ligands
762
     */
763
0
    bool isCisTransBond;
764
0
    std::vector<OBBond*>::iterator ib;
765
0
    for (OBBond *bond = mol->BeginBond(ib); bond; bond = mol->NextBond(ib)) {
766
0
      if (bond->IsInRing() && bond->IsAromatic())
767
0
        continue; // Exclude C=C in phenyl rings for example
768
769
0
      if (bond->GetBondOrder() == 2) {
770
0
        OBAtom *begin = bond->GetBeginAtom();
771
0
        OBAtom *end = bond->GetEndAtom();
772
0
        if (!begin || !end)
773
0
          continue;
774
775
0
        if (begin->GetTotalDegree() > 3 || end->GetTotalDegree() > 3)
776
0
          continue; // e.g. C=Ru where the Ru has four substituents
777
778
        // Needs to have at least one explicit single bond at either end
779
        // FIXME: timvdm: what about C=C=C=C
780
0
        if (!begin->HasSingleBond() || !end->HasSingleBond())
781
0
          continue;
782
783
0
        isCisTransBond = true;
784
0
        std::vector<OBBond*>::iterator j;
785
786
0
        if (begin->GetExplicitDegree() == 2) {
787
          // Begin atom has two explicit neighbors. One is the end atom. The other should
788
          // be a heavy atom - this is what we test here.
789
          // (There is a third, implicit, neighbor which is either a hydrogen
790
          // or a lone pair.)
791
0
          if (begin->ExplicitHydrogenCount() == 1)
792
0
            isCisTransBond = false;
793
0
        } else if (begin->GetExplicitDegree() == 3) {
794
0
          std::vector<unsigned int> tlist;
795
796
0
          for (OBAtom *nbr = begin->BeginNbrAtom(j); nbr; nbr = begin->NextNbrAtom(j)) {
797
            // skip end atom
798
0
            if (nbr->GetId() == end->GetId())
799
0
              continue;
800
            // do we already have an atom with this symmetry class?
801
0
            if (tlist.size()) {
802
              // compare second with first
803
0
              if (symClasses[nbr->GetIndex()] == tlist.at(0)) {
804
0
                isCisTransBond = false;
805
                // if same, might still be a para-stereocenter
806
0
                paraBonds.push_back(bond->GetIdx());
807
0
              }
808
0
              break;
809
0
            }
810
811
            // save first symmetry class
812
0
            tlist.push_back(symClasses[nbr->GetIndex()]);
813
0
          }
814
0
        } else {
815
          // Valence is not 2 or 3, for example SR3=NR
816
0
          isCisTransBond = false;
817
0
        }
818
819
0
        if (!isCisTransBond)
820
0
          continue;
821
822
0
        if (end->GetExplicitDegree() == 2) {
823
          // see comment above for begin atom
824
0
          if (end->ExplicitHydrogenCount() == 1)
825
0
            isCisTransBond = false;
826
0
        } else if (end->GetExplicitDegree() == 3) {
827
0
          std::vector<unsigned int> tlist;
828
829
0
          for (OBAtom *nbr = end->BeginNbrAtom(j); nbr; nbr = end->NextNbrAtom(j)) {
830
            // skip end atom
831
0
            if (nbr->GetId() == begin->GetId())
832
0
              continue;
833
            // do we already have an atom with this symmetry class?
834
0
            if (tlist.size()) {
835
              // compare second with first
836
0
              if (symClasses[nbr->GetIndex()] == tlist.at(0)) {
837
                // if same, might still be a para-stereocenter
838
0
                paraBonds.push_back(bond->GetIdx());
839
0
                isCisTransBond = false;
840
0
              }
841
0
              break;
842
0
            }
843
844
            // save first symmetry class
845
0
            tlist.push_back(symClasses[nbr->GetIndex()]);
846
0
          }
847
0
        } else {
848
          // Valence is not 2 or 3, for example SR3=NR
849
0
          isCisTransBond = false;
850
0
        }
851
852
0
        if (isCisTransBond)
853
          // true-stereocenter found
854
0
          units.push_back(OBStereoUnit(OBStereo::CisTrans, bond->GetId()));
855
0
      }
856
0
    }
857
858
    /**
859
     * Apply rule 1 from the Razinger paper recusively:
860
     *
861
     * All rings are merged "mergedRings". A merged ring is simply a fragment consisting
862
     * of all atoms of a ring system (bridged, spiro, adjacent, ...). If two rings in the
863
     * SSSR set share an atom, they are merged.
864
     *
865
     * Each merged must at least have two para-stereocenters (or 1 true + 1 para) in order
866
     * for the para-stereocenter to be valid. This is repeated until no new stereocenters
867
     * are identified.
868
     *
869
     * rule 1a for double bonds:
870
     * - bond atom in ring has two identical symmetry classes for it's neighbor atoms (-> para)
871
     * - other bond atom:
872
     *   - has two different symmetry classes for it's neighbours -> new stereocenter
873
     *   - has two identical symmetry classes, but the ligand contains at least 1 true or para stereocenter -> new stereocenter
874
     *
875
     * rule 1b for tetracoord atoms:
876
     * - at least two neighbour symmetry classes are the same (-> para)
877
     * - other pair:
878
     *   - has two different symmetry classes for it's neighbours -> new stereocenter
879
     *   - has two identical symmetry classes, but the ligand contains at least 1 true or para stereocenter -> new stereocenter
880
     *
881
     * NOTE: there must always be at least 2 new stereocenters (or one existing + 1 newly found) in order for them to be valid
882
     */
883
0
    std::vector<OBRing*> lssr = mol->GetLSSR();
884
0
    std::vector<StereoRing> rings;
885
886
    //cout << "=====================================================" << endl;
887
0
    for (std::size_t i = 0; i < lssr.size(); ++i) {
888
0
      rings.push_back(StereoRing());
889
0
      StereoRing &ring = rings.back();
890
891
892
0
      for (std::size_t j = 0; j < stereoAtoms.size(); ++j)
893
0
        if (lssr[i]->_pathset.BitIsSet(stereoAtoms[j]))
894
0
          ring.trueCount++;
895
896
      //cout << "StereoRing: trueCount = " << ring.trueCount << endl;
897
0
      for (std::size_t j = 0; j < paraAtoms.size(); ++j) {
898
0
        if (lssr[i]->_pathset.BitIsSet(paraAtoms[j])) {
899
0
          OBAtom *atom = mol->GetAtom(paraAtoms[j]);
900
0
          ring.paraAtoms.push_back(StereoRing::ParaAtom(atom->GetId(), paraAtoms[j]));
901
902
0
          FOR_NBORS_OF_ATOM (nbr, mol->GetAtom(paraAtoms[j])) {
903
0
            if (lssr[i]->_pathset.BitIsSet(nbr->GetIdx()))
904
0
              ring.paraAtoms.back().insideNbrs.push_back(&*nbr);
905
0
            else
906
0
              ring.paraAtoms.back().outsideNbrs.push_back(&*nbr);
907
0
          }
908
909
          //cout << "  ParaAtom(idx = " << ring.paraAtoms.back().inIdx << ", outside = " << ring.paraAtoms.back().outsideNbrs.size() << ")" << endl;
910
0
          if (ring.paraAtoms.back().insideNbrs.size() != 2)
911
0
            ring.paraAtoms.pop_back();
912
0
        }
913
0
      }
914
915
0
      for (std::size_t j = 0; j < paraBonds.size(); ++j) {
916
0
        OBBond *bond = mol->GetBond(paraBonds[j]);
917
0
        unsigned int beginIdx = bond->GetBeginAtomIdx();
918
0
        unsigned int endIdx = bond->GetEndAtomIdx();
919
920
0
        if (lssr[i]->_pathset.BitIsSet(beginIdx)) {
921
0
          ring.paraBonds.push_back(StereoRing::ParaBond(bond->GetId(), beginIdx, endIdx));
922
923
0
          FOR_NBORS_OF_ATOM (nbr, bond->GetBeginAtom()) {
924
0
            if (nbr->GetIdx() == endIdx)
925
0
              continue;
926
0
            ring.paraBonds.back().insideNbrs.push_back(&*nbr);
927
0
          }
928
0
          FOR_NBORS_OF_ATOM (nbr, bond->GetEndAtom()) {
929
0
            if (nbr->GetIdx() == beginIdx)
930
0
              continue;
931
0
            ring.paraBonds.back().outsideNbrs.push_back(&*nbr);
932
0
          }
933
934
          //cout << "  ParaBond(inIdx = " << beginIdx << ", outIdx = " << endIdx << ", outside = " << ring.paraBonds.back().outsideNbrs.size() << ")" << endl;
935
0
          if (ring.paraBonds.back().insideNbrs.size() != 2)
936
0
            ring.paraBonds.pop_back();
937
0
        }
938
939
0
        if (lssr[i]->_pathset.BitIsSet(endIdx)) {
940
0
          ring.paraBonds.push_back(StereoRing::ParaBond(bond->GetId(), endIdx, beginIdx));
941
942
0
          FOR_NBORS_OF_ATOM (nbr, bond->GetEndAtom()) {
943
0
            if (nbr->GetIdx() == beginIdx)
944
0
              continue;
945
0
            ring.paraBonds.back().insideNbrs.push_back(&*nbr);
946
0
          }
947
0
          FOR_NBORS_OF_ATOM (nbr, bond->GetBeginAtom()) {
948
0
            if (nbr->GetIdx() == endIdx)
949
0
              continue;
950
0
            ring.paraBonds.back().outsideNbrs.push_back(&*nbr);
951
0
          }
952
953
          //cout << "  ParaBond(inIdx = " << endIdx << ", outIdx = " << beginIdx << ", outside = " << ring.paraBonds.back().outsideNbrs.size() << ")" << endl;
954
0
          if (ring.paraBonds.back().insideNbrs.size() != 2)
955
0
            ring.paraBonds.pop_back();
956
0
        }
957
958
0
      }
959
960
0
      if (ring.paraAtoms.size() + ring.paraBonds.size() == 1) {
961
0
        ring.paraAtoms.clear();
962
0
        ring.paraBonds.clear();
963
0
      }
964
965
0
    }
966
    //cout << "=====================================================" << endl;
967
968
0
    unsigned int numStereoUnits;
969
0
    do {
970
0
      numStereoUnits = units.size();
971
0
      StartRule1(symClasses, rings, units, stereoAtoms);
972
0
    } while (units.size() > numStereoUnits);
973
974
975
0
    std::vector<OBBitVec> mergedRings = mergeRings(mol, symClasses);
976
    /**
977
     * Apply rule 2a for tetracoordinate carbon:
978
     * - 1 or 2 pair identical ligands
979
     * - each pair contains at least 1 true-stereocenter or 2 para-stereocenters
980
     *
981
     * Apply rule 2b for tetracoordinate carbon:
982
     * - 3 or 4 identical ligands with at least
983
     *   - 2 true-stereocenters
984
     *   - 2 separate assemblies of para-stereocenters
985
     */
986
0
    for (std::vector<unsigned int>::iterator idx = paraAtoms.begin(); idx != paraAtoms.end(); ++idx) {
987
0
      OBAtom *atom = mol->GetAtom(*idx);
988
      // make sure we didn't add this atom already from rule 1
989
0
      bool alreadyAdded = false;
990
0
      for (OBStereoUnitSet::iterator u2 = units.begin(); u2 != units.end(); ++u2) {
991
0
        if ((*u2).type == OBStereo::Tetrahedral)
992
0
          if (atom->GetId() == (*u2).id) {
993
0
            alreadyAdded = true;
994
0
          }
995
0
      }
996
0
      if (alreadyAdded)
997
0
        continue;
998
999
1000
0
      int classification = classifyTetrahedralNbrSymClasses(symClasses, atom);
1001
0
      switch (classification) {
1002
0
        case T1123:
1003
          // rule 2a with 1 pair
1004
0
          {
1005
0
            unsigned int duplicatedSymClass = findDuplicatedSymmetryClass(atom, symClasses);
1006
0
            OBAtom *ligandAtom = findAtomWithSymmetryClass(atom, duplicatedSymClass, symClasses);
1007
0
            if (containsAtLeast_1true_2para(ligandAtom, atom, units)) {
1008
0
              units.push_back(OBStereoUnit(OBStereo::Tetrahedral, atom->GetId(), true));
1009
0
            }
1010
0
          }
1011
0
          break;
1012
0
        case T1122:
1013
          // rule 2a with 2 pairs
1014
0
          {
1015
0
            unsigned int duplicatedSymClass1, duplicatedSymClass2;
1016
0
            findDuplicatedSymmetryClasses(atom, symClasses, duplicatedSymClass1, duplicatedSymClass2);
1017
0
            OBAtom *ligandAtom1 = findAtomWithSymmetryClass(atom, duplicatedSymClass1, symClasses);
1018
0
            OBAtom *ligandAtom2 = findAtomWithSymmetryClass(atom, duplicatedSymClass2, symClasses);
1019
0
            if (containsAtLeast_1true_2para(ligandAtom1, atom, units) &&
1020
0
                containsAtLeast_1true_2para(ligandAtom2, atom, units))
1021
0
              units.push_back(OBStereoUnit(OBStereo::Tetrahedral, atom->GetId(), true));
1022
0
          }
1023
0
          break;
1024
0
        case T1112:
1025
          // rule 2b with 3 identical
1026
0
          {
1027
0
            unsigned int duplicatedSymClass = findDuplicatedSymmetryClass(atom, symClasses);
1028
0
            OBAtom *ligandAtom = findAtomWithSymmetryClass(atom, duplicatedSymClass, symClasses);
1029
0
            if (containsAtLeast_2true_2paraAssemblies(ligandAtom, atom, units, mergedRings))
1030
0
              units.push_back(OBStereoUnit(OBStereo::Tetrahedral, atom->GetId(), true));
1031
0
          }
1032
0
          break;
1033
0
        case T1111:
1034
          // rule 2b with 4 identical
1035
0
          {
1036
0
            unsigned int duplicatedSymClass = findDuplicatedSymmetryClass(atom, symClasses);
1037
0
            OBAtom *ligandAtom = findAtomWithSymmetryClass(atom, duplicatedSymClass, symClasses);
1038
0
            if (containsAtLeast_2true_2paraAssemblies(ligandAtom, atom, units, mergedRings)) {
1039
0
              units.push_back(OBStereoUnit(OBStereo::Tetrahedral, atom->GetId(), true));
1040
0
            }
1041
0
          }
1042
0
          break;
1043
1044
0
      }
1045
1046
0
    }
1047
1048
    /**
1049
     * Apply rule 3 for double bonds.
1050
     * - 1 or 2 pair identical ligands (on begin and end atom)
1051
     * - each pair contains at least 1 true-stereocenter or 2 para-stereocenters (from rule1)
1052
     */
1053
0
    for (std::vector<unsigned int>::iterator idx = paraBonds.begin(); idx != paraBonds.end(); ++idx) {
1054
0
      OBBond *bond = mol->GetBond(*idx);
1055
1056
      // make sure we didn't add this atom already from rule 1
1057
0
      bool alreadyAdded = false;
1058
0
      for (OBStereoUnitSet::iterator u2 = units.begin(); u2 != units.end(); ++u2) {
1059
0
        if ((*u2).type == OBStereo::CisTrans)
1060
0
          if (bond->GetId() == (*u2).id) {
1061
0
            alreadyAdded = true;
1062
0
          }
1063
0
      }
1064
0
      if (alreadyAdded)
1065
0
        continue;
1066
1067
0
      OBAtom *begin = bond->GetBeginAtom();
1068
0
      OBAtom *end = bond->GetEndAtom();
1069
1070
0
      int beginClassification = classifyCisTransNbrSymClasses(symClasses, bond, bond->GetBeginAtom());
1071
0
      bool beginValid = false;
1072
0
      switch (beginClassification) {
1073
0
        case C12:
1074
0
          beginValid = true;
1075
0
          break;
1076
0
        case C11:
1077
0
          {
1078
            // find the ligand
1079
0
            OBAtom *ligandAtom = nullptr;
1080
0
            FOR_NBORS_OF_ATOM (nbr, begin) {
1081
0
              if ((nbr->GetIdx() != bond->GetBeginAtomIdx()) && (nbr->GetIdx() != bond->GetEndAtomIdx())) {
1082
0
                ligandAtom = &*nbr;
1083
0
                break;
1084
0
              }
1085
0
            }
1086
1087
0
            OBBitVec ligand = getFragment(ligandAtom, begin);
1088
0
            for (OBStereoUnitSet::iterator u2 = units.begin(); u2 != units.end(); ++u2) {
1089
0
              if ((*u2).type == OBStereo::Tetrahedral) {
1090
0
                if (ligand.BitIsSet((*u2).id))
1091
0
                  beginValid = true;
1092
0
              } else if((*u2).type == OBStereo::CisTrans) {
1093
0
                OBBond *bond = mol->GetBondById((*u2).id);
1094
0
                OBAtom *begin = bond->GetBeginAtom();
1095
0
                OBAtom *end = bond->GetEndAtom();
1096
0
                if (ligand.BitIsSet(begin->GetId()) || ligand.BitIsSet(end->GetId()))
1097
0
                  beginValid = true;
1098
0
              }
1099
0
            }
1100
0
          }
1101
0
          break;
1102
0
      }
1103
1104
0
      if (!beginValid)
1105
0
        continue;
1106
1107
0
      int endClassification = classifyCisTransNbrSymClasses(symClasses, bond, bond->GetEndAtom());
1108
0
      bool endValid = false;
1109
0
      switch (endClassification) {
1110
0
        case C12:
1111
0
          endValid = true;
1112
0
          break;
1113
0
        case C11:
1114
0
          {
1115
            // find the ligand
1116
0
            OBAtom *ligandAtom = nullptr;
1117
0
            FOR_NBORS_OF_ATOM (nbr, end) {
1118
0
              if ((nbr->GetIdx() != bond->GetBeginAtomIdx()) && (nbr->GetIdx() != bond->GetEndAtomIdx())) {
1119
0
                ligandAtom = &*nbr;
1120
0
                break;
1121
0
              }
1122
0
            }
1123
1124
0
            OBBitVec ligand = getFragment(ligandAtom, end);
1125
0
            for (OBStereoUnitSet::iterator u2 = units.begin(); u2 != units.end(); ++u2) {
1126
0
              if ((*u2).type == OBStereo::Tetrahedral) {
1127
0
                if (ligand.BitIsSet((*u2).id))
1128
0
                  endValid = true;
1129
0
              } else if((*u2).type == OBStereo::CisTrans) {
1130
0
                OBBond *bond = mol->GetBondById((*u2).id);
1131
0
                OBAtom *begin = bond->GetBeginAtom();
1132
0
                OBAtom *end = bond->GetEndAtom();
1133
0
                if (ligand.BitIsSet(begin->GetId()) || ligand.BitIsSet(end->GetId()))
1134
0
                  endValid = true;
1135
0
              }
1136
0
            }
1137
0
          }
1138
0
          break;
1139
0
      }
1140
1141
0
      if (endValid)
1142
0
        units.push_back(OBStereoUnit(OBStereo::CisTrans, bond->GetId(), true));
1143
0
    }
1144
1145
0
    if (DEBUG) {
1146
0
      for (OBStereoUnitSet::iterator unit = units.begin(); unit != units.end(); ++unit) {
1147
0
        if (unit->type == OBStereo::Tetrahedral)
1148
0
          cout << "Tetrahedral(center = " << unit->id << ", para = " << unit->para << ")" << endl;
1149
0
        if (unit->type == OBStereo::CisTrans)
1150
0
          cout << "CisTrans(bond = " << unit->id << ", para = " << unit->para << ")" << endl;
1151
0
        if (unit->type == OBStereo::SquarePlanar)
1152
0
          cout << "SquarePlanar(bond = " << unit->id << ", para = " << unit->para << ")" << endl;
1153
0
      }
1154
0
    }
1155
1156
1157
0
    return units;
1158
0
  }
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1181
1182
1183
1184
1185
1186
1187
  /**
1188
   * Helper function for FindStereogenicUnits using automorphisms.
1189
   *
1190
   * Find the duplicated symmetry class for neighbors of atom. This method only works if there is
1191
   * only one duplicated symmetry class (i.e. T1123, T1112, T1111).
1192
   */
1193
  unsigned int findDuplicatedSymmetryClass(OBAtom *atom, const std::vector<unsigned int> &symClasses)
1194
0
  {
1195
    // find the duplicated symmetry class
1196
0
    unsigned int duplicatedSymClass = OBGraphSym::NoSymmetryClass; // FIXME
1197
0
    std::vector<unsigned int> nbrSymClasses;
1198
0
    FOR_NBORS_OF_ATOM (nbr, atom) {
1199
0
      nbrSymClasses.push_back(symClasses.at(nbr->GetIndex()));
1200
0
    }
1201
0
    for (std::size_t i = 0; i < nbrSymClasses.size(); ++i) {
1202
0
      if (std::count(nbrSymClasses.begin(), nbrSymClasses.end(), nbrSymClasses.at(i)) >= 2) {
1203
0
        duplicatedSymClass = nbrSymClasses.at(i);
1204
0
        break;
1205
0
      }
1206
0
    }
1207
0
    return duplicatedSymClass;
1208
0
  }
1209
1210
  /**
1211
   * Helper function for FindStereogenicUnits using automorphisms.
1212
   *
1213
   * Find the duplicated symmetry classes for neighbors of atom. This method only works for the
1214
   * T1122 case.
1215
   */
1216
  void findDuplicatedSymmetryClasses(OBAtom *atom, const std::vector<unsigned int> &symClasses,
1217
      unsigned int &duplicated1, unsigned int &duplicated2)
1218
0
  {
1219
0
    std::vector<unsigned int> nbrSymClasses;
1220
0
    FOR_NBORS_OF_ATOM (nbr, atom)
1221
0
      nbrSymClasses.push_back(symClasses.at(nbr->GetIndex()));
1222
0
    std::sort(nbrSymClasses.begin(), nbrSymClasses.end());
1223
0
    duplicated1 = nbrSymClasses[0];
1224
0
    duplicated2 = nbrSymClasses[2];
1225
0
  }
1226
1227
  /**
1228
   * Helper function for FindStereogenicUnits using automorphisms.
1229
   *
1230
   * Find the duplicated symmetry classes for neighbors of atoms. This method works for all
1231
   * cases (i.e. T1234, T1123, T1112, T1111 and T1122).
1232
   */
1233
  std::vector<unsigned int> findDuplicatedSymmetryClasses(OBAtom *atom, const std::vector<unsigned int> &symClasses)
1234
0
  {
1235
0
    std::vector<unsigned int> nbrSymClasses, result;
1236
0
    FOR_NBORS_OF_ATOM (nbr, atom)
1237
0
      nbrSymClasses.push_back(symClasses.at(nbr->GetIndex()));
1238
1239
0
    std::sort(nbrSymClasses.begin(), nbrSymClasses.end());
1240
0
    for (std::size_t i = 0; i < nbrSymClasses.size(); ++i)
1241
0
      if (std::count(nbrSymClasses.begin(), nbrSymClasses.end(), nbrSymClasses[i]) > 1)
1242
0
        if (std::find(result.begin(), result.end(), nbrSymClasses[i]) == result.end())
1243
0
          result.push_back(nbrSymClasses[i]);
1244
0
    return result;
1245
0
  }
1246
1247
  inline bool ComparePairSecond(const std::pair<unsigned int, unsigned int> &a,
1248
      const std::pair<unsigned int, unsigned int> &b)
1249
0
  {
1250
0
    return (a.second < b.second);
1251
0
  }
1252
1253
1254
1255
  /**
1256
   * Helper functions for FindStereogenicUnits (using automorphisms).
1257
   *
1258
   * These functions determine if an automorphism permutation invert the
1259
   * configuration of stereocenters by exchanging equivalent neighbor atoms
1260
   * (i.e. neighbor atoms with the same topological symmetry class).
1261
   *
1262
   * @note: The molecule should be ordered by topological canonical labels.
1263
   */
1264
  struct StereoInverted {
1265
    struct Entry {
1266
      Automorphism p;
1267
      std::vector<OBAtom*> invertedAtoms;
1268
      std::vector<OBBond*> invertedBonds;
1269
    };
1270
1271
    /**
1272
     * Check if the specified automorphism causes an inversion of configuration
1273
     * for the specified tetrahedral stereogenic center.
1274
     */
1275
    static bool permutationInvertsTetrahedralCenter(const Automorphism &p,
1276
        OBAtom *center, const std::vector<unsigned int> &symmetry_classes,
1277
        const std::vector<unsigned int> &canon_labels)
1278
0
    {
1279
      // Find the duplicated ligand symmetry class(es)
1280
0
      std::vector<unsigned int> duplicatedSymClasses = findDuplicatedSymmetryClasses(center, symmetry_classes);
1281
1282
0
      if (DEBUG_INVERSIONS) {
1283
0
        cout << "permutationInvertsTetrahedralCenter(" << center->GetIndex() << ")" << endl;
1284
0
        print_vector("duplicatedSymClasses", duplicatedSymClasses);
1285
0
      }
1286
1287
0
      std::vector< std::vector<OBAtom*> > duplicatedAtoms;
1288
1289
0
      int permutated = 0;
1290
0
      for (std::size_t i = 0; i < duplicatedSymClasses.size(); ++i) {
1291
0
        unsigned int duplicatedSymClass = duplicatedSymClasses[i];
1292
1293
0
        duplicatedAtoms.resize(duplicatedAtoms.size()+1);
1294
1295
        // Store the ligand indexes for the atoms with the duplicated symmetry class
1296
0
        std::vector< std::pair<unsigned int, unsigned int> > tlist1;
1297
0
        FOR_NBORS_OF_ATOM (nbr, center) {
1298
0
          if (symmetry_classes[nbr->GetIndex()] == duplicatedSymClass) {
1299
0
            tlist1.push_back(std::make_pair(nbr->GetIndex(), canon_labels[nbr->GetIndex()]));
1300
0
            duplicatedAtoms.back().push_back(&*nbr);
1301
0
          }
1302
0
        }
1303
        // Sort the indexes
1304
0
        std::sort(tlist1.begin(), tlist1.end(), ComparePairSecond);
1305
1306
        //if (DEBUG_INVERSIONS) print_vector("tlist 1", tlist1);
1307
1308
        // Translate the sorted indexes using the automorphism
1309
0
        std::vector<unsigned long> tlist2;
1310
0
        for (std::size_t j = 0; j < tlist1.size(); ++j) {
1311
0
          unsigned int t;
1312
0
          if (MapsTo(p, tlist1[j].first, t))
1313
0
            tlist2.push_back(canon_labels[t]);
1314
0
        }
1315
1316
0
        if (DEBUG_INVERSIONS) print_vector("tlist 2", tlist2);
1317
1318
        // Permute the flag
1319
0
        if (OBStereo::NumInversions(tlist2) % 2)
1320
          //permutated = !permutated;
1321
0
          permutated++;
1322
0
      }
1323
1324
0
      if (permutated == 2) {
1325
0
        std::vector<OBRing*> lssr = center->GetParent()->GetLSSR();
1326
0
        assert( duplicatedAtoms.size() == 2 );
1327
0
        assert( duplicatedAtoms[0].size() == 2 );
1328
0
        assert( duplicatedAtoms[1].size() == 2 );
1329
0
        for (std::size_t i = 0; i < lssr.size(); ++i) {
1330
0
          if (lssr[i]->_pathset.BitIsSet(duplicatedAtoms[0][0]->GetIdx()) &&
1331
0
              lssr[i]->_pathset.BitIsSet(duplicatedAtoms[0][1]->GetIdx()))
1332
0
            return false;
1333
0
          if (lssr[i]->_pathset.BitIsSet(duplicatedAtoms[1][0]->GetIdx()) &&
1334
0
              lssr[i]->_pathset.BitIsSet(duplicatedAtoms[1][1]->GetIdx()))
1335
0
            return false;
1336
0
        }
1337
0
        return true;
1338
0
      }
1339
1340
0
      return permutated;
1341
0
    }
1342
1343
    static bool permutationInvertsCisTransBeginOrEndAtom(const Automorphism &p, OBBond *bond, OBAtom *beginOrEnd,
1344
        const std::vector<unsigned int> &canon_labels)
1345
0
    {
1346
0
      OBAtom *otherAtom = bond->GetNbrAtom(beginOrEnd);
1347
1348
0
      std::vector< std::pair<unsigned int, unsigned int> > tlist1;
1349
      // Store the neighbor indexes in tlist1
1350
0
      FOR_NBORS_OF_ATOM (nbr, beginOrEnd) {
1351
        // skip the other double bond atom
1352
0
        if (nbr->GetId() == otherAtom->GetId())
1353
0
          continue;
1354
0
        tlist1.push_back(std::make_pair(nbr->GetIndex(), canon_labels[nbr->GetIndex()]));
1355
0
      }
1356
      // Sort the indexes
1357
0
      std::sort(tlist1.begin(), tlist1.end(), ComparePairSecond);
1358
1359
      // Translate the sorted indexes using the automorphism
1360
0
      std::vector<unsigned long> tlist2;
1361
0
      for (std::size_t j = 0; j < tlist1.size(); ++j) {
1362
0
        unsigned int t;
1363
0
        if (MapsTo(p, tlist1[j].first, t))
1364
0
          tlist2.push_back(canon_labels[t]);
1365
0
      }
1366
1367
0
      return (OBStereo::NumInversions(tlist2) % 2);
1368
0
    }
1369
1370
    /**
1371
     * Check if the specified automorphism causes an inversion of configuration
1372
     * for the specfied stereogenic double bond.
1373
     */
1374
    static bool permutationInvertsCisTransCenter(const Automorphism &p, OBBond *bond,
1375
        const std::vector<unsigned int> &canon_labels)
1376
0
    {
1377
      // begin atom
1378
0
      bool beginInverted = permutationInvertsCisTransBeginOrEndAtom(p, bond, bond->GetBeginAtom(), canon_labels);
1379
      // end atom
1380
0
      bool endInverted = permutationInvertsCisTransBeginOrEndAtom(p, bond, bond->GetEndAtom(), canon_labels);
1381
1382
      // combine result using xor operation
1383
0
      if (beginInverted ^ endInverted)
1384
0
        return true;
1385
0
      return false;
1386
0
    }
1387
1388
    /**
1389
     * Perform the computation.
1390
     */
1391
    static std::vector<Entry> compute(OBMol *mol, const std::vector<unsigned int> &symClasses,
1392
        const Automorphisms &automorphisms)
1393
0
    {
1394
0
      if (DEBUG_INVERSIONS) cout << "ENTER StereoInverted::compute()" << endl;
1395
1396
      // We need topological canonical labels for this
1397
0
      std::vector<unsigned int> canon_labels;
1398
0
      CanonicalLabels(mol, symClasses, canon_labels, OBBitVec(), 5, true);
1399
1400
      // the result
1401
0
      std::vector<Entry> result;
1402
1403
      // make a list of stereogenic centers inverted by the automorphism permutations
1404
0
      for (std::size_t i = 0; i < automorphisms.size(); ++i) {
1405
0
        Entry entry;
1406
0
        entry.p = automorphisms[i];
1407
1408
0
        if (DEBUG_INVERSIONS) cout << "----> Checking automorphism " << i+1 << endl;
1409
1410
        // Check the atoms
1411
0
        std::vector<OBAtom*>::iterator ia;
1412
0
        for (OBAtom *atom = mol->BeginAtom(ia); atom; atom = mol->NextAtom(ia)) {
1413
          // consider only potential stereo centers
1414
0
          if (!isPotentialTetrahedral(atom))
1415
0
            continue;
1416
          // add the atom to the inverted list if the automorphism inverses it's configuration
1417
0
          if (permutationInvertsTetrahedralCenter(automorphisms[i], atom, symClasses, canon_labels))
1418
0
            entry.invertedAtoms.push_back(atom);
1419
0
        }
1420
1421
        // Check the bonds
1422
0
        std::vector<OBBond*>::iterator ib;
1423
0
        for (OBBond *bond = mol->BeginBond(ib); bond; bond = mol->NextBond(ib)) {
1424
          // consider only potential stereo centers
1425
0
          if (!isPotentialCisTrans(bond))
1426
0
            continue;
1427
          // add the bond to the inverted list if the automorphism inverses it's configuration
1428
0
          if (permutationInvertsCisTransCenter(entry.p, bond, canon_labels))
1429
0
            entry.invertedBonds.push_back(bond);
1430
0
        }
1431
1432
0
        if (DEBUG_INVERSIONS) {
1433
0
          cout << "automorphism " << i+1 << "     ";
1434
0
          for (std::size_t j = 0; j < mol->NumAtoms(); ++j) {
1435
0
            unsigned int t;
1436
0
            if (MapsTo(entry.p, j, t)) {
1437
0
              if (t < 10) {
1438
0
                cout << " " << t << " ";
1439
0
              } else {
1440
0
                cout << t << " ";
1441
0
              }
1442
0
            }
1443
0
          }
1444
0
          cout << endl;
1445
0
          cout << "  invertedAtoms: ";
1446
0
          for (std::size_t l = 0; l < entry.invertedAtoms.size(); ++l)
1447
0
            cout << entry.invertedAtoms[l]->GetId() << " ";
1448
0
          cout << endl;
1449
0
          cout << "  invertedBonds: ";
1450
0
          for (std::size_t l = 0; l < entry.invertedBonds.size(); ++l)
1451
0
            cout << entry.invertedBonds[l]->GetId() << " ";
1452
0
          cout << endl;
1453
0
        }
1454
1455
0
        result.push_back(entry);
1456
0
      }
1457
1458
0
      if (DEBUG_INVERSIONS) cout << "EXIT StereoInverted::compute()" << endl;
1459
1460
0
      return result;
1461
0
    }
1462
1463
1464
  };
1465
1466
  ////////////////////////////////////////////////////////////////////////////
1467
  ////////////////////////////////////////////////////////////////////////////
1468
  //
1469
  //
1470
  //  FindStereogenicUnits using automorphisms
1471
  //
1472
  //
1473
  ////////////////////////////////////////////////////////////////////////////
1474
  ////////////////////////////////////////////////////////////////////////////
1475
1476
  /**
1477
   * Find an atom with the specified symmetry class. The first found atom is returned
1478
   * or 0 when there is no such atom. This function is intended to be used in cases
1479
   * where any atom with the specified symmetry class can be used. For example, when
1480
   * checking a fragments for stereocenters, the result will be the same for any atom
1481
   * with a specified (duplicated) symmetry class.
1482
   */
1483
  OBAtom* findAtomWithSymmetryClass(OBAtom *atom, unsigned int symClass, const std::vector<unsigned int> &symClasses)
1484
0
  {
1485
0
    OBAtom *ligandAtom = nullptr;
1486
0
    FOR_NBORS_OF_ATOM (nbr, atom)
1487
0
      if (symClasses.at(nbr->GetIndex()) == symClass)
1488
0
        ligandAtom = &*nbr;
1489
0
    return ligandAtom;
1490
0
  }
1491
1492
  /**
1493
   * Helper function to determine if a stereogenic center with duplicated symmetry classes
1494
   * really is a stereogenic center.
1495
   *
1496
   * Check if the ligandAtom's fragment (see getFragment()) contains at least one
1497
   * true- or 1 para-stereocenter. This is rule 1 (a & b) in the Razinger paper on
1498
   * stereoisomer generation.
1499
   */
1500
  bool containsAtLeast_1true_1para(OBAtom *ligandAtom, OBAtom *skip, const OBStereoUnitSet &units)
1501
0
  {
1502
0
    OBMol *mol = skip->GetParent();
1503
    // create the fragment bitvec
1504
0
    OBBitVec ligand = getFragment(ligandAtom, skip);
1505
0
    for (OBStereoUnitSet::const_iterator u2 = units.begin(); u2 != units.end(); ++u2) {
1506
0
      if (isUnitInFragment(mol, *u2, ligand))
1507
0
        return true;
1508
0
    }
1509
0
    return false;
1510
0
  }
1511
1512
  /**
1513
   * Helper function to determine if a stereogenic center with duplicated symmetry classes
1514
   * really is a stereogenic center.
1515
   *
1516
   * Check if the ligandAtom's fragment (see getFragment()) contains at least one
1517
   * true- or 2 para-stereocenter. This is rule 2a and rule 3 in the Razinger
1518
   * paper on stereoisomer generation.
1519
   */
1520
  bool containsAtLeast_1true_2para(OBAtom *ligandAtom, OBAtom *atom, const OBStereoUnitSet &units)
1521
0
  {
1522
0
    OBMol *mol = atom->GetParent();
1523
    // check if ligand contains at least:
1524
    // - 1 true-stereocenter
1525
    // - 2 para-stereocenters
1526
0
    OBBitVec ligand = getFragment(ligandAtom, atom);
1527
0
    bool foundTrueStereoCenter = false;
1528
0
    int paraStereoCenterCount = 0;
1529
0
    for (OBStereoUnitSet::const_iterator u2 = units.begin(); u2 != units.end(); ++u2) {
1530
0
      if (isUnitInFragment(mol, *u2, ligand)) {
1531
0
        if ((*u2).para) {
1532
0
          paraStereoCenterCount++;
1533
0
        } else {
1534
0
          foundTrueStereoCenter = true;
1535
0
        }
1536
0
      }
1537
0
    }
1538
1539
0
    if (foundTrueStereoCenter || paraStereoCenterCount >= 2)
1540
0
      return true;
1541
0
    if (ligandAtom->IsInRing() && atom->IsInRing() && paraStereoCenterCount)
1542
0
      return true;
1543
0
    return false;
1544
0
  }
1545
1546
  /**
1547
   * Helper function to determine if a stereogenic center with duplicated symmetry classes
1548
   * really is a stereogenic center.
1549
   *
1550
   * Check if the ligandAtom's fragment (see getFragment()) contains at least one
1551
   * true- or 2 separate assemblies of at least 2 para-stereocenter. This is rule
1552
   * 2b in the Razinger paper on stereoisomer generation.
1553
   */
1554
  bool containsAtLeast_2true_2paraAssemblies(OBAtom *ligandAtom, OBAtom *atom, const OBStereoUnitSet &units, const std::vector<OBBitVec> &mergedRings)
1555
0
  {
1556
0
    OBMol *mol = atom->GetParent();
1557
    // check if ligand contains at least:
1558
    // - 2 true-stereocenter
1559
    // - 2 separate para-stereocenters assemblies
1560
0
    OBBitVec ligand = getFragment(ligandAtom, atom);
1561
0
    int trueStereoCenterCount = 0;
1562
0
    std::vector<unsigned int> ringIndices;
1563
0
    for (OBStereoUnitSet::const_iterator u2 = units.begin(); u2 != units.end(); ++u2) {
1564
0
      if ((*u2).type == OBStereo::Tetrahedral) {
1565
0
        if (ligand.BitIsSet((*u2).id)) {
1566
0
          if ((*u2).para) {
1567
0
            OBAtom *paraAtom = mol->GetAtomById((*u2).id);
1568
0
            for (std::size_t ringIdx = 0; ringIdx < mergedRings.size(); ++ringIdx) {
1569
0
              if (mergedRings.at(ringIdx).BitIsSet(paraAtom->GetIdx()))
1570
0
                if (std::find(ringIndices.begin(), ringIndices.end(), ringIdx) == ringIndices.end())
1571
0
                  ringIndices.push_back(ringIdx);
1572
0
            }
1573
0
          } else
1574
0
            trueStereoCenterCount++;
1575
0
        }
1576
0
      } else if((*u2).type == OBStereo::CisTrans) {
1577
0
        OBBond *bond = mol->GetBondById((*u2).id);
1578
0
        OBAtom *begin = bond->GetBeginAtom();
1579
0
        OBAtom *end = bond->GetEndAtom();
1580
0
        if (ligand.BitIsSet(begin->GetId()) || ligand.BitIsSet(end->GetId())) {
1581
0
          if ((*u2).para) {
1582
0
            for (std::size_t ringIdx = 0; ringIdx < mergedRings.size(); ++ringIdx) {
1583
0
              if (mergedRings.at(ringIdx).BitIsSet(begin->GetIdx()) || mergedRings.at(ringIdx).BitIsSet(end->GetIdx())) {
1584
0
                if (std::find(ringIndices.begin(), ringIndices.end(), ringIdx) == ringIndices.end()) {
1585
0
                  ringIndices.push_back(ringIdx);
1586
0
                }
1587
0
              }
1588
0
            }
1589
0
          } else {
1590
0
            trueStereoCenterCount++;
1591
0
          }
1592
0
        }
1593
0
      }
1594
0
    }
1595
1596
0
    if (trueStereoCenterCount >= 2 || ringIndices.size() >= 2)
1597
0
      return true;
1598
0
    return false;
1599
0
  }
1600
1601
  /**
1602
   * Find the stereogenic units in a molecule using automorphisms.
1603
   *
1604
   * This is a public function: see header for details.
1605
   */
1606
  OBStereoUnitSet FindStereogenicUnits(OBMol *mol,
1607
      const std::vector<unsigned int> &symClasses, const Automorphisms &automorphisms)
1608
0
  {
1609
0
    OBStereoUnitSet units;
1610
1611
    // do quick test to see if there are any possible stereogenic units
1612
0
    if (!mayHaveTetrahedralCenter(mol) && !mayHaveCisTransBond(mol))
1613
0
      return units;
1614
1615
    // make sure we have symmetry classes for all atoms
1616
0
    if (symClasses.size() != mol->NumAtoms())
1617
0
      return units;
1618
1619
    // Compute which automorphisms cause inversion of configuration
1620
    // for the stereogenic units
1621
0
    std::vector<StereoInverted::Entry> inverted = StereoInverted::compute(mol, symClasses, automorphisms);
1622
1623
0
    std::vector<OBBitVec> mergedRings = mergeRings(mol, symClasses);
1624
1625
0
    std::vector<unsigned long> doneAtoms, doneBonds;
1626
0
    unsigned int lastSize = units.size();
1627
0
    while (true) {
1628
0
      std::vector<OBAtom*>::iterator ia;
1629
0
      for (OBAtom *atom = mol->BeginAtom(ia); atom; atom = mol->NextAtom(ia)) {
1630
0
        if (std::find(doneAtoms.begin(), doneAtoms.end(), atom->GetId()) != doneAtoms.end())
1631
0
          continue;
1632
        // consider only potential steroecenters
1633
0
        if (!isPotentialTetrahedral(atom))
1634
0
          continue;
1635
1636
        // A potential stereocenter is really a stereocenter if there exists no automorphic
1637
        // permutation causing an inversion of the configuration of only the potential
1638
        // stereogenic unit under consideration.
1639
0
        bool foundPermutation = false; // invert __only__ configuration of atom
1640
0
        for (std::size_t i = 0; i < inverted.size(); ++i) {
1641
0
          const std::vector<OBAtom*> &atoms = inverted[i].invertedAtoms;
1642
0
          if (atoms.size() != 1)
1643
0
            continue;
1644
0
          const std::vector<OBBond*> &bonds = inverted[i].invertedBonds;
1645
0
          if (bonds.size())
1646
0
            continue;
1647
0
          if (atoms[0] == atom) {
1648
0
            foundPermutation = true;
1649
0
            break;
1650
0
          }
1651
0
        }
1652
1653
0
        int classification = classifyTetrahedralNbrSymClasses(symClasses, atom);
1654
1655
0
        if (DEBUG_INVERSIONS)
1656
0
          cout << "foundPermutation for id = " << atom->GetId() << ": " << foundPermutation << endl;
1657
1658
0
        if (!foundPermutation) {
1659
          // true-stereocenter found
1660
0
          bool isParaCenter = (classification == T1234) ? false : true;
1661
          //cout << "found(2) " << atom->GetId() << endl;
1662
0
          units.push_back(OBStereoUnit(OBStereo::Tetrahedral, atom->GetId(), isParaCenter));
1663
0
          doneAtoms.push_back(atom->GetId());
1664
0
        } else {
1665
          // count ligand configurations:
1666
          // If there exists at least one automorphic permutation causing the inversion of the
1667
          // configuration of only the stereogenic unit under consideration, then the potential
1668
          // stereocenter can be a stereocenter if the number of topologically equivalent neighbors
1669
          // (ligands) of potential stereogenic is less than or equal to the number of configurations
1670
          // of these ligands.
1671
          //
1672
          // In practise:
1673
          //    T1123 -> 1 true stereocenter OR 2 para stereocenters
1674
          //    T1122 -> 1 true stereocenter OR 2 para stereocenters (for both)
1675
          //    T1112 -> 2 true stereocenters OR 2 para stereocenter assemblies
1676
          //    T1111 -> 2 true stereocenters OR 2 para stereocenter assemblies
1677
0
          switch (classification) {
1678
0
            case T1123:
1679
0
              {
1680
0
                unsigned int duplicatedSymClass = findDuplicatedSymmetryClass(atom, symClasses);
1681
0
                OBAtom *ligandAtom = findAtomWithSymmetryClass(atom, duplicatedSymClass, symClasses);
1682
0
                if (containsAtLeast_1true_2para(ligandAtom, atom, units)) {
1683
0
                  units.push_back(OBStereoUnit(OBStereo::Tetrahedral, atom->GetId(), true));
1684
0
                  doneAtoms.push_back(atom->GetId());
1685
0
                }
1686
0
              }
1687
0
              break;
1688
0
            case T1122:
1689
0
              {
1690
0
                unsigned int duplicatedSymClass1, duplicatedSymClass2;
1691
0
                findDuplicatedSymmetryClasses(atom, symClasses, duplicatedSymClass1, duplicatedSymClass2);
1692
0
                OBAtom *ligandAtom1 = findAtomWithSymmetryClass(atom, duplicatedSymClass1, symClasses);
1693
0
                OBAtom *ligandAtom2 = findAtomWithSymmetryClass(atom, duplicatedSymClass2, symClasses);
1694
0
                if (containsAtLeast_1true_2para(ligandAtom1, atom, units) &&
1695
0
                    containsAtLeast_1true_2para(ligandAtom2, atom, units)) {
1696
0
                  units.push_back(OBStereoUnit(OBStereo::Tetrahedral, atom->GetId(), true));
1697
0
                  doneAtoms.push_back(atom->GetId());
1698
0
                }
1699
0
              }
1700
0
              break;
1701
0
            case T1112:
1702
0
            case T1111:
1703
0
              {
1704
0
                unsigned int duplicatedSymClass = findDuplicatedSymmetryClass(atom, symClasses);
1705
0
                OBAtom *ligandAtom = findAtomWithSymmetryClass(atom, duplicatedSymClass, symClasses);
1706
0
                if (containsAtLeast_2true_2paraAssemblies(ligandAtom, atom, units, mergedRings)) {
1707
0
                  units.push_back(OBStereoUnit(OBStereo::Tetrahedral, atom->GetId(), true));
1708
0
                  doneAtoms.push_back(atom->GetId());
1709
0
                }
1710
0
              }
1711
0
              break;
1712
0
          }
1713
0
        }
1714
0
      }
1715
1716
0
      std::vector<OBBond*>::iterator ib;
1717
0
      for (OBBond *bond = mol->BeginBond(ib); bond; bond = mol->NextBond(ib)) {
1718
0
        if (std::find(doneBonds.begin(), doneBonds.end(), bond->GetId()) != doneBonds.end())
1719
0
          continue;
1720
0
        if (!isPotentialCisTrans(bond))
1721
0
          continue;
1722
1723
        // A double bond is a stereogenic bond if there exists no automorphic
1724
        // permutation causing an inversion of the configuration of only the potential
1725
        // stereogenic unit under consideration.
1726
0
        bool foundPermutation = false; // invert __only__ configuration of atom
1727
0
        for (std::size_t i = 0; i < inverted.size(); ++i) {
1728
0
          const std::vector<OBAtom*> &atoms = inverted[i].invertedAtoms;
1729
          // if any atoms are inverted, the bond can't be the only inverted stereocenter
1730
0
          if (atoms.size())
1731
0
            continue;
1732
0
          const std::vector<OBBond*> &bonds = inverted[i].invertedBonds;
1733
          // the bond should be the only inverted stereocenter
1734
0
          if (bonds.size() != 1)
1735
0
            continue;
1736
          // check if it is this bond
1737
0
          if (bonds[0] == bond) {
1738
0
            foundPermutation = true;
1739
0
            break;
1740
0
          }
1741
0
        }
1742
1743
0
        int beginClassification = classifyCisTransNbrSymClasses(symClasses, bond, bond->GetBeginAtom());
1744
0
        int endClassification = classifyCisTransNbrSymClasses(symClasses, bond, bond->GetEndAtom());
1745
1746
0
        if (!foundPermutation) {
1747
          // true-stereocenter found
1748
0
          bool isParaCenter = (beginClassification == C12) && (endClassification == C12) ? false : true;
1749
0
          units.push_back(OBStereoUnit(OBStereo::CisTrans, bond->GetId(), isParaCenter));
1750
0
          doneBonds.push_back(bond->GetId());
1751
0
        } else {
1752
          // count ligand configurations:
1753
0
          bool beginValid = false;
1754
0
          switch (beginClassification) {
1755
0
            case C12:
1756
0
              beginValid = true;
1757
0
              break;
1758
0
            case C11:
1759
0
              {
1760
                // find the ligand
1761
0
                OBAtom *ligandAtom = nullptr;
1762
0
                FOR_NBORS_OF_ATOM (nbr, bond->GetBeginAtom()) {
1763
0
                  if ((nbr->GetIdx() != bond->GetBeginAtomIdx()) && (nbr->GetIdx() != bond->GetEndAtomIdx())) {
1764
0
                    ligandAtom = &*nbr;
1765
0
                    break;
1766
0
                  }
1767
0
                }
1768
0
                if (ligandAtom)
1769
0
                  beginValid = containsAtLeast_1true_1para(ligandAtom, bond->GetBeginAtom(), units);
1770
0
              }
1771
0
              break;
1772
0
          }
1773
1774
0
          if (!beginValid)
1775
0
            continue;
1776
1777
0
          bool endValid = false;
1778
0
          switch (endClassification) {
1779
0
            case C12:
1780
0
              endValid = true;
1781
0
              break;
1782
0
            case C11:
1783
0
              {
1784
                // find the ligand
1785
0
                OBAtom *ligandAtom = nullptr;
1786
0
                FOR_NBORS_OF_ATOM (nbr, bond->GetEndAtom()) {
1787
0
                  if ((nbr->GetIdx() != bond->GetBeginAtomIdx()) && (nbr->GetIdx() != bond->GetEndAtomIdx())) {
1788
0
                    ligandAtom = &*nbr;
1789
0
                    break;
1790
0
                  }
1791
0
                }
1792
0
                if (ligandAtom)
1793
0
                  endValid = containsAtLeast_1true_1para(ligandAtom, bond->GetEndAtom(), units);
1794
0
              }
1795
0
              break;
1796
0
          }
1797
1798
0
          if (endValid) {
1799
0
            units.push_back(OBStereoUnit(OBStereo::CisTrans, bond->GetId(), true));
1800
0
            doneBonds.push_back(bond->GetId());
1801
0
          }
1802
0
        }
1803
0
      }
1804
1805
1806
0
      if (units.size() == lastSize)
1807
0
        break;
1808
0
      lastSize = units.size();
1809
0
    }
1810
1811
0
    if (DEBUG) {
1812
0
      for (OBStereoUnitSet::iterator unit = units.begin(); unit != units.end(); ++unit) {
1813
0
        if (unit->type == OBStereo::Tetrahedral)
1814
0
          cout << "Tetrahedral(center = " << unit->id << ", para = " << unit->para << ")" << endl;
1815
0
        if (unit->type == OBStereo::CisTrans)
1816
0
          cout << "CisTrans(bond = " << unit->id << ", para = " << unit->para << ")" << endl;
1817
0
        if (unit->type == OBStereo::SquarePlanar)
1818
0
          cout << "SquarePlanar(bond = " << unit->id << ", para = " << unit->para << ")" << endl;
1819
0
      }
1820
0
    }
1821
1822
0
    return units;
1823
0
  } // FindStereogenicUnits using automorphisms
1824
1825
1826
  /**
1827
   * Perform symmetry analysis.
1828
   *
1829
   * @return vector containing symmetry classes index by OBAtom::GetIndex().
1830
   */
1831
  std::vector<unsigned int> FindSymmetry(OBMol *mol)
1832
0
  {
1833
0
    OBGraphSym symmetry(mol);
1834
0
    std::vector<unsigned int> symClasses;
1835
0
    symmetry.GetSymmetry(symClasses);
1836
0
    return symClasses;
1837
0
  }
1838
1839
  ////////////////////////////////////////////////////////////////////////////
1840
  ////////////////////////////////////////////////////////////////////////////
1841
  //
1842
  //
1843
  //  From0D
1844
  //
1845
  //
1846
  ////////////////////////////////////////////////////////////////////////////
1847
  ////////////////////////////////////////////////////////////////////////////
1848
1849
  void StereoFrom0D(OBMol *mol)
1850
0
  {
1851
0
    if (mol->HasChiralityPerceived())
1852
0
      return;
1853
1854
0
    obErrorLog.ThrowError(__FUNCTION__, "Ran OpenBabel::StereoFrom0D", obAuditMsg);
1855
1856
0
    std::vector<unsigned int> symmetry_classes = FindSymmetry(mol);
1857
0
    OBStereoUnitSet stereogenicUnits = FindStereogenicUnits(mol, symmetry_classes);
1858
1859
0
    TetrahedralFrom0D(mol, stereogenicUnits);
1860
0
    CisTransFrom0D(mol, stereogenicUnits);
1861
0
    mol->SetChiralityPerceived();
1862
0
  }
1863
1864
  std::vector<OBTetrahedralStereo*> TetrahedralFrom0D(OBMol *mol,
1865
      const OBStereoUnitSet &stereoUnits, bool addToMol)
1866
0
  {
1867
0
    std::vector<OBTetrahedralStereo*> configs;
1868
0
    obErrorLog.ThrowError(__FUNCTION__, "Ran OpenBabel::TetrahedralFrom0D", obAuditMsg);
1869
1870
    // Delete any existing stereo objects that are not a member of 'centers'
1871
    // and make a map of the remaining ones
1872
0
    std::map<unsigned long, OBTetrahedralStereo*> existingMap;
1873
0
    std::vector<OBGenericData*>::iterator data;
1874
0
    std::vector<OBGenericData*> stereoData = mol->GetAllData(OBGenericDataType::StereoData);
1875
0
    for (data = stereoData.begin(); data != stereoData.end(); ++data) {
1876
0
      if (static_cast<OBStereoBase*>(*data)->GetType() == OBStereo::Tetrahedral) {
1877
0
        OBTetrahedralStereo *ts = dynamic_cast<OBTetrahedralStereo*>(*data);
1878
0
        unsigned long center = ts->GetConfig().center;
1879
        // check if the center is really stereogenic
1880
0
        bool isStereogenic = false;
1881
0
        OBStereoUnitSet::const_iterator u;
1882
0
        for (u = stereoUnits.begin(); u != stereoUnits.end(); ++u) {
1883
0
          if ((*u).type == OBStereo::Tetrahedral)
1884
0
            if ((*u).id == center)
1885
0
              isStereogenic = true;
1886
0
        }
1887
1888
0
        if (isStereogenic) {
1889
0
          existingMap[center] = ts;
1890
0
          configs.push_back(ts);
1891
0
        } else {
1892
          // According to OpenBabel, this is not a tetrahedral stereo
1893
0
          obErrorLog.ThrowError(__FUNCTION__, "Removed spurious TetrahedralStereo object", obAuditMsg);
1894
0
          mol->DeleteData(ts);
1895
0
        }
1896
0
      }
1897
0
    }
1898
1899
0
    OBStereoUnitSet::const_iterator u;
1900
0
    for (u = stereoUnits.begin(); u != stereoUnits.end(); ++u) {
1901
      // skip non-tetrahedral units
1902
0
      if ((*u).type != OBStereo::Tetrahedral)
1903
0
        continue;
1904
      // if there already exists a OBTetrahedralStereo object for this
1905
      // center, continue
1906
0
      if (existingMap.find((*u).id) != existingMap.end())
1907
0
        continue;
1908
1909
0
      OBAtom *center = mol->GetAtomById((*u).id);
1910
1911
0
      OBTetrahedralStereo::Config config;
1912
0
      config.specified = false;
1913
0
      config.center = (*u).id;
1914
0
      FOR_NBORS_OF_ATOM(nbr, center) {
1915
0
        if (config.from == OBStereo::NoRef)
1916
0
          config.from = nbr->GetId();
1917
0
        else
1918
0
          config.refs.push_back(nbr->GetId());
1919
0
      }
1920
1921
0
      if ((config.refs.size() == 2))
1922
0
        config.refs.push_back(OBStereo::ImplicitRef); // need to add largest number on end to work
1923
1924
0
      OBTetrahedralStereo *th = new OBTetrahedralStereo(mol);
1925
0
      th->SetConfig(config);
1926
1927
0
      configs.push_back(th);
1928
      // add the data to the molecule if needed
1929
0
      if (addToMol)
1930
0
        mol->SetData(th);
1931
0
    }
1932
1933
0
    return configs;
1934
0
  }
1935
1936
  std::vector<OBCisTransStereo*> CisTransFrom0D(OBMol *mol,
1937
      const OBStereoUnitSet &stereoUnits,
1938
      bool addToMol)
1939
0
  {
1940
0
    std::vector<OBCisTransStereo*> configs;
1941
0
    obErrorLog.ThrowError(__FUNCTION__, "Ran OpenBabel::CisTransFrom0D", obAuditMsg);
1942
1943
0
    std::vector<unsigned long> bonds;
1944
0
    for (OBStereoUnitSet::const_iterator u = stereoUnits.begin(); u != stereoUnits.end(); ++u)
1945
0
      if ((*u).type == OBStereo::CisTrans)
1946
0
        bonds.push_back((*u).id);
1947
1948
    // Delete any existing stereo objects that are not a member of 'bonds'
1949
    // and make a map of the remaining ones
1950
0
    std::map<unsigned long, OBCisTransStereo*> existingMap;
1951
0
    std::vector<OBGenericData*>::iterator data;
1952
0
    std::vector<OBGenericData*> stereoData = mol->GetAllData(OBGenericDataType::StereoData);
1953
0
    for (data = stereoData.begin(); data != stereoData.end(); ++data) {
1954
0
      if (static_cast<OBStereoBase*>(*data)->GetType() == OBStereo::CisTrans) {
1955
0
        OBCisTransStereo *ct = dynamic_cast<OBCisTransStereo*>(*data);
1956
0
        OBCisTransStereo::Config config = ct->GetConfig();
1957
        // find the bond id from begin & end atom ids
1958
0
        unsigned long id = OBStereo::NoRef;
1959
0
        OBAtom *a = mol->GetAtomById(config.begin);
1960
0
        if (!a)
1961
0
          continue;
1962
0
        FOR_BONDS_OF_ATOM (bond, a) {
1963
0
          unsigned long beginId = bond->GetBeginAtom()->GetId();
1964
0
          unsigned long endId = bond->GetEndAtom()->GetId();
1965
0
          if ((beginId == config.begin && endId == config.end) ||
1966
0
              (beginId == config.end && endId == config.begin)) {
1967
0
            id = bond->GetId();
1968
0
            break;
1969
0
          }
1970
0
        }
1971
1972
0
        if (std::find(bonds.begin(), bonds.end(), id) == bonds.end()) {
1973
          // According to OpenBabel, this is not a cis trans stereo
1974
0
          obErrorLog.ThrowError(__FUNCTION__, "Removed spurious CisTransStereo object", obAuditMsg);
1975
0
          mol->DeleteData(ct);
1976
0
        }
1977
0
        else {
1978
0
          existingMap[id] = ct;
1979
0
          configs.push_back(ct);
1980
0
        }
1981
0
      }
1982
0
    }
1983
1984
0
    std::vector<unsigned long>::iterator i;
1985
0
    for (i = bonds.begin(); i != bonds.end(); ++i) {
1986
      // If there already exists a OBCisTransStereo object for this
1987
      // bond, leave it alone unless it's in a ring of small size
1988
1989
0
      bool alreadyExists = (existingMap.find(*i) != existingMap.end());
1990
0
      OBBond *bond = mol->GetBondById(*i);
1991
      // The bond id may not correspond to a bond in this molecule (e.g. for
1992
      // stale stereo data on a rebuilt structure); GetBondById returns null.
1993
0
      if (!bond)
1994
0
        continue;
1995
1996
0
      OBCisTransStereo *ct;
1997
0
      OBCisTransStereo::Config config;
1998
0
      if (alreadyExists)
1999
0
      {
2000
0
        ct = existingMap[*i];
2001
0
        config = ct->GetConfig();
2002
0
      }
2003
0
      else
2004
0
      {
2005
0
        OBAtom *begin = bond->GetBeginAtom();
2006
0
        OBAtom *end = bond->GetEndAtom();
2007
2008
0
        config.specified = false;
2009
        // begin
2010
0
        config.begin = begin->GetId();
2011
0
        FOR_NBORS_OF_ATOM (nbr, begin) {
2012
0
          if (nbr->GetId() == end->GetId())
2013
0
            continue;
2014
0
          config.refs.push_back(nbr->GetId());
2015
0
        }
2016
0
        if (config.refs.size() == 1) {
2017
0
          config.refs.push_back(OBStereo::ImplicitRef);
2018
0
        }
2019
        // end
2020
0
        config.end = end->GetId();
2021
0
        FOR_NBORS_OF_ATOM (nbr, end) {
2022
0
          if (nbr->GetId() == begin->GetId())
2023
0
            continue;
2024
0
          config.refs.push_back(nbr->GetId());
2025
0
        }
2026
0
        if (config.refs.size() == 3) {
2027
0
          config.refs.push_back(OBStereo::ImplicitRef);
2028
0
        }
2029
2030
0
        ct = new OBCisTransStereo(mol);
2031
0
        ct->SetConfig(config);
2032
0
      }
2033
2034
      // For a double bond in a ring of size IMPLICIT_CIS_RING_SIZE or less
2035
      // the stereochemistry is implicitly cis (in terms
2036
      // of the ring atoms)
2037
0
      OBRing* ring = bond->FindSmallestRing();
2038
0
      if (ring && ring->Size() <= IMPLICIT_CIS_RING_SIZE) {
2039
2040
        // Find the ring atoms in the config.refs
2041
0
        vector<unsigned int> ringrefs(2);
2042
0
        for (int i = 0; i<2; ++i) {
2043
0
          if (config.refs[i*2] != OBStereo::ImplicitRef && ring->IsMember(mol->GetAtomById(config.refs[i*2])))
2044
0
            ringrefs[i] = config.refs[i*2];
2045
0
          else
2046
0
            ringrefs[i] = config.refs[i*2 + 1];
2047
0
        }
2048
0
        if (!ct->IsCis(ringrefs[0], ringrefs[1])) // Need to invert the stereo
2049
0
          config.shape = OBStereo::ShapeZ;
2050
2051
0
        config.specified = true;
2052
0
        ct->SetConfig(config);
2053
0
      }
2054
2055
0
      configs.push_back(ct);
2056
      // add the data to the molecule if needed
2057
0
      if (addToMol && !alreadyExists)
2058
0
        mol->SetData(ct);
2059
2060
0
    }
2061
2062
0
    return configs;
2063
0
  }
2064
2065
  ////////////////////////////////////////////////////////////////////////////
2066
  ////////////////////////////////////////////////////////////////////////////
2067
  //
2068
  //
2069
  //  From3D
2070
  //
2071
  //
2072
  ////////////////////////////////////////////////////////////////////////////
2073
  ////////////////////////////////////////////////////////////////////////////
2074
2075
  void StereoFrom3D(OBMol *mol, bool force)
2076
0
  {
2077
0
    if (mol->HasChiralityPerceived() && !force)
2078
0
      return;
2079
2080
0
    obErrorLog.ThrowError(__FUNCTION__, "Ran OpenBabel::StereoFrom3D", obAuditMsg);
2081
2082
0
    std::vector<unsigned int> symmetry_classes = FindSymmetry(mol);
2083
0
    OBStereoUnitSet stereogenicUnits = FindStereogenicUnits(mol, symmetry_classes);
2084
2085
0
    mol->DeleteData(OBGenericDataType::StereoData);
2086
0
    TetrahedralFrom3D(mol, stereogenicUnits);
2087
0
    CisTransFrom3D(mol, stereogenicUnits);
2088
0
    mol->SetChiralityPerceived();
2089
0
  }
2090
2091
  //! Calculate the "sign of a volume" given by a set of 4 coordinates
2092
  double VolumeSign(const vector3 &a, const vector3 &b, const vector3 &c, const vector3 &d)
2093
0
  {
2094
0
    vector3 A, B, C;
2095
0
    A = b - a;
2096
0
    B = c - a;
2097
0
    C = d - a;
2098
0
    matrix3x3 m(A, B, C);
2099
0
    return m.determinant();
2100
0
  }
2101
2102
  std::vector<OBTetrahedralStereo*> TetrahedralFrom3D(OBMol *mol,
2103
      const OBStereoUnitSet &stereoUnits, bool addToMol)
2104
0
  {
2105
0
    std::vector<OBTetrahedralStereo*> configs;
2106
0
    OBUnitCell *uc = (OBUnitCell*)mol->GetData(OBGenericDataType::UnitCell);
2107
0
    obErrorLog.ThrowError(__FUNCTION__, "Ran OpenBabel::TetrahedralFrom3D", obAuditMsg);
2108
2109
    // find all tetrahedral centers
2110
0
    std::vector<unsigned long> centers;
2111
0
    for (OBStereoUnitSet::const_iterator u = stereoUnits.begin(); u != stereoUnits.end(); ++u)
2112
0
      if ((*u).type == OBStereo::Tetrahedral)
2113
0
        centers.push_back((*u).id);
2114
2115
0
    std::vector<unsigned long>::iterator i;
2116
0
    for (i = centers.begin(); i != centers.end(); ++i) {
2117
0
      OBAtom *center = mol->GetAtomById(*i);
2118
      // The center id may originate from pre-existing stereo data that no
2119
      // longer corresponds to an atom in this molecule (e.g. after rebuilding
2120
      // a malformed structure), in which case GetAtomById returns null.
2121
0
      if (!center)
2122
0
        continue;
2123
2124
      // the stereo unit may reference an id that is not a valid atom
2125
      // (e.g. a center removed since the unit was created)
2126
0
      if (!center)
2127
0
        continue;
2128
2129
      // make sure we have at least 3 heavy atom neighbors
2130
      // timvdm 28 Jun 2009: This is already checked in FindStereogenicUnits
2131
0
      if (center->GetHvyDegree() < 3) {
2132
0
        std::stringstream errorMsg;
2133
0
        errorMsg << "Cannot calculate a signed volume for an atom with a heavy atom valence of "
2134
0
                 << center->GetHvyDegree() << std::endl;
2135
0
        obErrorLog.ThrowError(__FUNCTION__, errorMsg.str(), obInfo);
2136
0
        continue;
2137
0
      }
2138
2139
0
      OBTetrahedralStereo::Config config;
2140
0
      config.center = *i;
2141
0
      FOR_NBORS_OF_ATOM(nbr, center) {
2142
0
        if (config.from == OBStereo::NoRef)
2143
0
          config.from = nbr->GetId();
2144
0
        else
2145
0
          config.refs.push_back(nbr->GetId());
2146
0
      }
2147
2148
0
      bool use_central_atom = false;
2149
2150
      // Create a vector with the coordinates of the neighbor atoms
2151
      // and check for a bond that indicates unspecified stereochemistry
2152
0
      std::vector<vector3> nbrCoords;
2153
0
      OBAtom *from = mol->GetAtomById(config.from);
2154
0
      OBBond *bond = mol->GetBond(from, center);
2155
0
      if (bond->IsWedgeOrHash() && bond->GetBeginAtom()==center)
2156
0
        config.specified = false;
2157
2158
0
      vector3 center_coord = center->GetVector();
2159
2160
0
      if (uc)
2161
0
        nbrCoords.push_back(uc->UnwrapCartesianNear(from->GetVector(), center_coord));
2162
0
      else
2163
0
        nbrCoords.push_back(from->GetVector());
2164
0
      for (OBStereo::RefIter id = config.refs.begin(); id != config.refs.end(); ++id) {
2165
0
        OBAtom *nbr = mol->GetAtomById(*id);
2166
0
        if (uc)
2167
0
          nbrCoords.push_back(uc->UnwrapCartesianNear(nbr->GetVector(), center_coord));
2168
0
        else
2169
0
          nbrCoords.push_back(nbr->GetVector());
2170
0
        OBBond *bond = mol->GetBond(nbr, center);
2171
0
        if (bond->IsWedgeOrHash() && bond->GetBeginAtom()==center)
2172
0
          config.specified = false;
2173
0
      }
2174
2175
        // Checks for a neighbour having 0 co-ords (added hydrogen etc)
2176
        /* FIXME: needed? if the molecule has 3D coords, additional
2177
         * hydrogens will get coords using OBAtom::GetNewBondVector
2178
        for (std::vector<vector3>::iterator coord = nbrCoords.begin(); coord != nbrCoords.end(); ++coord) {
2179
          // are the coordinates zero to 6 or more significant figures
2180
          if (coord->IsApprox(VZero, 1.0e-6)) {
2181
            if (!use_central_atom) {
2182
              use_central_atom = true;
2183
            } else {
2184
              obErrorLog.ThrowError(__FUNCTION__,
2185
                  "More than 2 neighbours have 0 co-ords when attempting 3D chiral calculation", obInfo);
2186
            }
2187
          }
2188
        }
2189
        */
2190
2191
      // If we have three heavy atoms we can use the chiral center atom itself for the fourth
2192
      // will always give same sign (for tetrahedron), magnitude will be smaller.
2193
0
      if ((config.refs.size() == 2) || use_central_atom) {
2194
0
        nbrCoords.push_back(center_coord);
2195
0
        config.refs.push_back(OBStereo::ImplicitRef); // need to add largest number on end to work
2196
0
      }
2197
2198
0
      double sign = VolumeSign(nbrCoords[0], nbrCoords[1], nbrCoords[2], nbrCoords[3]);
2199
0
      if (sign < 0.0)
2200
0
        config.winding = OBStereo::AntiClockwise;
2201
2202
0
      OBTetrahedralStereo *th = new OBTetrahedralStereo(mol);
2203
0
      th->SetConfig(config);
2204
2205
0
      configs.push_back(th);
2206
      // add the data to the molecule if needed
2207
0
      if (addToMol)
2208
0
        mol->SetData(th);
2209
0
    }
2210
2211
0
    return configs;
2212
0
  }
2213
2214
  std::vector<OBCisTransStereo*> CisTransFrom3D(OBMol *mol,
2215
      const OBStereoUnitSet &stereoUnits, bool addToMol)
2216
0
  {
2217
0
    std::vector<OBCisTransStereo*> configs;
2218
0
    OBUnitCell *uc = (OBUnitCell*)mol->GetData(OBGenericDataType::UnitCell);
2219
0
    obErrorLog.ThrowError(__FUNCTION__, "Ran OpenBabel::CisTransFrom3D", obAuditMsg);
2220
2221
    // find all cis/trans bonds
2222
0
    std::vector<unsigned long> bonds;
2223
0
    for (OBStereoUnitSet::const_iterator u = stereoUnits.begin(); u != stereoUnits.end(); ++u)
2224
0
      if ((*u).type == OBStereo::CisTrans)
2225
0
        bonds.push_back((*u).id);
2226
2227
0
    std::vector<unsigned long>::iterator i;
2228
0
    for (i = bonds.begin(); i != bonds.end(); ++i) {
2229
0
      OBBond *bond = mol->GetBondById(*i);
2230
      // the stereo unit may reference an id that is not a valid bond
2231
0
      if (!bond)
2232
0
        continue;
2233
0
      OBAtom *begin = bond->GetBeginAtom();
2234
0
      OBAtom *end = bond->GetEndAtom();
2235
2236
      // Create a vector with the coordinates of the neighbor atoms
2237
0
      std::vector<vector3> bondVecs;
2238
0
      OBCisTransStereo::Config config;
2239
      // begin
2240
0
      config.begin = begin->GetId();
2241
0
      FOR_NBORS_OF_ATOM (nbr, begin) {
2242
0
        if (nbr->GetId() == end->GetId())
2243
0
          continue;
2244
0
        config.refs.push_back(nbr->GetId());
2245
0
        if (uc)
2246
0
          bondVecs.push_back(uc->MinimumImageCartesian(nbr->GetVector() - begin->GetVector()));
2247
0
        else
2248
0
          bondVecs.push_back(nbr->GetVector() - begin->GetVector());
2249
0
      }
2250
0
      if (config.refs.size() == 1) {
2251
0
        config.refs.push_back(OBStereo::ImplicitRef);
2252
0
        vector3 pos;
2253
0
        begin->GetNewBondVector(pos, 1.0);
2254
        // WARNING: GetNewBondVector code has not yet been checked, since it's part of builder.cpp
2255
0
        if (uc)
2256
0
          bondVecs.push_back(uc->MinimumImageCartesian(pos - begin->GetVector()));
2257
0
        else
2258
0
          bondVecs.push_back(pos - begin->GetVector());
2259
0
      }
2260
      // the torsion code below indexes bondVecs[0..3], assuming exactly two
2261
      // reference vectors from begin (0,1) and two from end (2,3); a malformed
2262
      // cis/trans unit (sp atom, too many neighbors) breaks this assumption
2263
0
      size_t nBeginVecs = bondVecs.size();
2264
      // end
2265
0
      config.end = end->GetId();
2266
0
      vector3 end_vec = end->GetVector();
2267
0
      if (uc)
2268
0
        end_vec = uc->UnwrapCartesianNear(end_vec, begin->GetVector());
2269
0
      FOR_NBORS_OF_ATOM (nbr, end) {
2270
0
        if (nbr->GetId() == begin->GetId())
2271
0
          continue;
2272
0
        config.refs.push_back(nbr->GetId());
2273
0
        if (uc)
2274
0
          bondVecs.push_back(uc->MinimumImageCartesian(nbr->GetVector() - end_vec));
2275
0
        else
2276
0
          bondVecs.push_back(nbr->GetVector() - end_vec);
2277
0
      }
2278
0
      if (config.refs.size() == 3) {
2279
0
        config.refs.push_back(OBStereo::ImplicitRef);
2280
0
        vector3 pos;
2281
0
        end->GetNewBondVector(pos, 1.0);
2282
0
        if (uc)
2283
0
          bondVecs.push_back(uc->MinimumImageCartesian(pos - end_vec));
2284
0
        else
2285
0
          bondVecs.push_back(pos - end_vec);
2286
0
      }
2287
2288
      // A cis/trans bond needs two neighbor directions on each end (four in
2289
      // total, padded with implicit refs above). A malformed structure can
2290
      // leave one end with too few neighbors; skip it rather than indexing
2291
      // past the end of bondVecs.
2292
0
      if (nBeginVecs != 2 || bondVecs.size() != 4) {
2293
0
        obErrorLog.ThrowError(__FUNCTION__,
2294
0
          "Cannot determine cis/trans: unexpected neighbor count", obInfo);
2295
0
        continue;
2296
0
      }
2297
2298
0
      double tor02, tor03, tor12, tor13;
2299
0
      if (uc) {
2300
0
        vector3 v0 = begin->GetVector() + bondVecs[0];
2301
0
        vector3 v1 = begin->GetVector() + bondVecs[1];
2302
0
        vector3 v2 = end->GetVector() + bondVecs[2];
2303
0
        vector3 v3 = end->GetVector() + bondVecs[3];
2304
2305
0
        vector3 b, c, d;
2306
0
        b = uc->UnwrapCartesianNear(begin->GetVector(), v0);
2307
0
        c = uc->UnwrapCartesianNear(end->GetVector(), b);
2308
0
        d = uc->UnwrapCartesianNear(v2, c);
2309
0
        tor02 = CalcTorsionAngle(v0, b, c, d);
2310
2311
0
        d = uc->UnwrapCartesianNear(v3, c);
2312
0
        tor03 = CalcTorsionAngle(v0, b, c, d);
2313
2314
0
        b = uc->UnwrapCartesianNear(begin->GetVector(), v1);
2315
0
        c = uc->UnwrapCartesianNear(end->GetVector(), b);
2316
0
        d = uc->UnwrapCartesianNear(v2, c);
2317
0
        tor12 = CalcTorsionAngle(v1, b, c, d);
2318
2319
0
        d = uc->UnwrapCartesianNear(v3, c);
2320
0
        tor13 = CalcTorsionAngle(v1, b, c, d);
2321
0
      } else {
2322
0
        tor02 = CalcTorsionAngle(begin->GetVector() + bondVecs[0], begin->GetVector(), end->GetVector(), end->GetVector() + bondVecs[2]);
2323
0
        tor03 = CalcTorsionAngle(begin->GetVector() + bondVecs[0], begin->GetVector(), end->GetVector(), end->GetVector() + bondVecs[3]);
2324
0
        tor12 = CalcTorsionAngle(begin->GetVector() + bondVecs[1], begin->GetVector(), end->GetVector(), end->GetVector() + bondVecs[2]);
2325
0
        tor13 = CalcTorsionAngle(begin->GetVector() + bondVecs[1], begin->GetVector(), end->GetVector(), end->GetVector() + bondVecs[3]);
2326
0
      }
2327
2328
0
      if (std::abs(tor02) < 90.0 && std::abs(tor03) > 90.0) {
2329
        // 0      2 //
2330
        //  \    /  //
2331
        //   C==C   //
2332
        //  /    \  //
2333
        // 1      3 //
2334
0
        config.shape = OBStereo::ShapeZ;
2335
2336
0
        if (std::abs(tor12) < 90.0 || std::abs(tor13) > 90.0) {
2337
0
          obErrorLog.ThrowError(__FUNCTION__, "Could not determine cis/trans from 3D coordinates, using unspecified", obInfo);
2338
0
          config.specified = false;
2339
0
        }
2340
0
      } else if (std::abs(tor02) > 90.0 && std::abs(tor03) < 90.0) {
2341
        // 0      3 //
2342
        //  \    /  //
2343
        //   C==C   //
2344
        //  /    \  //
2345
        // 1      2 //
2346
0
        config.shape = OBStereo::ShapeU;
2347
2348
0
        if (std::abs(tor12) > 90.0 || std::abs(tor13) < 90.0) {
2349
0
          obErrorLog.ThrowError(__FUNCTION__, "Could not determine cis/trans from 3D coordinates, using unspecified", obInfo);
2350
0
          config.specified = false;
2351
0
        }
2352
0
      } else {
2353
0
        obErrorLog.ThrowError(__FUNCTION__, "Could not determine cis/trans from 3D coordinates, using unspecified", obInfo);
2354
0
        config.shape = OBStereo::ShapeU;
2355
0
        config.specified = false;
2356
0
      }
2357
2358
0
      OBCisTransStereo *ct = new OBCisTransStereo(mol);
2359
0
      ct->SetConfig(config);
2360
2361
0
      configs.push_back(ct);
2362
      // add the data to the molecule if needed
2363
0
      if (addToMol)
2364
0
        mol->SetData(ct);
2365
0
    }
2366
2367
0
    return configs;
2368
0
  }
2369
2370
  ////////////////////////////////////////////////////////////////////////////
2371
  ////////////////////////////////////////////////////////////////////////////
2372
  //
2373
  //  From2D
2374
  //
2375
  //  Reference:
2376
  //  [1] T. Cieplak, J.L. Wisniewski, A New Effective Algorithm for the
2377
  //  Unambiguous Identification of the Stereochemical Characteristics of
2378
  //  Compounds During Their Registration in Databases. Molecules 2000, 6,
2379
  //  915-926, http://www.mdpi.org/molecules/papers/61100915/61100915.htm
2380
  //
2381
  ////////////////////////////////////////////////////////////////////////////
2382
  ////////////////////////////////////////////////////////////////////////////
2383
2384
  void StereoFrom2D(OBMol *mol, std::map<OBBond*, enum OBStereo::BondDirection> *updown, bool force)
2385
0
  {
2386
0
    if (mol->HasChiralityPerceived() && !force)
2387
0
      return;
2388
2389
0
    obErrorLog.ThrowError(__FUNCTION__, "Ran OpenBabel::StereoFrom2D", obAuditMsg);
2390
2391
0
    std::vector<unsigned int> symmetry_classes = FindSymmetry(mol);
2392
0
    OBStereoUnitSet stereogenicUnits = FindStereogenicUnits(mol, symmetry_classes);
2393
2394
0
    mol->DeleteData(OBGenericDataType::StereoData);
2395
0
    TetrahedralFrom2D(mol, stereogenicUnits);
2396
0
    CisTransFrom2D(mol, stereogenicUnits, updown);
2397
0
    mol->SetChiralityPerceived();
2398
0
  }
2399
  //! Calculate the "sign of a triangle" given by a set of 3 2D coordinates
2400
  double TriangleSign(const vector3 &a, const vector3 &b, const vector3 &c)
2401
0
  {
2402
    // equation 6 from [1]
2403
0
    return (a.x() - c.x()) * (b.y() - c.y()) - (a.y() - c.y()) * (b.x() - c.x());
2404
0
  }
2405
  //! Calculate whether three vectors are arranged in order of increasing
2406
  //! angle anticlockwise (true) or clockwise (false) relative to a central point.
2407
  bool AngleOrder(const vector3 &a, const vector3 &b, const vector3 &c, const vector3 &center)
2408
0
  {
2409
0
    vector3 t, u, v;
2410
0
    t = a - center;
2411
0
    t.normalize();
2412
0
    u = b - center;
2413
0
    u.normalize();
2414
0
    v = c - center;
2415
0
    v.normalize();
2416
0
    return TriangleSign(t, u, v) > 0;
2417
0
  }
2418
  //! Get the angle between three atoms (from -180 to +180)
2419
  //! Note: OBAtom.GetAngle just returns 0->180
2420
  double GetAngle(OBAtom *a, OBAtom *b, OBAtom *c)
2421
0
  {
2422
0
   vector3 v1,v2;
2423
2424
0
    v1 = a->GetVector() - b->GetVector();
2425
0
    v2 = c->GetVector() - b->GetVector();
2426
0
    if (a->IsPeriodic()) {  // Adapted from OBAtom.GetAngle
2427
0
      OBMol *mol = (OBMol*)a->GetParent();
2428
0
      OBUnitCell *box = (OBUnitCell*)mol->GetData(OBGenericDataType::UnitCell);
2429
0
      v1 = box->MinimumImageCartesian(v1);
2430
0
      v2 = box->MinimumImageCartesian(v2);
2431
0
    }
2432
0
    if (fabs(v1.length()) < 1.0e-3
2433
0
      || fabs(v2.length()) < 1.0e-3) {
2434
0
        return(0.0);
2435
0
    }
2436
2437
0
    double angle = (atan2(v2.y(),v2.x()) - atan2(v1.y(),v1.x())) * RAD_TO_DEG;
2438
0
    while (angle < -180.0) angle += 360.0;
2439
0
    while (angle > 180.0) angle -= 360.0;
2440
0
    return angle;
2441
0
  }
2442
  std::vector<OBTetrahedralStereo*> TetrahedralFrom2D(OBMol *mol,
2443
      const OBStereoUnitSet &stereoUnits, bool addToMol)
2444
0
  {
2445
0
    std::vector<OBTetrahedralStereo*> configs;
2446
0
    obErrorLog.ThrowError(__FUNCTION__, "Ran OpenBabel::TetrahedralFrom2D", obAuditMsg);
2447
2448
    // find all tetrahedral centers
2449
0
    std::vector<unsigned long> centers;
2450
0
    for (OBStereoUnitSet::const_iterator u = stereoUnits.begin(); u != stereoUnits.end(); ++u)
2451
0
      if ((*u).type == OBStereo::Tetrahedral)
2452
0
        centers.push_back((*u).id);
2453
2454
2455
0
    std::vector<unsigned long>::iterator i;
2456
0
    for (i = centers.begin(); i != centers.end(); ++i) {
2457
0
      OBAtom *center = mol->GetAtomById(*i);
2458
2459
      // make sure we have at least 3 heavy atom neighbors
2460
0
      if (center->GetHvyDegree() < 3) {
2461
0
        std::stringstream errorMsg;
2462
0
        errorMsg << "Cannot calculate a signed volume for an atom with a heavy atom valence of "
2463
0
                 << center->GetHvyDegree() << std::endl;
2464
0
        obErrorLog.ThrowError(__FUNCTION__, errorMsg.str(), obInfo);
2465
0
        continue;
2466
0
      }
2467
2468
0
      OBTetrahedralStereo::Config config;
2469
0
      config.center = *i;
2470
2471
      // We assume the 'tip-only' convention. That is, wedge or hash bonds only
2472
      // determine the stereochemistry at their thin end (the BeginAtom)
2473
0
      bool tiponly = true;
2474
2475
      // find the hash, wedge and 2 plane atoms
2476
0
      std::vector<OBAtom*> planeAtoms;
2477
0
      std::vector<OBAtom*> wedgeAtoms;
2478
0
      std::vector<OBAtom*> hashAtoms;
2479
0
      FOR_BONDS_OF_ATOM(bond, center) {
2480
0
        OBAtom *nbr = bond->GetNbrAtom(center);
2481
        // hash bonds
2482
0
        if (bond->IsHash()) {
2483
0
          if (bond->GetBeginAtom()->GetId() == center->GetId()) {
2484
            // this is a 'real' hash bond going from center to nbr
2485
0
            hashAtoms.push_back(nbr);
2486
0
          } else {
2487
            // this is an 'inverted' hash bond going from nbr to center
2488
0
            if (tiponly)
2489
0
              planeAtoms.push_back(nbr);
2490
0
            else
2491
0
              wedgeAtoms.push_back(nbr);
2492
0
          }
2493
0
        } else if (bond->IsWedge()) {
2494
          // wedge bonds
2495
0
          if (bond->GetBeginAtom()->GetId() == center->GetId()) {
2496
            // this is a 'real' wedge bond going from center to nbr
2497
0
            wedgeAtoms.push_back(nbr);
2498
0
          } else {
2499
            // this is an 'inverted' wedge bond going from nbr to center
2500
0
            if (tiponly)
2501
0
              planeAtoms.push_back(nbr);
2502
0
            else
2503
0
              hashAtoms.push_back(nbr);
2504
0
          }
2505
0
        } else if (bond->IsWedgeOrHash()) {
2506
0
          if (!tiponly || (tiponly && bond->GetBeginAtom()->GetId() == center->GetId())) {
2507
0
            config.specified = true;
2508
0
            config.winding = OBStereo::UnknownWinding;
2509
0
            break;
2510
0
          }
2511
0
          else
2512
0
            planeAtoms.push_back(nbr);
2513
0
        } else {
2514
          // plane bonds
2515
0
          planeAtoms.push_back(nbr);
2516
0
        }
2517
0
      }
2518
2519
      // Handle the case of a tet center with four plane atoms or
2520
      //        3 plane atoms with the fourth bond implicit
2521
0
      if (planeAtoms.size() == 4 || (planeAtoms.size() == 3 && center->GetExplicitDegree()==3))
2522
0
        config.specified = false;
2523
2524
0
      bool success = true;
2525
2526
0
      using namespace std;
2527
0
      if (!config.specified || (config.specified && config.winding==OBStereo::UnknownWinding)) {
2528
        // unspecified or specified as unknown
2529
0
        FOR_NBORS_OF_ATOM (nbr, center)
2530
0
          if (config.from == OBStereo::NoRef)
2531
0
            config.from = nbr->GetId();
2532
0
          else
2533
0
            config.refs.push_back(nbr->GetId());
2534
0
        while (config.refs.size() < 3)
2535
0
          config.refs.push_back(OBStereo::ImplicitRef);
2536
0
      } else {
2537
2538
        // config.specified
2539
2540
0
        if (hashAtoms.size() == 4 || wedgeAtoms.size() == 4)
2541
0
        {
2542
0
          success = false;
2543
0
        } else if (planeAtoms.size() + hashAtoms.size() + wedgeAtoms.size() == 4)
2544
0
        {
2545
          // Handle all explicit tetra with at least one stereobond
2546
0
          vector<OBAtom*> order;
2547
2548
          // First of all, handle the case of three wedge (or three hash) and one other bond
2549
          //          by converting it into a single hash (or single wedge) and three planes
2550
0
          if (wedgeAtoms.size() == 3 || hashAtoms.size() == 3) {
2551
0
            vector<OBAtom*> *pwedge, *phash;
2552
0
            if (wedgeAtoms.size() == 3) {
2553
0
              pwedge = &wedgeAtoms; phash = &hashAtoms;
2554
0
            }
2555
0
            else {
2556
0
              phash = &wedgeAtoms; pwedge = &hashAtoms;
2557
0
            }
2558
0
            if (planeAtoms.size() == 0) { // Already has the hash bond
2559
0
              planeAtoms.insert(planeAtoms.end(), pwedge->begin(), pwedge->end());
2560
0
              pwedge->clear();
2561
0
            }
2562
0
            else { // Does not already have the hash bond
2563
0
              phash->push_back(planeAtoms[0]);
2564
0
              planeAtoms.clear();
2565
0
              pwedge->clear();
2566
0
            }
2567
0
          }
2568
2569
          // Pick a stereobond on which to base the stereochemistry:
2570
0
          bool wedge = wedgeAtoms.size() > 0;
2571
0
          order.push_back(wedge?wedgeAtoms[0]:hashAtoms[0]);
2572
0
          vector<OBAtom*> nbrs;
2573
0
          FOR_NBORS_OF_ATOM(nbr, center) {
2574
0
            if (&*nbr != order[0])
2575
0
              nbrs.push_back(&*nbr);
2576
0
          }
2577
          // Add "nbrs" to "order" in order of anticlockwise stereo
2578
0
          order.push_back(nbrs[0]);
2579
0
          if (AngleOrder(order[0]->GetVector(), order[1]->GetVector(), nbrs[1]->GetVector(), center->GetVector()))
2580
0
            order.push_back(nbrs[1]);
2581
0
          else
2582
0
            order.insert(order.begin()+1, nbrs[1]);
2583
0
          if (AngleOrder(order[0]->GetVector(), order[2]->GetVector(), nbrs[2]->GetVector(), center->GetVector()))
2584
0
            order.push_back(nbrs[2]);
2585
0
          else {
2586
0
            if (AngleOrder(order[0]->GetVector(), order[1]->GetVector(), nbrs[2]->GetVector(), center->GetVector()))
2587
0
              order.insert(order.begin()+2, nbrs[2]);
2588
0
            else
2589
0
              order.insert(order.begin()+1, nbrs[2]);
2590
0
          }
2591
2592
          // Handle the case of two planes with a wedge and hash bond opposite each other.
2593
          // This is handled as in the InChI TechMan (Figure 9) by marking it ambiguous if
2594
          // the (small) angle between the plane bonds is > 133, and basing the stereo on
2595
          // the 'inner' bond otherwise. This is commonly used for stereo in rings.
2596
          // See also Get2DTetrahedralAmbiguity() in ichister.c (part of InChI)
2597
0
          if (planeAtoms.size() == 2 && wedgeAtoms.size() == 1) { // Two planes, 1 wedge, 1 hash
2598
0
            if (order[2] == hashAtoms[0]) { // The wedge and hash are opposite
2599
0
              double angle = GetAngle(order[1], center, order[3]); // The anticlockwise angle between the plane atoms
2600
0
              if (angle > -133 && angle < 133) { // This value is from the InChI TechMan Figure 9
2601
0
                if (angle > 0) { // Change to three planes and the hash bond
2602
0
                  std::rotate(order.begin(), order.begin() + 2, order.end()); // Change the order so that it begins with the hash bond
2603
0
                  wedge = false;
2604
0
                  planeAtoms.push_back(wedgeAtoms[0]);
2605
0
                  wedgeAtoms.clear();
2606
0
                }
2607
0
                else { // Change to three planes and the wedge bond (note: order is already correct)
2608
0
                  planeAtoms.push_back(hashAtoms[0]);
2609
0
                  hashAtoms.clear();
2610
0
                }
2611
0
              } // No need for "else" statement, as this will be picked up as ambiguous stereo below
2612
0
            }
2613
0
          }
2614
2615
0
          config.from = order[0]->GetId();
2616
0
          config.refs.resize(3);
2617
0
          for(int i=0; i<3; ++i)
2618
0
            config.refs[i] = order[i+1]->GetId();
2619
0
          if (wedge)
2620
0
            config.winding = OBStereo::AntiClockwise;
2621
2622
          // Check for ambiguous stereo based on the members of "order".
2623
          // If the first is a wedge bond, then the next should be a plane/hash, then plane/wedge, then plane/hash
2624
          // If not, then the stereo is considered ambiguous.
2625
0
          vector<OBAtom*> *pwedge, *phash;
2626
0
          if (wedge) {
2627
0
            pwedge = &wedgeAtoms; phash = &hashAtoms;
2628
0
          }
2629
0
          else {
2630
0
            phash = &wedgeAtoms; pwedge = &hashAtoms;
2631
0
          }
2632
0
          if (std::find(pwedge->begin(), pwedge->end(), order[1]) != pwedge->end() ||
2633
0
              std::find(phash->begin(), phash->end(), order[2]) != phash->end()  ||
2634
0
              std::find(pwedge->begin(), pwedge->end(), order[3]) != pwedge->end()) { // Ambiguous stereo
2635
0
                success = false;
2636
0
          }
2637
0
        } else // 3 explicit bonds from here on
2638
0
          if(hashAtoms.size() == 0 || wedgeAtoms.size() == 0) {
2639
            // Composed of just wedge bonds and plane bonds, or just hash bonds and plane bonds
2640
2641
            // Pick a stereobond on which to base the stereochemistry:
2642
0
            vector<OBAtom*> order;
2643
0
            bool wedge = wedgeAtoms.size() > 0;
2644
0
            order.push_back(wedge?wedgeAtoms[0]:hashAtoms[0]);
2645
0
            vector<OBAtom*> nbrs;
2646
0
            FOR_NBORS_OF_ATOM(nbr, center) {
2647
0
              if (&*nbr != order[0])
2648
0
                nbrs.push_back(&*nbr);
2649
0
            }
2650
            // Add "nbrs" to "order" in order of anticlockwise stereo
2651
0
            order.push_back(nbrs[0]);
2652
0
            if (AngleOrder(order[0]->GetVector(), order[1]->GetVector(), nbrs[1]->GetVector(), center->GetVector()))
2653
0
              order.push_back(nbrs[1]);
2654
0
            else
2655
0
              order.insert(order.begin()+1, nbrs[1]);
2656
2657
            // Handle the case of two planes with a wedge/hash in the small angle between them.
2658
            // This is handled similar to the InChI TechMan (Figure 10) by treating the stereo bond
2659
            // as being in the large angle. This is consistent with Symyx Draw.
2660
0
            if (planeAtoms.size() == 2) { // Two planes, 1 stereo
2661
0
              double angle = GetAngle(order[1], center, order[2]); // The anticlockwise angle between the plane atoms
2662
0
              if (angle < 0) // Invert the stereo of the stereobond
2663
0
                wedge = !wedge;
2664
0
            }
2665
2666
0
            config.from = OBStereo::ImplicitRef;
2667
0
            config.refs.resize(3);
2668
0
            for(int i=0; i<3; ++i)
2669
0
              config.refs[i] = order[i]->GetId();
2670
0
            if (!wedge)
2671
0
              config.winding = OBStereo::AntiClockwise;
2672
0
        }  else { // 3 explicit bonds with at least one hash and at least one wedge
2673
0
          success = false;
2674
0
        }
2675
0
      } // end of config.specified
2676
2677
0
      if (!success) {
2678
//         std::stringstream errorMsg;
2679
//         errorMsg << "Symmetry analysis found atom with id " << center->GetId()
2680
//             << " to be a tetrahedral atom but the wedge/hash bonds can't be interpreted." << std::endl
2681
//             << " # in-plane bonds = " << planeAtoms.size() << std::endl
2682
//             << " # wedge bonds = " << wedgeAtoms.size() << std::endl
2683
//             << " # hash bonds = " << hashAtoms.size() << std::endl
2684
//             << std::endl;
2685
//         obErrorLog.ThrowError(__FUNCTION__, errorMsg.str(), obError);
2686
0
        continue;
2687
0
      }
2688
2689
2690
0
      OBTetrahedralStereo *th = new OBTetrahedralStereo(mol);
2691
0
      th->SetConfig(config);
2692
2693
0
      configs.push_back(th);
2694
      // add the data to the molecule if needed
2695
0
      if (addToMol)
2696
0
        mol->SetData(th);
2697
0
    }
2698
2699
0
    return configs;
2700
0
  }
2701
2702
  std::vector<OBCisTransStereo*> CisTransFrom2D(OBMol *mol,
2703
      const OBStereoUnitSet &stereoUnits,
2704
      const std::map<OBBond*, enum OBStereo::BondDirection> *updown, bool addToMol)
2705
0
  {
2706
0
    std::vector<OBCisTransStereo*> configs;
2707
0
    std::map<OBBond*, enum OBStereo::BondDirection>::const_iterator ud_cit;
2708
0
    obErrorLog.ThrowError(__FUNCTION__, "Ran OpenBabel::CisTransFrom2D", obAuditMsg);
2709
2710
    // find all cis/trans bonds
2711
0
    std::vector<unsigned long> bonds;
2712
0
    for (OBStereoUnitSet::const_iterator u = stereoUnits.begin(); u != stereoUnits.end(); ++u)
2713
0
      if ((*u).type == OBStereo::CisTrans)
2714
0
        bonds.push_back((*u).id);
2715
2716
0
    std::vector<unsigned long>::iterator i;
2717
0
    for (i = bonds.begin(); i != bonds.end(); ++i) {
2718
0
      OBBond *bond = mol->GetBondById(*i);
2719
      // the stereo unit may reference an id that is not a valid bond
2720
0
      if (!bond)
2721
0
        continue;
2722
0
      OBAtom *begin = bond->GetBeginAtom();
2723
0
      OBAtom *end = bond->GetEndAtom();
2724
2725
      // Create a vector with the coordinates of the neighbor atoms
2726
0
      std::vector<vector3> bondVecs;
2727
0
      OBCisTransStereo::Config config;
2728
0
      config.specified = true;
2729
2730
      // begin
2731
0
      config.begin = begin->GetId();
2732
0
      FOR_NBORS_OF_ATOM (nbr, begin) {
2733
0
        if (nbr->GetId() == end->GetId())
2734
0
          continue;
2735
0
        config.refs.push_back(nbr->GetId());
2736
0
        bondVecs.push_back(nbr->GetVector());
2737
2738
        // Check whether a single bond with unknown dir starts at the dbl bond (tip-only convention)
2739
0
        OBBond *b = mol->GetBond(begin, &*nbr);
2740
0
        if (updown) {
2741
0
          ud_cit = updown->find(b);
2742
0
          if (ud_cit!=updown->end() && ud_cit->second==OBStereo::UnknownDir && b->GetBeginAtom()==begin)
2743
0
            config.specified = false;
2744
0
        }
2745
0
      }
2746
0
      if (config.refs.size() == 1) {
2747
0
        config.refs.push_back(OBStereo::ImplicitRef);
2748
0
        vector3 pos;
2749
0
        begin->GetNewBondVector(pos, 1.0);
2750
0
        bondVecs.push_back(pos);
2751
0
      }
2752
      // the stereochemistry code below indexes bondVecs[0..3], assuming exactly
2753
      // two reference vectors from begin (0,1) and two from end (2,3)
2754
0
      size_t nBeginVecs = bondVecs.size();
2755
      // end
2756
0
      config.end = end->GetId();
2757
0
      FOR_NBORS_OF_ATOM (nbr, end) {
2758
0
        if (nbr->GetId() == begin->GetId())
2759
0
          continue;
2760
0
        config.refs.push_back(nbr->GetId());
2761
0
        bondVecs.push_back(nbr->GetVector());
2762
2763
        // Check whether a single bond with unknown dir starts at the dbl bond (tip-only convention)
2764
0
        OBBond *b = mol->GetBond(end, &*nbr);
2765
0
        if (updown) {
2766
0
          ud_cit = updown->find(b);
2767
0
          if (ud_cit!=updown->end() && ud_cit->second==OBStereo::UnknownDir && b->GetBeginAtom()==end)
2768
0
            config.specified = false;
2769
0
        }
2770
0
      }
2771
0
      if (config.refs.size() == 3) {
2772
0
        config.refs.push_back(OBStereo::ImplicitRef);
2773
0
        vector3 pos;
2774
0
        end->GetNewBondVector(pos, 1.0);
2775
0
        bondVecs.push_back(pos);
2776
0
      }
2777
2778
      // Handle the case where the dbl bond is marked as unknown stereo
2779
0
      if (updown) {
2780
0
        ud_cit = updown->find(bond);
2781
0
        if (ud_cit!=updown->end() && ud_cit->second==OBStereo::UnknownDir)
2782
0
            config.specified = false;
2783
0
      }
2784
2785
      // a malformed cis/trans unit (sp atom, too many neighbors) breaks the
2786
      // 2/2 split the stereochemistry code below relies on
2787
0
      if (nBeginVecs != 2 || bondVecs.size() != 4)
2788
0
        config.specified = false;
2789
2790
0
      if (config.specified==true) { // Work out the stereochemistry
2791
        // 0      3
2792
        //  \    /        2 triangles: 0-1-b & 2-3-a
2793
        //   a==b    -->  same sign: U
2794
        //  /    \        opposite sign: Z
2795
        // 1      2
2796
        /*
2797
        double sign1 = TriangleSign(begin->GetVector(), end->GetVector(), bondVecs[0]);
2798
        double sign2 = TriangleSign(begin->GetVector(), end->GetVector(), bondVecs[2]);
2799
        */
2800
0
        double sign1 = TriangleSign(bondVecs[0], bondVecs[1], end->GetVector());
2801
0
        double sign2 = TriangleSign(bondVecs[2], bondVecs[3], begin->GetVector());
2802
0
        double sign = sign1 * sign2;
2803
2804
0
        if (sign < 0.0) // opposite sign
2805
0
          config.shape = OBStereo::ShapeZ;
2806
0
      }
2807
2808
0
      OBCisTransStereo *ct = new OBCisTransStereo(mol);
2809
0
      ct->SetConfig(config);
2810
2811
0
      configs.push_back(ct);
2812
      // add the data to the molecule if needed
2813
0
      if (addToMol)
2814
0
        mol->SetData(ct);
2815
0
    }
2816
2817
0
    return configs;
2818
0
  }
2819
2820
  bool TetStereoToWedgeHash(OBMol &mol,
2821
      std::map<OBBond*, enum OBStereo::BondDirection> &updown,
2822
      std::map<OBBond*, OBStereo::Ref> &from)
2823
0
  {
2824
    // Store the tetcenters for the second loop (below)
2825
0
    std::set <unsigned long> tetcenters;
2826
0
    std::vector<OBGenericData*> vdata = mol.GetAllData(OBGenericDataType::StereoData);
2827
0
    for (std::vector<OBGenericData*>::iterator data = vdata.begin(); data != vdata.end(); ++data)
2828
0
      if (((OBStereoBase*)*data)->GetType() == OBStereo::Tetrahedral) {
2829
0
        OBTetrahedralStereo *ts = dynamic_cast<OBTetrahedralStereo*>(*data);
2830
0
        OBTetrahedralStereo::Config cfg = ts->GetConfig();
2831
0
        tetcenters.insert(cfg.center);
2832
0
      }
2833
2834
    // This loop sets one bond of each tet stereo to up or to down (2D only)
2835
0
    std::set <OBBond *> alreadyset;
2836
0
    OBUnitCell *uc = (OBUnitCell*)mol.GetData(OBGenericDataType::UnitCell);
2837
0
    for (std::vector<OBGenericData*>::iterator data = vdata.begin(); data != vdata.end(); ++data)
2838
0
      if (((OBStereoBase*)*data)->GetType() == OBStereo::Tetrahedral) {
2839
0
        OBTetrahedralStereo *ts = dynamic_cast<OBTetrahedralStereo*>(*data);
2840
0
        OBTetrahedralStereo::Config cfg = ts->GetConfig();
2841
2842
0
        if (cfg.specified) {
2843
0
          OBBond* chosen = nullptr;
2844
0
          OBAtom* center = mol.GetAtomById(cfg.center);
2845
0
          vector3 center_coord = center->GetVector();
2846
2847
          // Find the two bonds closest in angle and remember them if
2848
          // they are closer than DELTA_ANGLE_FOR_OVERLAPPING_BONDS
2849
0
          vector<OBAtom*> nbrs;
2850
0
          FOR_NBORS_OF_ATOM(a, center)
2851
0
            nbrs.push_back(&*a);
2852
0
          double min_angle = 359.0;
2853
0
          OBBond *close_bond_a = nullptr;
2854
0
          OBBond *close_bond_b = nullptr;
2855
0
          for (unsigned int i=0; i<nbrs.size() - 1; ++i)
2856
0
            for (unsigned int j=i+1; j<nbrs.size(); ++j) {
2857
0
              double angle = abs(nbrs[i]->GetAngle(center, nbrs[j]));
2858
0
              if (angle < min_angle) {
2859
0
                min_angle = angle;
2860
0
                close_bond_a = mol.GetBond(center, nbrs[i]);
2861
0
                close_bond_b = mol.GetBond(center, nbrs[j]);
2862
0
              }
2863
0
            }
2864
2865
0
          if (min_angle > DELTA_ANGLE_FOR_OVERLAPPING_BONDS) {
2866
0
            close_bond_a = nullptr;
2867
0
            close_bond_b = nullptr;
2868
0
          }
2869
2870
          // Find the best candidate bond to set to up/down
2871
          // 1. **Should not already be set**
2872
          // 2. Should not be connected to a 2nd tet center
2873
          //    (this is acceptable, as the wedge is only at one end, but will only confuse things)
2874
          // 3. Preferably is not in a cycle
2875
    // 4. Prefer neighbor with fewer bonds over neighbor with more bonds
2876
          // 5. Preferably is a terminal H, C, or heteroatom (in that order)
2877
          // 6. If two bonds are overlapping, choose one of these
2878
          //    (otherwise the InChI code will mark it as ambiguous)
2879
2880
0
          int max_bond_score = 0;   // The test below (score > max_bond_score)
2881
          // gave incorrect results when score < 0 and max_bond_score was an unsigned int
2882
          // see https://stackoverflow.com/questions/5416414/signed-unsigned-comparisons#5416498
2883
0
          FOR_BONDS_OF_ATOM(b, center) {
2884
0
            if (alreadyset.find(&*b) != alreadyset.end()) continue;
2885
2886
0
            OBAtom* nbr = b->GetNbrAtom(center);
2887
0
      int nbr_nbonds = nbr->GetExplicitDegree();
2888
0
            int score = 0;
2889
0
            if (!b->IsInRing()) {
2890
0
        if (!nbr->IsInRing())
2891
0
    score += 8;   // non-ring bond to non-ring atom is good
2892
0
        else
2893
0
    score += 2;   // non-ring bond to ring atom is bad
2894
0
      }
2895
0
            if (tetcenters.find(nbr->GetId()) == tetcenters.end()) // Not a tetcenter
2896
0
              score += 4;
2897
0
      if (nbr_nbonds == 1) // terminal atom...
2898
0
    score += 8;   // strongly prefer terminal atoms
2899
0
      else
2900
0
        score -= nbr_nbonds - 2; // bond to atom with many bonds is penalized
2901
0
      if (nbr->GetAtomicNum() == OBElements::Hydrogen)
2902
0
        score += 2;   // prefer H
2903
0
      else if (nbr->GetAtomicNum() == OBElements::Carbon)
2904
0
        score += 1;   // then C
2905
0
            if (&*b==close_bond_a || &*b==close_bond_b)
2906
0
              score += 16;
2907
2908
0
            if (score > max_bond_score) {
2909
0
              max_bond_score = score;
2910
0
              chosen = &*b;
2911
0
            }
2912
0
          }
2913
2914
0
          if (chosen == nullptr) { // There is a remote possibility of this but let's worry about 99.9% of cases first
2915
0
            obErrorLog.ThrowError(__FUNCTION__,
2916
0
              "Failed to set stereochemistry as unable to find an available bond", obError);
2917
0
            return false;
2918
0
          }
2919
0
          alreadyset.insert(chosen);
2920
2921
          // Determine whether this bond should be set hash or wedge (or indeed unknown)
2922
          // (Code inspired by perception.cpp, TetrahedralFrom2D: plane1 + plane2 + plane3, wedge)
2923
0
          OBStereo::BondDirection bonddir = OBStereo::UnknownDir;
2924
0
          if (cfg.winding != OBStereo::UnknownWinding) {
2925
0
            OBTetrahedralStereo::Config test_cfg = cfg;
2926
2927
            // If there is an implicit ref; let's make that the 'from' atom
2928
            // otherwise use the atom on the chosen bond
2929
0
            bool implicit = false;
2930
0
            if (test_cfg.from != OBStereo::ImplicitRef) {
2931
0
              OBStereo::RefIter ri = std::find(test_cfg.refs.begin(), test_cfg.refs.end(), (unsigned long) OBStereo::ImplicitRef);
2932
0
              if (ri!=test_cfg.refs.end()) {
2933
0
                test_cfg = OBTetrahedralStereo::ToConfig(test_cfg, OBStereo::ImplicitRef);
2934
0
                implicit = true;
2935
0
              }
2936
0
            }
2937
0
            else
2938
0
              implicit = true;
2939
2940
0
            bool anticlockwise_order;
2941
0
            bool useup;
2942
0
            if (implicit) {
2943
              // Put the ref for the stereo bond second
2944
0
              while (test_cfg.refs[1] != chosen->GetNbrAtom(center)->GetId())
2945
0
                std::rotate(test_cfg.refs.begin(), test_cfg.refs.begin() + 2, test_cfg.refs.end());
2946
0
              if (uc)
2947
0
                anticlockwise_order = AngleOrder(
2948
0
                  uc->UnwrapCartesianNear(mol.GetAtomById(test_cfg.refs[0])->GetVector(), center_coord),
2949
0
                  uc->UnwrapCartesianNear(mol.GetAtomById(test_cfg.refs[1])->GetVector(), center_coord),
2950
0
                  uc->UnwrapCartesianNear(mol.GetAtomById(test_cfg.refs[2])->GetVector(), center_coord),
2951
0
                  center_coord
2952
0
                  );
2953
0
              else
2954
0
                anticlockwise_order = AngleOrder(mol.GetAtomById(test_cfg.refs[0])->GetVector(),
2955
0
                  mol.GetAtomById(test_cfg.refs[1])->GetVector(), mol.GetAtomById(test_cfg.refs[2])->GetVector(),
2956
0
                  center->GetVector());
2957
              // Get the angle between the plane bonds
2958
0
              double angle = GetAngle(mol.GetAtomById(test_cfg.refs[0]), center, mol.GetAtomById(test_cfg.refs[2]));
2959
0
              if ((angle<0 && anticlockwise_order) || (angle>0 && !anticlockwise_order)) // Is the stereobond in the bigger angle?
2960
                // If the bonds are in anticlockwise order, a clockwise angle (<180) between plane bonds
2961
                // implies that the stereo bond is in the bigger angle. Otherwise it has the opposite meaning.
2962
0
                useup = anticlockwise_order;
2963
0
              else
2964
0
                useup = !anticlockwise_order;
2965
0
              }
2966
0
            else {
2967
0
              test_cfg = OBTetrahedralStereo::ToConfig(test_cfg, chosen->GetNbrAtom(center)->GetId());
2968
0
              if (uc)
2969
0
                anticlockwise_order = AngleOrder(
2970
0
                  uc->UnwrapCartesianNear(mol.GetAtomById(test_cfg.refs[0])->GetVector(), center_coord),
2971
0
                  uc->UnwrapCartesianNear(mol.GetAtomById(test_cfg.refs[1])->GetVector(), center_coord),
2972
0
                  uc->UnwrapCartesianNear(mol.GetAtomById(test_cfg.refs[2])->GetVector(), center_coord),
2973
0
                  center_coord
2974
0
                  );
2975
0
              else
2976
0
                anticlockwise_order = AngleOrder(mol.GetAtomById(test_cfg.refs[0])->GetVector(),
2977
0
                  mol.GetAtomById(test_cfg.refs[1])->GetVector(), mol.GetAtomById(test_cfg.refs[2])->GetVector(),
2978
0
                  center->GetVector());
2979
0
              if (anticlockwise_order)
2980
0
                useup = false;
2981
0
              else
2982
0
                useup = true;
2983
0
            }
2984
2985
2986
            // Set to UpBond (filled wedge from cfg.center to chosen_nbr) or DownBond
2987
0
            bonddir = useup ? OBStereo::UpBond : OBStereo::DownBond;
2988
0
          }
2989
0
          updown[chosen] = bonddir;
2990
0
          from[chosen] = cfg.center;
2991
0
        }
2992
0
      }
2993
0
      return true;
2994
0
  }
2995
2996
  set<OBBond*> GetUnspecifiedCisTrans(OBMol& mol)
2997
0
  {
2998
    // Get double bonds with unspecified CisTransStereo
2999
0
    set<OBBond*> unspec_ctstereo;
3000
0
    std::vector<OBGenericData*> vdata = mol.GetAllData(OBGenericDataType::StereoData);
3001
0
    for (std::vector<OBGenericData*>::iterator data = vdata.begin(); data != vdata.end(); ++data)
3002
0
      if (((OBStereoBase*)*data)->GetType() == OBStereo::CisTrans) {
3003
0
        OBCisTransStereo *ct = dynamic_cast<OBCisTransStereo*>(*data);
3004
0
        OBCisTransStereo::Config cfg = ct->GetConfig();
3005
0
        if (!cfg.specified) {
3006
0
          OBBond* dbl_bond = mol.GetBond(mol.GetAtomById(cfg.begin), mol.GetAtomById(cfg.end));
3007
0
          unspec_ctstereo.insert(dbl_bond);
3008
0
        }
3009
0
      }
3010
0
    return unspec_ctstereo;
3011
0
  }
3012
3013
0
  void StereoRefToImplicit(OBMol& mol, OBStereo::Ref atomId) {
3014
0
    std::vector<OBGenericData*> vdata = mol.GetAllData(OBGenericDataType::StereoData);
3015
0
    for (std::vector<OBGenericData*>::iterator data = vdata.begin(); data != vdata.end(); ++data) {
3016
0
      OBStereo::Type datatype = ((OBStereoBase*)*data)->GetType();
3017
3018
0
      if (datatype != OBStereo::CisTrans && datatype != OBStereo::Tetrahedral) {
3019
        // Maybe I should just unset the stereochemistry if this happens?
3020
0
        obErrorLog.ThrowError(__FUNCTION__,
3021
0
            "This function should be updated to handle additional stereo types.\nSome stereochemistry objects may contain explicit refs to hydrogens which have been removed.", obWarning);
3022
0
        continue;
3023
0
      }
3024
3025
      // Replace any references to atomId with ImplicitRef
3026
0
      if (datatype == OBStereo::CisTrans) {
3027
0
        OBCisTransStereo *ct = dynamic_cast<OBCisTransStereo*>(*data);
3028
0
        OBCisTransStereo::Config ct_cfg = ct->GetConfig();
3029
0
        replace(ct_cfg.refs.begin(), ct_cfg.refs.end(), atomId, (OBStereo::Ref) OBStereo::ImplicitRef);
3030
0
        ct->SetConfig(ct_cfg);
3031
0
      }
3032
0
      else if (datatype == OBStereo::Tetrahedral) {
3033
0
        OBTetrahedralStereo *ts = dynamic_cast<OBTetrahedralStereo*>(*data);
3034
0
        OBTetrahedralStereo::Config ts_cfg = ts->GetConfig();
3035
0
        if (ts_cfg.from == atomId) ts_cfg.from = OBStereo::ImplicitRef;
3036
0
        replace(ts_cfg.refs.begin(), ts_cfg.refs.end(), atomId, (OBStereo::Ref) OBStereo::ImplicitRef);
3037
0
        ts->SetConfig(ts_cfg);
3038
0
      }
3039
0
    }
3040
0
  }
3041
3042
0
  void ImplicitRefToStereo(OBMol& mol, OBStereo::Ref centerId, OBStereo::Ref newId) {
3043
0
    std::vector<OBGenericData*> vdata = mol.GetAllData(OBGenericDataType::StereoData);
3044
0
    for (std::vector<OBGenericData*>::iterator data = vdata.begin(); data != vdata.end(); ++data) {
3045
0
      OBStereo::Type datatype = ((OBStereoBase*)*data)->GetType();
3046
3047
0
      if (datatype != OBStereo::CisTrans && datatype != OBStereo::Tetrahedral) {
3048
        // Maybe I should just unset the stereochemistry if this happens?
3049
0
        obErrorLog.ThrowError(__FUNCTION__,
3050
0
            "This function should be updated to handle additional stereo types.\nSome stereochemistry objects may contain implicit refs to hydrogens which need to be converted to explicit.", obWarning);
3051
0
        continue;
3052
0
      }
3053
3054
      // Replace any references to ImplicitRef (attached to centerId) with newId
3055
0
      if (datatype == OBStereo::CisTrans) {
3056
0
        OBCisTransStereo *ct = dynamic_cast<OBCisTransStereo*>(*data);
3057
0
        OBCisTransStereo::Config ct_cfg = ct->GetConfig();
3058
0
        if (ct_cfg.begin == centerId || ct_cfg.end == centerId) {
3059
          // Assumption: the first two refs are on the begin atom, the last two on the end atom
3060
0
          if (ct_cfg.begin == centerId)
3061
0
            replace(ct_cfg.refs.begin(), ct_cfg.refs.begin()+2, (OBStereo::Ref) OBStereo::ImplicitRef, (OBStereo::Ref) newId);
3062
0
          if (ct_cfg.end == centerId)
3063
0
            replace(ct_cfg.refs.begin()+2, ct_cfg.refs.end(), (OBStereo::Ref) OBStereo::ImplicitRef, (OBStereo::Ref) newId);
3064
0
          ct->SetConfig(ct_cfg);
3065
0
        }
3066
0
      }
3067
0
      else if (datatype == OBStereo::Tetrahedral) {
3068
0
        OBTetrahedralStereo *ts = dynamic_cast<OBTetrahedralStereo*>(*data);
3069
0
        OBTetrahedralStereo::Config ts_cfg = ts->GetConfig();
3070
0
        if (ts_cfg.center == centerId) {
3071
0
          if (ts_cfg.from == OBStereo::ImplicitRef) ts_cfg.from = newId;
3072
0
          replace(ts_cfg.refs.begin(), ts_cfg.refs.end(), (OBStereo::Ref) OBStereo::ImplicitRef, (OBStereo::Ref) newId);
3073
0
          ts->SetConfig(ts_cfg);
3074
0
        }
3075
0
      }
3076
0
    }
3077
0
  }
3078
3079
}