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