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 static ffx.numerics.math.DoubleMath.dihedralAngle;
41 import static java.lang.System.arraycopy;
42 import static java.util.Arrays.copyOf;
43 import static org.apache.commons.math3.util.FastMath.acos;
44 import static org.apache.commons.math3.util.FastMath.max;
45 import static org.apache.commons.math3.util.FastMath.min;
46 import static org.apache.commons.math3.util.FastMath.sqrt;
47 import static org.apache.commons.math3.util.FastMath.toDegrees;
48
49 import ffx.numerics.atomic.AtomicDoubleArray3D;
50 import ffx.potential.parameters.BondType;
51 import ffx.potential.parameters.ForceField;
52 import ffx.potential.parameters.StretchTorsionType;
53 import ffx.potential.parameters.TorsionType;
54
55 import java.io.Serial;
56 import java.util.logging.Logger;
57
58
59
60
61
62
63
64
65 public class StretchTorsion extends BondedTerm implements LambdaInterface {
66
67 @Serial
68 private static final long serialVersionUID = 1L;
69
70 private static final Logger logger = Logger.getLogger(StretchTorsion.class.getName());
71
72 private static final String mathForm;
73
74 static {
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91 StringBuilder mathFormBuilder = new StringBuilder();
92
93 for (int m = 1; m < 4; m++) {
94 for (int n = 1; n < 4; n++) {
95
96 mathFormBuilder.append(
97 String.format("k%d%d*(bVal%d-b%d)*(1+cos(%d*tVal+phi%d))+", m, n, m, m, n, n));
98 }
99 }
100 int lenStr = mathFormBuilder.length();
101 mathFormBuilder.replace(lenStr - 1, lenStr, ";");
102
103 for (int m = 1; m < 4; m++) {
104 mathFormBuilder.append(String.format("bVal%d=distance(p%d,p%d);", m, m, (m + 1)));
105 }
106
107 mathFormBuilder.append("tVal=dihedral(p1,p2,p3,p4)");
108 mathForm = mathFormBuilder.toString();
109 }
110
111
112
113
114
115 private final double[] constants = new double[9];
116
117 public BondType bondType1 = null;
118
119 public BondType bondType2 = null;
120
121 public BondType bondType3 = null;
122
123 private double lambda = 1.0;
124
125 private double dEdL = 0.0;
126
127 private boolean lambdaTerm = false;
128
129 private StretchTorsionType stretchTorsionType = null;
130
131 private double[] tsin = new double[] {0.0, 0.0, 0.0};
132
133 private double[] tcos = new double[] {1.0, 1.0, 1.0};
134
135
136 private TorsionType torsionType = null;
137
138
139
140
141
142
143
144
145 private StretchTorsion(Bond b1, Bond b2, Bond b3) {
146 super();
147 bonds = new Bond[3];
148 bonds[0] = b1;
149 bonds[1] = b2;
150 bonds[2] = b3;
151 initialize();
152 }
153
154
155
156
157
158
159 private StretchTorsion(String n) {
160 super(n);
161 }
162
163
164
165
166
167
168
169
170 public static StretchTorsion stretchTorsionFactory(Torsion torsion, ForceField forceField) {
171 TorsionType torsionType = torsion.torsionType;
172 String key = torsionType.getKey();
173 StretchTorsionType stretchTorsionType = forceField.getStretchTorsionType(key);
174 if (stretchTorsionType != null) {
175 Bond bond1 = torsion.bonds[0];
176 Bond middleBond = torsion.bonds[1];
177 Bond bond3 = torsion.bonds[2];
178 StretchTorsion stretchTorsion = new StretchTorsion(bond1, middleBond, bond3);
179 stretchTorsion.stretchTorsionType = stretchTorsionType;
180 stretchTorsion.torsionType = torsion.torsionType;
181 stretchTorsion.bondType1 = bond1.bondType;
182 stretchTorsion.bondType2 = middleBond.bondType;
183 stretchTorsion.bondType3 = bond3.bondType;
184 Atom atom1 = torsion.atoms[0];
185 Atom atom2 = torsion.atoms[1];
186 Atom atom3 = torsion.atoms[2];
187 Atom atom4 = torsion.atoms[3];
188
189 stretchTorsion.setFlipped(atom1.getAtomType().atomClass != stretchTorsionType.atomClasses[0]
190 || atom2.getAtomType().atomClass != stretchTorsionType.atomClasses[1]
191 || atom3.getAtomType().atomClass != stretchTorsionType.atomClasses[2]
192 || atom4.getAtomType().atomClass != stretchTorsionType.atomClasses[3]);
193
194 return stretchTorsion;
195 }
196 return null;
197 }
198
199
200
201
202
203
204 public static String stretchTorsionForm() {
205 return mathForm;
206 }
207
208
209
210
211
212
213
214
215
216
217 public boolean compare(Atom a0, Atom a1, Atom a2, Atom a3) {
218 if (a0 == atoms[0] && a1 == atoms[1] && a2 == atoms[2] && a3 == atoms[3]) {
219 return true;
220 }
221 return (a0 == atoms[3] && a1 == atoms[2] && a2 == atoms[1] && a3 == atoms[0]);
222 }
223
224
225
226
227
228
229 @Override
230 public double energy(boolean gradient, int threadID, AtomicDoubleArray3D grad, AtomicDoubleArray3D lambdaGrad) {
231 energy = 0.0;
232 value = 0.0;
233 dEdL = 0.0;
234
235 if (!getUse()) {
236 return energy;
237 }
238 var atomA = atoms[0];
239 var atomB = atoms[1];
240 var atomC = atoms[2];
241 var atomD = atoms[3];
242 var va = atomA.getXYZ();
243 var vb = atomB.getXYZ();
244 var vc = atomC.getXYZ();
245 var vd = atomD.getXYZ();
246 var vba = vb.sub(va);
247 var vcb = vc.sub(vb);
248 var vdc = vd.sub(vc);
249 var rba2 = vba.length2();
250 var rcb2 = vcb.length2();
251 var rdc2 = vdc.length2();
252 if (min(min(rba2, rcb2), rdc2) == 0.0) {
253 return 0.0;
254 }
255 var rcb = sqrt(rcb2);
256 var t = vba.X(vcb);
257 var u = vcb.X(vdc);
258 var rt2 = max(t.length2(), 0.000001);
259 var ru2 = max(u.length2(), 0.000001);
260 var rtru = sqrt(rt2 * ru2);
261 var vca = vc.sub(va);
262 var vdb = vd.sub(vb);
263 var cosine = t.dot(u) / rtru;
264 var sine = vcb.dot(t.X(u)) / (rcb * rtru);
265 value = toDegrees(acos(cosine));
266 if (sine < 0.0) {
267 value = -value;
268 }
269
270
271 var phi1 = 1.0 + (cosine * tcos[0] + sine * tsin[0]);
272 var dphi1 = (cosine * tsin[0] - sine * tcos[0]);
273 var cosine2 = cosine * cosine - sine * sine;
274 var sine2 = 2.0 * cosine * sine;
275 var phi2 = 1.0 + (cosine2 * tcos[1] + sine2 * tsin[1]);
276 var dphi2 = 2.0 * (cosine2 * tsin[1] - sine2 * tcos[1]);
277 var sine3 = cosine * sine2 + sine * cosine2;
278 var cosine3 = cosine * cosine2 - sine * sine2;
279 var phi3 = 1.0 + (cosine3 * tcos[2] + sine3 * tsin[2]);
280 var dphi3 = 3.0 * (cosine3 * tsin[2] - sine3 * tcos[2]);
281
282
283 var c1 = constants[0];
284 var c2 = constants[1];
285 var c3 = constants[2];
286 var rba = sqrt(rba2);
287 var dr1 = rba - bondType1.distance;
288 var units = stretchTorsionType.strTorUnit;
289 var s1 = c1 * phi1 + c2 * phi2 + c3 * phi3;
290 var e1 = units * dr1 * s1;
291
292
293 var c4 = constants[3];
294 var c5 = constants[4];
295 var c6 = constants[5];
296 var dr2 = rcb - bondType2.distance;
297 var s2 = c4 * phi1 + c5 * phi2 + c6 * phi3;
298 var e2 = units * dr2 * s2;
299
300
301 var c7 = constants[6];
302 var c8 = constants[7];
303 var c9 = constants[8];
304 var rdc = sqrt(rdc2);
305 var dr3 = rdc - bondType3.distance;
306 var s3 = c7 * phi1 + c8 * phi2 + c9 * phi3;
307 var e3 = units * dr3 * s3;
308 energy = e1 + e2 + e3;
309 energy = energy * lambda;
310 dEdL = energy;
311 if (gradient || lambdaTerm) {
312
313 var dedphi = units * dr1 * (c1 * dphi1 + c2 * dphi2 + c3 * dphi3);
314 var ddrd = vba.scale(units * s1 / rba);
315 var dedt = t.X(vcb).scaleI(dedphi / (rt2 * rcb));
316 var dedu = u.X(vcb).scaleI(-dedphi / (ru2 * rcb));
317
318 var ga = dedt.X(vcb).subI(ddrd);
319 var gb = vca.X(dedt).addI(dedu.X(vdc)).addI(ddrd);
320 var gc = dedt.X(vba).addI(vdb.X(dedu));
321 var gd = dedu.X(vcb);
322
323
324 dedphi = units * dr2 * (c4 * dphi1 + c5 * dphi2 + c6 * dphi3);
325 ddrd = vcb.scale(units * s2 / rcb);
326 dedt = t.X(vcb).scaleI(dedphi / (rt2 * rcb));
327 dedu = u.X(vcb).scaleI(-dedphi / (ru2 * rcb));
328
329 ga.addI(dedt.X(vcb));
330 gb.addI(vca.X(dedt).addI(dedu.X(vdc)).subI(ddrd));
331 gc.addI(dedt.X(vba).addI(vdb.X(dedu)).addI(ddrd));
332 gd.addI(dedu.X(vcb));
333
334
335 dedphi = units * dr3 * (c7 * dphi1 + c8 * dphi2 + c9 * dphi3);
336 ddrd = vdc.scale(units * s3 / rdc);
337 dedt = t.X(vcb).scaleI(dedphi / (rt2 * rcb));
338 dedu = u.X(vcb).scaleI(-dedphi / (ru2 * rcb));
339
340 ga.addI(dedt.X(vcb));
341 gb.addI(vca.X(dedt).addI(dedu.X(vdc)));
342 gc.addI(dedt.X(vba).addI(vdb.X(dedu)).subI(ddrd));
343 gd.addI(dedu.X(vcb).addI(ddrd));
344 var ia = atomA.getIndex() - 1;
345 var ib = atomB.getIndex() - 1;
346 var ic = atomC.getIndex() - 1;
347 var id = atomD.getIndex() - 1;
348 if (lambdaTerm) {
349 lambdaGrad.add(threadID, ia, ga);
350 lambdaGrad.add(threadID, ib, gb);
351 lambdaGrad.add(threadID, ic, gc);
352 lambdaGrad.add(threadID, id, gd);
353 }
354 if (gradient) {
355 grad.add(threadID, ia, ga.scaleI(lambda));
356 grad.add(threadID, ib, gb.scaleI(lambda));
357 grad.add(threadID, ic, gc.scaleI(lambda));
358 grad.add(threadID, id, gd.scaleI(lambda));
359 }
360 }
361
362 return energy;
363 }
364
365
366
367
368
369
370
371
372 public Atom get1_4(Atom a) {
373 if (a == atoms[0]) {
374 return atoms[3];
375 }
376 if (a == atoms[3]) {
377 return atoms[0];
378 }
379 return null;
380 }
381
382
383
384
385
386
387 public double[] getConstants() {
388 return copyOf(constants, constants.length);
389 }
390
391
392 @Override
393 public double getLambda() {
394 return lambda;
395 }
396
397
398 @Override
399 public void setLambda(double lambda) {
400 if (applyAllLambda()) {
401 this.lambda = lambda;
402 lambdaTerm = true;
403 } else {
404 this.lambda = 1.0;
405 }
406 }
407
408
409 @Override
410 public double getd2EdL2() {
411 return 0.0;
412 }
413
414
415 @Override
416 public double getdEdL() {
417 if (lambdaTerm) {
418 return dEdL;
419 } else {
420 return 0.0;
421 }
422 }
423
424
425 @Override
426 public void getdEdXdL(double[] gradient) {
427
428 }
429
430
431 public void log() {
432 logger.info(
433 String.format(
434 " %-8s %6d-%s %6d-%s %6d-%s %6d-%s %10.4f %10.4f",
435 "Stretch-Torsion",
436 atoms[0].getIndex(),
437 atoms[0].getAtomType().name,
438 atoms[1].getIndex(),
439 atoms[1].getAtomType().name,
440 atoms[2].getIndex(),
441 atoms[2].getAtomType().name,
442 atoms[3].getIndex(),
443 atoms[3].getAtomType().name,
444 value,
445 energy));
446 }
447
448
449
450
451
452
453 @Override
454 public String toString() {
455 return String.format("%s (%7.1f,%7.2f)", id, value, energy);
456 }
457
458
459 private void initialize() {
460 atoms = new ffx.potential.bonded.Atom[4];
461 atoms[1] = bonds[0].getCommonAtom(bonds[1]);
462 atoms[0] = bonds[0].get1_2(atoms[1]);
463 atoms[2] = bonds[1].get1_2(atoms[1]);
464 atoms[3] = bonds[2].get1_2(atoms[2]);
465 setID_Key(false);
466 value =
467 dihedralAngle(
468 atoms[0].getXYZ(null),
469 atoms[1].getXYZ(null),
470 atoms[2].getXYZ(null),
471 atoms[3].getXYZ(null));
472 }
473
474
475
476
477
478
479 private void setFlipped(boolean flipped) {
480 if (flipped) {
481 constants[0] = stretchTorsionType.forceConstants[6];
482 constants[1] = stretchTorsionType.forceConstants[7];
483 constants[2] = stretchTorsionType.forceConstants[8];
484 constants[3] = stretchTorsionType.forceConstants[3];
485 constants[4] = stretchTorsionType.forceConstants[4];
486 constants[5] = stretchTorsionType.forceConstants[5];
487 constants[6] = stretchTorsionType.forceConstants[0];
488 constants[7] = stretchTorsionType.forceConstants[1];
489 constants[8] = stretchTorsionType.forceConstants[2];
490 } else {
491 arraycopy(stretchTorsionType.forceConstants, 0, constants, 0, 9);
492 }
493
494 tsin = new double[] {0.0, 0.0, 0.0};
495 tcos = new double[] {1.0, 1.0, 1.0};
496 arraycopy(torsionType.sine, 0, tsin, 0, min(torsionType.sine.length, 3));
497 arraycopy(torsionType.cosine, 0, tcos, 0, min(torsionType.cosine.length, 3));
498 }
499 }