Coverage Report

Created: 2026-09-04 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openbabel/src/ops/gen3d.cpp
Line
Count
Source
1
/**********************************************************************
2
gen3d.cpp - A OBOp for generation of 3D coordinates (wrapper for OBBuilder)
3
4
Copyright (C) 2006-2007 by Tim Vandermeersch
5
          (C) 2007 by Chris Morley
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
#include <openbabel/op.h>
21
#include <openbabel/mol.h>
22
#include <openbabel/builder.h>
23
#include <openbabel/distgeom.h>
24
#include <openbabel/forcefield.h>
25
#include <openbabel/oberror.h>
26
#include "../stereo/gen3dstereohelper.h"
27
28
#include <cstdlib> // needed for strtol and gcc 4.8
29
30
namespace OpenBabel
31
{
32
33
class OpGen3D : public OBOp
34
{
35
public:
36
2
  OpGen3D(const char* ID) : OBOp(ID, false){};
37
0
  const char* Description() override { return "Generate 3D coordinates"; }
38
39
0
  bool WorksWith(OBBase* pOb) const override { return dynamic_cast<OBMol*>(pOb) != nullptr; }
40
  bool Do(OBBase* pOb, const char* OptionText=nullptr, OpMap* pOptions=nullptr,
41
      OBConversion* pConv=nullptr) override;
42
};
43
44
/////////////////////////////////////////////////////////////////
45
OpGen3D theOpGen3D("gen3D"); //Global instance
46
47
/////////////////////////////////////////////////////////////////
48
bool OpGen3D::Do(OBBase* pOb, const char* OptionText, OpMap* /*pOptions*/, OBConversion* /*pConv*/)
49
0
{
50
0
  OBMol* pmol = dynamic_cast<OBMol*>(pOb);
51
0
  if(!pmol)
52
0
    return false;
53
54
  // As with gen2D, we need to perceive the stereo if coming from 0D.
55
  // Otherwise, unspecified cis/trans stereobonds become specified.
56
0
  if (pmol->GetDimension() == 0) {
57
0
    pmol->UnsetFlag(OB_CHIRALITY_MOL);
58
0
    StereoFrom0D(pmol);
59
0
  }
60
61
0
  OBGen3DStereoHelper stereoHelper;
62
0
  stereoHelper.Setup(pmol);
63
64
  // 1 is best quality, slowest
65
  // 2 is good quality, slow
66
  // 3 is balance   (FF cleanup + FastRotorSearch)
67
  // 4 is fast      (OBBuilder + FF cleanup)
68
  // 5 is fastest   (only OBBuilder)
69
  // 6 is DistanceGeometry
70
0
  int speed;
71
0
  bool useDistGeom = false;
72
73
0
  if (!OptionText)
74
0
    OptionText = "";
75
76
  // first try converting OptionText to an integer
77
0
  char *endptr;
78
0
  speed = strtol(OptionText, &endptr, 10);
79
0
  if (endptr == OptionText) { // not a number
80
0
    speed = 3; // we'll default to balanced
81
    // but let's also check if it's words like "fast" or "best"
82
0
    if (strncasecmp(OptionText, "fastest", 7) == 0)
83
0
      speed = 5;
84
0
    else if (strncasecmp(OptionText, "fast", 4) == 0) // already matched fastest
85
0
      speed = 4;
86
0
    else if (strncasecmp(OptionText, "med", 3) == 0) // or medium
87
0
      speed = 3;
88
0
    else if ( (strncasecmp(OptionText, "slowest", 7) == 0)
89
0
             || (strncasecmp(OptionText, "best", 4) == 0) )
90
0
      speed = 1;
91
0
    else if ( (strncasecmp(OptionText, "slow", 4) == 0)
92
0
              || (strncasecmp(OptionText, "better", 6) == 0) )
93
0
      speed = 2;
94
0
    else if ( (strncasecmp(OptionText, "dist", 4) == 0)
95
0
               || (strncasecmp(OptionText, "dg", 2) == 0) ) {
96
0
      useDistGeom = true;
97
0
      speed = 5;
98
0
    }
99
0
  }
100
101
  // Give some limits so we can use switch statements
102
0
  if (speed < 1)
103
0
    speed = 1;
104
0
  else if (speed > 5)
105
0
    speed = 5;
106
107
0
  bool success = false;
108
0
  unsigned int maxIter = 25;
109
0
  for (unsigned int trial = 0; trial < maxIter; trial++) {
110
0
    OBMol molCopy = *pmol;
111
112
    // This is done for all speed levels (i.e., create the structure)
113
0
    OBBuilder builder;
114
0
    bool attemptBuild = !useDistGeom;
115
0
    if (attemptBuild) {
116
0
      if (!builder.Build(molCopy) || !molCopy.HasNonZeroCoords()) {
117
0
        std::cerr << "Warning: 3D builder failed, using distance geometry instead" << std::endl;
118
0
        useDistGeom = true; // don't try building anymore
119
0
        attemptBuild = false; // don't use zero/garbage coords as distgeom seed
120
0
        molCopy = *pmol; // reset to original before distgeom
121
0
      }
122
0
    }
123
124
#ifdef HAVE_EIGEN3
125
    OBDistanceGeometry dg;
126
    if (useDistGeom) {
127
      // use the bond lengths and angles if we ran the builder
128
      if (!dg.GetGeometry(molCopy, attemptBuild)) // ensured to have correct stereo
129
        continue;
130
      speed = 3;
131
    }
132
#endif
133
134
    // rule-based builder worked
135
0
    molCopy.SetDimension(3);
136
0
    molCopy.AddHydrogens(false, false); // Add some hydrogens before running MMFF
137
138
0
    if (speed == 5)
139
0
      return true; // done
140
141
    // All other speed levels do some FF cleanup
142
    // Try MMFF94 first and UFF if that doesn't work
143
0
    OBForceField* pFF = OBForceField::FindForceField("MMFF94");
144
0
    if (!pFF)
145
0
      return true;
146
0
    if (!pFF->Setup(molCopy)) {
147
0
      pFF = OBForceField::FindForceField("UFF");
148
0
      if (!pFF || !pFF->Setup(molCopy)) return true; // can't use either MMFF94 or UFF
149
0
    }
150
151
    // Since we only want a rough geometry, use distance cutoffs for VDW, Electrostatics
152
0
    pFF->EnableCutOff(true);
153
0
    pFF->SetVDWCutOff(10.0);
154
0
    pFF->SetElectrostaticCutOff(20.0);
155
0
    pFF->SetUpdateFrequency(10); // update non-bonded distances infrequently
156
157
    // How many cleanup cycles?
158
0
    int iterations = 250;
159
0
    switch (speed) {
160
0
    case 1:
161
0
      iterations = 500;
162
0
      break;
163
0
    case 2:
164
0
      iterations = 250;
165
0
      break;
166
0
    case 3:
167
0
    case 4:
168
0
    default:
169
0
      iterations = 100;
170
0
    }
171
172
    // Initial cleanup for every level
173
0
    pFF->LBFGS(iterations, 1.0e-4);
174
175
0
    if (speed == 4) {
176
0
      pFF->GetCoordinates(molCopy);
177
0
      return true; // no conformer searching
178
0
    }
179
180
0
    switch(speed) {
181
0
    case 1:
182
0
      pFF->WeightedRotorSearch(250, 10); // maybe based on # of rotatable bonds?
183
0
      break;
184
0
    case 2:
185
0
      pFF->FastRotorSearch(true); // permute central rotors
186
0
      break;
187
0
    case 3:
188
0
    default:
189
0
      pFF->FastRotorSearch(false); // only one permutation
190
0
    }
191
192
    // Final cleanup and copy the new coordinates back
193
0
    pFF->LBFGS(iterations, 1.0e-6);
194
0
    pFF->GetCoordinates(molCopy);
195
196
    // Check stereochemistry
197
0
    success = stereoHelper.Check(&molCopy);
198
0
    if (success) {
199
0
      *pmol = molCopy;
200
0
      break;
201
0
    }
202
    // Builder produced wrong stereo; switch to distance geometry for
203
    // remaining trials (molCopy is reset to *pmol at the top of the loop).
204
0
    if (!useDistGeom) {
205
0
      std::cerr << "Warning: Stereochemistry is wrong, using distance geometry instead" << std::endl;
206
0
      useDistGeom = true;
207
0
    }
208
0
  }
209
210
0
  if (!success) {
211
0
    obErrorLog.ThrowError(__FUNCTION__, "3D coordinate generation failed", obError);
212
0
  }
213
214
0
  return true;
215
0
}
216
}//namespace