Coverage Report

Created: 2025-11-24 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openbabel/src/forcefields/forcefielduff.cpp
Line
Count
Source
1
/**********************************************************************
2
forcefielduff.cpp - UFF force field.
3
4
Copyright (C) 2007-2008 by Geoffrey Hutchison
5
Some portions Copyright (C) 2006-2008 by Tim Vandermeersch
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
20
#include <openbabel/babelconfig.h>
21
#include <openbabel/mol.h>
22
#include <openbabel/locale.h>
23
#include <openbabel/elements.h>
24
#include <openbabel/atom.h>
25
#include <openbabel/obiter.h>
26
#include <openbabel/generic.h>
27
#include <openbabel/bond.h>
28
#include <openbabel/parsmart.h>
29
30
#include <cstdlib>
31
32
#include "forcefielduff.h"
33
34
35
using namespace std;
36
37
// This implementation was created based on open code and reference websites:
38
// http://towhee.sourceforge.net/forcefields/uff.html
39
// http://rdkit.org/
40
// http://franklin.chm.colostate.edu/mmac/uff.html
41
// (for the last, use the Wayback Machine: http://www.archive.org/
42
43
// As well, the main UFF paper:
44
// Rappe, A. K., et. al.; J. Am. Chem. Soc. (1992) 114(25) p. 10024-10035.
45
46
namespace OpenBabel {
47
48
  template<bool gradients>
49
  void OBFFBondCalculationUFF::Compute()
50
0
  {
51
0
    if (OBForceField::IgnoreCalculation(idx_a, idx_b)) {
52
0
      energy = 0.0;
53
0
      return;
54
0
    }
55
56
0
    vector3 vab, da, db;
57
0
    double delta2, dE;
58
59
0
    if (gradients) {
60
0
      rab = OBForceField::VectorBondDerivative(pos_a, pos_b, force_a, force_b);
61
0
    } else {
62
0
      rab = OBForceField::VectorDistance(pos_a, pos_b);
63
0
    }
64
65
    // Harmonic bond stretching
66
0
    delta = rab - r0; // we pre-compute the r0 below
67
0
    delta2 = delta * delta;
68
0
    energy = kb * delta2; // we fold the 1/2 into kb below
69
70
0
    if (gradients) {
71
0
      dE = 2.0 * kb * delta;
72
0
      OBForceField::VectorSelfMultiply(force_a, dE);
73
0
      OBForceField::VectorSelfMultiply(force_b, dE);
74
0
    }
75
0
  }
Unexecuted instantiation: void OpenBabel::OBFFBondCalculationUFF::Compute<true>()
Unexecuted instantiation: void OpenBabel::OBFFBondCalculationUFF::Compute<false>()
76
77
  template<bool gradients>
78
  double OBForceFieldUFF::E_Bond()
79
0
  {
80
0
    vector<OBFFBondCalculationUFF>::iterator i;
81
0
    double energy = 0.0;
82
83
0
    IF_OBFF_LOGLVL_HIGH {
84
0
      OBFFLog("\nB O N D   S T R E T C H I N G\n\n");
85
0
      OBFFLog("ATOM TYPES  BOND    BOND       IDEAL       FORCE\n");
86
0
      OBFFLog(" I      J   TYPE   LENGTH     LENGTH     CONSTANT      DELTA      ENERGY\n");
87
0
      OBFFLog("------------------------------------------------------------------------\n");
88
0
    }
89
90
0
    for (i = _bondcalculations.begin(); i != _bondcalculations.end(); ++i) {
91
92
0
      i->template Compute<gradients>();
93
0
      energy += i->energy;
94
95
0
      if (gradients) {
96
0
        AddGradient((*i).force_a, (*i).idx_a);
97
0
        AddGradient((*i).force_b, (*i).idx_b);
98
0
      }
99
100
0
      IF_OBFF_LOGLVL_HIGH {
101
0
        snprintf(_logbuf, BUFF_SIZE, "%-5s %-5s  %4.2f%8.3f   %8.3f     %8.3f   %8.3f   %8.3f\n",
102
0
                 (*i).a->GetType(), (*i).b->GetType(),
103
0
                 (*i).bt, (*i).rab, (*i).r0, (*i).kb, (*i).delta, (*i).energy);
104
0
        OBFFLog(_logbuf);
105
0
      }
106
0
    }
107
108
0
    IF_OBFF_LOGLVL_MEDIUM {
109
0
      snprintf(_logbuf, BUFF_SIZE, "     TOTAL BOND STRETCHING ENERGY = %8.3f %s\n",  energy, GetUnit().c_str());
110
0
      OBFFLog(_logbuf);
111
0
    }
112
0
    return energy;
113
0
  }
Unexecuted instantiation: double OpenBabel::OBForceFieldUFF::E_Bond<true>()
Unexecuted instantiation: double OpenBabel::OBForceFieldUFF::E_Bond<false>()
114
115
  template<bool gradients>
116
  void OBFFAngleCalculationUFF::Compute()
117
0
  {
118
0
    if (OBForceField::IgnoreCalculation(idx_a, idx_b, idx_c)) {
119
0
      energy = 0.0;
120
0
      return;
121
0
    }
122
123
0
    vector3 da, db, dc;
124
0
    double dE;
125
126
0
    if (gradients) {
127
0
      theta = OBForceField::VectorAngleDerivative(pos_a, pos_b, pos_c, force_a, force_b, force_c);
128
129
      // Supply a small nudge if the angle is degenerate
130
0
      if (theta < 2.5 || theta > 357.5) {
131
0
        vector3 v1;
132
0
        v1.randomUnitVector();
133
0
        for (int i = 0; i < 3; ++i)
134
0
          force_a[i] += v1[i]*0.1;
135
0
      }
136
0
      theta *= DEG_TO_RAD;
137
0
    } else {
138
0
      theta = a->GetAngle(b, c) * DEG_TO_RAD;
139
0
    }
140
141
0
    if (!isfinite(theta))
142
0
      theta = 0.0; // doesn't explain why GetAngle is returning NaN but solves it for us
143
144
0
    double cosT;
145
146
0
    switch (coord) {
147
0
    case 1: // sp -- linear case, minima at 180 degrees, max (amplitude 2*ka) at 0, 360
148
      // Fixed typo from Rappe paper (i.e., it's NOT 1 - cosT)
149
0
      energy = ka*(1.0 + cos(theta));
150
0
      break;
151
0
    case 2: // sp2 -- trigonal planar or equatorial plane of trigonal bipyramidal
152
0
    case 4: // square planar
153
0
    case 6: // octahedral
154
      // ka already is pre-computed as ka/n^2 to save CPU cycles
155
      // UNLIKE Rappe paper, we add a penalty for angles close to zero, based on ESFF
156
      // i.e., if the angle is less than approx theta0, energy goes up exponentially
157
0
      energy = ka * (1 - cos(n*theta)) + exp(-20.0*(theta - theta0 + 0.25));
158
0
      break;
159
0
    case 7: // IF7 pentagonal -- pentagonal bipyramidal
160
      /* theta = 1/5 * 2 pi.  cosT = .30901699
161
       * theta = 2/5 * 2 pi.  cosT = -.80901699
162
       * theta = 3/5 * 2 pi.  cosT = -.80901699
163
       * theta = 4/5 * 2 pi.  cosT = .30901699
164
       */
165
0
      cosT = cos(theta);
166
0
      energy = ka * c1 * (cosT - .30901699) * (cosT - .30906199) * (cosT + .80901699) * (cosT + .8091699);
167
0
      break;
168
0
    default: // general (sp3) coordination
169
0
      cosT = cos(theta);
170
0
      energy = ka*(c0 + c1*cosT + c2*(2.0*cosT*cosT - 1.0)); // use cos 2t = (2cos^2 - 1)
171
0
    }
172
173
0
    if (gradients) {
174
0
      double sinT;
175
176
0
      switch (coord) {
177
0
      case 1: // sp -- linear case
178
0
        dE = -ka * sin(theta);
179
0
        break;
180
0
      case 2: // sp2 -- trigonal planar
181
0
      case 6: // octahedral
182
0
      case 4: // square planar
183
0
        dE = ka * n * sin(n * theta)  -20.0 * exp(-20.0*(theta - theta0 + 0.25));
184
0
        break;
185
0
      case 7: // pentagonal bipyramidal
186
0
        sinT = sin(theta);
187
0
        cosT = cos(theta);
188
0
        dE =
189
0
          c1 * -ka * (2 * sinT * (cosT - .30906199) * (cosT + .80901699) * (cosT + .8091699) +
190
0
                      2 * sinT * (cosT - .30901699) * (cosT - .30906199) * (cosT + .8091699));
191
        //dE = -ka * c1 * sin(5*theta) * 5;
192
0
        break;
193
0
      default: // general (sp3) coordination
194
0
        dE = -ka * (c1*sin(theta) + 2.0 * c2*sin(2 * theta));
195
0
      }
196
197
0
      OBForceField::VectorSelfMultiply(force_a, dE);
198
0
      OBForceField::VectorSelfMultiply(force_b, dE);
199
0
      OBForceField::VectorSelfMultiply(force_c, dE);
200
0
    }
201
0
  }
Unexecuted instantiation: void OpenBabel::OBFFAngleCalculationUFF::Compute<true>()
Unexecuted instantiation: void OpenBabel::OBFFAngleCalculationUFF::Compute<false>()
202
203
  template<bool gradients>
204
  double OBForceFieldUFF::E_Angle()
205
0
  {
206
0
    vector<OBFFAngleCalculationUFF>::iterator i;
207
0
    double energy = 0.0;
208
209
0
    IF_OBFF_LOGLVL_HIGH {
210
0
      OBFFLog("\nA N G L E   B E N D I N G\n\n");
211
0
      OBFFLog("ATOM TYPES       VALENCE     IDEAL      FORCE\n");
212
0
      OBFFLog(" I    J    K      ANGLE      ANGLE     CONSTANT      DELTA      ENERGY\n");
213
0
      OBFFLog("-----------------------------------------------------------------------------\n");
214
0
    }
215
216
0
    for (i = _anglecalculations.begin(); i != _anglecalculations.end(); ++i) {
217
218
0
      i->template Compute<gradients>();
219
0
      energy += i->energy;
220
221
0
      if (gradients) {
222
0
        AddGradient((*i).force_a, (*i).idx_a);
223
0
        AddGradient((*i).force_b, (*i).idx_b);
224
0
        AddGradient((*i).force_c, (*i).idx_c);
225
0
      }
226
227
0
      IF_OBFF_LOGLVL_HIGH {
228
0
        snprintf(_logbuf, BUFF_SIZE, "%-5s %-5s %-5s%8.3f  %8.3f     %8.3f   %8.3f   %8.3f\n", (*i).a->GetType(), (*i).b->GetType(),
229
0
                 (*i).c->GetType(), (*i).theta * RAD_TO_DEG, (*i).theta0, (*i).ka, (*i).delta, (*i).energy);
230
0
        OBFFLog(_logbuf);
231
0
      }
232
0
    }
233
234
0
    IF_OBFF_LOGLVL_MEDIUM {
235
0
      snprintf(_logbuf, BUFF_SIZE, "     TOTAL ANGLE BENDING ENERGY = %8.3f %s\n", energy, GetUnit().c_str());
236
0
      OBFFLog(_logbuf);
237
0
    }
238
0
    return energy;
239
0
  }
Unexecuted instantiation: double OpenBabel::OBForceFieldUFF::E_Angle<true>()
Unexecuted instantiation: double OpenBabel::OBForceFieldUFF::E_Angle<false>()
240
241
  template<bool gradients>
242
  void OBFFTorsionCalculationUFF::Compute()
