1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 package ffx.potential.bonded;
39
40 import ffx.numerics.atomic.AtomicDoubleArray3D;
41 import ffx.numerics.math.DoubleMath;
42 import ffx.potential.parameters.AtomType;
43 import ffx.potential.parameters.ForceField;
44 import ffx.potential.parameters.TorsionType;
45
46 import java.io.Serial;
47 import java.util.ArrayList;
48 import java.util.List;
49 import java.util.function.DoubleUnaryOperator;
50 import java.util.logging.Logger;
51
52 import static ffx.numerics.math.DoubleMath.dihedralAngle;
53 import static java.lang.String.format;
54 import static org.apache.commons.math3.util.FastMath.acos;
55 import static org.apache.commons.math3.util.FastMath.max;
56 import static org.apache.commons.math3.util.FastMath.sqrt;
57 import static org.apache.commons.math3.util.FastMath.toDegrees;
58
59
60
61
62
63
64
65 public class Torsion extends BondedTerm implements LambdaInterface {
66
67 @Serial
68 private static final long serialVersionUID = 1L;
69
70 private static final Logger logger = Logger.getLogger(Torsion.class.getName());
71
72
73
74 public TorsionType torsionType = null;
75
76
77
78 private double torsionScale = 1.0;
79
80
81
82 private double lambda = 1.0;
83
84
85
86 private double dEdL = 0.0;
87
88
89
90 private boolean lambdaTerm = false;
91
92
93
94 private final DoubleUnaryOperator lambdaMapper;
95
96
97
98 private static final double TORSION_TOLERANCE = 1.0e-4;
99
100
101
102
103
104
105
106
107 public Torsion(Bond b1, Bond b2, Bond b3) {
108 super();
109 bonds = new Bond[3];
110 bonds[0] = b1;
111 bonds[1] = b2;
112 bonds[2] = b3;
113 lambdaMapper = (double d) -> d;
114 initialize();
115 }
116
117
118
119
120
121
122
123
124
125 public static void logNoTorsionType(Atom a0, Atom a1, Atom a2, Atom a3, ForceField forceField) {
126 AtomType atomType0 = a0.getAtomType();
127 AtomType atomType1 = a1.getAtomType();
128 AtomType atomType2 = a2.getAtomType();
129 AtomType atomType3 = a3.getAtomType();
130 int[] c = {atomType0.atomClass, atomType1.atomClass, atomType2.atomClass, atomType3.atomClass};
131 String key = TorsionType.sortKey(c);
132 StringBuilder sb = new StringBuilder(
133 format("No TorsionType for key: %s\n %s -> %s\n %s -> %s\n %s -> %s\n %s -> %s", key, a0,
134 atomType0, a1, atomType1, a2, atomType2, a3, atomType3));
135 if (matchTorsions(a0, a1, a2, a3, forceField, sb, true) <= 0) {
136 matchTorsions(a0, a1, a2, a3, forceField, sb, false);
137 }
138 logger.severe(sb.toString());
139 }
140
141 private static int matchTorsions(Atom a0, Atom a1, Atom a2, Atom a3, ForceField forceField,
142 StringBuilder sb, boolean strict) {
143 AtomType atomType0 = a0.getAtomType();
144 AtomType atomType1 = a1.getAtomType();
145 AtomType atomType2 = a2.getAtomType();
146 AtomType atomType3 = a3.getAtomType();
147 int c0 = atomType0.atomClass;
148 int c1 = atomType1.atomClass;
149 int c2 = atomType2.atomClass;
150 int c3 = atomType3.atomClass;
151 List<AtomType> types0 = forceField.getSimilarAtomTypes(atomType0);
152 List<AtomType> types1 = forceField.getSimilarAtomTypes(atomType1);
153 List<AtomType> types2 = forceField.getSimilarAtomTypes(atomType2);
154 List<AtomType> types3 = forceField.getSimilarAtomTypes(atomType3);
155 List<TorsionType> torsionTypes = new ArrayList<>();
156 boolean match = false;
157 for (AtomType type1 : types1) {
158 for (AtomType type2 : types2) {
159
160 if ((type1.atomClass == c1 && type2.atomClass == c2) || (type1.atomClass == c2 && type2.atomClass == c1)) {
161 for (AtomType type0 : types0) {
162 for (AtomType type3 : types3) {
163
164 if (strict) {
165 if ((type0.atomClass != c0) && (type0.atomClass != c3) && (type3.atomClass != c0) && (type3.atomClass != c3)) {
166 continue;
167 }
168 }
169 TorsionType torsionType = forceField.getTorsionType(type0, type1, type2, type3);
170 if (torsionType != null && !torsionTypes.contains(torsionType)) {
171 if (!match) {
172 match = true;
173 sb.append("\n Similar Torsion Types:");
174 }
175 torsionTypes.add(torsionType);
176 sb.append(format("\n %s", torsionType));
177 }
178 }
179 }
180 }
181 }
182 }
183 return torsionTypes.size();
184 }
185
186
187
188
189
190
191
192
193
194
195
196 static Torsion torsionFactory(Bond bond1, Bond middleBond, Bond bond3, ForceField forceField) {
197 Atom a0 = bond1.getOtherAtom(middleBond);
198 Atom a1 = middleBond.getAtom(0);
199 Atom a2 = middleBond.getAtom(1);
200 Atom a3 = bond3.getOtherAtom(middleBond);
201
202 TorsionType torsionType = forceField.getTorsionType(a0.getAtomType(), a1.getAtomType(),
203 a2.getAtomType(), a3.getAtomType());
204
205
206 if (torsionType == null) {
207 logNoTorsionType(a0, a1, a2, a3, forceField);
208 return null;
209 }
210
211 Torsion torsion = new Torsion(bond1, middleBond, bond3);
212 torsion.setTorsionType(torsionType);
213
214 if (forceField.hasProperty("torsion-scale")) {
215
216 boolean isTerminal = (a0.isHydrogen() || a0.isHalogen() || a3.isHydrogen() || a3.isHalogen());
217 if (!isTerminal) {
218 double scale = forceField.getDouble("torsion-scale", 1.0);
219 torsion.setTorsionScale(scale);
220 }
221 }
222
223 return torsion;
224 }
225
226
227
228
229
230
231 public void setTorsionType(TorsionType torsionType) {
232 this.torsionType = torsionType;
233 }
234
235
236
237
238
239
240 public void setTorsionScale(double torsionScale) {
241 this.torsionScale = torsionScale;
242 }
243
244
245
246
247
248
249 public double getTorsionScale() {
250 return torsionScale;
251 }
252
253
254
255
256
257
258
259
260
261
262 public boolean compare(Atom a0, Atom a1, Atom a2, Atom a3) {
263 if (a0 == atoms[0] && a1 == atoms[1] && a2 == atoms[2] && a3 == atoms[3]) {
264 return true;
265 }
266 return (a0 == atoms[3] && a1 == atoms[2] && a2 == atoms[1] && a3 == atoms[0]);
267 }
268
269
270
271
272
273
274 public double measure() {
275 double angle = DoubleMath.dihedralAngle(atoms[0].getXYZ(null), atoms[1].getXYZ(null),
276 atoms[2].getXYZ(null), atoms[3].getXYZ(null));
277 return toDegrees(angle);
278 }
279
280
281
282
283
284
285 @Override
286 public double energy(boolean gradient, int threadID, AtomicDoubleArray3D grad, AtomicDoubleArray3D lambdaGrad) {
287 energy = 0.0;
288 value = 0.0;
289 dEdL = 0.0;
290
291 if (!getUse()) {
292 return energy;
293 }
294 var atomA = atoms[0];
295 var atomB = atoms[1];
296 var atomC = atoms[2];
297 var atomD = atoms[3];
298
299 var va = atomA.getXYZ();
300 var vb = atomB.getXYZ();
301 var vc = atomC.getXYZ();
302 var vd = atomD.getXYZ();
303 var vba = vb.sub(va);
304 var vcb = vc.sub(vb);
305 var vdc = vd.sub(vc);
306 var vt = vba.X(vcb);
307 var vu = vcb.X(vdc);
308 var rt2 = max(vt.length2(), TORSION_TOLERANCE);
309 var ru2 = max(vu.length2(), TORSION_TOLERANCE);
310 var rtru2 = rt2 * ru2;
311 var rr = sqrt(rtru2);
312 var rcb = max(vcb.length(), TORSION_TOLERANCE);
313 var cosine = vt.dot(vu) / rr;
314 var sine = vcb.dot(vt.X(vu)) / (rcb * rr);
315 value = toDegrees(acos(cosine));
316 if (sine < 0.0) {
317 value = -value;
318 }
319
320
321 var amp = torsionType.amplitude;
322 var tsin = torsionType.sine;
323 var tcos = torsionType.cosine;
324
325
326 energy = amp[0] * (1.0 + cosine * tcos[0] + sine * tsin[0]);
327 var dedphi = amp[0] * (cosine * tsin[0] - sine * tcos[0]);
328 var cosprev = cosine;
329 var sinprev = sine;
330 var n = torsionType.terms;
331
332
333 for (int i = 1; i < n; i++) {
334 var cosn = cosine * cosprev - sine * sinprev;
335 var sinn = sine * cosprev + cosine * sinprev;
336 var phi = 1.0 + cosn * tcos[i] + sinn * tsin[i];
337 var dphi = (1.0 + i) * (cosn * tsin[i] - sinn * tcos[i]);
338 energy = energy + amp[i] * phi;
339 dedphi = dedphi + amp[i] * dphi;
340 cosprev = cosn;
341 sinprev = sinn;
342 }
343
344 dEdL = torsionScale * torsionType.torsionUnit * energy;
345 energy = dEdL * lambda;
346
347
348 if (gradient || lambdaTerm) {
349
350 dedphi = torsionScale * torsionType.torsionUnit * dedphi;
351 var vca = vc.sub(va);
352 var vdb = vd.sub(vb);
353 var dedt = vt.X(vcb).scaleI(dedphi / (rt2 * rcb));
354 var dedu = vu.X(vcb).scaleI(-dedphi / (ru2 * rcb));
355
356
357 var ga = dedt.X(vcb);
358 var gb = vca.X(dedt).addI(dedu.X(vdc));
359 var gc = dedt.X(vba).addI(vdb.X(dedu));
360 var gd = dedu.X(vcb);
361 int ia = atomA.getIndex() - 1;
362 int ib = atomB.getIndex() - 1;
363 int ic = atomC.getIndex() - 1;
364 int id = atomD.getIndex() - 1;
365 if (lambdaTerm) {
366 lambdaGrad.add(threadID, ia, ga);
367 lambdaGrad.add(threadID, ib, gb);
368 lambdaGrad.add(threadID, ic, gc);
369 lambdaGrad.add(threadID, id, gd);
370 }
371 if (gradient) {
372 grad.add(threadID, ia, ga.scaleI(lambda));
373 grad.add(threadID, ib, gb.scaleI(lambda));
374 grad.add(threadID, ic, gc.scaleI(lambda));
375 grad.add(threadID, id, gd.scaleI(lambda));
376 }
377 }
378
379 return energy;
380 }
381
382
383
384
385
386
387
388
389 public Atom get1_4(Atom a) {
390 if (a == atoms[0]) {
391 return atoms[3];
392 }
393 if (a == atoms[3]) {
394 return atoms[0];
395 }
396 return null;
397 }
398
399
400
401
402 @Override
403 public double getLambda() {
404 return lambda;
405 }
406
407
408
409
410 @Override
411 public void setLambda(double lambda) {
412 if (applyAllLambda()) {
413 lambdaTerm = true;
414 }
415 this.lambda = lambdaTerm ? lambdaMapper.applyAsDouble(lambda) : 1.0;
416 }
417
418
419
420
421 @Override
422 public double getd2EdL2() {
423 return 0.0;
424 }
425
426
427
428
429 @Override
430 public double getdEdL() {
431 if (lambdaTerm) {
432 return dEdL;
433 } else {
434 return 0.0;
435 }
436 }
437
438
439
440
441 @Override
442 public void getdEdXdL(double[] gradient) {
443
444 }
445
446
447
448
449 public void log() {
450 logger.info(format(" %-8s %6d-%s %6d-%s %6d-%s %6d-%s %10.4f %10.4f %10.4f", "Torsional-Angle",
451 atoms[0].getIndex(), atoms[0].getAtomType().name, atoms[1].getIndex(),
452 atoms[1].getAtomType().name, atoms[2].getIndex(), atoms[2].getAtomType().name,
453 atoms[3].getIndex(), atoms[3].getAtomType().name, value, energy, torsionScale));
454 }
455
456
457
458
459
460
461 @Override
462 public String toString() {
463 return format("%s (%7.1f,%7.2f,%7.2f)", id, value, energy, torsionScale);
464 }
465
466
467
468
469 private void initialize() {
470 atoms = new Atom[4];
471 atoms[1] = bonds[0].getCommonAtom(bonds[1]);
472 atoms[0] = bonds[0].get1_2(atoms[1]);
473 atoms[2] = bonds[1].get1_2(atoms[1]);
474 atoms[3] = bonds[2].get1_2(atoms[2]);
475 atoms[0].setTorsion(this);
476 atoms[1].setTorsion(this);
477 atoms[2].setTorsion(this);
478 atoms[3].setTorsion(this);
479 setID_Key(false);
480 value = dihedralAngle(atoms[0].getXYZ(null), atoms[1].getXYZ(null), atoms[2].getXYZ(null),
481 atoms[3].getXYZ(null));
482 }
483 }