Coverage Report

Created: 2026-11-12 07:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/rdkit/Code/GraphMol/NontetrahedralStereo.cpp
Line
Count
Source
1
//
2
//  Copyright (C) 2022 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 <RDGeneral/types.h>
12
#include <RDGeneral/Invariant.h>
13
#include <RDGeneral/RDLog.h>
14
15
#include "Chirality.h"
16
17
namespace RDKit {
18
19
namespace Chirality {
20
21
constexpr unsigned char swap_squareplanar_table[4][6] = {
22
    // 0  0  0  1  1  2
23
    // 1  2  3  2  3  3
24
    {0, 0, 0, 0, 0, 0},
25
    {3, 1, 2, 2, 1, 3},  // SP1
26
    {2, 3, 1, 1, 3, 2},  // SP2
27
    {1, 2, 3, 3, 2, 1}   // SP3
28
};
29
30
constexpr unsigned char swap_trigonalbipyramidal_table[21][10] = {
31
    // 0   0   0   0   1   1   1   2   2   3
32
    // 1   2   3   4   2   3   4   3   4   4
33
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
34
    {9, 20, 17, 2, 2, 2, 7, 2, 6, 3},        // TB1
35
    {11, 15, 18, 1, 1, 1, 8, 1, 5, 4},       // TB2
36
    {10, 19, 4, 18, 4, 8, 4, 5, 4, 1},       // TB3
37
    {12, 16, 3, 17, 3, 7, 3, 6, 3, 2},       // TB4
38
    {13, 6, 16, 20, 7, 6, 6, 3, 2, 6},       // TB5
39
    {14, 5, 19, 15, 8, 5, 5, 4, 1, 5},       // TB6
40
    {8, 14, 10, 11, 5, 4, 1, 8, 8, 8},       // TB7
41
    {7, 13, 12, 9, 6, 3, 2, 7, 7, 7},        // TB8
42
    {1, 11, 11, 8, 15, 18, 11, 11, 14, 10},  // TB9
43
    {3, 12, 7, 12, 16, 12, 17, 13, 12, 9},   // TB10
44
    {2, 9, 9, 7, 20, 17, 9, 9, 13, 12},      // TB11
45
    {4, 10, 8, 10, 19, 10, 18, 14, 10, 11},  // TB12
46
    {5, 8, 14, 14, 14, 19, 15, 10, 11, 14},  // TB13
47
    {6, 7, 13, 13, 13, 16, 20, 12, 9, 13},   // TB14
48
    {20, 2, 20, 6, 9, 20, 13, 17, 20, 16},   // TB15
49
    {19, 4, 5, 19, 10, 14, 19, 19, 18, 15},  // TB16
50
    {18, 18, 1, 4, 18, 11, 10, 15, 19, 18},  // TB17
51
    {17, 17, 2, 3, 17, 9, 12, 20, 16, 17},   // TB18
52
    {16, 3, 6, 16, 12, 13, 16, 16, 17, 20},  // TB19
53
    {15, 1, 15, 5, 11, 15, 14, 18, 15, 19}   // TB20
54
};
55
56
constexpr unsigned char swap_octahedral_table[31][15] = {
57
    // 0   0   0   0   0   1   1   1   1   2   2   2   3   3   4
58
    // 1   2   3   4   5   2   3   4   5   3   4   5   4   5   5
59
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
60
    {17, 16, 30, 21, 2, 14, 2, 10, 25, 8, 2, 22, 4, 7, 3},       // OH1
61
    {7, 3, 25, 22, 1, 4, 1, 8, 30, 10, 1, 21, 14, 17, 16},       // OH2
62
    {18, 2, 29, 16, 22, 15, 16, 26, 11, 9, 21, 16, 6, 5, 1},     // OH3
63
    {15, 18, 19, 28, 14, 2, 8, 14, 27, 14, 10, 24, 1, 6, 5},     // OH4
64
    {14, 17, 20, 15, 27, 16, 9, 28, 15, 15, 23, 11, 7, 3, 4},    // OH5
65
    {16, 14, 18, 26, 24, 17, 29, 18, 13, 19, 12, 18, 3, 4, 7},   // OH6
66
    {2, 15, 17, 23, 25, 18, 30, 12, 17, 20, 17, 13, 5, 1, 6},    // OH7
67
    {23, 26, 11, 12, 10, 10, 4, 2, 29, 1, 14, 20, 10, 13, 9},    // OH8
68
    {24, 25, 10, 11, 13, 11, 5, 30, 16, 3, 19, 15, 12, 11, 8},   // OH9
69
    {20, 29, 9, 13, 8, 8, 14, 1, 26, 2, 4, 23, 8, 12, 11},       // OH10
70
    {19, 30, 8, 9, 12, 9, 15, 25, 3, 16, 24, 5, 13, 9, 10},      // OH11
71
    {22, 27, 13, 8, 11, 13, 28, 7, 18, 21, 6, 17, 9, 10, 13},    // OH12
72
    {21, 28, 12, 10, 9, 12, 27, 17, 6, 22, 18, 7, 11, 8, 12},    // OH13
73
    {5, 6, 24, 27, 4, 1, 10, 4, 28, 4, 8, 19, 2, 18, 15},        // OH14
74
    {4, 7, 23, 5, 28, 3, 11, 27, 5, 5, 20, 9, 17, 16, 14},       // OH15
75
    {6, 1, 26, 3, 21, 5, 3, 29, 9, 11, 22, 3, 18, 15, 2},        // OH16
76
    {1, 5, 7, 20, 30, 6, 25, 13, 7, 23, 7, 12, 15, 2, 18},       // OH17
77
    {3, 4, 6, 29, 19, 7, 26, 6, 12, 24, 13, 6, 16, 14, 17},      // OH18
78
    {11, 24, 4, 30, 18, 25, 23, 24, 22, 6, 9, 14, 21, 24, 20},   // OH19
79
    {10, 23, 5, 17, 29, 26, 24, 21, 23, 7, 15, 8, 23, 22, 19},   // OH20
80
    {13, 22, 28, 1, 16, 27, 22, 20, 24, 12, 3, 2, 19, 23, 22},   // OH21
81
    {12, 21, 27, 2, 3, 28, 21, 23, 19, 13, 16, 1, 24, 20, 21},   // OH22
82
    {8, 20, 15, 7, 26, 29, 19, 22, 20, 17, 5, 10, 20, 21, 24},   // OH23
83
    {9, 19, 14, 25, 6, 30, 20, 19, 21, 18, 11, 4, 22, 19, 23},   // OH24
84
    {30, 9, 2, 24, 7, 19, 17, 11, 1, 29, 30, 28, 27, 30, 26},    // OH25
85
    {29, 8, 16, 6, 23, 20, 18, 3, 10, 30, 27, 29, 29, 28, 25},   // OH26
86
    {28, 12, 22, 14, 5, 21, 13, 15, 4, 28, 26, 30, 25, 29, 28},  // OH27
87
    {27, 13, 21, 4, 15, 22, 12, 5, 14, 27, 29, 25, 30, 26, 27},  // OH28
88
    {26, 10, 3, 18, 20, 23, 6, 16, 8, 25, 28, 26, 26, 27, 30},   // OH29
89
    {25, 11, 1, 19, 17, 24, 7, 9, 2, 26, 25, 27, 28, 25, 29}     // OH30
90
};
91
92
static unsigned int swap_squareplanar(unsigned int perm, unsigned int x,
93
1.89k
                                      unsigned int y) {
94
1.89k
  constexpr unsigned int offset[3] = {0, 2, 3};
95
1.89k
  unsigned int swapidx;
96
1.89k
  if (x == y) {
97
0
    return perm;
98
0
  }
99
1.89k
  if (x < y) {
100
1.89k
    if (y > 3) {
101
0
      return 0;
102
0
    }
103
1.89k
    swapidx = offset[x] + (y - 1);
104
1.89k
  } else /* x > y */ {
105
0
    if (x > 3) {
106
0
      return 0;
107
0
    }
108
0
    swapidx = offset[y] + (x - 1);
109
0
  }
110
1.89k
  return perm < 4 ? swap_squareplanar_table[perm][swapidx] : 0;
111
1.89k
}
112
113
static unsigned int swap_trigonalbipyramidal(unsigned int perm, unsigned int x,
114
929
                                             unsigned int y) {
115
929
  constexpr unsigned int offset[4] = {0, 3, 5, 6};
116
929
  unsigned int swapidx;
117
929
  if (x == y) {
118
0
    return perm;
119
0
  }
120
929
  if (x < y) {
121
929
    if (y > 4) {
122
0
      return 0;
123
0
    }
124
929
    swapidx = offset[x] + (y - 1);
125
929
  } else /* x > y */ {
126
0
    if (x > 4) {
127
0
      return 0;
128
0
    }
129
0
    swapidx = offset[y] + (x - 1);
130
0
  }
131
929
  return perm < 21 ? swap_trigonalbipyramidal_table[perm][swapidx] : 0;
132
929
}
133
134
static unsigned int swap_octahedral(unsigned int perm, unsigned int x,
135
726
                                    unsigned int y) {
136
726
  constexpr unsigned int offset[5] = {0, 4, 7, 9, 10};
137
726
  unsigned int swapidx;
138
726
  if (x == y) {
139
0
    return perm;
140
0
  }
141
726
  if (x < y) {
142
726
    if (y > 5) {
143
0
      return 0;
144
0
    }
145
726
    swapidx = offset[x] + (y - 1);
146
726
  } else /* x > y */ {
147
0
    if (x > 5) {
148
0
      return 0;
149
0
    }
150
0
    swapidx = offset[y] + (x - 1);
151
0
  }
152
726
  return perm < 31 ? swap_octahedral_table[perm][swapidx] : 0;
153
726
}
154
155
constexpr unsigned char squareplanar_across[4][4] = {
156
    {4, 4, 4, 4},  //
157
    {2, 3, 0, 1},  // SP1
158
    {1, 0, 3, 2},  // SP2
159
    {3, 2, 1, 0}   // SP3
160
};
161
162
constexpr unsigned char trigonalbipyramidal_across[21][5] = {
163
    {5, 5, 5, 5, 5},  //
164
    {4, 5, 5, 5, 0},  // TB1
165
    {4, 5, 5, 5, 0},  // TB2
166
    {3, 5, 5, 0, 5},  // TB3
167
    {3, 5, 5, 0, 5},  // TB4
168
    {2, 5, 0, 5, 5},  // TB5
169
    {2, 5, 0, 5, 5},  // TB6
170
    {1, 0, 5, 5, 5},  // TB7
171
    {1, 0, 5, 5, 5},  // TB8
172
    {5, 4, 5, 5, 1},  // TB9
173
    {5, 3, 5, 1, 5},  // TB10
174
    {5, 4, 5, 5, 1},  // TB11
175
    {5, 3, 5, 1, 5},  // TB12
176
    {5, 2, 1, 5, 5},  // TB13
177
    {5, 2, 1, 5, 5},  // TB14
178
    {5, 5, 4, 5, 2},  // TB15
179
    {5, 5, 3, 2, 5},  // TB16
180
    {5, 5, 5, 4, 3},  // TB17
181
    {5, 5, 5, 4, 3},  // TB18
182
    {5, 5, 3, 2, 5},  // TB19
183
    {5, 5, 4, 5, 2}   // TB20
184
};
185
186
constexpr unsigned char octahedral_across[31][6] = {
187
    {6, 6, 6, 6, 6, 6},  //
188
    {5, 3, 4, 1, 2, 0},  // OH1
189
    {5, 3, 4, 1, 2, 0},  // OH2
190
    {4, 3, 5, 1, 0, 2},  // OH3
191
    {5, 4, 3, 2, 1, 0},  // OH4
192
    {4, 5, 3, 2, 0, 1},  // OH5
193
    {3, 4, 5, 0, 1, 2},  // OH6
194
    {3, 5, 4, 0, 2, 1},  // OH7
195
    {5, 2, 1, 4, 3, 0},  // OH8
196
    {4, 2, 1, 5, 0, 3},  // OH9
197
    {5, 2, 1, 4, 3, 0},  // OH10
198
    {4, 2, 1, 5, 0, 3},  // OH11
199
    {3, 2, 1, 0, 5, 4},  // OH12
200
    {3, 2, 1, 0, 5, 4},  // OH13
201
    {5, 4, 3, 2, 1, 0},  // OH14
202
    {4, 5, 3, 2, 0, 1},  // OH15
203
    {4, 3, 5, 1, 0, 2},  // OH16
204
    {3, 5, 4, 0, 2, 1},  // OH17
205
    {3, 4, 5, 0, 1, 2},  // OH18
206
    {2, 4, 0, 5, 1, 3},  // OH19
207
    {2, 5, 0, 4, 3, 1},  // OH20
208
    {2, 3, 0, 1, 5, 4},  // OH21
209
    {2, 3, 0, 1, 5, 4},  // OH22
210
    {2, 5, 0, 4, 3, 1},  // OH23
211
    {2, 4, 0, 5, 1, 3},  // OH24
212
    {1, 0, 4, 5, 2, 3},  // OH25
213
    {1, 0, 5, 4, 3, 2},  // OH26
214
    {1, 0, 3, 2, 5, 4},  // OH27
215
    {1, 0, 3, 2, 5, 4},  // OH28
216
    {1, 0, 5, 4, 3, 2},  // OH29
217
    {1, 0, 4, 5, 2, 3}   // OH30
218
};
219
220
constexpr unsigned char trigonalbipyramidal_axial[21][2] = {
221
    {5, 5},  //
222
    {0, 4},  // TB1
223
    {0, 4},  // TB2
224
    {0, 3},  // TB3
225
    {0, 3},  // TB4
226
    {0, 2},  // TB5
227
    {0, 2},  // TB6
228
    {0, 1},  // TB7
229
    {0, 1},  // TB8
230
    {1, 4},  // TB9
231
    {1, 4},  // TB10
232
    {1, 3},  // TB11
233
    {1, 3},  // TB12
234
    {1, 2},  // TB13
235
    {1, 2},  // TB14
236
    {2, 4},  // TB15
237
    {2, 3},  // TB16
238
    {3, 4},  // TB17
239
    {3, 4},  // TB18
240
    {2, 3},  // TB19
241
    {2, 4}   // TB20
242
};
243
244
0
int isTrigonalBipyramidalAxialBond(const Atom *cen, const Bond *qry) {
245
0
  PRECONDITION(cen, "bad center pointer");
246
0
  PRECONDITION(qry, "bad query pointer");
247
0
  PRECONDITION(cen->hasOwningMol() && qry->hasOwningMol(), "no owning mol");
248
0
  PRECONDITION(&cen->getOwningMol() == &qry->getOwningMol(),
249
0
               "center and query must come from the same molecule");
250
0
  if (cen->getDegree() > 5 ||
251
0
      cen->getChiralTag() != Atom::CHI_TRIGONALBIPYRAMIDAL) {
252
0
    return false;
253
0
  }
254
0
  unsigned int perm = 0;
255
0
  cen->getPropIfPresent(RDKit::common_properties::_chiralPermutation, perm);
256
0
  if (perm == 0 || perm > 20) {
257
0
    return 0;
258
0
  }
259
260
0
  unsigned int count = 0;
261
0
  for (const auto bnd : cen->getOwningMol().atomBonds(cen)) {
262
0
    if (bnd == qry) {
263
0
      if (count == trigonalbipyramidal_axial[perm][0]) {
264
0
        return 1;
265
0
      }
266
0
      if (count == trigonalbipyramidal_axial[perm][1]) {
267
0
        return -1;
268
0
      }
269
0
      return 0;
270
0
    }
271
0
    count++;
272
0
  }
273
0
  return 0;
274
0
}
275
276
0
int isTrigonalBipyramidalAxialAtom(const Atom *cen, const Atom *qry) {
277
0
  PRECONDITION(cen, "bad center pointer");
278
0
  PRECONDITION(qry, "bad query pointer");
279
0
  PRECONDITION(cen->hasOwningMol(), "no owning mol");
280
0
  auto bnd =
281
0
      cen->getOwningMol().getBondBetweenAtoms(cen->getIdx(), qry->getIdx());
282
0
  if (!bnd) {
283
0
    return 0;
284
0
  }
285
0
  return isTrigonalBipyramidalAxialBond(cen, bnd);
286
0
}
287
288
5.18k
unsigned int getMaxNbors(const Atom::ChiralType tag) {
289
5.18k
  switch (tag) {
290
0
    case Atom::CHI_TETRAHEDRAL_CW:
291
0
    case Atom::CHI_TETRAHEDRAL_CCW:
292
0
    case Atom::CHI_TETRAHEDRAL:  // fall through
293
0
      return 4;
294
0
    case Atom::CHI_ALLENE:
295
0
      return 2;  // not used other than SMI/SMA parsers?
296
3.57k
    case Atom::CHI_SQUAREPLANAR:
297
3.57k
      return 4;
298
862
    case Atom::CHI_TRIGONALBIPYRAMIDAL:
299
862
      return 5;
300
747
    case Atom::CHI_OCTAHEDRAL:
301
747
      return 6;
302
0
    default:
303
0
      BOOST_LOG(rdWarningLog)
304
0
          << "Warning: unexpected chiral tag getMaxNbors(): " << tag
305
0
          << std::endl;
306
0
      return 0;
307
5.18k
  }
308
5.18k
}
309
310
0
Bond *getChiralAcrossBond(const Atom *cen, const Bond *qry) {
311
0
  PRECONDITION(cen, "bad center pointer");
312
0
  PRECONDITION(qry, "bad query pointer");
313
0
  PRECONDITION(cen->hasOwningMol() && qry->hasOwningMol(), "no owning mol");
314
0
  PRECONDITION(&cen->getOwningMol() == &qry->getOwningMol(),
315
0
               "center and query must come from the same molecule");
316
317
0
  Atom::ChiralType tag = cen->getChiralTag();
318
0
  unsigned int perm = 0;
319
0
  cen->getPropIfPresent(common_properties::_chiralPermutation, perm);
320
0
  if (!perm) {
321
0
    return nullptr;
322
0
  }
323
324
0
  auto &mol = cen->getOwningMol();
325
0
  unsigned int count = 0;
326
0
  Bond *ref[6];
327
0
  int found = -1;
328
329
0
  unsigned int ref_max = getMaxNbors(tag);
330
0
  for (auto bnd : mol.atomBonds(cen)) {
331
0
    if (count == ref_max) {
332
0
      return nullptr;
333
0
    }
334
0
    ref[count] = bnd;
335
0
    if (bnd == qry) {
336
0
      found = count;
337
0
    }
338
0
    count++;
339
0
  }
340
341
0
  if (found >= 0) {
342
0
    switch (tag) {
343
0
      case Atom::ChiralType::CHI_SQUAREPLANAR:
344
0
        if (perm <= 3) {
345
0
          found = squareplanar_across[perm][found];
346
0
        } else {
347
0
          found = 4;
348
0
        }
349
0
        break;
350
0
      case Atom::ChiralType::CHI_TRIGONALBIPYRAMIDAL:
351
0
        if (perm <= 20) {
352
0
          found = trigonalbipyramidal_across[perm][found];
353
0
        } else {
354
0
          found = 5;
355
0
        }
356
0
        break;
357
0
      case Atom::ChiralType::CHI_OCTAHEDRAL:
358
0
        if (perm <= 30) {
359
0
          found = octahedral_across[perm][found];
360
0
        } else {
361
0
          found = 6;
362
0
        }
363
0
        break;
364
0
      default:
365
0
        return nullptr;
366
0
    }
367
0
    if (static_cast<unsigned int>(found) < count) {
368
0
      return ref[found];
369
0
    }
370
0
  }
371
0
  return nullptr;
372
0
}
373
374
/* Alternate wrappers */
375
0
Bond *getChiralAcrossBond(const Atom *cen, const Atom *qry) {
376
0
  PRECONDITION(cen, "bad center pointer");
377
0
  PRECONDITION(qry, "bad query pointer");
378
0
  PRECONDITION(cen->hasOwningMol(), "no owning mol");
379
0
  auto bnd =
380
0
      cen->getOwningMol().getBondBetweenAtoms(cen->getIdx(), qry->getIdx());
381
0
  if (!bnd) {
382
0
    return nullptr;
383
0
  }
384
0
  return getChiralAcrossBond(cen, bnd);
385
0
}
386
387
0
Atom *getChiralAcrossAtom(const Atom *cen, const Bond *qry) {
388
0
  auto bnd = getChiralAcrossBond(cen, qry);
389
0
  return bnd ? bnd->getOtherAtom(cen) : nullptr;
390
0
}
391
392
0
Atom *getChiralAcrossAtom(const Atom *cen, const Atom *qry) {
393
0
  PRECONDITION(cen, "bad center pointer");
394
0
  PRECONDITION(qry, "bad query pointer");
395
0
  PRECONDITION(cen->hasOwningMol(), "no owning mol");
396
0
  auto bnd =
397
0
      cen->getOwningMol().getBondBetweenAtoms(cen->getIdx(), qry->getIdx());
398
0
  if (!bnd) {
399
0
    return nullptr;
400
0
  }
401
0
  bnd = getChiralAcrossBond(cen, bnd);
402
0
  return bnd ? bnd->getOtherAtom(cen) : nullptr;
403
0
}
404
405
double getIdealAngleBetweenLigands(const Atom *cen, const Atom *lig1,
406
0
                                   const Atom *lig2) {
407
0
  PRECONDITION(cen, "bad center pointer");
408
0
  PRECONDITION(lig1 && lig2, "bad ligand pointer");
409
0
  PRECONDITION(
410
0
      cen->hasOwningMol() && lig1->hasOwningMol() && lig2->hasOwningMol(),
411
0
      "no owning mol");
412
0
  PRECONDITION(&cen->getOwningMol() == &lig1->getOwningMol() &&
413
0
                   &cen->getOwningMol() == &lig2->getOwningMol(),
414
0
               "center and ligands must come from the same molecule");
415
0
  auto tag = cen->getChiralTag();
416
0
  switch (tag) {
417
0
    case Atom::ChiralType::CHI_SQUAREPLANAR:
418
0
    case Atom::ChiralType::CHI_OCTAHEDRAL:
419
0
      return getChiralAcrossAtom(cen, lig1) == lig2 ? 180 : 90;
420
0
      break;
421
0
    case Atom::ChiralType::CHI_TRIGONALBIPYRAMIDAL:
422
0
      if (getChiralAcrossAtom(cen, lig1) == lig2) {
423
        // both are axial
424
0
        return 180;
425
0
      } else if (isTrigonalBipyramidalAxialAtom(cen, lig1) ||
426
0
                 isTrigonalBipyramidalAxialAtom(cen, lig2)) {
427
        // one is axial, the other equatorial
428
0
        return 90;
429
0
      } else {
430
        // both axial
431
0
        return 120;
432
0
      }
433
0
      break;
434
0
    default:
435
0
      return 0;
436
0
  }
437
0
}
438
439
0
Bond *getTrigonalBipyramidalAxialBond(const Atom *cen, int axial) {
440
0
  PRECONDITION(cen, "bad center pointer");
441
0
  PRECONDITION(cen->hasOwningMol(), "no owning mol");
442
0
  if (cen->getChiralTag() != RDKit::Atom::ChiralType::CHI_TRIGONALBIPYRAMIDAL ||
443
0
      cen->getDegree() > 5) {
444
0
    return nullptr;
445
0
  }
446
447
0
  unsigned int perm = 0;
448
0
  cen->getPropIfPresent(RDKit::common_properties::_chiralPermutation, perm);
449
0
  if (perm == 0 || perm > 20) {
450
0
    return nullptr;
451
0
  }
452
453
0
  unsigned int idx = (axial != -1) ? trigonalbipyramidal_axial[perm][0]
454
0
                                   : trigonalbipyramidal_axial[perm][1];
455
456
0
  unsigned int count = 0;
457
0
  for (const auto bnd : cen->getOwningMol().atomBonds(cen)) {
458
0
    if (count == idx) {
459
0
      return bnd;
460
0
    }
461
0
    count++;
462
0
  }
463
0
  return nullptr;
464
0
}
465
466
0
Atom *getTrigonalBipyramidalAxialAtom(const Atom *cen, int axial) {
467
0
  PRECONDITION(cen, "bad center pointer");
468
0
  PRECONDITION(cen->hasOwningMol(), "no owning mol");
469
0
  auto bnd = getTrigonalBipyramidalAxialBond(cen, axial);
470
0
  return bnd ? bnd->getOtherAtom(cen) : nullptr;
471
0
}
472
10.1k
bool hasNonTetrahedralStereo(const Atom *cen) {
473
10.1k
  PRECONDITION(cen, "bad center pointer");
474
10.1k
  if (!cen->hasOwningMol()) {
475
0
    return false;
476
0
  }
477
10.1k
  auto tag = cen->getChiralTag();
478
10.1k
  return tag == Atom::ChiralType::CHI_SQUAREPLANAR ||
479
10.0k
         tag == Atom::ChiralType::CHI_TRIGONALBIPYRAMIDAL ||
480
9.83k
         tag == Atom::ChiralType::CHI_OCTAHEDRAL;
481
10.1k
}
482
483
unsigned int getChiralPermutation(const Atom *cen, const INT_LIST &probe,
484
5.18k
                                  bool inverse) {
485
5.18k
  PRECONDITION(cen, "bad center pointer");
486
5.18k
  PRECONDITION(cen->hasOwningMol(), "no owning mol");
487
488
5.18k
  int perm = -1;
489
5.18k
  if (!cen->getPropIfPresent(common_properties::_chiralPermutation, perm) ||
490
4.31k
      perm <= 0) {
491
1.06k
    return 0;
492
1.06k
  }
493
494
4.11k
  decltype(&swap_octahedral) swap_func = nullptr;
495
4.11k
  switch (cen->getChiralTag()) {
496
747
    case Atom::ChiralType::CHI_OCTAHEDRAL:
497
747
      if (probe.size() > 6) {
498
72
        return 0;
499
72
      }
500
675
      swap_func = swap_octahedral;
501
675
      break;
502
749
    case Atom::ChiralType::CHI_TRIGONALBIPYRAMIDAL:
503
749
      if (probe.size() > 5) {
504
348
        return 0;
505
348
      }
506
401
      swap_func = swap_trigonalbipyramidal;
507
401
      break;
508
2.62k
    case Atom::ChiralType::CHI_SQUAREPLANAR:
509
2.62k
      if (probe.size() > 4) {
510
617
        return 0;
511
617
      }
512
2.00k
      swap_func = swap_squareplanar;
513
2.00k
      break;
514
0
    default:
515
0
      break;
516
4.11k
  }
517
3.08k
  if (!swap_func) {
518
0
    return 0;
519
0
  }
520
3.08k
  unsigned int nbrIdx = 0;
521
3.08k
  std::vector<int> order(cen->getOwningMol().getNumBonds(), -1);
522
7.11k
  for (const auto bnd : cen->getOwningMol().atomBonds(cen)) {
523
7.11k
    order[bnd->getIdx()] = nbrIdx++;
524
7.11k
  }
525
526
  // nbrPerm maps original index to array position
527
3.08k
  std::vector<unsigned int> nbrPerm(nbrIdx);
528
3.08k
  std::iota(nbrPerm.begin(), nbrPerm.end(), 0);
529
3.08k
  std::vector<unsigned int> probePerm(probe.size());
530
3.08k
  nbrIdx = 0;
531
14.0k
  for (auto v : probe) {
532
14.0k
    probePerm[nbrIdx++] = v < 0 ? -1 : order[v];
533
14.0k
  }
534
535
  // Missing (implicit) neighbors are at the end when in storage order
536
3.08k
  if (nbrPerm.size() < nbrIdx) {
537
2.49k
    nbrPerm.insert(nbrPerm.end(), nbrIdx - nbrPerm.size(), -1);
538
2.49k
  }
539
540
3.08k
  CHECK_INVARIANT(nbrPerm.size() == probePerm.size(),
541
3.08k
                  "probe vector size does not match");
542
543
3.08k
  if (inverse) {
544
3.08k
    std::swap(nbrPerm, probePerm);
545
3.08k
  }
546
547
3.08k
  boost::dynamic_bitset<> swapped(probe.size());
548
14.0k
  for (unsigned int i = 0; i < probePerm.size() - 1; ++i) {
549
10.9k
    auto pval = probePerm[i];
550
10.9k
    if (nbrPerm[i] == pval) {
551
7.44k
      continue;
552
7.44k
    }
553
3.54k
    auto tgt = std::find(nbrPerm.begin() + i, nbrPerm.end(), pval);
554
3.54k
    TEST_ASSERT(tgt != nbrPerm.end());
555
3.54k
    perm = swap_func(perm, i, tgt - nbrPerm.begin());
556
3.54k
    std::swap(*tgt, nbrPerm[i]);
557
3.54k
  }
558
559
3.08k
  return perm;
560
3.08k
}
561
562
}  // namespace Chirality
563
}  // namespace RDKit