243
0
  {
244
0
    if (OBForceField::IgnoreCalculation(idx_a, idx_b, idx_c, idx_d)) {
245
0
      energy = 0.0;
246
0
      return;
247
0
    }
248
249
0
    vector3 da, db, dc, dd;
250
0
    double cosine;
251
0
    double dE;
252
253
0
    if (gradients) {
254
0
      tor = OBForceField::VectorTorsionDerivative(pos_a, pos_b, pos_c, pos_d,
255
0
                                                  force_a, force_b, force_c, force_d);
256
0
      if (!isfinite(tor))
257
0
        tor = 1.0e-3;
258
0
      tor *= DEG_TO_RAD;
259
0
    } else {
260
0
      vector3 vab, vbc, vcd, abbc, bccd;
261
0
      vab = a->GetVector() - b->GetVector();
262
0
      vbc = b->GetVector() - c->GetVector();
263
0
      vcd = c->GetVector() - d->GetVector();
264
0
      abbc = cross(vab, vbc);
265
0
      bccd = cross(vbc, vcd);
266
267
0
      double dotAbbcBccd = dot(abbc,bccd);
268
0
      tor = acos(dotAbbcBccd / (abbc.length() * bccd.length()));
269
0
      if (IsNearZero(dotAbbcBccd) || !isfinite(tor)) { // stop any NaN or infinity
270
0
        tor = 1.0e-3; // rather than NaN
271
0
      }
272
0
      else if (dotAbbcBccd > 0.0) {
273
0
        tor = -tor;
274
0
      }
275
0
    }
276
277
0
    cosine = cos(tor * n);
278
0
    energy = V * (1.0 - cosNPhi0*cosine);
279
280
0
    if (gradients) {
281
0
      dE = -(V * n * cosNPhi0 * sin(n * tor));
282
0
      OBForceField::VectorSelfMultiply(force_a, dE);
283
0
      OBForceField::VectorSelfMultiply(force_b, dE);
284
0
      OBForceField::VectorSelfMultiply(force_c, dE);
285
0
      OBForceField::VectorSelfMultiply(force_d, dE);
286
0
    }
287
0
  }
Unexecuted instantiation: void OpenBabel::OBFFTorsionCalculationUFF::Compute<true>()
Unexecuted instantiation: void OpenBabel::OBFFTorsionCalculationUFF::Compute<false>()
288
289
  template<bool gradients>
290
  double OBForceFieldUFF::E_Torsion()
291
0
  {
292
0
    vector<OBFFTorsionCalculationUFF>::iterator i;
293
0
    double energy = 0.0;
294
295
0
    IF_OBFF_LOGLVL_HIGH {
296
0
      OBFFLog("\nT O R S I O N A L\n\n");
297
0
      OBFFLog("----ATOM TYPES-----    FORCE         TORSION\n");
298
0
      OBFFLog(" I    J    K    L     CONSTANT        ANGLE         ENERGY\n");
299
0
      OBFFLog("----------------------------------------------------------------\n");
300
0
    }
301
302
0
    for (i = _torsioncalculations.begin(); i != _torsioncalculations.end(); ++i) {
303
304
0
      i->template Compute<gradients>();
305
0
      energy += i->energy;
306
307
0
      if (gradients) {
308
0
        AddGradient((*i).force_a, (*i).idx_a);
309
0
        AddGradient((*i).force_b, (*i).idx_b);
310
0
        AddGradient((*i).force_c, (*i).idx_c);
311
0
        AddGradient((*i).force_d, (*i).idx_d);
312
0
      }
313
314
0
      IF_OBFF_LOGLVL_HIGH {
315
0
        snprintf(_logbuf, BUFF_SIZE, "%-5s %-5s %-5s %-5s%6.3f       %8.3f     %8.3f\n",
316
0
                 (*i).a->GetType(), (*i).b->GetType(),
317
0
                 (*i).c->GetType(), (*i).d->GetType(), (*i).V,
318
0
                 (*i).tor * RAD_TO_DEG, (*i).energy);
319
0
        OBFFLog(_logbuf);
320
0
      }
321
0
    }
322
323
0
    IF_OBFF_LOGLVL_MEDIUM {
324
0
      snprintf(_logbuf, BUFF_SIZE, "     TOTAL TORSIONAL ENERGY = %8.3f %s\n", energy, GetUnit().c_str());
325
0
      OBFFLog(_logbuf);
326
0
    }
327
328
0
    return energy;
329
0
  }
Unexecuted instantiation: double OpenBabel::OBForceFieldUFF::E_Torsion<true>()
Unexecuted instantiation: double OpenBabel::OBForceFieldUFF::E_Torsion<false>()
330
331
  /*
332
  //  a
333
  //   \
334
  //    b---d      plane = a-b-c
335
  //   /
336
  //  c
337
  */
338
  template<bool gradients>
339
  void OBFFOOPCalculationUFF::Compute()
340
0
  {
341
0
    if (OBForceField::IgnoreCalculation(idx_a, idx_b, idx_c, idx_d)) {
342
0
      energy = 0.0;
343
0
      return;
344
0
    }
345
346
0
    vector3 da, db, dc, dd;
347
0
    double dE;
348
349
0
    if (gradients) {
350
0
      angle = OBForceField::VectorOOPDerivative(pos_a, pos_b, pos_c, pos_d,
351
0
                                                force_a, force_b, force_c, force_d);
352
0
      angle *= DEG_TO_RAD;
353
354
0
      if (!isfinite(angle))
355
0
        angle = 0.0; // doesn't explain why GetAngle is returning NaN but solves it for us;
356
357
      // somehow we already get the -1 from the OOPDeriv -- so we'll omit it here
358
0
      dE = koop * (c1*sin(angle) + 2.0 * c2 * sin(2.0*angle));
359
0
      OBForceField::VectorSelfMultiply(force_a, dE);
360
0
      OBForceField::VectorSelfMultiply(force_b, dE);
361
0
      OBForceField::VectorSelfMultiply(force_c, dE);
362
0
      OBForceField::VectorSelfMultiply(force_d, dE);
363
0
    } else {
364
0
      angle = DEG_TO_RAD*Point2PlaneAngle(d->GetVector(), a->GetVector(), b->GetVector(), c->GetVector());
365
0
      if (!isfinite(angle))
366
0
        angle = 0.0; // doesn't explain why GetAngle is returning NaN but solves it for us;
367
0
    }
368
369
0
    energy = koop * (c0 + c1 * cos(angle) + c2 * cos(2.0*angle));
370
0
  }
Unexecuted instantiation: void OpenBabel::OBFFOOPCalculationUFF::Compute<true>()
Unexecuted instantiation: void OpenBabel::OBFFOOPCalculationUFF::Compute<false>()
371
372
  template<bool gradients>
373
  double OBForceFieldUFF::E_OOP()
374
0
  {
375
0
    vector<OBFFOOPCalculationUFF>::iterator i;
376
0
    double energy = 0.0;
377
378
0
    IF_OBFF_LOGLVL_HIGH {
379
0
      OBFFLog("\nO U T - O F - P L A N E   B E N D I N G\n\n");
380
0
      OBFFLog("ATOM TYPES                 OOP     FORCE \n");
381
0
      OBFFLog(" I    J     K     L       ANGLE   CONSTANT     ENERGY\n");
382
0
      OBFFLog("----------------------------------------------------------\n");
383
0
    }
384
385
0
    for (i = _oopcalculations.begin(); i != _oopcalculations.end(); ++i) {
386
0
      i->template Compute<gradients>();
387
0
      energy += i->energy;
388
389
0
      if (gradients) {
390
0
        AddGradient((*i).force_a, (*i).idx_a);
391
0
        AddGradient((*i).force_b, (*i).idx_b);
392
0
        AddGradient((*i).force_c, (*i).idx_c);
393
0
        AddGradient((*i).force_d, (*i).idx_d);
394
0
      }
395
396
0
      IF_OBFF_LOGLVL_HIGH {
397
0
        snprintf(_logbuf, BUFF_SIZE, "%-5s %-5s %-5s %-5s%8.3f   %8.3f     %8.3f\n", (*i).a->GetType(), (*i).b->GetType(), (*i).c->GetType(), (*i).d->GetType(),
398
0
                 (*i).angle * RAD_TO_DEG, (*i).koop, (*i).energy);
399
0
        OBFFLog(_logbuf);
400
0
      }
401
0
    }
402
403
0
    IF_OBFF_LOGLVL_HIGH {
404
0
      snprintf(_logbuf, BUFF_SIZE, "     TOTAL OUT-OF-PLANE BENDING ENERGY = %8.3f %s\n", energy, GetUnit().c_str());
405
0
      OBFFLog(_logbuf);
406
0
    }
407
0
    return energy;
408
0
  }
Unexecuted instantiation: double OpenBabel::OBForceFieldUFF::E_OOP<true>()
Unexecuted instantiation: double OpenBabel::OBForceFieldUFF::E_OOP<false>()
409
410
  template<bool gradients>
411
  void OBFFVDWCalculationUFF::Compute()
412
0
  {
413
0
    if (OBForceField::IgnoreCalculation(idx_a, idx_b)) {
414
0
      energy = 0.0;
415
0
      return;
416
0
    }
417
418
0
    vector3 da, db;
419
0
    double term6, term12, dE, term7, term13, rabSquared = 0.0;
420
421
0
    if (gradients) {
422
0
      rab = OBForceField::VectorDistanceDerivative(pos_a, pos_b, force_a, force_b);
423
424
0
      if (rab < 1.0e-3)
425
0
        rab = 1.0e-3;
426
427
0
      rabSquared = SQUARE(rab);
428
0
    } else {
429
      // Get distance squared (saves a sqrt and multiply)
430
      // for every energy evaluation
431
0
      double ab[3];
432
0
      for (unsigned int c = 0; c < 3; ++c)
433
0
        rabSquared += SQUARE(a->GetCoordinate()[c] - b->GetCoordinate()[c]);
434
435
      // make sure the energy doesn't blow up
436
0
      if (rabSquared < 1.0e-5)
437
0
        rabSquared = 1.0e-5;
438
0
    }
439
440
    // TODO: This actually should include zetas (not always exactly 6-12 for VDW paper)
441
442
0
    term6 = kaSquared / rabSquared; // ^2
443
0
    term6 = term6 * term6 * term6; // ^6
444
0
    term12 = term6 * term6; // ^12
445
446
0
    energy = kab * ((term12) - (2.0 * term6));
447
448
0
    if (gradients) {
449
0
      term13 = term12 / rab; // ^13
450
0
      term7 = term6 / rab; // ^7
451
0
      dE = kab * 12.0 * (term7 - term13);
452
0
      OBForceField::VectorSelfMultiply(force_a, dE);
453
0
      OBForceField::VectorSelfMultiply(force_b, dE);
454
0
    }
455
0
  }
Unexecuted instantiation: void OpenBabel::OBFFVDWCalculationUFF::Compute<true>()
Unexecuted instantiation: void OpenBabel::OBFFVDWCalculationUFF::Compute<false>()
456
457
  template<bool gradients>
458
  double OBForceFieldUFF::E_VDW()
459
0
  {
460
0
    vector<OBFFVDWCalculationUFF>::iterator i;
461
0
    double energy = 0.0;
462
463
0
    IF_OBFF_LOGLVL_HIGH {
464
0
      OBFFLog("\nV A N   D E R   W A A L S\n\n");
465
0
      OBFFLog("ATOM TYPES\n");
466
0
      OBFFLog(" I    J        Rij       kij       ENERGY\n");
467
0
      OBFFLog("-----------------------------------------\n");
468
      //          XX   XX     -000.000  -000.000  -000.000  -000.000
469
0
    }
470
471
0
    unsigned int j = 0;
472
0
    for (i = _vdwcalculations.begin(); i != _vdwcalculations.end(); ++i, ++j) {
473
      // Cut-off check
474
0
      if (_cutoff)
475
0
        if (!_vdwpairs.BitIsSet(j))
476
0
          continue;
477
478
0
      i->template Compute<gradients>();
479
0
      energy += i->energy;
480
481
0
      if (gradients) {
482
0
        AddGradient((*i).force_a, (*i).idx_a);
483
0
        AddGradient((*i).force_b, (*i).idx_b);
484
0
      }
485
486
0
      IF_OBFF_LOGLVL_HIGH {
487
0
        snprintf(_logbuf, BUFF_SIZE, "%-5s %-5s %8.3f  %8.3f  %8.3f\n", (*i).a->GetType(), (*i).b->GetType(),
488
0
                 (*i).rab, (*i).kab, (*i).energy);
489
0
        OBFFLog(_logbuf);
490
0
      }
491
0
    }
492
493
0
    IF_OBFF_LOGLVL_MEDIUM {
494
0
      snprintf(_logbuf, BUFF_SIZE, "     TOTAL VAN DER WAALS ENERGY = %8.3f %s\n", energy, GetUnit().c_str());
495
0
      OBFFLog(_logbuf);
496
0
    }
497
498
0
    return energy;
499
0
  }
