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