Coverage Report

Created: 2026-03-31 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/rdkit/Code/GraphMol/WedgeBonds.cpp
Line
Count
Source
1
//
2
//  Copyright (C) 2023 Greg Landrum and other RDKit contributors
3
//
4
//   @@ All Rights Reserved @@
5
//  This file is part of the RDKit.
6
//  The contents are covered by the terms of the BSD license
7
//  which is included in the file license.txt, found at the root
8
//  of the RDKit source tree.
9
//
10
#include <GraphMol/RDKitBase.h>
11
#include <GraphMol/Atropisomers.h>
12
#include <RDGeneral/types.h>
13
#include <sstream>
14
#include <set>
15
#include <algorithm>
16
#include <RDGeneral/utils.h>
17
#include <RDGeneral/Invariant.h>
18
#include <RDGeneral/RDLog.h>
19
20
#include <boost/dynamic_bitset.hpp>
21
#include <Geometry/point.h>
22
#include "Chirality.h"
23
24
#include <cstdlib>
25
26
namespace RDKit {
27
28
namespace Chirality {
29
30
const BondWedgingParameters defaultWedgingParams;
31
32
namespace {
33
std::tuple<unsigned int, unsigned int, unsigned int> getDoubleBondPresence(
34
0
    const ROMol &mol, const Atom &atom) {
35
0
  unsigned int hasDouble = 0;
36
0
  unsigned int hasKnownDouble = 0;
37
0
  unsigned int hasAnyDouble = 0;
38
0
  for (const auto bond : mol.atomBonds(&atom)) {
39
0
    if (bond->getBondType() == Bond::BondType::DOUBLE) {
40
0
      ++hasDouble;
41
0
      if (bond->getStereo() == Bond::BondStereo::STEREOANY) {
42
0
        ++hasAnyDouble;
43
0
      } else if (bond->getStereo() > Bond::BondStereo::STEREOANY) {
44
0
        ++hasKnownDouble;
45
0
      }
46
0
    }
47
0
  }
48
0
  return std::make_tuple(hasDouble, hasKnownDouble, hasAnyDouble);
49
0
}
50
}  // namespace
51
52
namespace detail {
53
54
0
std::pair<bool, INT_VECT> countChiralNbrs(const ROMol &mol, int noNbrs) {
55
0
  INT_VECT nChiralNbrs(mol.getNumAtoms(), noNbrs);
56
57
  // start by looking for bonds that are already wedged
58
0
  for (const auto bond : mol.bonds()) {
59
0
    if (bond->getBondDir() == Bond::BEGINWEDGE ||
60
0
        bond->getBondDir() == Bond::BEGINDASH ||
61
0
        bond->getBondDir() == Bond::UNKNOWN) {
62
0
      if (bond->getBeginAtom()->getChiralTag() == Atom::CHI_TETRAHEDRAL_CW ||
63
0
          bond->getBeginAtom()->getChiralTag() == Atom::CHI_TETRAHEDRAL_CCW) {
64
0
        nChiralNbrs[bond->getBeginAtomIdx()] = noNbrs + 1;
65
0
      } else if (bond->getEndAtom()->getChiralTag() ==
66
0
                     Atom::CHI_TETRAHEDRAL_CW ||
67
0
                 bond->getEndAtom()->getChiralTag() ==
68
0
                     Atom::CHI_TETRAHEDRAL_CCW) {
69
0
        nChiralNbrs[bond->getEndAtomIdx()] = noNbrs + 1;
70
0
      }
71
0
    }
72
0
  }
73
74
  // now rank atoms by the number of chiral neighbors or Hs they have:
75
0
  bool chiNbrs = false;
76
0
  for (const auto at : mol.atoms()) {
77
0
    if (nChiralNbrs[at->getIdx()] > noNbrs) {
78
      // std::cerr << " SKIPPING1: " << at->getIdx() << std::endl;
79
0
      continue;
80
0
    }
81
0
    auto type = at->getChiralTag();
82
0
    if (type != Atom::CHI_TETRAHEDRAL_CW && type != Atom::CHI_TETRAHEDRAL_CCW) {
83
0
      continue;
84
0
    }
85
0
    nChiralNbrs[at->getIdx()] = 0;
86
0
    chiNbrs = true;
87
0
    for (const auto nat : mol.atomNeighbors(at)) {
88
0
      if (nat->getAtomicNum() == 1) {
89
        // special case: it's an H... we weight these especially high:
90
0
        nChiralNbrs[at->getIdx()] -= 10;
91
0
        continue;
92
0
      }
93
0
      type = nat->getChiralTag();
94
0
      if (type != Atom::CHI_TETRAHEDRAL_CW &&
95
0
          type != Atom::CHI_TETRAHEDRAL_CCW) {
96
0
        continue;
97
0
      }
98
0
      nChiralNbrs[at->getIdx()] -= 1;
99
0
    }
100
0
  }
101
0
  return std::make_pair(chiNbrs, nChiralNbrs);
102
0
}
103
104
//
105
// Determine bond wedge state
106
///
107
Bond::BondDir determineBondWedgeState(const Bond *bond,
108
                                      unsigned int fromAtomIdx,
109
0
                                      const Conformer *conf) {
110
0
  PRECONDITION(bond, "no bond");
111
0
  PRECONDITION(bond->getBondType() == Bond::SINGLE,
112
0
               "bad bond order for wedging");
113
0
  const auto mol = &(bond->getOwningMol());
114
0
  PRECONDITION(mol, "no mol");
115
116
0
  auto res = bond->getBondDir();
117
0
  if (!conf) {
118
0
    return res;
119
0
  }
120
121
0
  Atom *atom;
122
0
  Atom *bondAtom;
123
0
  if (bond->getBeginAtom()->getIdx() == fromAtomIdx) {
124
0
    atom = bond->getBeginAtom();
125
0
    bondAtom = bond->getEndAtom();
126
0
  } else {
127
0
    atom = bond->getEndAtom();
128
0
    bondAtom = bond->getBeginAtom();
129
0
  }
130
131
0
  auto chiralType = atom->getChiralTag();
132
0
  TEST_ASSERT(chiralType == Atom::CHI_TETRAHEDRAL_CW ||
133
0
              chiralType == Atom::CHI_TETRAHEDRAL_CCW);
134
135
  // if we got this far, we really need to think about it:
136
0
  std::list<int> neighborBondIndices;
137
0
  std::list<double> neighborBondAngles;
138
0
  auto centerLoc = conf->getAtomPos(atom->getIdx());
139
0
  auto tmpPt = conf->getAtomPos(bondAtom->getIdx());
140
0
  centerLoc.z = 0.0;
141
0
  tmpPt.z = 0.0;
142
143
0
  RDGeom::Point3D refVect;
144
0
  try {
145
0
    refVect = centerLoc.directionVector(tmpPt);
146
0
  } catch (const std::runtime_error &) {
147
    // we have a problem with the reference bond;
148
    // it's probably that the center and the tmp atom overlap
149
0
    return res;
150
0
  }
151
152
0
  neighborBondIndices.push_back(bond->getIdx());
153
0
  neighborBondAngles.push_back(0.0);
154
0
  for (const auto nbrBond : mol->atomBonds(atom)) {
155
0
    const auto otherAtom = nbrBond->getOtherAtom(atom);
156
0
    if (nbrBond != bond) {
157
0
      tmpPt = conf->getAtomPos(otherAtom->getIdx());
158
0
      tmpPt.z = 0.0;
159
0
      RDGeom::Point3D tmpVect;
160
0
      try {
161
0
        tmpVect = centerLoc.directionVector(tmpPt);
162
0
      } catch (const std::runtime_error &) {
163
        // we have a problem with the tmp bond;
164
        // it's probably that the atoms overlap
165
0
        return res;
166
0
      }
167
0
      auto angle = refVect.signedAngleTo(tmpVect);
168
0
      if (angle < 0.0) {
169
0
        angle += 2. * M_PI;
170
0
      }
171
0
      auto nbrIt = neighborBondIndices.begin();
172
0
      auto angleIt = neighborBondAngles.begin();
173
      // find the location of this neighbor in our angle-sorted list
174
      // of neighbors:
175
0
      while (angleIt != neighborBondAngles.end() && angle > (*angleIt)) {
176
0
        ++angleIt;
177
0
        ++nbrIt;
178
0
      }
179
0
      neighborBondAngles.insert(angleIt, angle);
180
0
      neighborBondIndices.insert(nbrIt, nbrBond->getIdx());
181
0
    }
182
0
  }
183
184
  // at this point, neighborBondIndices contains a list of bond
185
  // indices from the central atom.  They are arranged starting
186
  // at the reference bond in CCW order (based on the current
187
  // depiction).
188
189
  // if we already have one bond with direction set, then we can use it to
190
  // decide what the direction of this one is
191
192
  // we're starting from scratch... do the work!
193
0
  int nSwaps = atom->getPerturbationOrder(neighborBondIndices);
194
195
  // in the case of three-coordinated atoms we may have to worry about
196
  // the location of the implicit hydrogen - Issue 209
197
  // Check if we have one of these situation
198
  //
199
  //      0        1 0 2
200
  //      *         \*/
201
  //  1 - C - 2      C
202
  //
203
  // here the hydrogen will be between 1 and 2 and we need to add an
204
  // additional swap
205
0
  if (neighborBondAngles.size() == 3) {
206
    // three coordinated
207
0
    auto angleIt = neighborBondAngles.begin();
208
0
    ++angleIt;  // the first is the 0 (or reference bond - we will ignore
209
                // that
210
0
    double angle1 = (*angleIt);
211
0
    ++angleIt;
212
0
    double angle2 = (*angleIt);
213
0
    constexpr double angleTol =
214
0
        M_PI * 1.9 / 180.;  // just under 2 degrees tolerance, which is what we
215
                            // use when perceiving T-shaped geometries
216
0
    if (angle2 - angle1 >= (M_PI - angleTol)) {
217
      // we have the above situation
218
0
      nSwaps++;
219
0
    }
220
0
  }
221
222
#ifdef VERBOSE_STEREOCHEM
223
  BOOST_LOG(rdDebugLog) << "--------- " << nSwaps << std::endl;
224
  std::copy(neighborBondIndices.begin(), neighborBondIndices.end(),
225
            std::ostream_iterator<int>(BOOST_LOG(rdDebugLog), " "));
226
  BOOST_LOG(rdDebugLog) << std::endl;
227
  std::copy(neighborBondAngles.begin(), neighborBondAngles.end(),
228
            std::ostream_iterator<double>(BOOST_LOG(rdDebugLog), " "));
229
  BOOST_LOG(rdDebugLog) << std::endl;
230
#endif
231
0
  if (chiralType == Atom::CHI_TETRAHEDRAL_CCW) {
232
0
    if (nSwaps % 2 == 1) {
233
0
      res = Bond::BEGINDASH;
234
0
    } else {
235
0
      res = Bond::BEGINWEDGE;
236
0
    }
237
0
  } else {
238
0
    if (nSwaps % 2 == 1) {
239
0
      res = Bond::BEGINWEDGE;
240
0
    } else {
241
0
      res = Bond::BEGINDASH;
242
0
    }
243
0
  }
244
245
0
  return res;
246
0
}
247
Bond::BondDir determineBondWedgeState(
248
    const Bond *bond,
249
    const std::map<int, std::unique_ptr<RDKit::Chirality::WedgeInfoBase>>
250
        &wedgeBonds,
251
0
    const Conformer *conf) {
252
0
  PRECONDITION(bond, "no bond");
253
0
  int bid = bond->getIdx();
254
0
  auto wbi = wedgeBonds.find(bid);
255
0
  if (wbi == wedgeBonds.end()) {
256
0
    return bond->getBondDir();
257
0
  }
258
259
0
  if (wbi->second->getType() ==
260
0
      Chirality::WedgeInfoType::WedgeInfoTypeAtropisomer) {
261
0
    return wbi->second->getDir();
262
0
  } else {
263
0
    return determineBondWedgeState(bond, wbi->second->getIdx(), conf);
264
0
  }
265
0
}
266
267
// Logic for two wedges at one atom (based on IUPAC stuff)
268
// - at least four neighbors
269
// - neighboring bonds get wedged
270
// - same rules for picking which one for first
271
// - not ring bonds (?)
272
273
// picks a bond for atom that we will wedge when we write the mol file
274
// returns idx of that bond.
275
int pickBondToWedge(
276
    const Atom *atom, const ROMol &mol, const INT_VECT &nChiralNbrs,
277
    const std::map<int, std::unique_ptr<Chirality::WedgeInfoBase>> &wedgeBonds,
278
0
    int noNbrs) {
279
  // here is what we are going to do
280
  // - at each chiral center look for a bond that is begins at the atom and
281
  //   is not yet picked to be wedged for a different chiral center, preferring
282
  //   bonds to Hs
283
  // - if we do not find a bond that begins at the chiral center - we will take
284
  //   the first bond that is not yet picked by any other chiral centers
285
  // we use the orders calculated above to determine which order to do the
286
  // wedging
287
288
  // we need ring information; make sure findSSSR has been called before
289
  // if not call now
290
0
  if (!mol.getRingInfo()->isSssrOrBetter()) {
291
0
    MolOps::findSSSR(mol);
292
0
  }
293
294
0
  std::vector<std::pair<int, int>> nbrScores;
295
0
  for (const auto bond : mol.atomBonds(atom)) {
296
    // can only wedge single bonds:
297
0
    if (bond->getBondType() != Bond::SINGLE) {
298
0
      continue;
299
0
    }
300
301
0
    int bid = bond->getIdx();
302
0
    if (wedgeBonds.find(bid) == wedgeBonds.end()) {
303
      // very strong preference for Hs:
304
0
      auto *oatom = bond->getOtherAtom(atom);
305
0
      if (oatom->getAtomicNum() == 1) {
306
0
        nbrScores.emplace_back(-1000000,
307
0
                               bid);  // lower than anything else can be
308
0
        continue;
309
0
      }
310
      // prefer lower atomic numbers with lower degrees and no specified
311
      // chirality:
312
0
      int nbrScore = oatom->getAtomicNum() + 100 * oatom->getDegree() +
313
0
                     1000 * ((oatom->getChiralTag() != Atom::CHI_UNSPECIFIED));
314
      // prefer neighbors that are nonchiral or have as few chiral neighbors
315
      // as possible:
316
0
      int oIdx = oatom->getIdx();
317
0
      if (nChiralNbrs[oIdx] < noNbrs) {
318
        // the counts are negative, so we have to subtract them off
319
0
        nbrScore -= 100000 * nChiralNbrs[oIdx];
320
0
      }
321
      // prefer bonds to non-ring atoms:
322
0
      nbrScore += 10000 * mol.getRingInfo()->numAtomRings(oIdx);
323
      // prefer non-ring bonds;
324
0
      nbrScore += 20000 * mol.getRingInfo()->numBondRings(bid);
325
      // prefer bonds to atoms which don't have a double bond from them
326
0
      auto [hasDoubleBond, hasKnownDoubleBond, hasAnyDoubleBond] =
327
0
          getDoubleBondPresence(mol, *oatom);
328
0
      nbrScore += 11000 * hasDoubleBond;
329
0
      nbrScore += 12000 * hasKnownDoubleBond;
330
0
      nbrScore += 23000 * hasAnyDoubleBond;
331
332
      // if at all possible, do not go to marked attachment points
333
      // since they may well be removed when we write a mol block
334
0
      if (oatom->hasProp(common_properties::_fromAttachPoint)) {
335
0
        nbrScore += 500000;
336
0
      }
337
      // std::cerr << "    nrbScore: " << idx << " - " << oIdx << " : "
338
      //           << nbrScore << " nChiralNbrs: " << nChiralNbrs[oIdx]
339
      //           << std::endl;
340
0
      nbrScores.emplace_back(nbrScore, bid);
341
0
    }
342
0
  }
343
  // There's still one situation where this whole thing can fail: an unlucky
344
  // situation where all neighbors of all neighbors of an atom are chiral
345
  // and that atom ends up being the last one picked for stereochem
346
  // assignment. This also happens in cases where the chiral atom doesn't
347
  // have all of its neighbors (like when working with partially sanitized
348
  // fragments)
349
  //
350
  // We'll bail here by returning -1
351
0
  if (nbrScores.empty()) {
352
0
    return -1;
353
0
  }
354
0
  auto minPr = std::min_element(nbrScores.begin(), nbrScores.end());
355
0
  return minPr->second;
356
0
}
357
358
}  // namespace detail
359
360
// returns map of bondIdx -> bond begin atom for those bonds that
361
// need wedging.
362
363
std::map<int, std::unique_ptr<Chirality::WedgeInfoBase>> pickBondsToWedge(
364
0
    const ROMol &mol, const BondWedgingParameters *params) {
365
0
  const Conformer *conf = nullptr;
366
0
  if (mol.getNumConformers()) {
367
0
    conf = &mol.getConformer();
368
0
  }
369
370
0
  return pickBondsToWedge(mol, params, conf);
371
0
}
372
373
std::map<int, std::unique_ptr<Chirality::WedgeInfoBase>> pickBondsToWedge(
374
    const ROMol &mol, const BondWedgingParameters *params,
375
0
    const Conformer *conf) {
376
0
  if (!params) {
377
0
    params = &defaultWedgingParams;
378
0
  }
379
0
  std::vector<unsigned int> indices(mol.getNumAtoms());
380
0
  std::iota(indices.begin(), indices.end(), 0);
381
0
  static int noNbrs = 100;
382
0
  auto [chiNbrs, nChiralNbrs] = detail::countChiralNbrs(mol, noNbrs);
383
0
  if (chiNbrs) {
384
0
    std::sort(indices.begin(), indices.end(),
385
0
              [&nChiralNbrs = nChiralNbrs](auto i1, auto i2) {
386
0
                return nChiralNbrs[i1] < nChiralNbrs[i2];
387
0
              });
388
0
  }
389
0
  std::map<int, std::unique_ptr<Chirality::WedgeInfoBase>> wedgeInfo;
390
0
  for (auto idx : indices) {
391
0
    if (nChiralNbrs[idx] > noNbrs) {
392
      // std::cerr << " SKIPPING2: " << idx << std::endl;
393
0
      continue;  // already have a wedged bond here
394
0
    }
395
0
    auto atom = mol.getAtomWithIdx(idx);
396
0
    auto type = atom->getChiralTag();
397
    // the indices are ordered such that all chiral atoms come first. If
398
    // this has no chiral flag, we can stop the whole loop:
399
0
    if (type != Atom::CHI_TETRAHEDRAL_CW && type != Atom::CHI_TETRAHEDRAL_CCW) {
400
0
      break;
401
0
    }
402
0
    auto bnd1 =
403
0
        detail::pickBondToWedge(atom, mol, nChiralNbrs, wedgeInfo, noNbrs);
404
0
    if (bnd1 >= 0) {
405
0
      auto wi = std::unique_ptr<RDKit::Chirality::WedgeInfoChiral>(
406
0
          new RDKit::Chirality::WedgeInfoChiral(idx));
407
0
      wedgeInfo[bnd1] = std::move(wi);
408
0
    }
409
0
  }
410
0
  RDKit::Atropisomers::wedgeBondsFromAtropisomers(mol, conf, wedgeInfo);
411
412
0
  return wedgeInfo;
413
0
}
414
415
namespace {
416
// conditions here:
417
// 1. only degree four atoms (IUPAC)
418
// 2. no ring bonds (IUPAC)
419
// 3. not to chiral atoms (general IUPAC wedging rule)
420
void addSecondWedgeAroundAtom(ROMol &mol, Bond *refBond,
421
0
                              const Conformer *conf) {
422
0
  PRECONDITION(refBond, "no reference bond provided");
423
0
  PRECONDITION(conf, "no conformer provided");
424
0
  auto atom = refBond->getBeginAtom();
425
  // we only do degree four atoms (per IUPAC recommendation)
426
0
  if (atom->getDegree() < 4) {
427
0
    return;
428
0
  }
429
0
  auto aloc = conf->getAtomPos(atom->getIdx());
430
0
  aloc.z = 0.0;
431
0
  auto refVect = conf->getAtomPos(refBond->getEndAtomIdx());
432
0
  refVect.z = 0.0;
433
0
  refVect = aloc.directionVector(refVect);
434
0
  double minAngle = 10000.0;
435
0
  unsigned int bestDegree = 100;
436
0
  Bond *bondToWedge = nullptr;
437
0
  for (auto bond : mol.atomBonds(atom)) {
438
0
    if (bond == refBond || bond->getBondType() != Bond::BondType::SINGLE ||
439
0
        bond->getBondDir() != Bond::BondDir::NONE ||
440
0
        bond->getOtherAtom(atom)->getChiralTag() !=
441
0
            Atom::ChiralType::CHI_UNSPECIFIED ||
442
0
        mol.getRingInfo()->numBondRings(bond->getIdx())) {
443
0
      continue;
444
0
    }
445
446
    // FIX: There's more checking required here
447
448
0
    auto bVect = conf->getAtomPos(bond->getOtherAtomIdx(atom->getIdx()));
449
0
    bVect.z = 0.0;
450
0
    bVect = aloc.directionVector(bVect);
451
0
    auto angle = refVect.angleTo(bVect);
452
0
    if ((angle - minAngle) < 5 * M_PI / 180 &&
453
0
        bond->getOtherAtom(atom)->getDegree() <= bestDegree) {
454
0
      bondToWedge = bond;
455
0
      minAngle = angle;
456
0
      bestDegree = bond->getOtherAtom(atom)->getDegree();
457
0
    }
458
0
  }
459
  // if we got a bond and the angle is < 120 degrees (quasi-arbitrary)
460
0
  if (bondToWedge && minAngle < 2 * M_PI / 3) {
461
0
    bondToWedge->setBondDir(refBond->getBondDir() == Bond::BondDir::BEGINDASH
462
0
                                ? Bond::BondDir::BEGINWEDGE
463
0
                                : Bond::BondDir::BEGINDASH);
464
0
    if (bondToWedge->getBeginAtomIdx() != atom->getIdx()) {
465
0
      bondToWedge->setEndAtomIdx(bondToWedge->getBeginAtomIdx());
466
0
      bondToWedge->setBeginAtomIdx(atom->getIdx());
467
0
    }
468
0
  }
469
0
}
470
}  // namespace
471
472
void wedgeMolBonds(ROMol &mol, const Conformer *conf,
473
0
                   const BondWedgingParameters *params) {
474
0
  PRECONDITION(conf || mol.getNumConformers(), "no conformer available");
475
0
  if (!conf) {
476
0
    conf = &mol.getConformer();
477
0
  }
478
0
  if (!params) {
479
0
    params = &defaultWedgingParams;
480
0
  }
481
  // we need ring info
482
0
  if (!mol.getRingInfo() || !mol.getRingInfo()->isSssrOrBetter()) {
483
0
    MolOps::findSSSR(mol);
484
0
  }
485
486
0
  auto wedgeBonds = Chirality::pickBondsToWedge(mol, params, conf);
487
488
  // loop over the bonds we need to wedge:
489
0
  for (const auto &[wbi, wedgeInfo] : wedgeBonds) {
490
0
    if (wedgeInfo->getType() ==
491
0
        Chirality::WedgeInfoType::WedgeInfoTypeAtropisomer) {
492
0
      mol.getBondWithIdx(wbi)->setBondDir(wedgeInfo->getDir());
493
0
    } else {  // chiral atom needs wedging
494
0
      auto bond = mol.getBondWithIdx(wbi);
495
0
      auto dir =
496
0
          detail::determineBondWedgeState(bond, wedgeInfo->getIdx(), conf);
497
0
      if (dir == Bond::BEGINWEDGE || dir == Bond::BEGINDASH) {
498
0
        bond->setBondDir(dir);
499
500
        // it is possible that this
501
        // wedging was determined by a chiral atom at the end of the
502
        // bond (instead of at the beginning). In this case we need to
503
        // reverse the begin and end atoms for the bond
504
0
        if (static_cast<unsigned int>(wedgeInfo->getIdx()) !=
505
0
            bond->getBeginAtomIdx()) {
506
0
          auto tmp = bond->getBeginAtomIdx();
507
0
          bond->setBeginAtomIdx(bond->getEndAtomIdx());
508
0
          bond->setEndAtomIdx(tmp);
509
0
        }
510
0
      }
511
0
    }
512
0
  }
513
0
  if (params->wedgeTwoBondsIfPossible) {
514
    // This should probably check whether the existing wedge
515
    // is in agreement with the chiral tag on the atom.
516
517
0
    for (const auto atom : mol.atoms()) {
518
0
      if (atom->getChiralTag() != Atom::CHI_TETRAHEDRAL_CW &&
519
0
          atom->getChiralTag() != Atom::CHI_TETRAHEDRAL_CCW) {
520
0
        continue;
521
0
      }
522
0
      unsigned numWedged = 0;
523
0
      Bond *wedgedBond = nullptr;
524
0
      for (const auto bond : mol.atomBonds(atom)) {
525
0
        if (bond->getBeginAtom() == atom &&
526
0
            bond->getBondType() == Bond::SINGLE &&
527
0
            (bond->getBondDir() == Bond::BEGINWEDGE ||
528
0
             bond->getBondDir() == Bond::BEGINDASH)) {
529
0
          ++numWedged;
530
0
          wedgedBond = bond;
531
0
        }
532
0
      }
533
0
      if (numWedged == 1) {
534
0
        addSecondWedgeAroundAtom(mol, wedgedBond, conf);
535
0
      }
536
0
    }
537
0
  }
538
0
}
539
540
0
void wedgeBond(Bond *bond, unsigned int fromAtomIdx, const Conformer *conf) {
541
0
  PRECONDITION(bond, "no bond");
542
0
  PRECONDITION(conf, "no conformer");
543
0
  PRECONDITION(&conf->getOwningMol() == &bond->getOwningMol(),
544
0
               "bond and conformer do not belong to same molecule");
545
0
  if (bond->getBondType() != Bond::SINGLE) {
546
0
    return;
547
0
  }
548
0
  Bond::BondDir dir = detail::determineBondWedgeState(bond, fromAtomIdx, conf);
549
0
  if (dir == Bond::BEGINWEDGE || dir == Bond::BEGINDASH) {
550
0
    bond->setBondDir(dir);
551
0
  }
552
0
}
553
554
0
bool wedgingHasChirality(const ROMol &mol, const Bond *b) {
555
  // see if this wedge should have wedging.  It can if the begin atom
556
  // has chirality or the begin atom is the part of an atropisomer bond
557
558
0
  Atom *atom = b->getBeginAtom();
559
0
  if (atom->getChiralTag() != Atom::CHI_UNSPECIFIED) {
560
0
    return true;
561
0
  }
562
563
  // see if this is part of an atropisomer bond
564
0
  for (const auto bond2 : mol.atomBonds(atom)) {
565
0
    if (bond2->getBondType() == Bond::BondType::SINGLE) {
566
0
      if (bond2 == b) {
567
0
        continue;  // a bond is NOT its own neighbor
568
0
      }
569
0
      if (bond2->getStereo() == Bond::STEREOATROPCCW ||
570
0
          bond2->getStereo() == Bond::STEREOATROPCW) {
571
0
        return true;
572
0
      }
573
0
    }
574
0
  }
575
576
0
  return false;
577
0
}
578
579
void reapplyMolBlockWedging(ROMol &mol, bool allBondTypes,
580
0
                            bool verify) {
581
0
  MolOps::clearDirFlags(mol, true);
582
0
  for (auto b : mol.bonds()) {
583
0
    int explicit_unknown_stereo = -1;
584
0
    if (b->getPropIfPresent<int>(common_properties::_UnknownStereo,
585
0
                                 explicit_unknown_stereo) &&
586
0
        explicit_unknown_stereo) {
587
0
      b->setBondDir(Bond::UNKNOWN);
588
0
    }
589
590
    // if the bond is not a double bond, and it is not connected to an
591
    // atropisomer bond AND if the start atom  is not chiral, we will skip it -
592
    // it should not have a wedge or dash
593
594
0
    if (verify &&
595
0
        (!canHaveDirection(*b) || !wedgingHasChirality(mol, b))) {
596
0
      continue;
597
0
    }
598
599
0
    int bond_dir = -1;
600
0
    if (b->getPropIfPresent<int>(common_properties::_MolFileBondStereo,
601
0
                                 bond_dir)) {
602
0
      if (allBondTypes || canHaveDirection(*b)) {
603
0
        if (bond_dir == 1) {
604
0
          b->setBondDir(Bond::BEGINWEDGE);
605
0
        } else if (bond_dir == 6) {
606
0
          b->setBondDir(Bond::BEGINDASH);
607
0
        }
608
0
      }
609
0
      if (b->getBondType() == Bond::DOUBLE) {
610
0
        if (bond_dir == 0 && b->getStereo() == Bond::STEREOANY) {
611
0
          b->setBondDir(Bond::NONE);
612
0
          b->setStereo(Bond::STEREONONE);
613
0
        } else if (bond_dir == 3) {
614
0
          b->setBondDir(Bond::EITHERDOUBLE);
615
0
          b->setStereo(Bond::STEREOANY);
616
0
        }
617
0
      }
618
0
    }
619
0
    int cfg = -1;
620
0
    b->getPropIfPresent<int>(common_properties::_MolFileBondCfg, cfg);
621
0
    switch (cfg) {
622
0
      case 1:
623
0
        if (allBondTypes || canHaveDirection(*b)) {
624
0
          b->setBondDir(Bond::BEGINWEDGE);
625
0
        }
626
0
        break;
627
0
      case 2:
628
0
        if (canHaveDirection(*b)) {
629
0
          b->setBondDir(Bond::UNKNOWN);
630
0
        } else if (b->getBondType() == Bond::DOUBLE) {
631
0
          b->setBondDir(Bond::EITHERDOUBLE);
632
0
          b->setStereo(Bond::STEREOANY);
633
0
        }
634
0
        break;
635
0
      case 3:
636
0
        if (allBondTypes || canHaveDirection(*b)) {
637
0
          b->setBondDir(Bond::BEGINDASH);
638
0
        }
639
0
        break;
640
0
      case 0:
641
0
      case -1:
642
0
        if (bond_dir == -1 && b->getBondType() == Bond::DOUBLE &&
643
0
            b->getStereo() == Bond::STEREOANY) {
644
0
          b->setBondDir(Bond::NONE);
645
0
          b->setStereo(Bond::STEREONONE);
646
0
        }
647
0
    }
648
0
  }
649
0
}
650
651
0
void clearMolBlockWedgingInfo(ROMol &mol) {
652
0
  for (auto b : mol.bonds()) {
653
0
    b->clearProp(common_properties::_MolFileBondStereo);
654
0
    b->clearProp(common_properties::_MolFileBondCfg);
655
0
  }
656
0
}
657
658
0
void invertMolBlockWedgingInfo(ROMol &mol) {
659
0
  for (auto b : mol.bonds()) {
660
0
    int bond_dir = -1;
661
0
    if (b->getPropIfPresent<int>(common_properties::_MolFileBondStereo,
662
0
                                 bond_dir)) {
663
0
      if (bond_dir == 1) {
664
0
        b->setProp<int>(common_properties::_MolFileBondStereo, 6);
665
0
      } else if (bond_dir == 6) {
666
0
        b->setProp<int>(common_properties::_MolFileBondStereo, 1);
667
0
      }
668
0
    }
669
0
    int cfg = -1;
670
0
    if (b->getPropIfPresent<int>(common_properties::_MolFileBondCfg, cfg)) {
671
0
      if (cfg == 1) {
672
0
        b->setProp<int>(common_properties::_MolFileBondCfg, 3);
673
0
      } else if (cfg == 3) {
674
0
        b->setProp<int>(common_properties::_MolFileBondCfg, 1);
675
0
      }
676
0
    }
677
0
  }
678
0
}
679
680
}  // namespace Chirality
681
}  // namespace RDKit