Unexecuted instantiation: double OpenBabel::OBForceFieldUFF::E_VDW<true>()
Unexecuted instantiation: double OpenBabel::OBForceFieldUFF::E_VDW<false>()
500
501
  template<bool gradients>
502
  void OBFFElectrostaticCalculationUFF::Compute()
503
0
  {
504
0
    if (OBForceField::IgnoreCalculation(idx_a, idx_b)) {
505
0
      energy = 0.0;
506
0
      return;
507
0
    }
508
509
0
    vector3 da, db;
510
0
    double dE, rab2;
511
512
0
    if (gradients) {
513
0
      da = a->GetVector();
514
0
      db = b->GetVector();
515
0
      rab = OBForceField::VectorLengthDerivative(da, db);
516
0
    } else
517
0
      rab = a->GetDistance(b);
518
519
0
    if (IsNearZero(rab, 1.0e-3))
520
0
      rab = 1.0e-3;
521
522
0
    energy = qq / rab;
523
524
0
    if (gradients) {
525
0
      rab2 = rab * rab;
526
0
      dE = -qq / rab2;
527
0
      da *= dE;
528
0
      db *= dE;
529
0
      da.Get(force_a);
530
0
      db.Get(force_b);
531
0
    }
532
0
  }
Unexecuted instantiation: void OpenBabel::OBFFElectrostaticCalculationUFF::Compute<true>()
Unexecuted instantiation: void OpenBabel::OBFFElectrostaticCalculationUFF::Compute<false>()
533
534
  template<bool gradients>
535
  double OBForceFieldUFF::E_Electrostatic()
536
0
  {
537
0
    vector<OBFFElectrostaticCalculationUFF>::iterator i;
538
0
    double energy = 0.0;
539
540
0
    IF_OBFF_LOGLVL_HIGH {
541
0
      OBFFLog("\nE L E C T R O S T A T I C   I N T E R A C T I O N S\n\n");
542
0
      OBFFLog("ATOM TYPES\n");
543
0
      OBFFLog(" I    J           Rij   332.17*QiQj  ENERGY\n");
544
0
      OBFFLog("-------------------------------------------\n");
545
      //            XX   XX     -000.000  -000.000  -000.000
546
0
    }
547
548
0
    unsigned int j = 0;
549
0
    for (i = _electrostaticcalculations.begin(); i != _electrostaticcalculations.end(); ++i, ++j) {
550
      // Cut-off check
551
0
      if (_cutoff)
552
0
        if (!_elepairs.BitIsSet(j))
553
0
          continue;
554
555
0
      i->template Compute<gradients>();
556
0
      energy += i->energy;
557
558
0
      if (gradients) {
559
0
        AddGradient((*i).force_a, (*i).idx_a);
560
0
        AddGradient((*i).force_b, (*i).idx_b);
561
0
      }
562
563
0
      IF_OBFF_LOGLVL_HIGH {
564
0
        snprintf(_logbuf, BUFF_SIZE, "%-5s %-5s   %8.3f  %8.3f  %8.3f\n", (*i).a->GetType(), (*i).b->GetType(),
565
0
                 (*i).rab, (*i).qq, (*i).energy);
566
0
        OBFFLog(_logbuf);
567
0
      }
568
0
    }
569
570
0
    IF_OBFF_LOGLVL_MEDIUM {
571
0
      snprintf(_logbuf, BUFF_SIZE, "     TOTAL ELECTROSTATIC ENERGY = %8.3f %s\n", energy, GetUnit().c_str());
572
0
      OBFFLog(_logbuf);
573
0
    }
574
575
0
    return energy;
576
0
  }
Unexecuted instantiation: double OpenBabel::OBForceFieldUFF::E_Electrostatic<true>()
Unexecuted instantiation: double OpenBabel::OBForceFieldUFF::E_Electrostatic<false>()
577
578
  //***********************************************
579
  //Make a global instance
580
  OBForceFieldUFF theForceFieldUFF("UFF", true);
581
  //***********************************************
582
583
  OBForceFieldUFF::~OBForceFieldUFF()
584
0
  {
585
0
  }
586
587
  OBForceFieldUFF &OBForceFieldUFF::operator=(OBForceFieldUFF &src)
588
0
  {
589
0
    _mol = src._mol;
590
591
0
    _ffparams    = src._ffparams;
592
593
0
    _bondcalculations          = src._bondcalculations;
594
0
    _anglecalculations         = src._anglecalculations;
595
0
    _torsioncalculations       = src._torsioncalculations;
596
0
    _oopcalculations           = src._oopcalculations;
597
0
    _vdwcalculations           = src._vdwcalculations;
598
0
    _electrostaticcalculations = src._electrostaticcalculations;
599
0
    _init                      = src._init;
600
601
0
    return *this;
602
0
  }
603
604
  double CalculateBondDistance(OBFFParameter *i, OBFFParameter *j, double bondorder)
605
0
  {
606
0
    double ri, rj;
607
0
    double chiI, chiJ;
608
0
    double rbo, ren;
609
0
    ri = i->_dpar[0];
610
0
    rj = j->_dpar[0];
611
0
    chiI = i->_dpar[8];
612
0
    chiJ = j->_dpar[8];
613
614
    // Precompute the equilibrium bond distance
615
    // From equation 3
616
0
    rbo = -0.1332*(ri+rj)*log(bondorder);
617
    // From equation 4
618
0
    ren = ri*rj*(pow((sqrt(chiI) - sqrt(chiJ)),2.0)) / (chiI*ri + chiJ*rj);
619
    // From equation 2
620
    // NOTE: See http://towhee.sourceforge.net/forcefields/uff.html
621
    // There is a typo in the published paper
622
0
    return(ri + rj + rbo - ren);
623
0
  }
624
625
  bool OBForceFieldUFF::SetupVDWCalculation(OBAtom *a, OBAtom *b, OBFFVDWCalculationUFF &vdwcalc)
626
0
  {
627
0
    OBFFParameter *parameterA, *parameterB;
628
0
    parameterA = GetParameterUFF(a->GetType(), _ffparams);
629
0
    parameterB = GetParameterUFF(b->GetType(), _ffparams);
630
631
0
    if (parameterA == nullptr || parameterB == nullptr) {
632
0
      IF_OBFF_LOGLVL_LOW {
633
0
        snprintf(_logbuf, BUFF_SIZE, "    COULD NOT FIND PARAMETERS FOR VDW INTERACTION %d-%d (IDX)...\n",
634
0
                 a->GetIdx(), b->GetIdx());
635
0
        OBFFLog(_logbuf);
636
0
      }
637
0
      return false;
638
0
    }
639
640
0
    vdwcalc.Ra = parameterA->_dpar[2];
641
0
    vdwcalc.ka = parameterA->_dpar[3];
642
0
    vdwcalc.Rb = parameterB->_dpar[2];
643
0
    vdwcalc.kb = parameterB->_dpar[3];
644
645
0
    vdwcalc.a = &*a;
646
0
    vdwcalc.b = &*b;
647
648
    //this calculations only need to be done once for each pair,
649
    //we do them now and save them for later use
650
0
    vdwcalc.kab = KCAL_TO_KJ * sqrt(vdwcalc.ka * vdwcalc.kb);
651
652
    // 1-4 scaling
653
    // This isn't mentioned in the UFF paper, but is common for other methods
654
    //       if (a->IsOneFour(b))
655
    //         vdwcalc.kab *= 0.5;
656
657
    // ka now represents the xij in equation 20 -- the expected vdw distance
658
0
    vdwcalc.kaSquared = (vdwcalc.Ra * vdwcalc.Rb);
659
0
    vdwcalc.ka = sqrt(vdwcalc.kaSquared);
660
661
0
    vdwcalc.SetupPointers();
662
0
    return true;
663
0
  }
664
665
  int GetCoordination(OBAtom *b, int ipar)
666
0
  {
667
0
    int coordination;
668
669
    // Work out coordination
670
    // including possible hypervalent compounds
671
0
    int valenceElectrons = 0;
672
0
    switch(b->GetAtomicNum())
673
0
      {
674
0
      case 15:
675
0
      case 33:
676
0
      case 51:
677
0
      case 83:
678
        // old "group 5": P, As, Sb, Bi
679
0
        valenceElectrons = 5;
680
0
        break;
681
0
      case 16:
682
0
      case 34:
683
0
      case 52:
684
0
      case 84:
685
        // old "group 6": S, Se, Te, Po
686
0
        valenceElectrons = 6;
687
0
        break;
688
0
      case 35:
689
0
      case 53:
690
0
      case 85:
691
        // old "group 7": Br, I, At
692
0
        valenceElectrons = 7;
693
0
        break;
694
0
      case 36:
695
0
      case 54:
696
0
      case 86:
697
        // hypervalent noble gases (Kr, Xe, Rn)
698
0
        valenceElectrons = 8;
699
0
        break;
700
0
      }
701
0
    if (valenceElectrons) {
702
      // calculate the number of lone pairs
703
      // e.g. for IF3 => "T-shaped"
704
0
      valenceElectrons -= b->GetFormalCharge(); // make sure to look for I+F4 -> see-saw
705
0
      double lonePairs = (valenceElectrons - b->GetExplicitValence()) / 2.0;
706
      // we actually need to round up here -- single e- take room too.
707
0
      int sites = (int)ceil(lonePairs);
708
0
      coordination = b->GetExplicitDegree() + sites;
709
0
      if (coordination <= 4) { // normal valency
710
0
        coordination = ipar;
711
0
      } else if (b->GetAtomicNum() == OBElements::Sulfur && b->CountFreeOxygens() == 3) {
712
        // SO3, should be planar
713
        // PR#2971473, thanks to Philipp Rumpf
714
0
        coordination = 2; // i.e., sp2
715
0
      }
716
      /* planar coordination of hexavalent molecules.*/
717
0
      if (lonePairs == 0 && b->GetExplicitDegree() == 3 && b->GetExplicitValence() == 6) {
718
0
        coordination = 2;
719
0
      }
720
0
      if (lonePairs == 0 && b->GetExplicitDegree() == 7) {
721
0
        coordination = 7;
722
0
      }
723
      // Check to see if coordination is really correct
724
      // if not (e.g., 5- or 7- or 8-coord...)
725
      // then create approximate angle bending terms
726
0
    } else {
727
0
      coordination = ipar; // coordination of central atom
728
0
    }
729
0
    if (b->GetExplicitDegree() > 4) {
730
0
      coordination = b->GetExplicitDegree();
731
0
    } else {
732
0
      int coordDifference = ipar - b->GetExplicitDegree();
733
0
      if (abs(coordDifference) > 2)
734
        // low valent, but very different than expected by ipar
735
0
        coordination = b->GetExplicitDegree() - 1; // 4 coordinate == sp3
736
0
    }
737
0
    return coordination;
738
0
  }
739
740
  bool OBForceFieldUFF::SetupCalculations()
