/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 ¤tPara, 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 ¤tPara, 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 ¶Atom = 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 ¶Bond = 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 ¶Atom = 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 ¶Atom2 = 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 ¶Bond = 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 ¶Bond2 = 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 ¢er) |
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 | | } |