/src/openbabel/src/builder.cpp
Line | Count | Source |
1 | | /********************************************************************** |
2 | | builder.cpp - Class to create structures. |
3 | | |
4 | | Copyright (C) 2007-2008 by Tim Vandermeersch |
5 | | <tim.vandermeersch@gmail.com> |
6 | | |
7 | | This file is part of the Open Babel project. |
8 | | For more information, see <http://openbabel.org/> |
9 | | |
10 | | This program is free software; you can redistribute it and/or modify |
11 | | it under the terms of the GNU General Public License as published by |
12 | | the Free Software Foundation version 2 of the License. |
13 | | |
14 | | This program is distributed in the hope that it will be useful, |
15 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
17 | | GNU General Public License for more details. |
18 | | ***********************************************************************/ |
19 | | #include <openbabel/babelconfig.h> |
20 | | |
21 | | |
22 | | #include <openbabel/builder.h> |
23 | | |
24 | | #include <openbabel/mol.h> |
25 | | #include <openbabel/atom.h> |
26 | | #include <openbabel/bond.h> |
27 | | #include <openbabel/obiter.h> |
28 | | #include <openbabel/math/matrix3x3.h> |
29 | | #include <openbabel/rotamer.h> |
30 | | #include <openbabel/rotor.h> |
31 | | #include <openbabel/obconversion.h> |
32 | | #include <openbabel/locale.h> |
33 | | #include <openbabel/distgeom.h> |
34 | | #include <openbabel/elements.h> |
35 | | |
36 | | #include <openbabel/stereo/stereo.h> |
37 | | #include <openbabel/stereo/cistrans.h> |
38 | | #include <openbabel/stereo/tetrahedral.h> |
39 | | #include <openbabel/stereo/squareplanar.h> |
40 | | /* OBBuilder::GetNewBondVector(): |
41 | | * - is based on OBAtom::GetNewBondVector() |
42 | | * - but: when extending a long chain all the bonds are trans |
43 | | * |
44 | | * fragments.txt: |
45 | | * - fragments should be ordered from complex to simple |
46 | | * - practically speaking, this means they are ordered by the number of atoms |
47 | | * */ |
48 | | |
49 | | using namespace std; |
50 | | |
51 | | namespace OpenBabel |
52 | | { |
53 | | /** \class OBBuilder builder.h <openbabel/builder.h> |
54 | | \brief Class for 3D structure generation |
55 | | \since version 2.2 |
56 | | |
57 | | The OBBuilder class is used for generating 3D structures. |
58 | | |
59 | | Below is and example which explain the basics. |
60 | | |
61 | | \code |
62 | | // |
63 | | // code to read molecule from smiles goes here... |
64 | | // |
65 | | OBBuilder builder; |
66 | | builder.Build(mol); |
67 | | // |
68 | | // code to write molecule to 3D file format goes here... |
69 | | // |
70 | | \endcode |
71 | | **/ |
72 | | //std::map<std::string, double> OBBuilder::_torsion; |
73 | | std::vector<std::string> OBBuilder::_rigid_fragments; |
74 | | std::map<std::string, int> OBBuilder::_rigid_fragments_index; |
75 | | std::map<std::string, std::vector<vector3> > OBBuilder::_rigid_fragments_cache; |
76 | | std::vector<std::pair<OBSmartsPattern*, std::vector<vector3> > > OBBuilder::_ring_fragments; |
77 | | |
78 | | void OBBuilder::AddRingFragment(OBSmartsPattern *sp, const std::vector<vector3> &coords) |
79 | 0 | { |
80 | 0 | bool hasAllZeroCoords = true; |
81 | 0 | for (std::size_t i = 0; i < coords.size(); ++i) { |
82 | 0 | if (fabs(coords[i].x()) > 10e-8 || |
83 | 0 | fabs(coords[i].y()) > 10e-8 || |
84 | 0 | fabs(coords[i].z()) > 10e-8) { |
85 | 0 | hasAllZeroCoords = false; |
86 | 0 | break; |
87 | 0 | } |
88 | 0 | } |
89 | |
|
90 | 0 | if (hasAllZeroCoords) { |
91 | 0 | std::stringstream ss; |
92 | 0 | ss << "Ring fragment " << sp->GetSMARTS() << " in ring-fragments.txt has all zero coordinates. Ignoring fragment."; |
93 | 0 | obErrorLog.ThrowError(__FUNCTION__, ss.str(), obError); |
94 | 0 | } else |
95 | 0 | _ring_fragments.push_back(pair<OBSmartsPattern*, vector<vector3> > (sp, coords)); |
96 | 0 | } |
97 | | |
98 | 0 | void OBBuilder::LoadFragments() { |
99 | | // open data/fragments.txt |
100 | 0 | ifstream ifs; |
101 | 0 | if (OpenDatafile(ifs, "rigid-fragments-index.txt").length() == 0) { |
102 | 0 | obErrorLog.ThrowError(__FUNCTION__, "Cannot open ring-fragments-index.txt", obError); |
103 | 0 | return; |
104 | 0 | } |
105 | | |
106 | | // Set the locale for number parsing to avoid locale issues: PR#1785463 |
107 | | // TODO: Use OpenDatafile() |
108 | 0 | obLocale.SetLocale(); |
109 | |
|
110 | 0 | std::string smiles; |
111 | 0 | int index; |
112 | 0 | while(ifs >> smiles >> index) { |
113 | 0 | _rigid_fragments.push_back(smiles); |
114 | 0 | _rigid_fragments_index[smiles] = index; |
115 | 0 | } |
116 | |
|
117 | 0 | if (OpenDatafile(ifs, "ring-fragments.txt").length() == 0) { |
118 | 0 | obErrorLog.ThrowError(__FUNCTION__, "Cannot open ring-fragments.txt", obError); |
119 | 0 | return; |
120 | 0 | } |
121 | | |
122 | 0 | char buffer[BUFF_SIZE]; |
123 | 0 | vector<string> vs; |
124 | 0 | OBSmartsPattern *sp = nullptr; |
125 | 0 | vector<vector3> coords; |
126 | 0 | while (ifs.getline(buffer, BUFF_SIZE)) { |
127 | 0 | if (buffer[0] == '#') // skip comment line (at the top) |
128 | 0 | continue; |
129 | | |
130 | 0 | tokenize(vs, buffer); |
131 | |
|
132 | 0 | if (vs.size() == 1) { // SMARTS pattern |
133 | 0 | if (sp != nullptr) |
134 | 0 | AddRingFragment(sp, coords); |
135 | |
|
136 | 0 | coords.clear(); |
137 | 0 | sp = new OBSmartsPattern; |
138 | 0 | if (!sp->Init(vs[0])) { |
139 | 0 | delete sp; |
140 | 0 | sp = nullptr; |
141 | 0 | obErrorLog.ThrowError(__FUNCTION__, " Could not parse SMARTS from contribution data file", obInfo); |
142 | 0 | } |
143 | 0 | } else if (vs.size() == 3) { // XYZ coordinates |
144 | 0 | vector3 coord(atof(vs[0].c_str()), atof(vs[1].c_str()), atof(vs[2].c_str())); |
145 | 0 | coords.push_back(coord); |
146 | 0 | } |
147 | 0 | } |
148 | 0 | AddRingFragment(sp, coords); |
149 | | |
150 | | // return the locale to the original one |
151 | 0 | obLocale.RestoreLocale(); |
152 | | |
153 | | /*if (OpenDatafile(ifs, "torsion.txt").length() == 0) { |
154 | | obErrorLog.ThrowError(__FUNCTION__, "Cannot open torsion.txt", obError); |
155 | | return; |
156 | | } |
157 | | double angle; |
158 | | while(ifs >> smiles >> angle) { |
159 | | _torsion[smiles] = angle; |
160 | | } |
161 | | */ |
162 | 0 | } |
163 | | |
164 | 0 | std::vector<vector3> OBBuilder::GetFragmentCoord(std::string smiles) { |
165 | 0 | if (_rigid_fragments_cache.count(smiles) > 0) { |
166 | 0 | return _rigid_fragments_cache[smiles]; |
167 | 0 | } |
168 | | |
169 | 0 | std::vector<vector3> coords; |
170 | 0 | if (_rigid_fragments_index.count(smiles) == 0) { |
171 | 0 | return coords; |
172 | 0 | } |
173 | | |
174 | 0 | ifstream ifs; |
175 | 0 | if (OpenDatafile(ifs, "rigid-fragments.txt").length() == 0) { |
176 | 0 | obErrorLog.ThrowError(__FUNCTION__, "Cannot open rigid-fragments.txt", obError); |
177 | 0 | return coords; |
178 | 0 | } |
179 | | |
180 | 0 | ifs.clear(); |
181 | 0 | ifs.seekg(_rigid_fragments_index[smiles]); |
182 | 0 | char buffer[BUFF_SIZE]; |
183 | 0 | vector<string> vs; |
184 | 0 | bool hasAllZeroCoords = true; |
185 | 0 | while (ifs.getline(buffer, BUFF_SIZE)) { |
186 | 0 | tokenize(vs, buffer); |
187 | 0 | if (vs.size() == 4) { // XYZ coordinates |
188 | 0 | vector3 coord(atof(vs[1].c_str()), atof(vs[2].c_str()), atof(vs[3].c_str())); |
189 | 0 | if (fabs(coord.x()) > 10e-8 || |
190 | 0 | fabs(coord.y()) > 10e-8 || |
191 | 0 | fabs(coord.z()) > 10e-8) |
192 | 0 | hasAllZeroCoords = false; |
193 | 0 | coords.push_back(coord); |
194 | 0 | } else if (vs.size() == 1) { // SMARTS pattern |
195 | 0 | break; |
196 | 0 | } |
197 | 0 | } |
198 | |
|
199 | 0 | if (hasAllZeroCoords) { |
200 | 0 | std::stringstream ss; |
201 | 0 | ss << "Rigid fragment " << smiles << " in rigid-fragments.txt has all zero coordinates."; |
202 | 0 | obErrorLog.ThrowError(__FUNCTION__, ss.str(), obError); |
203 | 0 | } |
204 | |
|
205 | 0 | return coords; |
206 | 0 | } |
207 | | |
208 | | vector3 GetCorrectedBondVector(OBAtom *atom1, OBAtom *atom2, int bondOrder = 1) |
209 | 0 | { |
210 | 0 | double bondLength = 0.0; |
211 | | |
212 | | // We create an estimate of the bond length based on the two atoms |
213 | | // Scaling is performed by the bond order corrections below |
214 | | // .. so we will use the straight covalent radii |
215 | 0 | bondLength += OBElements::GetCovalentRad(atom1->GetAtomicNum()); |
216 | 0 | bondLength += OBElements::GetCovalentRad(atom2->GetAtomicNum()); |
217 | |
|
218 | 0 | if (bondLength < 1.0) |
219 | 0 | bondLength = 1.0; |
220 | | |
221 | | // These are based on OBBond::GetEquibLength |
222 | | // Numbers come from averaged values of Pyykko and Atsumi |
223 | 0 | if (bondOrder == -1) // aromatic |
224 | 0 | bondLength *= 0.9475; // 0.9475 = average of 1.0 and 0.8950 |
225 | 0 | else if (bondOrder == 2) |
226 | 0 | bondLength *= 0.8950; // 0.8950 |
227 | 0 | else if (bondOrder == 3) |
228 | 0 | bondLength *= 0.8578; // 0.8578 |
229 | |
|
230 | 0 | return OBBuilder::GetNewBondVector(atom1, bondLength); |
231 | 0 | } |
232 | | |
233 | | vector3 OBBuilder::GetNewBondVector(OBAtom *atom) |
234 | 0 | { |
235 | 0 | return GetNewBondVector(atom, 1.5); |
236 | 0 | } |
237 | | |
238 | | vector3 OBBuilder::GetNewBondVector(OBAtom *atom, double length) |
239 | 0 | { |
240 | 0 | vector3 bond1, bond2, bond3, bond4, bond5, v1, v2, v3, newbond; |
241 | |
|
242 | 0 | bond1 = VZero; |
243 | 0 | bond2 = VZero; |
244 | 0 | bond3 = VZero; |
245 | 0 | bond4 = VZero; |
246 | 0 | bond5 = VZero; |
247 | |
|
248 | 0 | if (atom == nullptr) |
249 | 0 | return VZero; |
250 | | |
251 | 0 | int dimension = ((OBMol*)atom->GetParent())->GetDimension(); |
252 | |
|
253 | 0 | if (dimension != 2) { |
254 | | ///////////////// |
255 | | // 3D or 0D // |
256 | | ///////////////// |
257 | | |
258 | | // |
259 | | // a ---> a--* |
260 | | // |
261 | 0 | if (atom->GetExplicitDegree() == 0) { |
262 | 0 | newbond = atom->GetVector() + VX * length; |
263 | 0 | return newbond; |
264 | 0 | } |
265 | | |
266 | | // hyb * = 1 |
267 | | // ^^^^^^^^^ |
268 | | // |
269 | | // (a-1)--a ---> (a-1)--a--* angle(a-1, a, *) = 180 |
270 | | // |
271 | | // hyb * = 2 |
272 | | // ^^^^^^^^^ |
273 | | // make sure we place the new atom trans to a-2 (if there is an a-2 atom) |
274 | | // |
275 | | // (a-2) (a-2) |
276 | | // \ \ // |
277 | | // (a-1)==a ---> (a-1)==a angle(a-1, a, *) = 120 |
278 | | // \ // |
279 | | // * |
280 | | // hyb * = 3 |
281 | | // ^^^^^^^^^ |
282 | | // make sure we place the new atom trans to a-2 (if there is an a-2 atom) |
283 | | // |
284 | | // (a-2) (a-2) |
285 | | // \ \ // |
286 | | // (a-1)--a ---> (a-1)--a angle(a-1, a, *) = 109 |
287 | | // \ // |
288 | | // * |
289 | 0 | if (atom->GetExplicitDegree() == 1) { |
290 | 0 | bool isCarboxylateO = atom->IsCarboxylOxygen(); |
291 | |
|
292 | 0 | FOR_NBORS_OF_ATOM (nbr, atom) { |
293 | 0 | bond1 = atom->GetVector() - nbr->GetVector(); |
294 | |
|
295 | 0 | if (nbr->GetHyb() == 1) // Fix #2119034 & #2013814 |
296 | 0 | continue; |
297 | | |
298 | 0 | FOR_NBORS_OF_ATOM (nbr2, &*nbr) { |
299 | 0 | if (&*nbr2 != atom) { |
300 | 0 | bond2 = nbr->GetVector() - nbr2->GetVector(); |
301 | |
|
302 | 0 | if (isCarboxylateO && nbr2->GetAtomicNum() == OBElements::Oxygen) |
303 | 0 | break; // make sure that the hydrogen is trans to the C=O |
304 | 0 | } |
305 | 0 | } |
306 | 0 | } |
307 | |
|
308 | 0 | bond1 = bond1.normalize(); |
309 | 0 | v1 = cross(bond1, bond2); |
310 | 0 | if (bond2 == VZero || v1 == VZero) { |
311 | 0 | vector3 vrand; |
312 | 0 | vrand.randomUnitVector(); |
313 | 0 | double angle = fabs(acos(dot(bond1, vrand)) * RAD_TO_DEG); |
314 | 0 | while (angle < 45.0 || angle > 135.0) { |
315 | 0 | vrand.randomUnitVector(); |
316 | 0 | angle = fabs(acos(dot(bond1, vrand)) * RAD_TO_DEG); |
317 | 0 | } |
318 | | // there is no a-2 atom |
319 | 0 | v1 = cross(bond1, vrand); // so find a perpendicular, given the random vector (this doesn't matter here) |
320 | 0 | } |
321 | 0 | v2 = cross(bond1, v1); |
322 | 0 | v2 = v2.normalize(); |
323 | | |
324 | | // check to see if atom is a square planar in disguise |
325 | 0 | if (atom->GetHyb() == 3) { |
326 | 0 | OBStereoFacade stereoFacade((OBMol*)atom->GetParent(), false); // Don't reperceive |
327 | 0 | if (stereoFacade.HasSquarePlanarStereo(atom->GetId())) |
328 | 0 | atom->SetHyb(4); // force sq. planar geometry for sq. planar stereo |
329 | 0 | } |
330 | |
|
331 | 0 | if (atom->GetHyb() == 1) |
332 | 0 | newbond = bond1; // i.e., in the exact opposite direction |
333 | 0 | else if (atom->GetHyb() == 2) |
334 | 0 | newbond = bond1 - v2 * tan(DEG_TO_RAD*60.0); |
335 | 0 | else if (atom->GetHyb() == 3) |
336 | 0 | newbond = bond1 - v2 * tan(DEG_TO_RAD*(180.0 - 109.471)); |
337 | 0 | else if (atom->GetHyb() == 4) |
338 | 0 | newbond = bond1; // like 5-coordinate below, we want a 180-degree bond (trans) |
339 | 0 | else if (atom->GetHyb() == 5) { |
340 | | /* the first two atoms are the axial ones; the third, fourth, and fifth atom are equatorial */ |
341 | 0 | newbond = bond1; |
342 | 0 | } else if (atom->GetHyb() == 6) { |
343 | 0 | newbond = bond1 - v2 * tan(DEG_TO_RAD*90.0); |
344 | 0 | } |
345 | |
|
346 | 0 | newbond = newbond.normalize(); |
347 | 0 | newbond *= length; |
348 | 0 | newbond += atom->GetVector(); |
349 | 0 | return newbond; |
350 | 0 | } |
351 | | |
352 | | // |
353 | | // \ \ // |
354 | | // X ---> X--* |
355 | | // / / |
356 | | // |
357 | 0 | if (atom->GetExplicitDegree() == 2) { |
358 | 0 | FOR_NBORS_OF_ATOM (nbr, atom) { |
359 | 0 | if (bond1 == VZero) |
360 | 0 | bond1 = atom->GetVector() - nbr->GetVector(); |
361 | 0 | else if (bond2 == VZero) |
362 | 0 | bond2 = atom->GetVector() - nbr->GetVector(); |
363 | 0 | } |
364 | |
|
365 | 0 | bond1 = bond1.normalize(); |
366 | 0 | bond2 = bond2.normalize(); |
367 | 0 | v1 = bond1 + bond2; |
368 | 0 | v1 = v1.normalize(); |
369 | |
|
370 | 0 | if (atom->GetHyb() == 2) |
371 | 0 | newbond = v1; |
372 | 0 | else if (atom->GetHyb() == 3) { |
373 | 0 | v2 = cross(bond1, bond2); // find the perpendicular |
374 | 0 | v2.normalize(); |
375 | 0 | newbond = bond1 - v2 * tan(DEG_TO_RAD*(180.0 - 109.471)); |
376 | 0 | newbond = v2 + v1 * (sqrt(2.0) / 2.0); // used to be tan(70.53 degrees/2) which is sqrt(2.0) / 2.0 |
377 | 0 | } |
378 | 0 | else if (atom->GetHyb() == 5 || atom->GetHyb() == 4) { |
379 | | /* add the first equatorial atom, orthogonally to bond1 (and bond2 = -bond1) */ |
380 | | /* is atom order correct? I don't think it matters, but I might have to ask a chemist |
381 | | * whether PClF4 would be more likely to have an equatorial or axial Cl-P bond */ |
382 | 0 | vector3 vrand; |
383 | 0 | vrand.randomUnitVector(); |
384 | 0 | double angle = fabs(acos(dot(bond1, vrand)) * RAD_TO_DEG); |
385 | 0 | while (angle < 45.0 || angle > 135.0) { |
386 | 0 | vrand.randomUnitVector(); |
387 | 0 | angle = fabs(acos(dot(bond1, vrand)) * RAD_TO_DEG); |
388 | 0 | } |
389 | 0 | v1 = cross(bond1, vrand); |
390 | 0 | v1 = v1.normalize(); |
391 | 0 | newbond = v1; |
392 | 0 | } |
393 | 0 | else if (atom->GetHyb() == 6) { |
394 | 0 | v2 = cross(bond1, bond2); |
395 | 0 | newbond = v2; |
396 | 0 | } |
397 | |
|
398 | 0 | newbond = newbond.normalize(); //if newbond was not set, it will become non-finite here |
399 | 0 | newbond *= length; |
400 | 0 | newbond += atom->GetVector(); |
401 | 0 | return newbond; |
402 | 0 | } |
403 | | |
404 | | |
405 | | /* UFF: |
406 | | * b lg dg o y |
407 | | * b - 45 30 45 30 |
408 | | * lg - 45 0 45 |
409 | | * dg - 45 30 |
410 | | * o - 45 |
411 | | * y - |
412 | | |
413 | | * 94s: |
414 | | * b lg dg o y |
415 | | * b - 34 34 34 34 |
416 | | * lg - 48 21 48 |
417 | | * dg - 48 21 |
418 | | * o - 48 |
419 | | * y - |
420 | | |
421 | | // |
422 | | // \ \ |
423 | | // --X ---> --X--* |
424 | | // / / |
425 | | // |
426 | | */ |
427 | 0 | if (atom->GetExplicitDegree() == 3) { |
428 | 0 | if (atom->GetHyb() == 3) { |
429 | 0 | FOR_NBORS_OF_ATOM (nbr, atom) { |
430 | 0 | if (bond1 == VZero) |
431 | 0 | bond1 = atom->GetVector() - nbr->GetVector(); |
432 | 0 | else if (bond2 == VZero) |
433 | 0 | bond2 = atom->GetVector() - nbr->GetVector(); |
434 | 0 | else if (bond3 == VZero) |
435 | 0 | bond3 = atom->GetVector() - nbr->GetVector(); |
436 | 0 | } |
437 | |
|
438 | 0 | bond1 = bond1.normalize(); |
439 | 0 | bond2 = bond2.normalize(); |
440 | 0 | bond3 = bond3.normalize(); |
441 | 0 | newbond = bond1 + bond2 + bond3; |
442 | 0 | newbond = newbond.normalize(); |
443 | 0 | newbond *= length; |
444 | 0 | newbond += atom->GetVector(); |
445 | 0 | return newbond; |
446 | 0 | } |
447 | | |
448 | 0 | if (atom->GetHyb() == 4) { // OK, we want this at -bond3, since bond1 & bond2 are opposite |
449 | 0 | FOR_NBORS_OF_ATOM (nbr, atom) { |
450 | 0 | if (bond1 == VZero) |
451 | 0 | bond1 = atom->GetVector() - nbr->GetVector(); |
452 | 0 | else if (bond2 == VZero) |
453 | 0 | bond2 = atom->GetVector() - nbr->GetVector(); |
454 | 0 | else if (bond3 == VZero) |
455 | 0 | bond3 = atom->GetVector() - nbr->GetVector(); |
456 | 0 | } |
457 | |
|
458 | 0 | bond3 = bond3.normalize(); |
459 | |
|
460 | 0 | newbond = bond3; |
461 | 0 | newbond = newbond.normalize(); |
462 | 0 | newbond *= length; |
463 | 0 | newbond += atom->GetVector(); |
464 | 0 | return newbond; |
465 | 0 | } |
466 | | |
467 | 0 | if (atom->GetHyb() == 5) { |
468 | 0 | FOR_NBORS_OF_ATOM (nbr, atom) { |
469 | 0 | if (bond1 == VZero) |
470 | 0 | bond1 = atom->GetVector() - nbr->GetVector(); |
471 | 0 | else if (bond2 == VZero) |
472 | 0 | bond2 = atom->GetVector() - nbr->GetVector(); |
473 | 0 | else if (bond3 == VZero) |
474 | 0 | bond3 = atom->GetVector() - nbr->GetVector(); |
475 | 0 | } |
476 | |
|
477 | 0 | bond1 = bond1.normalize(); |
478 | 0 | bond2 = bond2.normalize(); |
479 | 0 | bond3 = bond3.normalize(); |
480 | |
|
481 | 0 | v1 = cross(bond1, bond3); |
482 | 0 | v1 = v1.normalize(); |
483 | |
|
484 | 0 | newbond = v1 + tan(DEG_TO_RAD*30.0) * bond3; |
485 | 0 | newbond = newbond.normalize(); |
486 | 0 | newbond *= length; |
487 | 0 | newbond += atom->GetVector(); |
488 | 0 | return newbond; |
489 | |
|
490 | 0 | } |
491 | | |
492 | 0 | if (atom->GetHyb() == 6) { |
493 | 0 | FOR_NBORS_OF_ATOM (nbr, atom) { |
494 | 0 | if (bond1 == VZero) |
495 | 0 | bond1 = atom->GetVector() - nbr->GetVector(); |
496 | 0 | else if (bond2 == VZero) |
497 | 0 | bond2 = atom->GetVector() - nbr->GetVector(); |
498 | 0 | else if (bond3 == VZero) |
499 | 0 | bond3 = atom->GetVector() - nbr->GetVector(); |
500 | 0 | } |
501 | | |
502 | | /* the way things work, newbond is equal to bond1, but will show up at -bond1 next time around */ |
503 | 0 | newbond = bond1; |
504 | 0 | newbond = newbond.normalize(); |
505 | 0 | newbond *= length; |
506 | 0 | newbond += atom->GetVector(); |
507 | 0 | return newbond; |
508 | 0 | } |
509 | 0 | } |
510 | | |
511 | 0 | if (atom->GetExplicitDegree() == 4) { |
512 | 0 | if (atom->GetHyb() == 6) { |
513 | 0 | FOR_NBORS_OF_ATOM (nbr, atom) { |
514 | 0 | if (bond1 == VZero) |
515 | 0 | bond1 = atom->GetVector() - nbr->GetVector(); |
516 | 0 | else if (bond2 == VZero) |
517 | 0 | bond2 = atom->GetVector() - nbr->GetVector(); |
518 | 0 | else if (bond3 == VZero) |
519 | 0 | bond3 = atom->GetVector() - nbr->GetVector(); |
520 | 0 | else if (bond4 == VZero) |
521 | 0 | bond4 = atom->GetVector() - nbr->GetVector(); |
522 | 0 | } |
523 | |
|
524 | 0 | newbond = bond2; |
525 | 0 | newbond = newbond.normalize(); |
526 | 0 | newbond *= length; |
527 | 0 | newbond += atom->GetVector(); |
528 | 0 | return newbond; |
529 | 0 | } |
530 | | |
531 | 0 | if (atom->GetHyb() == 5) { |
532 | 0 | FOR_NBORS_OF_ATOM (nbr, atom) { |
533 | 0 | if (bond1 == VZero) |
534 | 0 | bond1 = atom->GetVector() - nbr->GetVector(); |
535 | 0 | else if (bond2 == VZero) |
536 | 0 | bond2 = atom->GetVector() - nbr->GetVector(); |
537 | 0 | else if (bond3 == VZero) |
538 | 0 | bond3 = atom->GetVector() - nbr->GetVector(); |
539 | 0 | else if (bond4 == VZero) |
540 | 0 | bond4 = atom->GetVector() - nbr->GetVector(); |
541 | 0 | } |
542 | |
|
543 | 0 | bond1 = bond1.normalize(); |
544 | 0 | bond2 = bond2.normalize(); |
545 | 0 | bond3 = bond3.normalize(); |
546 | 0 | bond4 = bond4.normalize(); |
547 | |
|
548 | 0 | v1 = cross(bond1, bond3); |
549 | 0 | v1 = v1.normalize(); |
550 | |
|
551 | 0 | newbond = -v1 + tan(DEG_TO_RAD*30.0) * bond3; |
552 | 0 | newbond = newbond.normalize(); |
553 | 0 | newbond *= length; |
554 | 0 | newbond += atom->GetVector(); |
555 | 0 | return newbond; |
556 | |
|
557 | 0 | } |
558 | |
|
559 | 0 | } |
560 | | |
561 | 0 | if (atom->GetExplicitDegree() == 5) { |
562 | 0 | if (atom->GetHyb() == 6) { |
563 | 0 | FOR_NBORS_OF_ATOM (nbr, atom) { |
564 | 0 | if (bond1 == VZero) |
565 | 0 | bond1 = atom->GetVector() - nbr->GetVector(); |
566 | 0 | else if (bond2 == VZero) |
567 | 0 | bond2 = atom->GetVector() - nbr->GetVector(); |
568 | 0 | else if (bond3 == VZero) |
569 | 0 | bond3 = atom->GetVector() - nbr->GetVector(); |
570 | 0 | else if (bond4 == VZero) |
571 | 0 | bond4 = atom->GetVector() - nbr->GetVector(); |
572 | 0 | else if (bond5 == VZero) |
573 | 0 | bond5 = atom->GetVector() - nbr->GetVector(); |
574 | 0 | } |
575 | |
|
576 | 0 | newbond = bond3; |
577 | 0 | newbond = newbond.normalize(); |
578 | 0 | newbond *= length; |
579 | 0 | newbond += atom->GetVector(); |
580 | 0 | return newbond; |
581 | 0 | } |
582 | 0 | } |
583 | | |
584 | | // Undefined case -- return a random vector of length specified |
585 | 0 | newbond.randomUnitVector(); |
586 | 0 | newbond *= length; |
587 | 0 | newbond += atom->GetVector(); |
588 | 0 | return newbond; |
589 | 0 | } else { |
590 | | //////////// |
591 | | // 2D // |
592 | | //////////// |
593 | 0 | OBBondIterator i; |
594 | | |
595 | | // |
596 | | // a ---> a---* |
597 | | // |
598 | 0 | if (atom->GetExplicitDegree() == 0) { |
599 | 0 | newbond = atom->GetVector() + VX * length; |
600 | | // Check that the vector is still finite before returning |
601 | 0 | if (!isfinite(newbond.x()) || !isfinite(newbond.y())) |
602 | 0 | newbond.Set(0.0, 0.0, 0.0); |
603 | 0 | return newbond; |
604 | 0 | } |
605 | | |
606 | | // hyb * = 1 // |
607 | | // ^^^^^^^^^ // |
608 | | // // |
609 | | // (a-1)--a ---> (a-1)--a--* angle(a-1, a, *) = 180 // |
610 | | // // |
611 | | // hyb * = 2 // |
612 | | // ^^^^^^^^^ // |
613 | | // make sure we place the new atom trans to a-2 (if there is an a-2 atom) // |
614 | | // // |
615 | | // (a-2) (a-2) // |
616 | | // \ \ // |
617 | | // (a-1)==a ---> (a-1)==a angle(a-1, a, *) = 120 // |
618 | | // \ // |
619 | | // * // |
620 | | // hyb * = 3 // |
621 | | // ^^^^^^^^^ // |
622 | | // make sure we place the new atom trans to a-2 (if there is an a-2 atom) // |
623 | | // // |
624 | | // (a-2) (a-2) // |
625 | | // \ \ // |
626 | | // (a-1)--a ---> (a-1)--a angle(a-1, a, *) = 109 // |
627 | | // \ // |
628 | | // * // |
629 | 0 | if (atom->GetExplicitDegree() == 1) { |
630 | 0 | OBAtom *nbr = atom->BeginNbrAtom(i); |
631 | 0 | if (!nbr) |
632 | 0 | return VZero; |
633 | 0 | bond1 = atom->GetVector() - nbr->GetVector(); // bond (a-1)--a |
634 | |
|
635 | 0 | for (OBAtom *nbr2 = nbr->BeginNbrAtom(i); nbr2; nbr2 = nbr->NextNbrAtom(i)) |
636 | 0 | if (nbr2 != atom) |
637 | 0 | bond2 = nbr->GetVector() - nbr2->GetVector(); // bond (a-2)--(a-1) |
638 | |
|
639 | 0 | int hyb = atom->GetHyb(); |
640 | 0 | if (hyb == 1) |
641 | 0 | newbond = bond1; |
642 | 0 | else if (hyb == 2 || hyb == 3 || hyb == 0) { |
643 | 0 | matrix3x3 m; |
644 | 0 | m.RotAboutAxisByAngle(VZ, 60.0); |
645 | 0 | newbond = m*bond1; |
646 | 0 | } |
647 | 0 | newbond.normalize(); |
648 | 0 | newbond *= length; |
649 | 0 | newbond += atom->GetVector(); |
650 | 0 | return newbond; |
651 | 0 | } // GetExplicitDegree() == 1 |
652 | | |
653 | | // // |
654 | | // \ \ // |
655 | | // X ---> X--* // |
656 | | // / / // |
657 | | // // |
658 | 0 | if (atom->GetExplicitDegree() == 2) { |
659 | 0 | for (OBAtom *nbr = atom->BeginNbrAtom(i); nbr; nbr = atom->NextNbrAtom(i)) { |
660 | 0 | if (bond1 == VZero) |
661 | 0 | bond1 = atom->GetVector() - nbr->GetVector(); |
662 | 0 | else |
663 | 0 | bond2 = atom->GetVector() - nbr->GetVector(); |
664 | 0 | } |
665 | 0 | bond1.normalize(); |
666 | 0 | bond2.normalize(); |
667 | 0 | newbond = bond1 + bond2; |
668 | 0 | newbond.normalize(); |
669 | 0 | newbond *= length; |
670 | 0 | newbond += atom->GetVector(); |
671 | 0 | return newbond; |
672 | 0 | } |
673 | | |
674 | | // // |
675 | | // \ \ // |
676 | | // --X ---> --X--* // |
677 | | // / / // |
678 | | // // |
679 | 0 | if (atom->GetExplicitDegree() == 3) { |
680 | 0 | OBStereoFacade stereoFacade((OBMol*)atom->GetParent()); |
681 | 0 | if (stereoFacade.HasTetrahedralStereo(atom->GetId())) { |
682 | 0 | OBBond *hash = nullptr; |
683 | 0 | OBBond *wedge = nullptr; |
684 | 0 | vector<OBBond*> plane; |
685 | 0 | for (OBAtom *nbr = atom->BeginNbrAtom(i); nbr; nbr = atom->NextNbrAtom(i)) { |
686 | 0 | OBBond *bond = atom->GetBond(nbr); |
687 | |
|
688 | 0 | if (bond->IsWedge()) { |
689 | 0 | if (atom == bond->GetBeginAtom()) |
690 | 0 | wedge = bond; |
691 | 0 | else |
692 | 0 | hash = bond; |
693 | 0 | } else |
694 | 0 | if (bond->IsHash()) { |
695 | 0 | if (atom == bond->GetBeginAtom()) |
696 | 0 | hash = bond; |
697 | 0 | else |
698 | 0 | wedge = bond; |
699 | 0 | } else |
700 | 0 | plane.push_back(bond); |
701 | 0 | } |
702 | |
|
703 | 0 | if (wedge && !plane.empty()) { |
704 | 0 | bond2 = atom->GetVector() - wedge->GetNbrAtom(atom)->GetVector(); |
705 | 0 | bond3 = atom->GetVector() - plane[0]->GetNbrAtom(atom)->GetVector(); |
706 | 0 | } else if (hash && !plane.empty()) { |
707 | 0 | bond2 = atom->GetVector() - hash->GetNbrAtom(atom)->GetVector(); |
708 | 0 | bond3 = atom->GetVector() - plane[0]->GetNbrAtom(atom)->GetVector(); |
709 | 0 | } else if (plane.size() >= 2) { |
710 | 0 | bond2 = atom->GetVector() - plane[0]->GetNbrAtom(atom)->GetVector(); |
711 | 0 | bond3 = atom->GetVector() - plane[1]->GetNbrAtom(atom)->GetVector(); |
712 | 0 | } else if (hash && wedge) { |
713 | 0 | bond2 = atom->GetVector() - wedge->GetNbrAtom(atom)->GetVector(); |
714 | 0 | bond3 = atom->GetVector() - hash->GetNbrAtom(atom)->GetVector(); |
715 | 0 | } |
716 | 0 | } else { |
717 | 0 | for (OBAtom *nbr = atom->BeginNbrAtom(i); nbr; nbr = atom->NextNbrAtom(i)) { |
718 | 0 | if (bond1 == VZero) |
719 | 0 | bond1 = atom->GetVector() - nbr->GetVector(); |
720 | 0 | else if (bond2 == VZero) |
721 | 0 | bond2 = atom->GetVector() - nbr->GetVector(); |
722 | 0 | else |
723 | 0 | bond3 = atom->GetVector() - nbr->GetVector(); |
724 | 0 | } |
725 | 0 | } |
726 | |
|
727 | 0 | bond2.normalize(); |
728 | 0 | bond3.normalize(); |
729 | 0 | newbond = -(bond2 + bond3); |
730 | 0 | newbond.normalize(); |
731 | 0 | newbond *= length; |
732 | 0 | newbond += atom->GetVector(); |
733 | 0 | return newbond; |
734 | 0 | } |
735 | | |
736 | | // Undefined case -- return a random vector of length specified |
737 | 0 | newbond.randomUnitVector(); |
738 | 0 | newbond.z() = 0.0; |
739 | 0 | newbond.normalize(); |
740 | 0 | newbond *= length; |
741 | 0 | newbond += atom->GetVector(); |
742 | 0 | return newbond; |
743 | 0 | } |
744 | | |
745 | 0 | return VZero; //previously undefined |
746 | 0 | } |
747 | | |
748 | | |
749 | | // The OBMol mol contains both the molecule to which we want to connect the |
750 | | // fragment and the fragment itself. The fragment containing b will be |
751 | | // rotated and translated. Atom a is the atom from |
752 | | // the main molecule to which we want to connect atom b. |
753 | | // NOTE: newpos now uses CorrectedBondVector, so we don't do that below |
754 | | bool OBBuilder::Connect(OBMol &mol, int idxA, int idxB, |
755 | | vector3 &newpos, int bondOrder) |
756 | 0 | { |
757 | 0 | OBAtom *a = mol.GetAtom(idxA); |
758 | 0 | OBAtom *b = mol.GetAtom(idxB); |
759 | |
|
760 | 0 | if (a == nullptr) |
761 | 0 | return false; |
762 | 0 | if (b == nullptr) |
763 | 0 | return false; |
764 | | |
765 | 0 | OBBitVec fragment = GetFragment(b); |
766 | 0 | if (fragment == GetFragment(a)) |
767 | 0 | return false; // a and b are in the same fragment |
768 | | |
769 | 0 | bool connectedFrag = true; // normal case |
770 | | // If we don't have any neighbors, assume that the fragment |
771 | | // is inserted at the end of the molecule and set anything after the atom. |
772 | | // This lets us place fragments like Cp rings with dummy atoms |
773 | 0 | if (b->GetAtomicNum() == 0) { |
774 | 0 | connectedFrag = false; |
775 | 0 | fragment.SetRangeOn(b->GetIdx(), mol.NumAtoms()); |
776 | 0 | } |
777 | |
|
778 | 0 | vector3 posa = a->GetVector(); |
779 | 0 | vector3 posb = b->GetVector(); |
780 | | // |
781 | | // translate fragment so that atom b is at the origin |
782 | | // |
783 | 0 | for (unsigned int i = 1; i <= mol.NumAtoms(); ++i) { |
784 | 0 | if (fragment.BitIsSet(i)) { |
785 | | // the atom is part of the fragment, translate it |
786 | 0 | vector3 tmpvec = mol.GetAtom(i)->GetVector(); |
787 | 0 | tmpvec -= posb; |
788 | 0 | mol.GetAtom(i)->SetVector(tmpvec); |
789 | 0 | } |
790 | 0 | } |
791 | | // |
792 | | // rotate the fragment to align the bond directions Mol-a-b and a-b-fragment |
793 | | // |
794 | 0 | matrix3x3 xymat, xzmat, yzmat; |
795 | 0 | vector3 moldir = newpos - posa; |
796 | 0 | double xyang, yzang, xzang; |
797 | |
|
798 | 0 | vector3 fragdir = GetNewBondVector(b); // b is at origin |
799 | 0 | vector3 crossdir; |
800 | 0 | if (!connectedFrag) { // nothing bonded to b, like a Cp ring |
801 | 0 | vector3 firstDir, secondDir; |
802 | | // Try finding the next atom |
803 | 0 | OBAtom *nextAtom = mol.GetAtom(b->GetIdx() + 1); |
804 | 0 | if (nextAtom) { |
805 | 0 | firstDir = nextAtom->GetVector() - b->GetVector(); |
806 | | // we'll try finding another atom |
807 | 0 | OBAtom *secondAtom = mol.GetAtom(b->GetIdx() + 2); |
808 | 0 | if (secondAtom) |
809 | 0 | secondDir = secondAtom->GetVector() - b->GetVector(); |
810 | 0 | else |
811 | 0 | secondDir.randomUnitVector(); // pick something at random |
812 | | // but not too shallow, or the cross product won't work well |
813 | 0 | double angle = fabs(acos(dot(firstDir, secondDir)) * RAD_TO_DEG); |
814 | 0 | while (angle < 45.0 || angle > 135.0) { |
815 | 0 | secondDir.randomUnitVector(); |
816 | 0 | angle = fabs(acos(dot(firstDir, secondDir)) * RAD_TO_DEG); |
817 | 0 | } |
818 | | // Now we find a perpendicular vector to the fragment |
819 | 0 | crossdir = cross(firstDir, secondDir); |
820 | 0 | fragdir = crossdir; |
821 | 0 | } |
822 | 0 | } |
823 | 0 | xyang = vectorAngle(vector3(moldir.x(), moldir.y(), 0.0), vector3(fragdir.x(), fragdir.y(), 0.0)); |
824 | 0 | if (cross(vector3(moldir.x(), moldir.y(), 0.0), vector3(fragdir.x(), fragdir.y(), 0.0)).z() > 0) { |
825 | 0 | xyang = 180 + xyang; |
826 | 0 | } else if (cross(vector3(moldir.x(), moldir.y(), 0.0), vector3(fragdir.x(), fragdir.y(), 0.0)).z() < 0) { |
827 | 0 | xyang = 180 - xyang; |
828 | 0 | } else { |
829 | 0 | xyang = 0.0; |
830 | 0 | } |
831 | 0 | xymat.SetupRotMat(0.0, 0.0, xyang); |
832 | 0 | for (unsigned int i = 1; i <= mol.NumAtoms(); ++i) { |
833 | 0 | if (fragment.BitIsSet(i)) { |
834 | 0 | vector3 tmpvec = mol.GetAtom(i)->GetVector(); |
835 | 0 | tmpvec *= xymat; //apply the rotation |
836 | 0 | mol.GetAtom(i)->SetVector(tmpvec); |
837 | 0 | } |
838 | 0 | } |
839 | |
|
840 | 0 | fragdir = GetNewBondVector(b); |
841 | 0 | if (!connectedFrag) |
842 | 0 | fragdir = crossdir; |
843 | 0 | xzang = vectorAngle(vector3(moldir.x(), moldir.z(), 0.0), vector3(fragdir.x(), fragdir.z(), 0.0)); |
844 | 0 | if (cross(vector3(moldir.x(), moldir.z(), 0.0), vector3(fragdir.x(), fragdir.z(), 0.0)).z() > 0) { |
845 | 0 | xzang = 180 - xzang; |
846 | 0 | } else if (cross(vector3(moldir.x(), moldir.z(), 0.0), vector3(fragdir.x(), fragdir.z(), 0.0)).z() < 0) { |
847 | 0 | xzang = 180 + xzang; |
848 | 0 | } else { |
849 | 0 | xzang = 0.0; |
850 | 0 | } |
851 | 0 | xzmat.SetupRotMat(0.0, xzang, 0.0); |
852 | 0 | for (unsigned int i = 1; i <= mol.NumAtoms(); ++i) { |
853 | 0 | if (fragment.BitIsSet(i)) { |
854 | 0 | vector3 tmpvec = mol.GetAtom(i)->GetVector(); |
855 | 0 | tmpvec *= xzmat; //apply the rotation |
856 | 0 | mol.GetAtom(i)->SetVector(tmpvec); |
857 | 0 | } |
858 | 0 | } |
859 | |
|
860 | 0 | fragdir = GetNewBondVector(b); |
861 | 0 | if (!connectedFrag) |
862 | 0 | fragdir = crossdir; |
863 | 0 | yzang = vectorAngle(vector3(moldir.y(), moldir.z(), 0.0), vector3(fragdir.y(), fragdir.z(), 0.0)); |
864 | 0 | if (cross(vector3(moldir.y(), moldir.z(), 0.0), vector3(fragdir.y(), fragdir.z(), 0.0)).z() > 0) { |
865 | 0 | yzang = 180 + yzang; |
866 | 0 | } else if (cross(vector3(moldir.y(), moldir.z(), 0.0), vector3(fragdir.y(), fragdir.z(), 0.0)).z() < 0) { |
867 | 0 | yzang = 180 - yzang; |
868 | 0 | } else { |
869 | 0 | yzang = 0.0; |
870 | 0 | } |
871 | 0 | yzmat.SetupRotMat(yzang, 0.0, 0.0); |
872 | 0 | for (unsigned int i = 1; i <= mol.NumAtoms(); ++i) { |
873 | 0 | if (fragment.BitIsSet(i)) { |
874 | 0 | vector3 tmpvec = mol.GetAtom(i)->GetVector(); |
875 | 0 | tmpvec *= yzmat; //apply the rotation |
876 | 0 | mol.GetAtom(i)->SetVector(tmpvec); |
877 | 0 | } |
878 | 0 | } |
879 | | // |
880 | | // translate fragment |
881 | | //for(vector<OBMol>::iterator f=fragments.begin(); f!=fragments.end(); f++) { |
882 | | // |
883 | | // |
884 | 0 | for (unsigned int i = 1; i <= mol.NumAtoms(); ++i) { |
885 | 0 | if (fragment.BitIsSet(i)) { |
886 | | // translate the fragment |
887 | 0 | vector3 tmpvec = mol.GetAtom(i)->GetVector(); |
888 | 0 | tmpvec += newpos; |
889 | 0 | mol.GetAtom(i)->SetVector(tmpvec); |
890 | 0 | } |
891 | 0 | } |
892 | | // |
893 | | // Get a neighbor of a and of b for setting the dihedral later |
894 | | // |
895 | 0 | OBAtom* nbr_a = nullptr; |
896 | 0 | FOR_NBORS_OF_ATOM(nbr, a) { |
897 | 0 | nbr_a = &*nbr; |
898 | 0 | break; |
899 | 0 | } |
900 | 0 | OBAtom* nbr_b = nullptr; |
901 | 0 | FOR_NBORS_OF_ATOM(nbr, b) { |
902 | 0 | if (fragment.BitIsSet(nbr->GetIdx())) { |
903 | 0 | nbr_b = &*nbr; |
904 | 0 | break; |
905 | 0 | } |
906 | 0 | } |
907 | | // |
908 | | // Create the bond between the two fragments |
909 | | // |
910 | 0 | OBBond *bond = mol.NewBond(); |
911 | 0 | bond->SetBegin(a); |
912 | 0 | bond->SetEnd(b); |
913 | 0 | bond->SetBondOrder(bondOrder); |
914 | 0 | a->AddBond(bond); |
915 | 0 | b->AddBond(bond); |
916 | | // |
917 | | // Set the dihedral between the two fragments |
918 | | // |
919 | | // For example, if a double bond is coming off a ring, then the dihedral |
920 | | // should be 180, e.g. for I/C=C\1/NC1 (don't worry about whether cis or trans |
921 | | // at this point - this will be corrected later) |
922 | | // |
923 | 0 | if (bondOrder == 2 && a->GetHyb() == 2 && b->GetHyb() == 2 && nbr_a && |
924 | 0 | nbr_b) |
925 | 0 | mol.SetTorsion(nbr_a, a, b, nbr_b, 180 * DEG_TO_RAD); |
926 | | |
927 | | // another special case is a single bond between two sp2 carbons - twist |
928 | | // e.g. biphenyl |
929 | 0 | if (bondOrder == 1 && a->GetHyb() == 2 && b->GetHyb() == 2 && nbr_a && |
930 | 0 | nbr_b) |
931 | 0 | mol.SetTorsion(nbr_a, a, b, nbr_b, 45.0 * DEG_TO_RAD); |
932 | | |
933 | | // another special case is building dihedrals between two rings |
934 | | // twist it a little bit (e.g., Platinum 00J_2XIR_A) |
935 | 0 | if (bondOrder == 1 && ((nbr_a && nbr_a->IsInRing() && b->IsInRing()) || |
936 | 0 | (nbr_b && nbr_b->IsInRing() && a->IsInRing()))) |
937 | 0 | mol.SetTorsion(nbr_a, a, b, nbr_b, 60.0 * DEG_TO_RAD); |
938 | | // TODO - other inter-fragment dihedrals here |
939 | |
|
940 | 0 | return true; |
941 | 0 | } |
942 | | |
943 | | bool OBBuilder::Connect(OBMol &mol, int idxA, int idxB, int bondOrder) |
944 | 0 | { |
945 | 0 | vector3 newpos = GetCorrectedBondVector(mol.GetAtom(idxA), mol.GetAtom(idxB), bondOrder); |
946 | 0 | return Connect(mol, idxA, idxB, newpos, bondOrder); |
947 | 0 | } |
948 | | |
949 | | // Variation of OBBuilder::Swap that allows swapping with a vector3 rather than |
950 | | // an explicit bond. This is useful for correcting stereochemistry at Tet Centers |
951 | | // where it is sometimes necessary to swap an existing bond with the location |
952 | | // of an implicit hydrogen (or lone pair), in order to correct the stereo. |
953 | | bool OBBuilder::SwapWithVector(OBMol &mol, int idxA, int idxB, int idxC, const vector3 &newlocation) |
954 | 0 | { |
955 | 0 | OBAtom *a = mol.GetAtom(idxA); |
956 | 0 | OBAtom *b = mol.GetAtom(idxB); |
957 | 0 | OBAtom *c = mol.GetAtom(idxC); |
958 | | |
959 | | // make sure the atoms exist |
960 | 0 | if (a == nullptr || b == nullptr || c == nullptr) |
961 | 0 | return false; |
962 | | |
963 | 0 | OBBond *bond1 = mol.GetBond(idxA, idxB); |
964 | | |
965 | | // make sure a-b is connected |
966 | 0 | if (bond1 == nullptr) |
967 | 0 | return false; |
968 | | |
969 | | // make sure the bond are not in a ring |
970 | 0 | if (bond1->IsInRing()) |
971 | 0 | return false; |
972 | | |
973 | | // save the original bond order |
974 | 0 | int bondOrder1 = bond1->GetBondOrder(); |
975 | | |
976 | | // delete the bond |
977 | 0 | mol.DeleteBond(bond1); |
978 | | |
979 | | // Get the old bond vector |
980 | 0 | vector3 bondB = b->GetVector() - a->GetVector(); |
981 | 0 | vector3 bondD = newlocation - c->GetVector(); |
982 | | |
983 | | // Get the new positions for B and D |
984 | 0 | vector3 newB = c->GetVector() + bondB.length() * (bondD/bondD.length()); |
985 | 0 | vector3 newD = a->GetVector() + bondD.length() * (bondB/bondB.length()); |
986 | | |
987 | | // connect the fragments |
988 | 0 | if (!Connect(mol, idxC, idxB, newB, bondOrder1)) |
989 | 0 | return false; |
990 | | |
991 | 0 | return true; |
992 | 0 | } |
993 | | |
994 | | bool OBBuilder::Swap(OBMol &mol, int idxA, int idxB, int idxC, int idxD) |
995 | 0 | { |
996 | 0 | OBAtom *a = mol.GetAtom(idxA); |
997 | 0 | OBAtom *b = mol.GetAtom(idxB); |
998 | 0 | OBAtom *c = mol.GetAtom(idxC); |
999 | 0 | OBAtom *d = mol.GetAtom(idxD); |
1000 | | |
1001 | | // make sure the atoms exist |
1002 | 0 | if (a == nullptr || b == nullptr || c == nullptr || d == nullptr) |
1003 | 0 | return false; |
1004 | | |
1005 | 0 | OBBond *bond1 = mol.GetBond(idxA, idxB); |
1006 | 0 | OBBond *bond2 = mol.GetBond(idxC, idxD); |
1007 | | |
1008 | | // make sure a-b and c-d are connected |
1009 | 0 | if (bond1 == nullptr || bond2 == nullptr) |
1010 | 0 | return false; |
1011 | | |
1012 | | // make sure the bonds are not in a ring |
1013 | 0 | if (bond1->IsInRing() || bond2->IsInRing()) |
1014 | 0 | return false; |
1015 | | |
1016 | | // save the original bond orders |
1017 | 0 | int bondOrder1 = bond1->GetBondOrder(); |
1018 | 0 | int bondOrder2 = bond2->GetBondOrder(); |
1019 | | |
1020 | | // delete the bonds |
1021 | 0 | mol.DeleteBond(bond1); |
1022 | 0 | mol.DeleteBond(bond2); |
1023 | | |
1024 | | // Get the bond vectors |
1025 | 0 | vector3 bondB = b->GetVector() - a->GetVector(); |
1026 | 0 | vector3 bondD = d->GetVector() - c->GetVector(); |
1027 | | |
1028 | | // Get the new positions for B and D |
1029 | 0 | vector3 newB = c->GetVector() + bondB.length() * (bondD/bondD.length()); |
1030 | 0 | vector3 newD = a->GetVector() + bondD.length() * (bondB/bondB.length()); |
1031 | | |
1032 | | // connect the fragments |
1033 | 0 | if (!Connect(mol, idxA, idxD, newD, bondOrder2)) |
1034 | 0 | return false; |
1035 | 0 | if (!Connect(mol, idxC, idxB, newB, bondOrder1)) |
1036 | 0 | return false; |
1037 | | |
1038 | 0 | return true; |
1039 | 0 | } |
1040 | | |
1041 | | |
1042 | | void OBBuilder::AddNbrs(OBBitVec &fragment, OBAtom *atom) |
1043 | 0 | { |
1044 | 0 | FOR_NBORS_OF_ATOM (nbr, atom) { |
1045 | 0 | if (!fragment.BitIsSet(nbr->GetIdx())) { |
1046 | 0 | fragment.SetBitOn(nbr->GetIdx()); |
1047 | 0 | AddNbrs(fragment, &*nbr); |
1048 | 0 | } |
1049 | 0 | } |
1050 | 0 | } |
1051 | | |
1052 | | OBBitVec OBBuilder::GetFragment(OBAtom *atom) |
1053 | 0 | { |
1054 | 0 | OBBitVec fragment; |
1055 | |
|
1056 | 0 | fragment.SetBitOn(atom->GetIdx()); |
1057 | 0 | AddNbrs(fragment, atom); |
1058 | |
|
1059 | 0 | return fragment; |
1060 | 0 | } |
1061 | | |
1062 | | // First we find the most complex fragments in our molecule. Once we have a match, |
1063 | | // vfrag is set for all the atoms in the fragment. A second match (smaller, more |
1064 | | // simple part of the 1st match) is ignored. |
1065 | | // |
1066 | | // Next we iterate over the atoms. There are 3 possibilities: |
1067 | | // |
1068 | | // 1) The atom is already added (vdone is set) --> continue |
1069 | | // 2) The atom belongs to a fragment --> a) This is the first atom to add: leave fragment as it is |
1070 | | // b) Not the first atom: rotate, translate and connect the fragment |
1071 | | // 3) The atom doesn't belong to a fragment: a) First atom: place at origin |
1072 | | // b) Not first atom: Find position and place atom |
1073 | | bool OBBuilder::Build(OBMol &mol, bool stereoWarnings) |
1074 | 0 | { |
1075 | 0 | OBBitVec vdone; // Atoms that are done, need no further manipulation. |
1076 | 0 | OBBitVec vfrag; // Atoms that are part of a fragment found in the database. |
1077 | | // These atoms have coordinates, but the fragment still has |
1078 | | // to be rotated and translated. |
1079 | 0 | vector3 molvec, moldir; |
1080 | 0 | vector<vector<int> >::iterator j; |
1081 | 0 | vector<int>::iterator k, k2; |
1082 | 0 | vector<vector3>::iterator l; |
1083 | 0 | vector<vector<int> > mlist; // match list for fragments |
1084 | |
|
1085 | 0 | OBConversion conv; |
1086 | 0 | conv.SetOutFormat("can"); // Canonical SMILES |
1087 | | |
1088 | | // Trigger hybridisation perception now so it will be copied to workMol |
1089 | 0 | mol.GetFirstAtom()->GetHyb(); |
1090 | | |
1091 | | // copy the molecule to private data |
1092 | 0 | OBMol workMol = mol; |
1093 | | |
1094 | | // Treat a 2D structure like a 0D one |
1095 | 0 | if (workMol.GetDimension() == 2) |
1096 | 0 | workMol.SetDimension(0); |
1097 | | |
1098 | | |
1099 | | // Delete all bonds in the working molecule |
1100 | | // (we will add them back at the end) |
1101 | 0 | while (workMol.NumBonds()) |
1102 | 0 | workMol.DeleteBond(workMol.GetBond(0)); |
1103 | | |
1104 | | // Deleting the bonds unsets HybridizationPerceived. To prevent our |
1105 | | // perceived values being reperceived (incorrectly), we must set |
1106 | | // this flag again. |
1107 | 0 | workMol.SetHybridizationPerceived(); |
1108 | | |
1109 | | |
1110 | | // I think just deleting rotable bond and separate is enough, |
1111 | | // but it did not work. |
1112 | | |
1113 | | // Get fragments using CopySubstructure |
1114 | | // Copy all atoms |
1115 | 0 | OBBitVec atomsToCopy; |
1116 | 0 | FOR_ATOMS_OF_MOL(atom, mol) { |
1117 | 0 | atomsToCopy.SetBitOn(atom->GetIdx()); |
1118 | 0 | } |
1119 | | |
1120 | | // Exclude rotatable bonds |
1121 | 0 | OBBitVec bondsToExclude; |
1122 | 0 | FOR_BONDS_OF_MOL(bond, mol) { |
1123 | 0 | if (bond->IsRotor()) { |
1124 | 0 | bondsToExclude.SetBitOn(bond->GetIdx()); |
1125 | 0 | } |
1126 | 0 | } |
1127 | | |
1128 | | // Generate fragments by copy |
1129 | 0 | OBMol mol_copy; |
1130 | 0 | mol.CopySubstructure(mol_copy, &atomsToCopy, &bondsToExclude); |
1131 | | |
1132 | | // Separate each disconnected fragments as different molecules |
1133 | 0 | vector<OBMol> fragments = mol_copy.Separate(); |
1134 | | |
1135 | | // datafile is read only on first use of Build() |
1136 | 0 | if(_rigid_fragments.empty()) |
1137 | 0 | LoadFragments(); |
1138 | | |
1139 | |
|
1140 | 0 | for(vector<OBMol>::iterator f = fragments.begin(); f != fragments.end(); ++f) { |
1141 | 0 | std::string fragment_smiles = conv.WriteString(&*f, true); |
1142 | 0 | bool isMatchRigid = false; |
1143 | | // if rigid fragment is in database |
1144 | 0 | if (_rigid_fragments_index.count(fragment_smiles) > 0) { |
1145 | 0 | OBSmartsPattern sp; |
1146 | 0 | if (!sp.Init(fragment_smiles)) { |
1147 | 0 | obErrorLog.ThrowError(__FUNCTION__, " Could not parse SMARTS from fragment", obInfo); |
1148 | 0 | } else if (sp.Match(mol)) { // for all matches |
1149 | 0 | isMatchRigid = true; |
1150 | 0 | mlist = sp.GetUMapList(); |
1151 | 0 | for (j = mlist.begin(); j != mlist.end(); ++j) { |
1152 | | // Have any atoms of this match already been added? |
1153 | 0 | bool alreadydone = false; |
1154 | 0 | for (k = j->begin(); k != j->end(); ++k) |
1155 | 0 | if (vfrag.BitIsSet(*k)) { |
1156 | 0 | alreadydone = true; |
1157 | 0 | break; |
1158 | 0 | } |
1159 | 0 | if (alreadydone) continue; |
1160 | | |
1161 | 0 | for (k = j->begin(); k != j->end(); ++k) |
1162 | 0 | vfrag.SetBitOn(*k); // Set vfrag for all atoms of fragment |
1163 | |
|
1164 | 0 | int counter; |
1165 | 0 | std::vector<vector3> coords = GetFragmentCoord(fragment_smiles); |
1166 | 0 | for (k = j->begin(), counter=0; k != j->end(); ++k, ++counter) { // for all atoms of the fragment |
1167 | | // set coordinates for atoms |
1168 | 0 | OBAtom *atom = workMol.GetAtom(*k); |
1169 | 0 | atom->SetVector(coords[counter]); |
1170 | 0 | } |
1171 | | |
1172 | | // add the bonds for the fragment |
1173 | 0 | for (k = j->begin(); k != j->end(); ++k) { |
1174 | 0 | OBAtom *atom1 = mol.GetAtom(*k); |
1175 | 0 | for (k2 = j->begin(); k2 != j->end(); ++k2) { |
1176 | 0 | OBAtom *atom2 = mol.GetAtom(*k2); |
1177 | 0 | OBBond *bond = atom1->GetBond(atom2); |
1178 | 0 | if (bond != nullptr) { |
1179 | 0 | workMol.AddBond(*bond); |
1180 | 0 | } |
1181 | 0 | } |
1182 | 0 | } |
1183 | 0 | } |
1184 | 0 | } |
1185 | 0 | } |
1186 | 0 | if(!isMatchRigid) { // if rigid fragment is not in database |
1187 | | // Count the number of ring atoms. |
1188 | 0 | unsigned int ratoms = 0; |
1189 | 0 | FOR_ATOMS_OF_MOL(a, mol) { |
1190 | 0 | if (a->IsInRing()) { |
1191 | 0 | ratoms++; |
1192 | 0 | } |
1193 | 0 | } |
1194 | 0 | if (ratoms < 3) continue; // Smallest ring fragment has 3 atoms |
1195 | | |
1196 | 0 | vector<pair<OBSmartsPattern*, vector<vector3 > > >::iterator i; |
1197 | | // Skip all fragments that are too big to match |
1198 | | // Note: It would be faster to compare to the size of the largest |
1199 | | // isolated ring system instead of comparing to ratoms |
1200 | 0 | for (i = _ring_fragments.begin(); i != _ring_fragments.end() && i->first->NumAtoms() > ratoms; ++i); |
1201 | | |
1202 | | // Loop through the remaining fragments and assign the coordinates from |
1203 | | // the first (most complex) fragment. |
1204 | | // Stop if there are no unassigned ring atoms (ratoms). |
1205 | 0 | for (; i != _ring_fragments.end() && ratoms; ++i) { |
1206 | 0 | if (i->first != nullptr && i->first->Match(*f)) { // if match to fragment |
1207 | 0 | i->first->Match(mol); // match over mol |
1208 | 0 | mlist = i->first->GetUMapList(); |
1209 | 0 | for (j = mlist.begin();j != mlist.end();++j) { // for all matches |
1210 | | // Have any atoms of this match already been added? |
1211 | 0 | bool alreadydone = false; |
1212 | 0 | for (k = j->begin(); k != j->end(); ++k) { // for all atoms of the fragment |
1213 | 0 | if (vfrag.BitIsSet(*k)) { |
1214 | 0 | alreadydone = true; |
1215 | 0 | break; |
1216 | 0 | } |
1217 | 0 | } |
1218 | 0 | if (alreadydone) continue; |
1219 | 0 | for (k = j->begin(); k != j->end(); ++k) |
1220 | 0 | vfrag.SetBitOn(*k); // Set vfrag for all atoms of fragment |
1221 | |
|
1222 | 0 | int counter; |
1223 | 0 | for (k = j->begin(), counter=0; k != j->end(); ++k, ++counter) { // for all atoms of the fragment |
1224 | | // set coordinates for atoms |
1225 | 0 | OBAtom *atom = workMol.GetAtom(*k); |
1226 | 0 | atom->SetVector(i->second[counter]); |
1227 | 0 | } |
1228 | | // add the bonds for the fragment |
1229 | 0 | for (k = j->begin(); k != j->end(); ++k) { |
1230 | 0 | OBAtom *atom1 = mol.GetAtom(*k); |
1231 | 0 | for (k2 = j->begin(); k2 != j->end(); ++k2) { |
1232 | 0 | OBAtom *atom2 = mol.GetAtom(*k2); |
1233 | 0 | OBBond *bond = atom1->GetBond(atom2); |
1234 | 0 | if (bond != nullptr) |
1235 | 0 | workMol.AddBond(*bond); |
1236 | 0 | } |
1237 | 0 | } |
1238 | 0 | } |
1239 | 0 | } |
1240 | 0 | } |
1241 | 0 | } |
1242 | 0 | } // for all fragments |
1243 | | |
1244 | | // iterate over all atoms to place them in 3D space |
1245 | 0 | FOR_DFS_OF_MOL (a, mol) { |
1246 | 0 | if (vdone.BitIsSet(a->GetIdx())) // continue if the atom is already added |
1247 | 0 | continue; |
1248 | | |
1249 | | // find an atom connected to the current atom that is already added |
1250 | 0 | OBAtom *prev = nullptr; |
1251 | 0 | FOR_NBORS_OF_ATOM (nbr, &*a) { |
1252 | 0 | if (vdone.BitIsSet(nbr->GetIdx())) |
1253 | 0 | prev = &*nbr; |
1254 | 0 | } |
1255 | |
|
1256 | 0 | if (vfrag.BitIsSet(a->GetIdx())) { // Is this atom part of a fragment? |
1257 | 0 | if (prev != nullptr) { // if we have a previous atom, translate/rotate the fragment and connect it |
1258 | 0 | Connect(workMol, prev->GetIdx(), a->GetIdx(), mol.GetBond(prev, &*a)->GetBondOrder()); |
1259 | | // set the correct bond order |
1260 | 0 | int bondOrder = mol.GetBond(prev->GetIdx(), a->GetIdx())->GetBondOrder(); |
1261 | 0 | workMol.GetBond(prev->GetIdx(), a->GetIdx())->SetBondOrder(bondOrder); |
1262 | 0 | } |
1263 | |
|
1264 | 0 | OBBitVec fragment = GetFragment(workMol.GetAtom(a->GetIdx())); |
1265 | 0 | vdone |= fragment; // mark this fragment as done |
1266 | |
|
1267 | 0 | continue; |
1268 | 0 | } |
1269 | | |
1270 | | // |
1271 | | // below is the code to add non-fragment atoms |
1272 | | // |
1273 | | |
1274 | | // get the position for the new atom, this is done with GetNewBondVector |
1275 | 0 | if (prev != nullptr) { |
1276 | 0 | int bondType = a->GetBond(prev)->GetBondOrder(); |
1277 | 0 | if (a->GetBond(prev)->IsAromatic()) |
1278 | 0 | bondType = -1; |
1279 | |
|
1280 | 0 | molvec = GetCorrectedBondVector(workMol.GetAtom(prev->GetIdx()), |
1281 | 0 | workMol.GetAtom(a->GetIdx()), |
1282 | 0 | bondType); |
1283 | 0 | moldir = molvec - workMol.GetAtom(prev->GetIdx())->GetVector(); |
1284 | 0 | } else { |
1285 | | // We don't want to plant all base atoms at exactly the same spot. |
1286 | | // (or in exactly the same direction) |
1287 | | // So we'll add a slight tweak -- fixes problem reported by Kasper Thofte |
1288 | 0 | vector3 randomOffset; |
1289 | 0 | randomOffset.randomUnitVector(); |
1290 | 0 | molvec = VX + 0.1 * randomOffset; |
1291 | 0 | moldir = VX + 0.01 * randomOffset; |
1292 | 0 | } |
1293 | |
|
1294 | 0 | vdone.SetBitOn(a->GetIdx()); |
1295 | | |
1296 | | // place the atom |
1297 | 0 | OBAtom *atom = workMol.GetAtom(a->GetIdx()); |
1298 | 0 | atom->SetVector(molvec); |
1299 | | |
1300 | | // add bond between previous part and added atom |
1301 | 0 | if (prev != nullptr) { |
1302 | 0 | OBBond *bond = a->GetBond(prev); // from mol |
1303 | 0 | workMol.AddBond(*bond); |
1304 | 0 | } |
1305 | |
|
1306 | 0 | } |
1307 | | |
1308 | | // Make sure we keep the bond indexes the same |
1309 | | // so we'll delete the bonds again and copy them |
1310 | | // Fixes PR#3448379 (and likely other topology issues) |
1311 | 0 | while (workMol.NumBonds()) |
1312 | 0 | workMol.DeleteBond(workMol.GetBond(0)); |
1313 | |
|
1314 | 0 | int beginIdx, endIdx; |
1315 | 0 | FOR_BONDS_OF_MOL(b, mol) { |
1316 | 0 | beginIdx = b->GetBeginAtomIdx(); |
1317 | 0 | endIdx = b->GetEndAtomIdx(); |
1318 | 0 | workMol.AddBond(beginIdx, endIdx, b->GetBondOrder(), b->GetFlags()); |
1319 | 0 | } |
1320 | | |
1321 | | /* |
1322 | | FOR_BONDS_OF_MOL(bond, mol) { |
1323 | | if(bond->IsRotor()) { |
1324 | | OBBitVec atomsToCopy; |
1325 | | OBAtom *atom = bond->GetBeginAtom(); |
1326 | | FOR_NBORS_OF_ATOM(a, &*atom) { |
1327 | | atomsToCopy.SetBitOn(a->GetIdx()); |
1328 | | } |
1329 | | atom = bond->GetEndAtom(); |
1330 | | FOR_NBORS_OF_ATOM(a, &*atom) { |
1331 | | atomsToCopy.SetBitOn(a->GetIdx()); |
1332 | | } |
1333 | | OBMol mol_copy; |
1334 | | mol.CopySubstructure(mol_copy, &atomsToCopy); |
1335 | | string smiles = conv.WriteString(&mol_copy, true); |
1336 | | |
1337 | | if(_torsion.count(smiles) > 0) { |
1338 | | OBAtom* b = bond->GetBeginAtom(); |
1339 | | OBAtom* c = bond->GetEndAtom(); |
1340 | | OBAtom* a = nullptr; |
1341 | | FOR_NBORS_OF_ATOM(t, &*b) { |
1342 | | a = &*t; |
1343 | | if(a != c) |
1344 | | break; |
1345 | | } |
1346 | | OBAtom* d = nullptr; |
1347 | | FOR_NBORS_OF_ATOM(t, &*c) { |
1348 | | d = &*t; |
1349 | | if(d != b) |
1350 | | break; |
1351 | | } |
1352 | | double angle = _torsion[smiles] * DEG_TO_RAD; |
1353 | | mol.SetTorsion(a, b, c, d, angle); |
1354 | | } else { |
1355 | | ; // Do something |
1356 | | } |
1357 | | } |
1358 | | } |
1359 | | */ |
1360 | | |
1361 | | // We may have to change these success check |
1362 | | // correct the chirality |
1363 | 0 | bool success = CorrectStereoBonds(workMol); |
1364 | | // we only succeed if we corrected all stereochemistry |
1365 | 0 | success = success && CorrectStereoAtoms(workMol, stereoWarnings); |
1366 | | |
1367 | | /* |
1368 | | // if the stereo failed, we should use distance geometry instead |
1369 | | OBDistanceGeometry dg; |
1370 | | dg.Setup(workMol); |
1371 | | dg.GetGeometry(workMol); // ensured to have correct stereo |
1372 | | */ |
1373 | |
|
1374 | 0 | mol = workMol; |
1375 | 0 | mol.SetChiralityPerceived(); |
1376 | 0 | mol.SetDimension(3); |
1377 | |
|
1378 | 0 | bool isNanExist = false; |
1379 | 0 | FOR_ATOMS_OF_MOL(a, mol) { |
1380 | 0 | vector3 v = a->GetVector(); |
1381 | 0 | if(IsNan(v.x()) || IsNan(v.y()) || IsNan(v.z())) { |
1382 | 0 | isNanExist = true; |
1383 | 0 | break; |
1384 | 0 | } |
1385 | 0 | } |
1386 | 0 | if(isNanExist) |
1387 | 0 | obErrorLog.ThrowError(__FUNCTION__, "There exists NaN in calculated coordinates.", obWarning); |
1388 | |
|
1389 | 0 | return success; |
1390 | 0 | } |
1391 | | |
1392 | | void OBBuilder::ConnectFrags(OBMol &mol, OBMol &workMol, vector<int> match, vector<vector3> coords, |
1393 | | vector<int> pivot) |
1394 | 0 | { |
1395 | 0 | if (pivot.size() != 1) // Only handle spiro at the moment |
1396 | 0 | return; |
1397 | | |
1398 | 0 | OBAtom* p = workMol.GetAtom(pivot[0]); |
1399 | 0 | OBBitVec frag = GetFragment(p); // The existing fragment |
1400 | 0 | vector3 posp = p->GetVector(); |
1401 | | |
1402 | | // Set coords of new fragment to place the pivot at the origin |
1403 | 0 | vector3 posp_new; |
1404 | 0 | vector<int>::iterator match_it; |
1405 | 0 | int counter; |
1406 | 0 | for (match_it=match.begin(), counter=0; match_it!=match.end(); ++match_it, ++counter) |
1407 | 0 | if (*match_it == pivot[0]) { |
1408 | 0 | posp_new = coords[counter]; |
1409 | 0 | break; |
1410 | 0 | } |
1411 | 0 | counter = 0; |
1412 | 0 | for (match_it=match.begin(), counter=0; match_it!=match.end(); ++match_it, ++counter) |
1413 | 0 | workMol.GetAtom(*match_it)->SetVector( coords[counter] - posp_new ); |
1414 | | |
1415 | | // Find vector that bisects existing angles at the pivot in each fragment |
1416 | | // and align them |
1417 | | // \ / |
1418 | | // \ \ / bisect \ P align \ / |
1419 | | // P and P ---> P--v1 and | ---> P--v1 and v2--P |
1420 | | // / / v2 / \ // |
1421 | | |
1422 | | // Get v1 (from the existing fragment) |
1423 | 0 | vector3 bond1, bond2, bond3, bond4, v1; |
1424 | 0 | bond1 = VZero; |
1425 | 0 | OBAtom atom1, atom2; |
1426 | 0 | FOR_NBORS_OF_ATOM(nbr, p) { |
1427 | 0 | if (bond1 == VZero) { |
1428 | 0 | atom1 = *nbr; |
1429 | 0 | bond1 = posp - atom1.GetVector(); |
1430 | 0 | } |
1431 | 0 | else { |
1432 | 0 | atom2 = *nbr; |
1433 | 0 | bond2 = posp - atom2.GetVector(); |
1434 | 0 | } |
1435 | 0 | } |
1436 | 0 | bond1 = bond1.normalize(); |
1437 | 0 | bond2 = bond2.normalize(); |
1438 | 0 | v1 = bond1 + bond2; |
1439 | 0 | v1 = v1.normalize(); |
1440 | | |
1441 | | // Get v2 (from the new fragment) |
1442 | 0 | vector3 v2; |
1443 | 0 | vector<int> nbrs; |
1444 | 0 | vector<vector3> nbr_pos; |
1445 | 0 | FOR_NBORS_OF_ATOM(nbr, mol.GetAtom(pivot[0])) |
1446 | 0 | if (nbr->GetIdx() != atom1.GetIdx() && nbr->GetIdx() != atom2.GetIdx()) { |
1447 | 0 | nbrs.push_back(nbr->GetIdx()); |
1448 | 0 | nbr_pos.push_back(workMol.GetAtom(nbr->GetIdx())->GetVector()); |
1449 | 0 | } |
1450 | | //assert(nbrs.size()==2); |
1451 | 0 | bond3 = nbr_pos[0] - VZero; // The pivot is at the origin, hence VZero |
1452 | 0 | bond4 = nbr_pos[1] - VZero; |
1453 | 0 | bond3 = bond3.normalize(); |
1454 | 0 | bond4 = bond4.normalize(); |
1455 | 0 | v2 = bond3 + bond4; |
1456 | 0 | v2 = v2.normalize(); |
1457 | | |
1458 | | // Set up matrix to rotate around v1 x v2 by the angle between them |
1459 | 0 | double ang = vectorAngle(v1, v2); |
1460 | 0 | vector3 cp = cross(v1, v2); |
1461 | 0 | matrix3x3 mat; |
1462 | 0 | mat.RotAboutAxisByAngle(cp, ang); |
1463 | | |
1464 | | // Apply rotation |
1465 | 0 | vector3 tmpvec; |
1466 | 0 | for (match_it=match.begin(); match_it!=match.end(); ++match_it) { |
1467 | 0 | tmpvec = workMol.GetAtom(*match_it)->GetVector(); |
1468 | 0 | tmpvec *= mat; |
1469 | 0 | workMol.GetAtom(*match_it)->SetVector( tmpvec ); |
1470 | 0 | } |
1471 | | |
1472 | | // Rotate the new fragment 90 degrees to make a tetrahedron |
1473 | 0 | tmpvec = cross(bond1, bond2); // The normal to the ring |
1474 | 0 | v1 = cross(tmpvec, v1); // In the plane of the ring, orthogonal to tmpvec and the original v1 |
1475 | 0 | v2 = cross(bond3, bond4); // The normal to ring2 - we want to align v2 to v1 |
1476 | 0 | ang = vectorAngle(v1, v2); // Should be 90 |
1477 | 0 | cp = cross(v1, v2); |
1478 | 0 | mat.RotAboutAxisByAngle(cp, ang); |
1479 | 0 | for (match_it=match.begin(); match_it!=match.end(); ++match_it) { |
1480 | 0 | tmpvec = workMol.GetAtom(*match_it)->GetVector(); |
1481 | 0 | tmpvec *= mat; |
1482 | 0 | workMol.GetAtom(*match_it)->SetVector( tmpvec ); |
1483 | 0 | } |
1484 | | |
1485 | | // Translate to existing pivot location |
1486 | 0 | for (match_it=match.begin(); match_it!=match.end(); ++match_it) |
1487 | 0 | workMol.GetAtom(*match_it)->SetVector( workMol.GetAtom(*match_it)->GetVector() + posp ); |
1488 | | |
1489 | | // Create the bonds between the two fragments |
1490 | 0 | for (vector<int>::iterator nbr_id=nbrs.begin(); nbr_id!=nbrs.end(); ++nbr_id) |
1491 | 0 | workMol.AddBond(p->GetIdx(), *nbr_id, 1, mol.GetBond(p->GetIdx(), *nbr_id)->GetFlags()); |
1492 | |
|
1493 | 0 | return; |
1494 | 0 | } |
1495 | | |
1496 | | bool OBBuilder::CorrectStereoBonds(OBMol &mol) |
1497 | 0 | { |
1498 | | // Get CisTransStereos and make a vector of corresponding OBStereoUnits |
1499 | 0 | std::vector<OBCisTransStereo*> cistrans, newcistrans; |
1500 | 0 | OBStereoUnitSet sgunits; |
1501 | 0 | std::vector<OBGenericData*> vdata = mol.GetAllData(OBGenericDataType::StereoData); |
1502 | 0 | OBStereo::Ref bond_id; |
1503 | 0 | for (std::vector<OBGenericData*>::iterator data = vdata.begin(); data != vdata.end(); ++data) |
1504 | 0 | if (((OBStereoBase*)*data)->GetType() == OBStereo::CisTrans) { |
1505 | 0 | OBCisTransStereo *ct = dynamic_cast<OBCisTransStereo*>(*data); |
1506 | 0 | if (ct->GetConfig().specified) { |
1507 | 0 | cistrans.push_back(ct); |
1508 | 0 | bond_id = mol.GetBond(mol.GetAtomById(ct->GetConfig().begin), |
1509 | 0 | mol.GetAtomById(ct->GetConfig().end))->GetId(); |
1510 | 0 | sgunits.push_back(OBStereoUnit(OBStereo::CisTrans, bond_id)); |
1511 | 0 | } |
1512 | 0 | } |
1513 | | |
1514 | | // Perceive CisTransStereos |
1515 | 0 | newcistrans = CisTransFrom3D(&mol, sgunits, false); |
1516 | | |
1517 | | // Compare and correct if necessary |
1518 | 0 | double newangle, angle; |
1519 | 0 | OBAtom *a, *b, *c, *d; |
1520 | 0 | std::vector<OBCisTransStereo*>::iterator origct, newct; |
1521 | 0 | for (origct=cistrans.begin(), newct=newcistrans.begin(); origct!=cistrans.end(); ++origct, ++newct) { |
1522 | 0 | OBCisTransStereo::Config config = (*newct)->GetConfig(OBStereo::ShapeU); |
1523 | |
|
1524 | 0 | if ((*origct)->GetConfig(OBStereo::ShapeU) != config) { // Wrong cis/trans stereochemistry |
1525 | | |
1526 | | // refs[0] refs[3] |
1527 | | // \ / |
1528 | | // begin==end |
1529 | | // / \ / |
1530 | | // refs[1] refs[2] |
1531 | |
|
1532 | 0 | a = mol.GetAtomById(config.refs[0]); |
1533 | 0 | b = mol.GetAtomById(config.begin); |
1534 | 0 | c = mol.GetAtomById(config.end); |
1535 | 0 | if (config.refs[3] != OBStereo::ImplicitRef) |
1536 | 0 | d = mol.GetAtomById(config.refs[3]); |
1537 | 0 | else |
1538 | 0 | d = mol.GetAtomById(config.refs[2]); |
1539 | 0 | angle = mol.GetTorsion(a, b, c, d); // In degrees |
1540 | 0 | newangle = angle * DEG_TO_RAD + M_PI; // flip the bond by 180 deg (PI radians) |
1541 | | // if it's a ring, break a ring bond before rotating |
1542 | 0 | mol.SetTorsion(a, b, c, d, newangle); // In radians |
1543 | 0 | } |
1544 | 0 | } |
1545 | |
|
1546 | 0 | return true; // was all the ring bond stereochemistry corrected? |
1547 | 0 | } |
1548 | | |
1549 | | bool OBBuilder::IsSpiroAtom(unsigned long atomId, OBMol &mol) |
1550 | 0 | { |
1551 | 0 | OBMol workmol = mol; // Make a copy (this invalidates Ids, but not Idxs) |
1552 | 0 | OBAtom* watom = workmol.GetAtom(mol.GetAtomById(atomId)->GetIdx()); |
1553 | 0 | if (watom->GetHvyDegree() != 4) // QUESTION: Do I need to restrict it further? |
1554 | 0 | return false; |
1555 | | |
1556 | 0 | int atomsInSameRing = 0; |
1557 | 0 | int atomsInDiffRings = 0; |
1558 | 0 | FOR_NBORS_OF_ATOM(n, watom) { |
1559 | 0 | if (!n->IsInRing()) |
1560 | 0 | return false; |
1561 | 0 | if (mol.AreInSameRing(&*n, watom)) |
1562 | 0 | atomsInSameRing++; |
1563 | 0 | else |
1564 | 0 | atomsInDiffRings++; |
1565 | 0 | } |
1566 | | |
1567 | 0 | if (atomsInSameRing == 2 && atomsInDiffRings == 2) |
1568 | 0 | return true; |
1569 | | |
1570 | 0 | return false; |
1571 | 0 | } |
1572 | | |
1573 | | void OBBuilder::FlipSpiro(OBMol &mol, int idx) |
1574 | 0 | { |
1575 | 0 | OBAtom *p = mol.GetAtom(idx); // The pivot atom |
1576 | | |
1577 | | // Find two of the four bonds that are in the same ring |
1578 | 0 | vector<int> nbrs; |
1579 | 0 | FOR_NBORS_OF_ATOM(nbr, p) |
1580 | 0 | nbrs.push_back(nbr->GetIdx()); |
1581 | | //assert(nbrs.size() == 4); |
1582 | | |
1583 | | // Which neighbour is in the same ring as nbrs[0]? The answer is 'ringnbr'. |
1584 | 0 | vector<int> children; |
1585 | 0 | mol.FindChildren(children, idx, nbrs[0]); |
1586 | 0 | int ringnbr = -1; |
1587 | 0 | for (vector<int>::iterator nbr=nbrs.begin() + 1; nbr!=nbrs.end(); ++nbr) |
1588 | 0 | if (find(children.begin(), children.end(), *nbr) != children.end()) { |
1589 | 0 | ringnbr = *nbr; |
1590 | 0 | break; |
1591 | 0 | } |
1592 | | //assert (ringnbr != -1); |
1593 | | |
1594 | | // Split into a fragment to be flipped |
1595 | 0 | OBMol workMol = mol; |
1596 | 0 | workMol.DeleteBond(workMol.GetBond(idx, nbrs[0])); |
1597 | 0 | workMol.DeleteBond(workMol.GetBond(idx, ringnbr)); |
1598 | 0 | OBBitVec fragment = GetFragment(workMol.GetAtom(nbrs[0])); |
1599 | | |
1600 | | // Translate fragment to origin |
1601 | 0 | vector3 posP = p->GetVector(); |
1602 | 0 | for (unsigned int i = 1; i <= workMol.NumAtoms(); ++i) |
1603 | 0 | if (fragment.BitIsSet(i)) |
1604 | 0 | workMol.GetAtom(i)->SetVector(workMol.GetAtom(i)->GetVector() - posP); |
1605 | | |
1606 | | // Rotate 180 deg around the bisector of nbrs[0]--p--ringnbr |
1607 | 0 | vector3 bond1 = posP - mol.GetAtom(nbrs[0])->GetVector(); |
1608 | 0 | vector3 bond2 = posP - mol.GetAtom(ringnbr)->GetVector(); |
1609 | 0 | bond1.normalize(); |
1610 | 0 | bond2.normalize(); |
1611 | 0 | vector3 axis = bond1 + bond2; // The bisector of bond1 and bond2 |
1612 | |
|
1613 | 0 | matrix3x3 mat; |
1614 | 0 | mat.RotAboutAxisByAngle(axis, 180); |
1615 | 0 | vector3 tmpvec; |
1616 | 0 | for (unsigned int i = 1; i <= workMol.NumAtoms(); ++i) |
1617 | 0 | if (fragment.BitIsSet(i)) { |
1618 | 0 | tmpvec = workMol.GetAtom(i)->GetVector(); |
1619 | 0 | tmpvec *= mat; |
1620 | 0 | workMol.GetAtom(i)->SetVector( tmpvec ); |
1621 | 0 | } |
1622 | | |
1623 | | // Set the coordinates of the original molecule using those of workmol |
1624 | 0 | for (unsigned int i = 1; i <= workMol.NumAtoms(); ++i) |
1625 | 0 | if (fragment.BitIsSet(i)) |
1626 | 0 | mol.GetAtom(i)->SetVector(workMol.GetAtom(i)->GetVector() + posP); |
1627 | 0 | } |
1628 | | |
1629 | | bool OBBuilder::CorrectStereoAtoms(OBMol &mol, bool warn) |
1630 | 0 | { |
1631 | 0 | bool success = true; // for now |
1632 | | |
1633 | | // Get TetrahedralStereos and make a vector of corresponding OBStereoUnits |
1634 | 0 | std::vector<OBTetrahedralStereo*> tetra, newtetra; |
1635 | 0 | OBStereoUnitSet sgunits; |
1636 | 0 | std::vector<OBGenericData*> vdata = mol.GetAllData(OBGenericDataType::StereoData); |
1637 | 0 | OBStereo::Ref atom_id; |
1638 | 0 | for (std::vector<OBGenericData*>::iterator data = vdata.begin(); data != vdata.end(); ++data) |
1639 | 0 | if (((OBStereoBase*)*data)->GetType() == OBStereo::Tetrahedral) { |
1640 | 0 | OBTetrahedralStereo *th = dynamic_cast<OBTetrahedralStereo*>(*data); |
1641 | 0 | if (th->GetConfig().specified) { |
1642 | 0 | tetra.push_back(th); |
1643 | 0 | atom_id = th->GetConfig().center; |
1644 | 0 | sgunits.push_back(OBStereoUnit(OBStereo::Tetrahedral, atom_id)); |
1645 | 0 | } |
1646 | 0 | } |
1647 | | |
1648 | | // Perceive TetrahedralStereos |
1649 | 0 | newtetra = TetrahedralFrom3D(&mol, sgunits, false); |
1650 | | |
1651 | | // Identify any ring stereochemistry and whether it is right or wrong |
1652 | | // - ring stereo involves 3 ring bonds, or 4 ring bonds but the |
1653 | | // atom must not be spiro |
1654 | 0 | OBAtom* center; |
1655 | 0 | bool existswrongstereo = false; // Is there at least one wrong ring stereo? |
1656 | 0 | typedef std::pair<OBStereo::Ref, bool> IsThisStereoRight; |
1657 | 0 | std::vector<IsThisStereoRight> ringstereo; |
1658 | 0 | std::vector<OBTetrahedralStereo*> nonringtetra, nonringnewtetra; |
1659 | 0 | std::vector<OBTetrahedralStereo*>::iterator origth, newth; |
1660 | 0 | for (origth=tetra.begin(), newth=newtetra.begin(); origth!=tetra.end(); ++origth, ++newth) { |
1661 | 0 | OBTetrahedralStereo::Config config = (*newth)->GetConfig(OBStereo::Clockwise, OBStereo::ViewFrom); |
1662 | |
|
1663 | 0 | center = mol.GetAtomById(config.center); |
1664 | 0 | int ringbonds = 0; |
1665 | 0 | FOR_BONDS_OF_ATOM(b, center) |
1666 | 0 | if (b->IsInRing()) |
1667 | 0 | ringbonds++; |
1668 | |
|
1669 | 0 | if (ringbonds == 3 || (ringbonds==4 && !OBBuilder::IsSpiroAtom(config.center, mol))) { |
1670 | 0 | bool rightstereo = (*origth)->GetConfig(OBStereo::Clockwise, OBStereo::ViewFrom) == config; |
1671 | 0 | ringstereo.push_back(IsThisStereoRight(config.center, rightstereo)); |
1672 | 0 | if (!rightstereo) |
1673 | 0 | existswrongstereo = true; |
1674 | 0 | } |
1675 | 0 | else { // A non-ring stereocenter |
1676 | 0 | nonringtetra.push_back(*origth); |
1677 | 0 | nonringnewtetra.push_back(*newth); |
1678 | 0 | } |
1679 | 0 | } |
1680 | |
|
1681 | 0 | if (existswrongstereo) { |
1682 | | // Fix ring stereo |
1683 | 0 | OBStereo::Refs unfixed; |
1684 | 0 | bool inversion = FixRingStereo(ringstereo, mol, unfixed); |
1685 | | |
1686 | | // Output warning message if necessary |
1687 | 0 | if (unfixed.size() > 0 && warn) { |
1688 | 0 | stringstream errorMsg; |
1689 | 0 | errorMsg << "Could not correct " << unfixed.size() << " stereocenter(s) in this molecule (" << mol.GetTitle() << ")"; |
1690 | 0 | errorMsg << std::endl << " with Atom Ids as follows:"; |
1691 | 0 | for (OBStereo::RefIter ref=unfixed.begin(); ref!=unfixed.end(); ++ref) |
1692 | 0 | errorMsg << " " << *ref; |
1693 | 0 | obErrorLog.ThrowError(__FUNCTION__, errorMsg.str(), obWarning); |
1694 | |
|
1695 | 0 | success = false; // uncorrected bond |
1696 | 0 | } |
1697 | | |
1698 | | // Reperceive non-ring TetrahedralStereos if an inversion occurred |
1699 | 0 | if (inversion) { |
1700 | 0 | sgunits.clear(); |
1701 | 0 | for (origth = nonringtetra.begin(); origth != nonringtetra.end(); ++origth) |
1702 | 0 | sgunits.push_back(OBStereoUnit(OBStereo::Tetrahedral, (*origth)->GetConfig().center)); |
1703 | 0 | nonringnewtetra = TetrahedralFrom3D(&mol, sgunits, false); |
1704 | 0 | } |
1705 | 0 | } |
1706 | | |
1707 | | // Correct the non-ring stereo |
1708 | 0 | for (origth=nonringtetra.begin(), newth=nonringnewtetra.begin(); origth!=nonringtetra.end(); ++origth, ++newth) { |
1709 | 0 | OBTetrahedralStereo::Config config = (*newth)->GetConfig(OBStereo::Clockwise, OBStereo::ViewFrom); |
1710 | 0 | if ((*origth)->GetConfig(OBStereo::Clockwise, OBStereo::ViewFrom) != config) { |
1711 | | // Wrong tetrahedral stereochemistry |
1712 | | |
1713 | | // Try to find two non-ring bonds |
1714 | 0 | center = mol.GetAtomById(config.center); |
1715 | 0 | vector<unsigned int> idxs; |
1716 | 0 | FOR_BONDS_OF_ATOM(b, center) |
1717 | 0 | if (!b->IsInRing()) |
1718 | 0 | idxs.push_back(b->GetNbrAtom(center)->GetIdx()); |
1719 | |
|
1720 | 0 | if (idxs.size() == 0 && OBBuilder::IsSpiroAtom(config.center, mol)) |
1721 | 0 | FlipSpiro(mol, center->GetIdx()); |
1722 | 0 | else if (idxs.size() >= 2) |
1723 | 0 | Swap(mol, center->GetIdx(), idxs.at(0), center->GetIdx(), idxs.at(1)); |
1724 | 0 | else { |
1725 | | // It will only reach here if it can only find one non-ring bond |
1726 | | // -- this is the case if the other non-ring bond is an implicit H |
1727 | | // or a lone pair |
1728 | | // Solution: Find where a new bond vector would be placed, and |
1729 | | // replace the atom's coordinates with these |
1730 | 0 | OBAtom* non_ring_atom = mol.GetAtom(idxs.at(0)); |
1731 | 0 | OBBond* non_ring_bond = mol.GetBond(center, non_ring_atom); |
1732 | 0 | vector3 newcoords = OBBuilder::GetNewBondVector(center, non_ring_bond->GetLength()); |
1733 | 0 | SwapWithVector(mol, center->GetIdx(), idxs.at(0), center->GetIdx(), newcoords); |
1734 | 0 | } |
1735 | 0 | } |
1736 | 0 | } |
1737 | |
|
1738 | 0 | return success; // did we fix all atoms, including ring stereo? |
1739 | 0 | } |
1740 | | |
1741 | | bool OBBuilder::FixRingStereo(std::vector<std::pair<OBStereo::Ref, bool> > atomIds, OBMol &mol, |
1742 | | OBStereo::Refs &unfixedcenters) |
1743 | 0 | { |
1744 | 0 | bool inversion = false; |
1745 | 0 | if (atomIds.size() == 0) return inversion; |
1746 | | |
1747 | | // Have we dealt with a particular ring stereo? (Indexed by Id) |
1748 | 0 | OBBitVec seen; |
1749 | |
|
1750 | 0 | for(unsigned int n=0; n<atomIds.size(); ++n) { |
1751 | | // Keep looping until you come to an unseen wrong stereo |
1752 | 0 | if (seen.BitIsSet(atomIds[n].first) || atomIds[n].second) continue; |
1753 | | |
1754 | 0 | OBBitVec fragment; // Indexed by Id |
1755 | 0 | AddRingNbrs(fragment, mol.GetAtomById(atomIds[n].first), mol); |
1756 | | |
1757 | | // Which ring stereos does this fragment contain, and |
1758 | | // are the majority of them right or wrong? |
1759 | 0 | OBStereo::Refs wrong, right; |
1760 | 0 | for (unsigned int i=0; i<atomIds.size(); ++i) |
1761 | 0 | if (fragment.BitIsSet(atomIds[i].first)) { |
1762 | 0 | if (atomIds[i].second) |
1763 | 0 | right.push_back(atomIds[i].first); |
1764 | 0 | else |
1765 | 0 | wrong.push_back(atomIds[i].first); |
1766 | 0 | seen.SetBitOn(atomIds[i].first); |
1767 | 0 | } |
1768 | |
|
1769 | 0 | if (right > wrong) { // Inverting would make things worse! |
1770 | 0 | unfixedcenters.insert(unfixedcenters.end(), wrong.begin(), wrong.end()); |
1771 | 0 | continue; |
1772 | 0 | } |
1773 | 0 | unfixedcenters.insert(unfixedcenters.end(), right.begin(), right.end()); |
1774 | | |
1775 | | // Invert the coordinates (QUESTION: should I invert relative to the centroid?) |
1776 | 0 | inversion = true; |
1777 | 0 | FOR_ATOMS_OF_MOL(a, mol) |
1778 | 0 | if (fragment.BitIsSet(a->GetId())) |
1779 | 0 | a->SetVector( - a->GetVector()); |
1780 | | |
1781 | | // Add neighbouring bonds back onto the fragment |
1782 | | // TODO: Handle spiro |
1783 | 0 | std::vector<OBBond*> reconnect; |
1784 | 0 | FOR_ATOMS_OF_MOL(a, mol) |
1785 | 0 | if (fragment.BitIsSet(a->GetId())) |
1786 | 0 | FOR_BONDS_OF_ATOM(b, &*a) |
1787 | 0 | if (!b->IsInRing()) |
1788 | 0 | reconnect.push_back(&*b); |
1789 | |
|
1790 | 0 | for (std::vector<OBBond*>::iterator bi=reconnect.begin(); bi!=reconnect.end(); ++bi) { |
1791 | 0 | OBBond* b = *bi; |
1792 | 0 | int bo = b->GetBondOrder(); |
1793 | 0 | int begin = b->GetBeginAtomIdx(); |
1794 | 0 | int end = b->GetEndAtomIdx(); |
1795 | 0 | mol.DeleteBond(b); |
1796 | 0 | OBBuilder::Connect(mol, begin, end, bo); |
1797 | 0 | } |
1798 | 0 | } |
1799 | |
|
1800 | 0 | return inversion; |
1801 | 0 | } |
1802 | | |
1803 | | void OBBuilder::AddRingNbrs(OBBitVec &fragment, OBAtom *atom, OBMol &mol) |
1804 | 0 | { |
1805 | | // Add the nbrs to the fragment, but don't add the neighbours of a spiro atom. |
1806 | 0 | FOR_NBORS_OF_ATOM (nbr, atom) { |
1807 | 0 | if (mol.GetBond(&*nbr, atom)->IsInRing() && !fragment.BitIsSet(nbr->GetId()) |
1808 | 0 | && !OBBuilder::IsSpiroAtom(atom->GetId(), mol)) { |
1809 | 0 | fragment.SetBitOn(nbr->GetId()); |
1810 | 0 | AddRingNbrs(fragment, &*nbr, mol); |
1811 | 0 | } |
1812 | 0 | } |
1813 | 0 | } |
1814 | | |
1815 | | } // end namespace OpenBabel |
1816 | | |
1817 | | |
1818 | | //! \file builder.cpp |
1819 | | //! \brief Handle OBBuilder class |