741
0
  {
742
0
    OBFFParameter *parameterA, *parameterB, *parameterC;
743
0
    OBAtom *a, *b, *c, *d;
744
0
    double bondorder;
745
0
    OBFFBondCalculationUFF bondcalc;
746
0
    OBFFAngleCalculationUFF anglecalc;
747
0
    OBFFTorsionCalculationUFF torsioncalc;
748
0
    OBFFOOPCalculationUFF oopcalc;
749
0
    OBFFVDWCalculationUFF vdwcalc;
750
751
0
    IF_OBFF_LOGLVL_LOW
752
0
      OBFFLog("\nS E T T I N G   U P   C A L C U L A T I O N S\n\n");
753
754
    // Clear previous calculations
755
0
    _bondcalculations.clear();
756
0
    _anglecalculations.clear();
757
0
    _torsioncalculations.clear();
758
0
    _oopcalculations.clear();
759
0
    _vdwcalculations.clear();
760
761
    // Clear and reset any 5-coordinate axial/equatorial marks (i.e., strange coordination)
762
    // Now should fit standard VSEPR rules, although we can't easily handle lone pairs
763
0
    int coordination;
764
0
    FOR_ATOMS_OF_MOL(atom, _mol) {
765
      // remove any previous designation
766
0
      atom->DeleteData("UFF_AXIAL_ATOM");
767
0
      atom->DeleteData("UFF_CENTRAL_ATOM");
768
0
    }
769
770
0
    FOR_ATOMS_OF_MOL(atom, _mol) {
771
0
      parameterB = GetParameterUFF(atom->GetType(), _ffparams);
772
773
      // GitHub issue #1794
774
0
      if (parameterB == nullptr) {
775
0
        snprintf(_logbuf, BUFF_SIZE, "    COULD NOT FIND PARAMETERS FOR ATOM %d (IDX)...\n",
776
0
                 atom->GetIdx());
777
0
        obErrorLog.ThrowError(__FUNCTION__, _logbuf, obWarning);
778
0
        IF_OBFF_LOGLVL_LOW
779
0
          OBFFLog(_logbuf);
780
0
        return false;
781
0
      }
782
783
0
      if (GetCoordination(&*atom, parameterB->_ipar[0]) == 5) { // we need to do work for trigonal-bipy!
784
        // First, find the two largest neighbors
785
0
        OBAtom *largestNbr, *current, *secondLargestNbr = nullptr;
786
0
        double largestRadius;
787
0
        OBBondIterator i;
788
0
        largestNbr = atom->BeginNbrAtom(i);
789
        // work out the radius
790
0
        parameterA = GetParameterUFF(largestNbr->GetType(), _ffparams);
791
792
0
        if (parameterA == nullptr) {
793
0
          IF_OBFF_LOGLVL_LOW {
794
0
            snprintf(_logbuf, BUFF_SIZE, "    COULD NOT FIND PARAMETERS FOR ATOM %d (IDX)...\n",
795
0
                largestNbr->GetIdx());
796
0
            OBFFLog(_logbuf);
797
0
          }
798
0
          return false;
799
0
        }
800
801
0
        largestRadius = parameterA->_dpar[0];
802
803
0
        for (current = atom->NextNbrAtom(i); current; current = atom->NextNbrAtom(i)) {
804
0
          parameterA = GetParameterUFF(current->GetType(), _ffparams);
805
806
0
          if (parameterA == nullptr) {
807
0
            IF_OBFF_LOGLVL_LOW {
808
0
              snprintf(_logbuf, BUFF_SIZE, "    COULD NOT FIND PARAMETERS FOR ATOM %d (IDX)...\n",
809
0
                  current->GetIdx());
810
0
              OBFFLog(_logbuf);
811
0
            }
812
0
            return false;
813
0
          }
814
815
0
          if (parameterA->_dpar[0] > largestRadius) {
816
            // New largest neighbor
817
0
            secondLargestNbr = largestNbr;
818
0
            largestRadius = parameterA->_dpar[0];
819
0
            largestNbr = current;
820
0
          }
821
0
          if (secondLargestNbr == nullptr) {
822
            // save this atom
823
0
            secondLargestNbr = current;
824
0
          }
825
0
        }
826
827
        // OK, now we tag the central atom
828
0
        OBPairData *label = new OBPairData;
829
0
        label->SetAttribute("UFF_CENTRAL_ATOM");
830
0
        label->SetValue("True"); // doesn't really matter
831
0
        atom->SetData(label);
832
833
0
        label = new OBPairData;
834
0
        label->SetAttribute("UFF_AXIAL_ATOM");
835
0
        label->SetValue("True");
836
0
        largestNbr->SetData(label);
837
838
0
        if (secondLargestNbr != nullptr) { // check for NULL, no guarantee
839
0
          label = new OBPairData;
840
0
          label->SetAttribute("UFF_AXIAL_ATOM");
841
0
          label->SetValue("True");
842
0
          secondLargestNbr->SetData(label);
843
0
        }
844
845
0
      } // end work for 5-coordinate angles
846
0
      if (GetCoordination(&*atom, parameterB->_ipar[0]) == 7) { // pentagonal bipyramidal
847
        // First, find the two largest neighbors
848
0
        OBAtom *largestNbr, *current, *secondLargestNbr = nullptr;
849
0
        double largestRadius;
850
0
        OBBondIterator i;
851
0
        largestNbr = atom->BeginNbrAtom(i);
852
        // work out the radius
853
0
        parameterA = GetParameterUFF(largestNbr->GetType(), _ffparams);
854
855
0
        if (parameterA == nullptr) {
856
0
          IF_OBFF_LOGLVL_LOW {
857
0
            snprintf(_logbuf, BUFF_SIZE, "    COULD NOT FIND PARAMETERS FOR ATOM %d (IDX)...\n",
858
0
                largestNbr->GetIdx());
859
0
            OBFFLog(_logbuf);
860
0
          }
861
0
          return false;
862
0
        }
863
864
0
        largestRadius = parameterA->_dpar[0];
865
866
0
        for (current = atom->NextNbrAtom(i); current; current = atom->NextNbrAtom(i)) {
867
0
          parameterA = GetParameterUFF(current->GetType(), _ffparams);
868
869
0
          if (parameterA == nullptr) {
870
0
            IF_OBFF_LOGLVL_LOW {
871
0
              snprintf(_logbuf, BUFF_SIZE, "    COULD NOT FIND PARAMETERS FOR ATOM %d (IDX)...\n",
872
0
                  current->GetIdx());
873
0
              OBFFLog(_logbuf);
874
0
            }
875
0
            return false;
876
0
          }
877
878
0
          if (parameterA->_dpar[0] > largestRadius) {
879
            // New largest neighbor
880
0
            secondLargestNbr = largestNbr;
881
0
            largestRadius = parameterA->_dpar[0];
882
0
            largestNbr = current;
883
0
          }
884
0
          if (secondLargestNbr == nullptr) {
885
            // save this atom
886
0
            secondLargestNbr = current;
887
0
          }
888
0
        }
889
890
        // OK, now we tag the central atom
891
0
        OBPairData *label = new OBPairData;
892
0
        label->SetAttribute("UFF_CENTRAL_ATOM");
893
0
        label->SetValue("True"); // doesn't really matter
894
0
        atom->SetData(label);
895
        // And tag the axial substituents
896
0
        label = new OBPairData;
897
0
        label->SetAttribute("UFF_AXIAL_ATOM");
898
0
        label->SetValue("True");
899
0
        largestNbr->SetData(label);
900
0
        if (secondLargestNbr != nullptr) { // check for NULL, no guarantee
901
0
          label = new OBPairData;
902
0
          label->SetAttribute("UFF_AXIAL_ATOM");
903
0
          label->SetValue("True");
904
0
          secondLargestNbr->SetData(label);
905
0
        }
906
0
      }
907
0
    } // end loop through atoms
908
909
    //
910
    // Bond Calculations
911
0
    IF_OBFF_LOGLVL_LOW
912
0
      OBFFLog("SETTING UP BOND CALCULATIONS...\n");
913
914
0
    FOR_BONDS_OF_MOL(bond, _mol) {
915
0
      a = bond->GetBeginAtom();
916
0
      b = bond->GetEndAtom();
917
918
      // skip this bond if the atoms are ignored
919
0
      if ( _constraints.IsIgnored(a->GetIdx()) || _constraints.IsIgnored(b->GetIdx()) )
920
0
        continue;
921
922
      // if there are any groups specified, check if the two bond atoms are in a single intraGroup
923
0
      if (HasGroups()) {
924
0
        bool validBond = false;
925
0
        for (unsigned int i=0; i < _intraGroup.size(); ++i) {
926
0
          if (_intraGroup[i].BitIsSet(a->GetIdx()) && _intraGroup[i].BitIsSet(b->GetIdx()))
927
0
            validBond = true;
928
0
        }
929
0
        if (!validBond)
930
0
          continue;
931
0
      }
932
933
0
      bondorder = bond->GetBondOrder();
934
0
      if (bond->IsAromatic())
935
0
        bondorder = 1.5;
936
      // e.g., in Cp rings, may not be "aromatic" by OB
937
      // but check for explicit hydrogen counts (e.g., biphenyl inter-ring is not aromatic)
938
0
      if ((a->GetType()[2] == 'R' && b->GetType()[2] == 'R')
939
0
          && (a->ExplicitHydrogenCount() == 1 && b->ExplicitHydrogenCount() == 1))
940
0
        bondorder = 1.5;
941
0
      if (bond->IsAmide())
942
0
        bondorder = 1.41;
943
944
0
      bondcalc.a = a;
945
0
      bondcalc.b = b;
946
0
      bondcalc.bt = bondorder;
947
948
0
      parameterA = GetParameterUFF(a->GetType(), _ffparams);
949
0
      parameterB = GetParameterUFF(b->GetType(), _ffparams);
950
951
0
      if (parameterA == nullptr || parameterB == nullptr) {
952
0
        IF_OBFF_LOGLVL_LOW {
953
0
          snprintf(_logbuf, BUFF_SIZE, "    COULD NOT FIND PARAMETERS FOR BOND %d-%d (IDX)...\n",
954
0
                   a->GetIdx(), b->GetIdx());
955
0
          OBFFLog(_logbuf);
956
0
        }
957
0
        continue;
958
0
      }
959
960
0
      bondcalc.r0 = CalculateBondDistance(parameterA, parameterB, bondorder);
961
962
      // here we fold the 1/2 into the kij from equation 1a
963
      // Otherwise, this is equation 6 from the UFF paper.
964
0
      bondcalc.kb = (0.5 * KCAL_TO_KJ * 664.12
965
0
                     * parameterA->_dpar[5] * parameterB->_dpar[5])
966
0
        / (bondcalc.r0 * bondcalc.r0 * bondcalc.r0);
967
968
0
      bondcalc.SetupPointers();
969
0
      _bondcalculations.push_back(bondcalc);
970
0
    }
971
972
    //
973
    // Angle Calculations
974
    //
975
0
    IF_OBFF_LOGLVL_LOW
976
0
      OBFFLog("SETTING UP ANGLE CALCULATIONS...\n");
977
978
0
    double sinT0;
979
0
    double rab, rbc, rac;
980
0
    OBBond *bondPtr;
981
0
    FOR_ANGLES_OF_MOL(angle, _mol) {
982
0
      b = _mol.GetAtom((*angle)[0] + 1);
983
0
      a = _mol.GetAtom((*angle)[1] + 1);
984
0
      c = _mol.GetAtom((*angle)[2] + 1);
985
986
      // skip this angle if the atoms are ignored
987
0
      if ( _constraints.IsIgnored(a->GetIdx())
988
0
           || _constraints.IsIgnored(b->GetIdx())
989
0
           || _constraints.IsIgnored(c->GetIdx()) )
990
0
        continue;
991
992
      // if there are any groups specified,
993
      // check if the three angle atoms are in a single intraGroup
994
0
      if (HasGroups()) {
995
0
        bool validAngle = false;
996
0
        for (unsigned int i=0; i < _intraGroup.size(); ++i) {
997
0
          if (_intraGroup[i].BitIsSet(a->GetIdx()) && _intraGroup[i].BitIsSet(b->GetIdx()) &&
998
0
              _intraGroup[i].BitIsSet(c->GetIdx()))
999
0
            validAngle = true;
1000
0
        }
1001
0
        if (!validAngle)
1002
0
          continue;
1003
0
      }
1004
1005
0
      anglecalc.a = a;
1006
0
      anglecalc.b = b;
1007
0
      anglecalc.c = c;
1008
1009
0
      parameterA = GetParameterUFF(a->GetType(), _ffparams);
1010
0
      parameterB = GetParameterUFF(b->GetType(), _ffparams);
1011
0
      parameterC = GetParameterUFF(c->GetType(), _ffparams);
1012
1013
0
      if (parameterA == nullptr || parameterB == nullptr || parameterC == nullptr) {
1014
0
        IF_OBFF_LOGLVL_LOW {
1015
0
          snprintf(_logbuf, BUFF_SIZE, "    COULD NOT FIND PARAMETERS FOR ANGLE %d-%d-%d (IDX)...\n",
1016
0
                   a->GetIdx(), b->GetIdx(), c->GetIdx());
1017
0
          OBFFLog(_logbuf);
1018
0
        }
1019
0
        return false;
1020
0
      }
1021
1022
0
      coordination = GetCoordination(b, parameterB->_ipar[0]);
1023
1024
0
      if (coordination != parameterB->_ipar[0]) {
1025
0
        IF_OBFF_LOGLVL_LOW {
1026
0
          snprintf(_logbuf, BUFF_SIZE, "    CORRECTED COORDINATION FOR ANGLE %d-%d-%d (IDX)... WAS %d NOW %d\n",
1027
0
                   a->GetIdx(), b->GetIdx(), c->GetIdx(), parameterB->_ipar[0], coordination);
1028
0
          OBFFLog(_logbuf);
1029
0
        }
1030
0
      }
1031
1032
      //double currentTheta;
1033
0
      if (coordination > 7) {
1034
        // large coordination sphere (e.g., [ReH9]-2 or [Ce(NO3)6]-2)
1035
        // just resort to using VDW 1-3 interactions to push atoms into place
1036
        // there's not much else we can do without real parameters
1037
0
        if (SetupVDWCalculation(a, c, vdwcalc)) {
1038
0
          _vdwcalculations.push_back(vdwcalc);
1039
0
        }
1040
        // We're not installing an angle term for this set
1041
        // We can't even approximate one.
1042
        // The downside is that we can't easily handle lone pairs.
1043
0
        continue;
1044
1045
0
      } else if (coordination == 7) { // pentagonal bipyramidal
1046
        // This doesn't work so well because it's hard to classify between
1047
        // axial-equatorial (90 degrees) and proximal equatorial (~72 degrees).
1048
0
        double currentTheta;
1049
0
        currentTheta =  a->GetAngle(&*b, &*c);
1050
1051
0
        anglecalc.c0 = 1.0;
1052
0
        if (b->HasData("UFF_CENTRAL_ATOM")
1053
0
              && a->HasData("UFF_AXIAL_ATOM")
1054
0
              && c->HasData("UFF_AXIAL_ATOM")) { // axial ligands = linear
1055
0
          anglecalc.coord = 1; // like sp
1056
0
          anglecalc.theta0 = 180.0 * DEG_TO_RAD;
1057
0
          anglecalc.c1 = 1.0;
1058
0
        } else if ( (a->HasData("UFF_AXIAL_ATOM") && !c->HasData("UFF_AXIAL_ATOM"))
1059
0
                    || (c->HasData("UFF_AXIAL_ATOM") && !a->HasData("UFF_AXIAL_ATOM")) ) { // axial-equatorial ligands
1060
0
          anglecalc.coord = 4; // like sq. planar or octahedral
1061
0
          anglecalc.theta0 = 90.0 * DEG_TO_RAD;
1062
0
          anglecalc.c1 = 1.0;
1063
0
        } else { // equatorial - equatorial
1064
0
          anglecalc.coord = 7; // unlike anything else, as theta0 is ignored.
1065
0
          anglecalc.theta0 = (currentTheta > 108.0 ? 144.0 : 72.0) * DEG_TO_RAD;
1066
0
          anglecalc.c1 = 1.0;
1067
0
        }
1068
0
        anglecalc.c2 = 0.0;
1069
1070
        /*
1071
        if (0) {
1072
          if (currentTheta >= 155.0) { // axial ligands = linear
1073
            anglecalc.coord = 1; // like sp
1074
            anglecalc.theta0 = 180.0 * DEG_TO_RAD;
1075
            anglecalc.c1 = 1.0;
1076
          } else if (currentTheta < 155.0 && currentTheta >= 110.0) { // distal equatorial
1077
            anglecalc.coord = 7; // like sp3
1078
            anglecalc.theta0 = 144.0 * DEG_TO_RAD;
1079
            anglecalc.c1 = 1.0;
1080
          } else if (currentTheta < 110.0 && currentTheta >= 85.0) { // axial-equatorial
1081
            anglecalc.coord = 4; // like sq. planar or octahedral
1082
            anglecalc.theta0 = 90.0 * DEG_TO_RAD;
1083
            anglecalc.c1 = 1.0;
1084
          } else if (currentTheta < 85.0) { // proximal equatorial
1085
            anglecalc.coord = 7; // general case (i.e., like sp3)
1086
            anglecalc.theta0 = 72.0 * DEG_TO_RAD;
1087
            anglecalc.c1 = 1.0;
1088
          }
1089
          anglecalc.c2 = 0.0;
1090
        } else {
1091
        */
1092
1093
0
      } else if (coordination == 5) { // trigonal bipyramidal
1094
0
        anglecalc.c0 = 1.0;
1095
        // We've already done some of our work above -- look for axial markings
1096
0
        if (b->HasData("UFF_CENTRAL_ATOM")
1097
0
            && a->HasData("UFF_AXIAL_ATOM")
1098
0
            && c->HasData("UFF_AXIAL_ATOM")) { // axial ligands = linear
1099
0
          anglecalc.coord = 1; // like sp
1100
0
          anglecalc.theta0 = 180.0 * DEG_TO_RAD;
1101
0
          anglecalc.c1 = 1.0;
1102
0
        } else if ( (a->HasData("UFF_AXIAL_ATOM") && !c->HasData("UFF_AXIAL_ATOM"))
1103
0
                    || (c->HasData("UFF_AXIAL_ATOM") && !a->HasData("UFF_AXIAL_ATOM")) ) { // axial-equatorial ligands
1104
0
          anglecalc.coord = 4; // like sq. planar or octahedral
1105
0
          anglecalc.theta0 = 90.0 * DEG_TO_RAD;
1106
0
          anglecalc.c1 = 1.0;
1107
0
        } else { // equatorial - equatorial
1108
0
          anglecalc.coord = 2; // like sp2
1109
0
          anglecalc.theta0 = 120.0 * DEG_TO_RAD;
1110
0
          anglecalc.c1 = -1.0;
1111
0
        }
1112
0
        anglecalc.c2 = 0.0;
1113
0
      }
1114
0
      else { // normal coordination: sp, sp2, sp3, square planar, octahedral
1115
0
        anglecalc.coord = coordination;
1116
0
        anglecalc.theta0 = parameterB->_dpar[1] * DEG_TO_RAD;
1117
0
        if (coordination != parameterB->_ipar[0]) {
1118
0
          switch (coordination)
1119
0
            {
1120
0
            case 1:
1121
0
              anglecalc.theta0 = 180.0 * DEG_TO_RAD;
1122
0
              break;
1123
0
            case 2:
1124
0
              anglecalc.theta0 = 120.0 * DEG_TO_RAD;
1125
0
              break;
1126
0
            case 4: // sq. planar
1127
0
            case 5: // axial / equatorial
1128
0
            case 6: // octahedral
1129
0
            case 7: // axial equatorial
1130
0
              anglecalc.theta0 = 90.0 * DEG_TO_RAD;
1131
0
              break;
1132
0
            case 3: // tetrahedral
1133
0
            default:
1134
0
              anglecalc.theta0 = 109.5 * DEG_TO_RAD;
1135
0
              break;
1136
0
            }
1137
0
        }
1138
0
        anglecalc.cosT0 = cos(anglecalc.theta0);
1139
0
        sinT0 = sin(anglecalc.theta0);
1140
0
        anglecalc.c2 = 1.0 / (4.0 * sinT0 * sinT0);
1141
0
        anglecalc.c1 = -4.0 * anglecalc.c2 * anglecalc.cosT0;
1142
0
        anglecalc.c0 = anglecalc.c2*(2.0*anglecalc.cosT0*anglecalc.cosT0 + 1.0);
1143
0
      }
1144
1145
0
      anglecalc.cosT0 = cos(anglecalc.theta0);
1146
0
      anglecalc.zi = parameterA->_dpar[5];
1147
0
      anglecalc.zk = parameterC->_dpar[5];
1148
      // Precompute the force constant
1149
0
      bondPtr = _mol.GetBond(a,b);
1150
0
      bondorder = bondPtr->GetBondOrder();
1151
0
      if (bondPtr->IsAromatic())
1152
0
        bondorder = 1.5;
1153
0
      if (bondPtr->IsAmide())
1154
0
        bondorder = 1.41;
1155
0
      rab = CalculateBondDistance(parameterA, parameterB, bondorder);
1156
1157
0
      bondPtr = _mol.GetBond(b,c);
1158
0
      bondorder = bondPtr->GetBondOrder();
1159
0
      if (bondPtr->IsAromatic())
1160
0
        bondorder = 1.5;
1161
0
      if (bondPtr->IsAmide())
1162
0
        bondorder = 1.41;
1163
0
      rbc = CalculateBondDistance(parameterB, parameterC, bondorder);
1164
0
      rac = sqrt(rab*rab + rbc*rbc - 2.0 * rab*rbc*anglecalc.cosT0);
1165
1166
      // Equation 13 from paper -- corrected by Towhee
1167
      // Note that 1/(rij * rjk) cancels with rij*rjk in eqn. 13
1168
0
      anglecalc.ka = (664.12 * KCAL_TO_KJ) * (anglecalc.zi * anglecalc.zk / (pow(rac, 5.0)));
1169
0
      anglecalc.ka *= (3.0*rab*rbc*(1.0 - anglecalc.cosT0*anglecalc.cosT0) - rac*rac*anglecalc.cosT0);
1170
      // Make sure to divide by n^2 to save CPU cycles
1171
0
      switch (anglecalc.coord) {
1172
0
      case 2: // sp2, so divide by 3^2
1173
0
        anglecalc.n = 3;
1174
0
        anglecalc.ka = anglecalc.ka / 9.0;
1175
0
        break;
1176
0
      case 4: // divide by 4^2
1177
0
      case 6:
1178
0
        anglecalc.n = 4;
1179
0
        anglecalc.ka = anglecalc.ka / 16.0;
1180
0
        break;
1181
0
      default:
1182
0
        break;
1183
0
      }
1184
1185
0
      anglecalc.SetupPointers();
1186
0
      _anglecalculations.push_back(anglecalc);
1187
0
    }
1188
1189
    //
1190
    // Torsion Calculations
1191
    //
1192
0
    IF_OBFF_LOGLVL_LOW
1193
0
      OBFFLog("SETTING UP TORSION CALCULATIONS...\n");
1194
1195
0
    double torsiontype;
1196
0
    double phi0 = 0.0;
1197
1198
0
    double vi, vj;
1199
0
    FOR_TORSIONS_OF_MOL(t, _mol) {
1200
0
      a = _mol.GetAtom((*t)[0] + 1);
1201
0
      b = _mol.GetAtom((*t)[1] + 1);
1202
0
      c = _mol.GetAtom((*t)[2] + 1);
1203
0
      d = _mol.GetAtom((*t)[3] + 1);
1204
1205
      // skip this torsion if the atoms are ignored
1206
0
      if ( _constraints.IsIgnored(a->GetIdx()) || _constraints.IsIgnored(b->GetIdx()) ||
1207
0
           _constraints.IsIgnored(c->GetIdx()) || _constraints.IsIgnored(d->GetIdx()) )
1208
0
        continue;
1209
1210
      // if there are any groups specified, check if the four torsion atoms are in a single intraGroup
1211
0
      if (HasGroups()) {
1212
0
        bool validTorsion = false;
1213
0
        for (unsigned int i=0; i < _intraGroup.size(); ++i) {
1214
0
          if (_intraGroup[i].BitIsSet(a->GetIdx()) && _intraGroup[i].BitIsSet(b->GetIdx()) &&
1215
0
              _intraGroup[i].BitIsSet(c->GetIdx()) && _intraGroup[i].BitIsSet(d->GetIdx()))
1216
0
            validTorsion = true;
1217
0
        }
1218
0
        if (!validTorsion)
1219
0
          continue;
1220
0
      }
1221
1222
0
      OBBond *bc = _mol.GetBond(b, c);
1223
0
      torsiontype = bc->GetBondOrder();
1224
0
      if (bc->IsAromatic())
1225
0
        torsiontype = 1.5;
1226
0
      if (bc->IsAmide())
1227
0
        torsiontype = 1.41;
1228
1229
0
      torsioncalc.a = a;
1230
0
      torsioncalc.b = b;
1231
0
      torsioncalc.c = c;
1232
0
      torsioncalc.d = d;
1233
0
      torsioncalc.tt = torsiontype;
1234
1235
0
      parameterB = GetParameterUFF(b->GetType(), _ffparams);
1236
0
      parameterC = GetParameterUFF(c->GetType(), _ffparams);
1237
1238
0
      if (parameterB == nullptr || parameterC == nullptr) {
1239
0
        IF_OBFF_LOGLVL_LOW {
1240
0
          snprintf(_logbuf, BUFF_SIZE, "    COULD NOT FIND PARAMETERS FOR TORSION X-%d-%d-X (IDX)...\n",
1241
0
                   b->GetIdx(), c->GetIdx());
1242
0
          OBFFLog(_logbuf);
1243
0
        }
1244
0
        return false;
1245
0
      }
1246
1247
0
      if (parameterB->_ipar[0] == 3 && parameterC->_ipar[0] == 3) {
1248
        // two sp3 centers
1249
0
        phi0 = 60.0;
1250
0
        torsioncalc.n = 3;
1251
0
        vi = parameterB->_dpar[6];
1252
0
        vj = parameterC->_dpar[6];
1253
1254
        // exception for a pair of group 6 sp3 atoms
1255
0
        switch (b->GetAtomicNum()) {
1256
0
        case 8:
1257
0
          vi = 2.0;
1258
0
          torsioncalc.n = 2;
1259
0
          phi0 = 90.0;
1260
0
          break;
1261
0
        case 16:
1262
0
        case 34:
1263
0
        case 52:
1264
0
        case 84:
1265
0
          vi = 6.8;
1266
0
          torsioncalc.n = 2;
1267
0
          phi0 = 90.0;
1268
0
        }
1269
0
        switch (c->GetAtomicNum()) {
1270
0
        case 8:
1271
0
          vj = 2.0;
1272
0
          torsioncalc.n = 2;
1273
0
          phi0 = 90.0;
1274
0
          break;
1275
0
        case 16:
1276
0
        case 34:
1277
0
        case 52:
1278
0
        case 84:
1279
0
          vj = 6.8;
1280
0
          torsioncalc.n = 2;
1281
0
          phi0 = 90.0;
1282
0
        }
1283
1284
0
        torsioncalc.V = 0.5 * KCAL_TO_KJ * sqrt(vi * vj);
1285
1286
0
      } else if (parameterB->_ipar[0] == 2 && parameterC->_ipar[0] == 2) {
1287
        // two sp2 centers
1288
0
        phi0 = 180.0;
1289
0
        torsioncalc.n = 2;
1290
0
        torsioncalc.V = 0.5 * KCAL_TO_KJ * 5.0 *
1291
0
          sqrt(parameterB->_dpar[7]*parameterC->_dpar[7]) *
1292
0
          (1.0 + 4.18 * log(torsiontype));
1293
0
      } else if ((parameterB->_ipar[0] == 2 && parameterC->_ipar[0] == 3)
1294
0
                 || (parameterB->_ipar[0] == 3 && parameterC->_ipar[0] == 2)) {
1295
        // one sp3, one sp2
1296
0
        phi0 = 0.0;
1297
0
        torsioncalc.n = 6;
1298
0
        torsioncalc.V = 0.5 * KCAL_TO_KJ * 1.0;
1299
1300
        // exception for group 6 sp3
1301
0
        if (parameterC->_ipar[0] == 3) {
1302
0
          switch (c->GetAtomicNum()) {
1303
0
          case 8:
1304
0
          case 16:
1305
0
          case 34:
1306
0
          case 52:
1307
0
          case 84:
1308
0
            torsioncalc.n = 2;
1309
0
            phi0 = 90.0;
1310
0
          }
1311
0
        }
1312
0
        if (parameterB->_ipar[0] == 3) {
1313
0
          switch (b->GetAtomicNum()) {
1314
0
          case 8:
1315
0
          case 16:
1316
0
          case 34:
1317
0
          case 52:
1318
0
          case 84:
1319
0
            torsioncalc.n = 2;
1320
0
            phi0 = 90.0;
1321
0
          }
1322
0
        }
1323
0
      }
1324
1325
0
      if (IsNearZero(torsioncalc.V)) // don't bother calcuating this torsion
1326
0
        continue;
1327
1328
      // still need to implement special case of sp2-sp3 with sp2-sp2
1329
1330
0
      torsioncalc.cosNPhi0 = cos(torsioncalc.n * DEG_TO_RAD * phi0);
1331
0
      torsioncalc.SetupPointers();
1332
0
      _torsioncalculations.push_back(torsioncalc);
1333
0
    }
1334
1335
    //
1336
    // OOP/Inversion Calculations
1337
    //
1338
0
    IF_OBFF_LOGLVL_LOW
1339
0
      OBFFLog("SETTING UP OOP CALCULATIONS...\n");
1340
1341
0
    double phi;
1342
    // The original Rappe paper in JACS isn't very clear about the parameters
1343
    // The following was adapted from Towhee
1344
0
    FOR_ATOMS_OF_MOL(atom, _mol) {
1345
0
      b = (OBAtom*) &*atom;
1346
1347
0
      switch (b->GetAtomicNum()) {
1348
0
      case 6: // carbon
1349
0
      case 7: // nitrogen
1350
0
      case 8: // oxygen
1351
0
      case 15: // phos.
1352
0
      case 33: // as
1353
0
      case 51: // sb
1354
0
      case 83: // bi
1355
0
        break;
1356
0
      default: // no inversion term for this element
1357
0
        continue;
1358
0
      }
1359
1360
0
      if (b->GetExplicitDegree() > 3) // no OOP for hypervalent atoms
1361
0
        continue;
1362
1363
0
      a = nullptr;
1364
0
      c = nullptr;
1365
0
      d = nullptr;
1366
1367
0
      if (EQn(b->GetType(), "N_3", 3) ||
1368
0
          EQn(b->GetType(), "N_2", 3) ||
1369
0
          EQn(b->GetType(), "N_R", 3) ||
1370
0
          EQn(b->GetType(), "O_2", 3) ||
1371
0
          EQn(b->GetType(), "O_R", 3)) {
1372
0
        oopcalc.c0 = 1.0;
1373
0
        oopcalc.c1 = -1.0;
1374
0
        oopcalc.c2 = 0.0;
1375
0
        oopcalc.koop = 6.0 * KCAL_TO_KJ;
1376
0
      }
1377
0
      else if (EQn(b->GetType(), "P_3+3", 5) ||
1378
0
               EQn(b->GetType(), "As3+3", 5) ||
1379
0
               EQn(b->GetType(), "Sb3+3", 5) ||
1380
0
               EQn(b->GetType(), "Bi3+3", 5)) {
1381
1382
0
        if (EQn(b->GetType(), "P_3+3", 5))
1383
0
          phi = 84.4339 * DEG_TO_RAD;
1384
0
        else if (EQn(b->GetType(), "As3+3", 5))
1385
0
          phi = 86.9735 * DEG_TO_RAD;
1386
0
        else if (EQn(b->GetType(), "Sb3+3", 5))
1387
0
          phi = 87.7047 * DEG_TO_RAD;
1388
0
        else
1389
0
          phi = 90.0 * DEG_TO_RAD;
1390
1391
0
        oopcalc.c1 = -4.0 * cos(phi);
1392
0
        oopcalc.c2 = 1.0;
1393
0
        oopcalc.c0 = -1.0*oopcalc.c1 * cos(phi) + oopcalc.c2*cos(2.0*phi);
1394
0
        oopcalc.koop = 22.0 * KCAL_TO_KJ;
1395
0
      }
1396
0
      else if (!(EQn(b->GetType(), "C_2", 3) || EQn(b->GetType(), "C_R", 3)))
1397
0
        continue; // inversion not defined for this atom type
1398
1399
0
      FOR_NBORS_OF_ATOM(nbr, b) {
1400
0
        if (a == nullptr)
1401
0
          a = (OBAtom*) &*nbr;
1402
0
        else if (c == nullptr)
1403
0
          c = (OBAtom*) &*nbr;
1404
0
        else
1405
0
          d = (OBAtom*) &*nbr;
1406
0
      }
1407
1408
0
      if (a == nullptr || c == nullptr || d == nullptr)
1409
0
        continue;
1410
1411
      // skip this oop if the atoms are ignored
1412
0
      if ( _constraints.IsIgnored(a->GetIdx()) ||
1413
0
           _constraints.IsIgnored(b->GetIdx()) ||
1414
0
           _constraints.IsIgnored(c->GetIdx()) ||
1415
0
           _constraints.IsIgnored(d->GetIdx()) )
1416
0
        continue;
1417
1418
      // if there are any groups specified,
1419
      // check if the four oop atoms are in a single intraGroup
1420
0
      if (HasGroups()) {
1421
0
        bool validOOP = false;
1422
0
        for (unsigned int i=0; i < _intraGroup.size(); ++i) {
1423
0
          if (_intraGroup[i].BitIsSet(a->GetIdx()) &&
1424
0
              _intraGroup[i].BitIsSet(b->GetIdx()) &&
1425
0
              _intraGroup[i].BitIsSet(c->GetIdx()) &&
1426
0
              _intraGroup[i].BitIsSet(d->GetIdx()))
1427
0
            validOOP = true;
1428
0
        }
1429
0
        if (!validOOP)
1430
0
          continue;
1431
0
      }
1432
1433
      // C atoms, we should check if we're bonded to O
1434
0
      if (EQn(b->GetType(), "C_2", 3) || EQn(b->GetType(), "C_R", 3)) {
1435
0
        oopcalc.c0 = 1.0;
1436
0
        oopcalc.c1 = -1.0;
1437
0
        oopcalc.c2 = 0.0;
1438
0
        oopcalc.koop = 6.0 * KCAL_TO_KJ;
1439
0
        if (EQn(a->GetType(), "O_2", 3) ||
1440
0
            EQn(c->GetType(), "O_2", 3) ||
1441
0
            EQn(d->GetType(), "O_2", 3)) {
1442
0
          oopcalc.koop = 50.0 * KCAL_TO_KJ;
1443
0
        }
1444
0
      }
1445
1446
      // A-B-CD || C-B-AD  PLANE = ABC
1447
0
      oopcalc.a = a;
1448
0
      oopcalc.b = b;
1449
0
      oopcalc.c = c;
1450
0
      oopcalc.d = d;
1451
0
      oopcalc.koop /= 3.0; // three OOPs to consider
1452
1453
0
      oopcalc.SetupPointers();
1454
0
      _oopcalculations.push_back(oopcalc);
1455
1456
      // C-B-DA || D-B-CA  PLANE BCD
1457
0
      oopcalc.a = d;
1458
0
      oopcalc.d = a;
1459
1460
0
      oopcalc.SetupPointers();
1461
0
      _oopcalculations.push_back(oopcalc);
1462
1463
      // A-B-DC || D-B-AC  PLANE ABD
1464
0
      oopcalc.a = a;
1465
0
      oopcalc.c = d;
1466
0
      oopcalc.d = c;
1467
1468
0
      oopcalc.SetupPointers();
1469
0
      _oopcalculations.push_back(oopcalc);
1470
0
    } // for all atoms
1471
1472
    //
1473
    // VDW Calculations
1474
    //
1475
0
    IF_OBFF_LOGLVL_LOW
1476
0
      OBFFLog("SETTING UP VAN DER WAALS CALCULATIONS...\n");
1477
1478
0
    FOR_PAIRS_OF_MOL(p, _mol) {
1479
0
      a = _mol.GetAtom((*p)[0]);
1480
0
      b = _mol.GetAtom((*p)[1]);
1481
1482
      // skip this vdw if the atoms are ignored
1483
0
      if ( _constraints.IsIgnored(a->GetIdx()) || _constraints.IsIgnored(b->GetIdx()) )
1484
0
        continue;
1485
1486
      // if there are any groups specified, check if the two atoms are in a single _interGroup or if
1487
      // two two atoms are in one of the _interGroups pairs.
1488
0
      if (HasGroups()) {
1489
0
        bool validVDW = false;
1490
0
        for (unsigned int i=0; i < _interGroup.size(); ++i) {
1491
0
          if (_interGroup[i].BitIsSet(a->GetIdx()) && _interGroup[i].BitIsSet(b->GetIdx()))
1492
0
            validVDW = true;
1493
0
        }
1494
0
        for (unsigned int i=0; i < _interGroups.size(); ++i) {
1495
0
          if (_interGroups[i].first.BitIsSet(a->GetIdx()) && _interGroups[i].second.BitIsSet(b->GetIdx()))
1496
0
            validVDW = true;
1497
0
          if (_interGroups[i].first.BitIsSet(b->GetIdx()) && _interGroups[i].second.BitIsSet(a->GetIdx()))
1498
0
            validVDW = true;
1499
0
        }
1500
1501
0
        if (!validVDW)
1502
0
          continue;
1503
0
      }
1504
1505
0
      if (a->IsConnected(b)) {
1506
0
        continue;
1507
0
      }
1508
0
      if (a->IsOneThree(b)) {
1509
0
        continue;
1510
0
      }
1511
1512
0
      if (SetupVDWCalculation(a, b, vdwcalc)) {
1513
0
        _vdwcalculations.push_back(vdwcalc);
1514
0
      }
1515
0
    }
1516
1517
    // NOTE: No electrostatics are set up
1518
    // If you want electrostatics with UFF, you will need to call
1519
    // SetupElectrostatics() manually
1520
1521
0
    return true;
1522
0
  }
