Coverage Report

Created: 2026-02-26 07:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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