1523
1524
  bool OBForceFieldUFF::SetupElectrostatics()
1525
0
  {
1526
    //
1527
    // Electrostatic Calculations
1528
    //
1529
0
    OBAtom *a, *b;
1530
1531
0
    IF_OBFF_LOGLVL_LOW
1532
0
      OBFFLog("SETTING UP ELECTROSTATIC CALCULATIONS...\n");
1533
1534
0
    OBFFElectrostaticCalculationUFF elecalc;
1535
1536
0
    _electrostaticcalculations.clear();
1537
1538
    // Note that while the UFF paper mentions an electrostatic term,
1539
    // it does not actually use it. Both Towhee and the UFF FAQ
1540
    // discourage the use of electrostatics with UFF.
1541
1542
0
    FOR_PAIRS_OF_MOL(p, _mol) {
1543
0
      a = _mol.GetAtom((*p)[0]);
1544
0
      b = _mol.GetAtom((*p)[1]);
1545
1546
      // skip this ele if the atoms are ignored
1547
0
      if ( _constraints.IsIgnored(a->GetIdx()) || _constraints.IsIgnored(b->GetIdx()) )
1548
0
        continue;
1549
1550
      // if there are any groups specified, check if the two atoms are in a single _interGroup or if
1551
      // two two atoms are in one of the _interGroups pairs.
1552
0
      if (HasGroups()) {
1553
0
        bool validEle = false;
1554
0
        for (unsigned int i=0; i < _interGroup.size(); ++i) {
1555
0
          if (_interGroup[i].BitIsSet(a->GetIdx()) && _interGroup[i].BitIsSet(b->GetIdx()))
1556
0
            validEle = true;
1557
0
        }
1558
0
        for (unsigned int i=0; i < _interGroups.size(); ++i) {
1559
0
          if (_interGroups[i].first.BitIsSet(a->GetIdx()) && _interGroups[i].second.BitIsSet(b->GetIdx()))
1560
0
            validEle = true;
1561
0
          if (_interGroups[i].first.BitIsSet(b->GetIdx()) && _interGroups[i].second.BitIsSet(a->GetIdx()))
1562
0
            validEle = true;
1563
0
        }
1564
1565
0
        if (!validEle)
1566
0
          continue;
1567
0
      }
1568
1569
0
      if (a->IsConnected(b)) {
1570
0
        continue;
1571
0
      }
1572
0
      if (a->IsOneThree(b)) {
1573
0
        continue;
1574
0
      }
1575
1576
      // Remember that at the moment, this term is not currently used
1577
      // These are also the Gasteiger charges, not the Qeq mentioned in the UFF paper
1578
0
      elecalc.qq = KCAL_TO_KJ * 332.0637 * a->GetPartialCharge() * b->GetPartialCharge();
1579
1580
0
      if (elecalc.qq) {
1581
0
        elecalc.a = &*a;
1582
0
        elecalc.b = &*b;
1583
1584
0
        elecalc.SetupPointers();
1585
0
        _electrostaticcalculations.push_back(elecalc);
1586
0
      }
1587
0
    }
1588
0
    return true;
1589
0
  }
1590
1591
  bool OBForceFieldUFF::SetupPointers()
1592
0
  {
1593
0
    for (unsigned int i = 0; i < _bondcalculations.size(); ++i)
1594
0
      _bondcalculations[i].SetupPointers();
1595
0
    for (unsigned int i = 0; i < _anglecalculations.size(); ++i)
1596
0
      _anglecalculations[i].SetupPointers();
1597
0
    for (unsigned int i = 0; i < _torsioncalculations.size(); ++i)
1598
0
      _torsioncalculations[i].SetupPointers();
1599
0
     for (unsigned int i = 0; i < _oopcalculations.size(); ++i)
1600
0
      _oopcalculations[i].SetupPointers();
1601
0
    for (unsigned int i = 0; i < _vdwcalculations.size(); ++i)
1602
0
      _vdwcalculations[i].SetupPointers();
1603
0
    for (unsigned int i = 0; i < _electrostaticcalculations.size(); ++i)
1604
0
      _electrostaticcalculations[i].SetupPointers();
1605
1606
0
    return true;
1607
0
  }
1608
1609
  bool OBForceFieldUFF::ParseParamFile()
1610
0
  {
1611
0
    vector<string> vs;
1612
0
    char buffer[BUFF_SIZE];
1613
1614
0
    OBFFParameter parameter;
1615
1616
    // open data/UFF.prm
1617
0
    ifstream ifs;
1618
0
    if (OpenDatafile(ifs, "UFF.prm").length() == 0) {
1619
0
      obErrorLog.ThrowError(__FUNCTION__, "Cannot open UFF.prm", obError);
1620
0
      return false;
1621
0
    }
1622
1623
    // Set the locale for number parsing to avoid locale issues: PR#1785463
1624
0
    obLocale.SetLocale();
1625
1626
0
    while (ifs.getline(buffer, BUFF_SIZE)) {
1627
0
      tokenize(vs, buffer);
1628
0
      if (vs.size() < 13)
1629
0
        continue;
1630
1631
0
      if (EQn(buffer, "param", 5)) {
1632
        // set up all parameters from this
1633
0
        parameter.clear();
1634
0
        parameter._a = vs[1]; // atom type
1635
0
        parameter._dpar.push_back(atof(vs[2].c_str())); // r1
1636
0
        parameter._dpar.push_back(atof(vs[3].c_str())); // theta0
1637
0
        parameter._dpar.push_back(atof(vs[4].c_str())); // x1
1638
0
        parameter._dpar.push_back(atof(vs[5].c_str())); // D1
1639
0
        parameter._dpar.push_back(atof(vs[6].c_str())); // zeta
1640
0
        parameter._dpar.push_back(atof(vs[7].c_str())); // Z1
1641
0
        parameter._dpar.push_back(atof(vs[8].c_str())); // Vi
1642
0
        parameter._dpar.push_back(atof(vs[9].c_str())); // Uj
1643
0
        parameter._dpar.push_back(atof(vs[10].c_str())); // Xi
1644
0
        parameter._dpar.push_back(atof(vs[11].c_str())); // Hard
1645
0
        parameter._dpar.push_back(atof(vs[12].c_str())); // Radius
1646
1647
0
        parameter.b = 0; // used for tracking number of angles in 5-coordinate
1648
0
        parameter.c = 0;
1649
1650
0
        char coord = vs[1][1] ? vs[1][2] : '\0'; // 3rd character of atom type, if any
1651
0
        switch (coord) {
1652
0
        case '1': // linear
1653
0
          parameter._ipar.push_back(1);
1654
0
          break;
1655
0
        case '2': // trigonal planar (sp2)
1656
0
        case 'R': // aromatic (N_R)
1657
0
          parameter._ipar.push_back(2);
1658
0
          break;
1659
0
        case '3': // tetrahedral (sp3)
1660
0
          parameter._ipar.push_back(3);
1661
0
          break;
1662
0
        case '4': // square planar
1663
0
          parameter._ipar.push_back(4);
1664
0
          break;
1665
0
        case '5': // trigonal bipyramidal -- not actually in parameterization
1666
0
          parameter._ipar.push_back(5);
1667
0
          break;
1668
0
        case '6': // octahedral
1669
0
          parameter._ipar.push_back(6);
1670
0
          break;
1671
0
        case '7': // pentagonal bipyramidal -- not actually in parameterization
1672
0
          parameter._ipar.push_back(7);
1673
0
          break;
1674
0
        default: // general case (unknown coordination)
1675
          // These atoms appear to generally be linear coordination like Cl
1676
0
          parameter._ipar.push_back(1);
1677
0
        }
1678
1679
0
        _ffparams.push_back(parameter);
1680
0
      }
1681
0
    }
1682
1683
0
    if (ifs)
1684
0
      ifs.close();
1685
1686
    // return the locale to the original one
1687
0
    obLocale.RestoreLocale();
1688
1689
0
    return 0;
1690
0
  }
1691
1692
  bool OBForceFieldUFF::SetTypes()
1693
0
  {
1694
0
    vector<vector<int> > _mlist; //!< match list for atom typing
1695
0
    vector<pair<OBSmartsPattern*,string> > _vexttyp; //!< external atom type rules
1696
0
    vector<vector<int> >::iterator j;
1697
0
    vector<pair<OBSmartsPattern*,string> >::iterator i;
1698
0
    OBSmartsPattern *sp;
1699
0
    vector<string> vs;
1700
0
    char buffer[BUFF_SIZE];
1701
1702
0
    _mol.SetAtomTypesPerceived();
1703
1704
    // open data/UFF.prm
1705
0
    ifstream ifs;
1706
0
    if (OpenDatafile(ifs, "UFF.prm").length() == 0) {
1707
0
      obErrorLog.ThrowError(__FUNCTION__, "Cannot open UFF.prm", obError);
1708
0
      return false;
1709
0
    }
1710
1711
0
    while (ifs.getline(buffer, BUFF_SIZE)) {
1712
0
      if (EQn(buffer, "atom", 4)) {
1713
0
        tokenize(vs, buffer);
1714
1715
0
        sp = new OBSmartsPattern;
1716
0
        if (sp->Init(vs[1])) {
1717
0
          _vexttyp.push_back(pair<OBSmartsPattern*,string> (sp,vs[2]));
1718
0
        }
1719
0
        else {
1720
0
          delete sp;
1721
0
          sp = nullptr;
1722
0
          obErrorLog.ThrowError(__FUNCTION__, " Could not parse atom type table from UFF.prm", obInfo);
1723
0
          return false;
1724
0
        }
1725
0
      }
1726
0
    }
1727
1728
0
    for (i = _vexttyp.begin();i != _vexttyp.end();++i) {
1729
0
      if (i->first->Match(_mol)) {
1730
0
        _mlist = i->first->GetMapList();
1731
0
        for (j = _mlist.begin();j != _mlist.end();++j) {
1732
0
          _mol.GetAtom((*j)[0])->SetType(i->second);
1733
0
        }
1734
0
      }
1735
0
    }
1736
1737
    // Special atom types (i.e., P_3+q)
1738
    // (We can't easily do this with a SMARTS)
1739
0
    FOR_ATOMS_OF_MOL(a, _mol) {
1740
0
      if (a->GetAtomicNum() == 15) {
1741
        // loop through all the neighbors and see if we have a metal coordination
1742
0
        bool organomet = false;
1743
0
        int nbrElement;
1744
0
        FOR_NBORS_OF_ATOM (nbr, &*a) {
1745
0
          nbrElement = nbr->GetAtomicNum();
1746
0
          if ( (nbrElement >= 21 && nbrElement <= 31) // Sc to Ga
1747
0
               || (nbrElement >= 39 && nbrElement <= 50) // Y to Sn
1748
0
               || (nbrElement >= 57 && nbrElement <= 83) // La to Bi
1749
0
               || (nbrElement >= 89) ) {
1750
0
            organomet = true;
1751
0
            break; // done!
1752
0
          }
1753
0
        }
1754
0
        if (organomet)
1755
0
          a->SetType("P_3+q");
1756
0
      }
1757
0
      else if (a->GetAtomicNum() > 102) { // superheavy
1758
0
        a->SetType("Lw6+3"); // prevent a crash with atoms beyond the parameterization Avogadro PR#741
1759
0
      }
1760
0
    }
1761
1762
0
    IF_OBFF_LOGLVL_LOW {
1763
0
      OBFFLog("\nA T O M   T Y P E S\n\n");
1764
0
      OBFFLog("IDX\tTYPE\tRING\n");
1765
1766
0
      FOR_ATOMS_OF_MOL (a, _mol) {
1767
0
        snprintf(_logbuf, BUFF_SIZE, "%d\t%s\t%s\n", a->GetIdx(), a->GetType(),
1768
0
    (a->IsInRing() ? (a->IsAromatic() ? "AR" : "AL") : "NO"));
1769
0
        OBFFLog(_logbuf);
1770
0
      }
1771
1772
0
    }
1773
1774
0
    if (ifs)
1775
0
      ifs.close();
1776
1777
    // Free memory
1778
0
    for (i = _vexttyp.begin();i != _vexttyp.end();++i) {
1779
0
      sp = i->first;
1780
0
      delete sp;
1781
0
    }
1782
1783
0
    return true;
1784
0
  }
1785
1786
  double OBForceFieldUFF::Energy(bool gradients)
1787
0
  {
1788
0
    double energy;
1789
1790
0
    IF_OBFF_LOGLVL_MEDIUM
1791
0
      OBFFLog("\nE N E R G Y\n\n");
1792
1793
0
    if (gradients) {
1794
0
      ClearGradients();
1795
0
      energy  = E_Bond<true>();
1796
0
      energy += E_Angle<true>();
1797
0
      energy += E_Torsion<true>();
1798
0
      energy += E_OOP<true>();
1799
0
      energy += E_VDW<true>();
1800
0
    } else {
1801
0
      energy  = E_Bond<false>();
1802
0
      energy += E_Angle<false>();
1803
0
      energy += E_Torsion<false>();
1804
0
      energy += E_OOP<false>();
1805
0
      energy += E_VDW<false>();
1806
0
    }
1807
1808
    // The electrostatic term, by default is 0.0
1809
    // You will need to call SetupEletrostatics if you want it
1810
    // energy += E_Electrostatic(gradients);
1811
1812
0
    IF_OBFF_LOGLVL_MEDIUM {
1813
0
      snprintf(_logbuf, BUFF_SIZE, "\nTOTAL ENERGY = %8.5f %s\n", energy, GetUnit().c_str());
1814
0
      OBFFLog(_logbuf);
1815
0
    }
1816
1817
0
    return energy;
1818
0
  }
1819
1820
  OBFFParameter* OBForceFieldUFF::GetParameterUFF(std::string a, vector<OBFFParameter> &parameter)
1821
0
  {
1822
0
    for (unsigned int idx=0; idx < parameter.size(); ++idx) {
1823
0
      if (a == parameter[idx]._a) {
1824
0
        return &parameter[idx];
1825
0
      }
1826
0
    }
1827
0
    return nullptr;
1828
0
  }
1829
1830
  bool OBForceFieldUFF::ValidateGradients ()
1831
0
  {
1832
0
    vector3 numgrad, anagrad, err;
1833
0
    bool passed = true; // set to false if any component fails
1834
0
    int coordIdx;
1835
1836
0
    OBFFLog("\nV A L I D A T E   G R A D I E N T S\n\n");
1837
0
    OBFFLog("ATOM IDX      NUMERICAL GRADIENT           ANALYTICAL GRADIENT        REL. ERROR (%)   \n");
1838
0
    OBFFLog("----------------------------------------------------------------------------------------\n");
1839
    //     "XX       (000.000, 000.000, 000.000)  (000.000, 000.000, 000.000)  (00.00, 00.00, 00.00)"
1840
1841
0
    FOR_ATOMS_OF_MOL (a, _mol) {
1842
0
      coordIdx = (a->GetIdx() - 1) * 3;
1843
1844
      // OBFF_ENERGY (i.e., overall)
1845
0
      numgrad = NumericalDerivative(&*a, OBFF_ENERGY);
1846
0
      Energy(); // compute
1847
0
      anagrad.Set(_gradientPtr[coordIdx], _gradientPtr[coordIdx+1], _gradientPtr[coordIdx+2]);
1848
0
      err = ValidateGradientError(numgrad, anagrad);
1849
1850
0
      snprintf(_logbuf, BUFF_SIZE, "%2d       (%7.3f, %7.3f, %7.3f)  (%7.3f, %7.3f, %7.3f)  (%5.2f, %5.2f, %5.2f)\n", a->GetIdx(), numgrad.x(), numgrad.y(), numgrad.z(),
1851
0
               anagrad.x(), anagrad.y(), anagrad.z(), err.x(), err.y(), err.z());
1852
0
      OBFFLog(_logbuf);
1853
1854
      // OBFF_EBOND
1855
0
      numgrad = NumericalDerivative(&*a, OBFF_EBOND);
1856
0
      ClearGradients();
1857
0
      E_Bond(); // compute
1858
0
      anagrad.Set(_gradientPtr[coordIdx], _gradientPtr[coordIdx+1], _gradientPtr[coordIdx+2]);
1859
0
      err = ValidateGradientError(numgrad, anagrad);
1860
1861
0
      snprintf(_logbuf, BUFF_SIZE, "    bond    (%7.3f, %7.3f, %7.3f)  (%7.3f, %7.3f, %7.3f)  (%5.2f, %5.2f, %5.2f)\n", numgrad.x(), numgrad.y(), numgrad.z(),
1862
0
               anagrad.x(), anagrad.y(), anagrad.z(), err.x(), err.y(), err.z());
1863
0
      OBFFLog(_logbuf);
1864
0
      if (err.x() > 5.0 || err.y() > 5.0 || err.z() > 5.0)
1865
0
        passed = false;
1866
1867
      // OBFF_EANGLE
1868
0
      numgrad = NumericalDerivative(&*a, OBFF_EANGLE);
1869
0
      ClearGradients();
1870
0
      E_Angle(); // compute
1871
0
      anagrad.Set(_gradientPtr[coordIdx], _gradientPtr[coordIdx+1], _gradientPtr[coordIdx+2]);
1872
0
      err = ValidateGradientError(numgrad, anagrad);
1873
1874
0
      snprintf(_logbuf, BUFF_SIZE, "    angle   (%7.3f, %7.3f, %7.3f)  (%7.3f, %7.3f, %7.3f)  (%5.2f, %5.2f, %5.2f)\n", numgrad.x(), numgrad.y(), numgrad.z(),
1875
0
               anagrad.x(), anagrad.y(), anagrad.z(), err.x(), err.y(), err.z());
1876
0
      OBFFLog(_logbuf);
1877
0
      if (err.x() > 8.0 || err.y() > 8.0 || err.z() > 8.0)
1878
0
        passed = false;
1879
1880
      // OBFF_ETORSION
1881
0
      numgrad = NumericalDerivative(&*a, OBFF_ETORSION);
1882
0
      ClearGradients();
1883
0
      E_Torsion(); // compute
1884
0
      anagrad.Set(_gradientPtr[coordIdx], _gradientPtr[coordIdx+1], _gradientPtr[coordIdx+2]);
1885
0
      err = ValidateGradientError(numgrad, anagrad);
1886
1887
0
      snprintf(_logbuf, BUFF_SIZE, "    torsion (%7.3f, %7.3f, %7.3f)  (%7.3f, %7.3f, %7.3f)  (%5.2f, %5.2f, %5.2f)\n", numgrad.x(), numgrad.y(), numgrad.z(),
1888
0
               anagrad.x(), anagrad.y(), anagrad.z(), err.x(), err.y(), err.z());
1889
0
      OBFFLog(_logbuf);
1890
      // 8% tolerance here because some 180 torsions cause numerical instability
1891
0
      if (err.x() > 8.0 || err.y() > 8.0 || err.z() > 8.0)
1892
0
        passed = false;
1893
1894
      // OBFF_EOOP
1895
0
      numgrad = NumericalDerivative(&*a, OBFF_EOOP);
1896
0
      ClearGradients();
1897
0
      E_OOP(); // compute
1898
0
      anagrad.Set(_gradientPtr[coordIdx], _gradientPtr[coordIdx+1], _gradientPtr[coordIdx+2]);
1899
0
      err = ValidateGradientError(numgrad, anagrad);
1900
1901
0
      snprintf(_logbuf, BUFF_SIZE, "    oop     (%7.3f, %7.3f, %7.3f)  (%7.3f, %7.3f, %7.3f)  (%5.2f, %5.2f, %5.2f)\n", numgrad.x(), numgrad.y(), numgrad.z(),
1902
0
               anagrad.x(), anagrad.y(), anagrad.z(), err.x(), err.y(), err.z());
1903
0
      OBFFLog(_logbuf);
1904
      // We don't care if the OOP error is relatively large
1905
      //      if (err.x() > 5.0 || err.y() > 5.0 || err.z() > 5.0)
1906
      //        passed = false;
1907
1908
      // OBFF_EVDW
1909
0
      numgrad = NumericalDerivative(&*a, OBFF_EVDW);
1910
0
      ClearGradients();
1911
0
      E_VDW(); // compute
1912
0
      anagrad.Set(_gradientPtr[coordIdx], _gradientPtr[coordIdx+1], _gradientPtr[coordIdx+2]);
1913
0
      err = ValidateGradientError(numgrad, anagrad);
1914
1915
0
      snprintf(_logbuf, BUFF_SIZE, "    vdw     (%7.3f, %7.3f, %7.3f)  (%7.3f, %7.3f, %7.3f)  (%5.2f, %5.2f, %5.2f)\n", numgrad.x(), numgrad.y(), numgrad.z(),
1916
0
               anagrad.x(), anagrad.y(), anagrad.z(), err.x(), err.y(), err.z());
1917
0
      OBFFLog(_logbuf);
1918
0
      if (err.x() > 5.0 || err.y() > 5.0 || err.z() > 5.0)
1919
0
        passed = false;
1920
1921
      // OBFF_EELECTROSTATIC
1922
0
      numgrad = NumericalDerivative(&*a, OBFF_EELECTROSTATIC);
1923
0
      ClearGradients();
1924
0
      E_Electrostatic(); // compute
1925
0
      anagrad.Set(_gradientPtr[coordIdx], _gradientPtr[coordIdx+1], _gradientPtr[coordIdx+2]);
1926
0
      err = ValidateGradientError(numgrad, anagrad);
1927
1928
0
      snprintf(_logbuf, BUFF_SIZE, "    electro (%7.3f, %7.3f, %7.3f)  (%7.3f, %7.3f, %7.3f)  (%5.2f, %5.2f, %5.2f)\n", numgrad.x(), numgrad.y(), numgrad.z(),
1929
0
               anagrad.x(), anagrad.y(), anagrad.z(), err.x(), err.y(), err.z());
1930
0
      OBFFLog(_logbuf);
1931
0
      if (err.x() > 5.0 || err.y() > 5.0 || err.z() > 5.0)
1932
0
        passed = false;
1933
0
    }
1934
1935
0
    return passed; // did we pass every single component?
1936
0
  }
1937
1938
} // end namespace OpenBabel
1939
1940
//! \file forcefieldUFF.cpp
1941
//! \brief UFF force field