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.parameters;
39
40 import ffx.utilities.FFXProperty;
41 import org.w3c.dom.Document;
42 import org.w3c.dom.Element;
43
44 import java.io.BufferedReader;
45 import java.io.IOException;
46 import java.util.Arrays;
47 import java.util.Comparator;
48 import java.util.HashMap;
49 import java.util.Map;
50 import java.util.logging.Level;
51 import java.util.logging.Logger;
52
53 import static ffx.potential.parameters.ForceField.ForceFieldType.TORTORS;
54 import static ffx.utilities.Constants.KCAL_TO_KJ;
55 import static ffx.utilities.PropertyGroup.EnergyUnitConversion;
56 import static ffx.utilities.PropertyGroup.PotentialFunctionParameter;
57 import static java.lang.Double.parseDouble;
58 import static java.lang.Integer.parseInt;
59 import static java.lang.String.format;
60 import static java.lang.System.arraycopy;
61 import static java.util.Arrays.sort;
62 import static org.apache.commons.math3.util.FastMath.abs;
63
64
65
66
67
68
69
70 @FFXProperty(name = "tortors", clazz = String[].class, propertyGroup = PotentialFunctionParameter, description = """
71 [7 integers, then multiple lines of 2 integers and 1 real]
72 Provides the values for a single torsion-torsion parameter.
73 The first five integer modifiers give the atom class numbers for the atoms involved in the two adjacent torsional angles to be defined.
74 The last two integer modifiers contain the number of data grid points that lie along each axis of the torsion-torsion map.
75 For example, this value will be 13 for a 30 degree torsional angle spacing, i.e., 360/30 = 12, but 13
76 values are required since data values for -180 and +180 degrees must both be supplied.
77 The subsequent lines contain the torsion-torsion map data as the integer values in degrees of each
78 torsional angle and the target energy value in kcal/mole.
79 """)
80 public final class TorsionTorsionType extends BaseType implements Comparator<String> {
81
82
83
84
85 public static final double DEFAULT_TORTOR_UNIT = 1.0;
86
87
88
89 @FFXProperty(name = "tortorunit", propertyGroup = EnergyUnitConversion, defaultValue = "1.0", description = """
90 Sets the scale factor needed to convert the energy value computed by the torsion-torsion potential into units of kcal/mole.
91 The correct value is force field dependent and typically provided in the header of the master force field parameter file.
92 """)
93 public double torTorUnit = DEFAULT_TORTOR_UNIT;
94
95 private static final Logger logger = Logger.getLogger(TorsionTorsionType.class.getName());
96
97
98
99 public final int[] atomClasses;
100
101
102
103 public final double[] energy;
104
105
106
107 public final int nx;
108
109
110
111 public final int ny;
112
113
114
115 public final double[] tx;
116
117
118
119 public final double[] ty;
120
121
122
123 public final double[] dx;
124
125
126
127 public final double[] dy;
128
129
130
131 public final double[] dxy;
132
133
134
135 private final int[] gridPoints;
136
137
138
139
140
141
142
143
144
145
146 public TorsionTorsionType(int[] atomClasses, int[] gridPoints, double[] torsion1,
147 double[] torsion2, double[] energy) {
148 super(TORTORS, sortKey(atomClasses));
149 this.atomClasses = atomClasses;
150 nx = gridPoints[0];
151 ny = gridPoints[1];
152 if (nx != ny) {
153 logger.severe("Untested TORTOR parameters: nx != ny: " + nx + ", " + ny);
154 }
155
156 this.energy = energy;
157 this.gridPoints = gridPoints;
158 tx = new double[nx];
159 ty = new double[ny];
160 dx = new double[nx * ny];
161 dy = new double[nx * ny];
162 dxy = new double[nx * ny];
163 sort(torsion1);
164 sort(torsion2);
165 tx[0] = torsion1[0];
166 ty[0] = torsion2[0];
167 int j1 = 1;
168 int j2 = 1;
169 for (int i = 1; i < nx; i++) {
170 while (torsion1[j1] == tx[i - 1]) {
171 j1++;
172 }
173 while (torsion2[j2] == ty[i - 1]) {
174 j2++;
175 }
176 tx[i] = torsion1[j1];
177 ty[i] = torsion2[j2];
178 }
179
180
181 boolean isCyclic = true;
182 double eps = 0.0001;
183 if (abs(tx[0] - tx[nx - 1]) - 360.0 > eps) {
184 isCyclic = false;
185 if (logger.isLoggable(Level.FINEST)) {
186 logger.finest(" tortor is aperiodic: " + tx[0] + ", " + tx[nx - 1]);
187 }
188 }
189 if (isCyclic) {
190 for (int i = 0; i < ny; i++) {
191 int k = i * nx;
192 if (abs(energy[k] - energy[k + nx - 1]) > eps) {
193 isCyclic = false;
194 if (logger.isLoggable(Level.FINEST)) {
195 logger.finest(" tortor is apreriodic: " + k + ", " + (k + nx - 1) + ": " + abs(
196 energy[k] - energy[k + nx - 1]));
197 }
198 break;
199 }
200 }
201 }
202 if (isCyclic) {
203 int k = (ny - 1) * nx;
204 for (int i = 0; i < nx; i++) {
205 if (abs(energy[i] - energy[i + k]) > eps) {
206 if (logger.isLoggable(Level.FINEST)) {
207 logger.fine(
208 " tortor is aperiodic: " + i + ", " + i + k + ": " + abs(energy[i] - energy[i + k]));
209 }
210 isCyclic = false;
211 break;
212 }
213 }
214 }
215
216 boolean cyclic = isCyclic;
217 double[] tmp1 = new double[nx];
218 double[] tmp2 = new double[nx];
219 double[] tmp3 = new double[nx];
220 double[] tmp4 = new double[nx];
221 double[] tmp5 = new double[nx];
222 double[] tmp6 = new double[nx];
223 double[] tmp7 = new double[nx];
224 double[] bs = new double[nx];
225 double[] cs = new double[nx];
226 double[] ds = new double[nx];
227
228
229 arraycopy(tx, 0, tmp1, 0, nx);
230
231 int m = 0;
232 for (int j = 0; j < ny; j++) {
233 arraycopy(energy, m, tmp2, 0, nx);
234 if (cyclic) {
235 cspline(nx - 1, tmp1, tmp2, bs, cs, ds, tmp3, tmp4, tmp5, tmp6, tmp7);
236 } else {
237 nspline(nx - 1, tmp1, tmp2, 0.0, 0.0, bs, cs, tmp3, tmp4, tmp5, tmp6, tmp7);
238 }
239 arraycopy(bs, 0, dx, m, nx);
240 m = m + nx;
241 }
242
243
244 arraycopy(ty, 0, tmp1, 0, ny);
245
246 m = 0;
247 for (int j = 0; j < nx; j++) {
248 for (int k = 0; k < ny; k++) {
249 tmp2[k] = energy[m + k * nx];
250 }
251 if (cyclic) {
252 cspline(ny - 1, tmp1, tmp2, bs, cs, ds, tmp3, tmp4, tmp5, tmp6, tmp7);
253 } else {
254 nspline(ny - 1, tmp1, tmp2, 0.0, 0.0, bs, cs, tmp3, tmp4, tmp5, tmp6, tmp7);
255 }
256 for (int k = 0; k < ny; k++) {
257 dy[m + k * nx] = bs[k];
258 }
259 m = m + 1;
260 }
261
262
263 m = 0;
264 for (int j = 0; j < nx; j++) {
265 for (int k = 0; k < ny; k++) {
266 tmp2[k] = dx[m + k * nx];
267 }
268 if (cyclic) {
269 cspline(ny - 1, tmp1, tmp2, bs, cs, ds, tmp3, tmp4, tmp5, tmp6, tmp7);
270 } else {
271 nspline(ny - 1, tmp1, tmp2, 0.0, 0.0, bs, cs, tmp3, tmp4, tmp5, tmp6, tmp7);
272 }
273 for (int k = 0; k < ny; k++) {
274 dxy[m + k * nx] = bs[k];
275 }
276 m = m + 1;
277 }
278 }
279
280
281
282
283
284
285
286
287
288 public static TorsionTorsionType average(TorsionTorsionType torsionTorsionType1,
289 TorsionTorsionType torsionTorsionType2, int[] atomClasses) {
290 if (torsionTorsionType1 == null || torsionTorsionType2 == null || atomClasses == null) {
291 return null;
292 }
293 return null;
294 }
295
296
297
298
299
300
301
302
303
304 public static TorsionTorsionType parse(String input, String[] tokens, BufferedReader br) {
305 if (tokens.length < 8) {
306 logger.log(Level.WARNING, "Invalid TORTORS type:\n{0}", input);
307 } else {
308 try {
309 int[] atomClasses = new int[5];
310 for (int i = 0; i < 5; i++) {
311 atomClasses[i] = parseInt(tokens[i + 1]);
312 }
313 int[] gridPoints = new int[2];
314 gridPoints[0] = parseInt(tokens[6]);
315 gridPoints[1] = parseInt(tokens[7]);
316 int points = gridPoints[0] * gridPoints[1];
317 double[] torsion1 = new double[points];
318 double[] torsion2 = new double[points];
319 double[] energy = new double[points];
320 for (int i = 0; i < points; i++) {
321 input = br.readLine();
322 tokens = input.trim().split(" +");
323 if (tokens.length != 3) {
324 logger.log(Level.WARNING, "Invalid TORTORS type:\n{0}", input);
325 return null;
326 }
327 torsion1[i] = parseDouble(tokens[0]);
328 torsion2[i] = parseDouble(tokens[1]);
329 energy[i] = parseDouble(tokens[2]);
330 }
331 return new TorsionTorsionType(atomClasses, gridPoints, torsion1, torsion2, energy);
332 } catch (NumberFormatException | IOException e) {
333 String message = "Exception parsing TORTORS type:\n" + input + "\n";
334 logger.log(Level.SEVERE, message, e);
335 }
336 }
337 return null;
338 }
339
340
341
342
343
344
345
346
347 public static TorsionTorsionType parse(String input, String[] tokens) {
348 if (tokens.length < 8) {
349 logger.log(Level.WARNING, "Invalid TORTORS type:\n{0}", input);
350 } else {
351 try {
352 int[] atomClasses = new int[5];
353 for (int i = 0; i < 5; i++) {
354 atomClasses[i] = parseInt(tokens[i + 1]);
355 }
356 int[] gridPoints = new int[2];
357 gridPoints[0] = parseInt(tokens[6]);
358 gridPoints[1] = parseInt(tokens[7]);
359 int points = gridPoints[0] * gridPoints[1];
360 int numTokens = points * 3 + 8;
361 if (tokens.length < numTokens) {
362 logger.log(Level.WARNING, "Invalid TORTORS type:\n{0}", input);
363 return null;
364 }
365 double[] torsion1 = new double[points];
366 double[] torsion2 = new double[points];
367 double[] energy = new double[points];
368 int index = 8;
369 for (int i = 0; i < points; i++) {
370 torsion1[i] = parseDouble(tokens[index++]);
371 torsion2[i] = parseDouble(tokens[index++]);
372 energy[i] = parseDouble(tokens[index++]);
373 }
374 return new TorsionTorsionType(atomClasses, gridPoints, torsion1, torsion2, energy);
375 } catch (NumberFormatException e) {
376 String message = "Exception parsing TORTORS type:\n" + input + "\n";
377 logger.log(Level.SEVERE, message, e);
378 }
379 }
380 return null;
381 }
382
383
384
385
386
387
388
389 public static String reverseKey(int[] c) {
390 return c[4] + " " + c[3] + " " + c[2] + " " + c[1] + " " + c[0];
391 }
392
393
394
395
396
397
398
399 public static String sortKey(int[] c) {
400 return c[0] + " " + c[1] + " " + c[2] + " " + c[3] + " " + c[4];
401 }
402
403
404
405
406
407
408
409
410
411
412
413
414 private static int cytsy(int n, double[] dm, double[] du, double[] cr, double[] rs, double[] c) {
415 if (n < 3) {
416 return -2;
417 }
418
419
420 if (cytsyp(n, dm, du, cr) == 1) {
421
422 cytsys(n, dm, du, cr, rs, c);
423 return 1;
424 }
425 return -1;
426 }
427
428
429
430
431
432
433
434
435
436
437
438 private static int cytsyp(int n, double[] dm, double[] du, double[] cr) {
439 double eps = 0.00000001;
440
441 if (n < 3) {
442 return -2;
443 }
444
445
446 double row = abs(dm[1]) + abs(du[1]) + abs(du[n]);
447 if (row == 0.0) {
448 return 0;
449 }
450 double d = 1.0 / row;
451 if (dm[1] < 0.0) {
452 return -1;
453 } else if (abs(dm[1]) * d < eps) {
454 return 0;
455 }
456
457
458 double temp1 = du[1];
459 double temp2 = 0.0;
460 du[1] = du[1] / dm[1];
461 cr[1] = du[n] / dm[1];
462 for (int i = 2; i < n; i++) {
463 row = abs(dm[i]) + abs(du[i]) + abs(temp1);
464 if (row == 0.0) {
465 return 0;
466 }
467 d = 1.0 / row;
468 dm[i] = dm[i] - temp1 * du[i - 1];
469 if (dm[i] < 0.0) {
470 return -1;
471 } else if (abs(dm[i]) * d < eps) {
472 return 0;
473 }
474 if (i < (n - 1)) {
475 cr[i] = -temp1 * cr[i - 1] / dm[i];
476 temp1 = du[i];
477 du[i] = du[i] / dm[i];
478 } else {
479 temp2 = du[i];
480 du[i] = (du[i] - temp1 * cr[i - 1]) / dm[i];
481 }
482 }
483 row = abs(du[n]) + abs(dm[n]) + abs(temp2);
484 if (row == 0.0) {
485 return 0;
486 }
487 d = 1.0 / row;
488 dm[n] = dm[n] - dm[n - 1] * du[n - 1] * du[n - 1];
489 temp1 = 0.0;
490 for (int i = 1; i < n - 1; i++) {
491 temp1 = temp1 + dm[i] * cr[i] * cr[i];
492 }
493 dm[n] = dm[n] - temp1;
494 if (dm[n] < 0.0) {
495 return -1;
496 } else if (abs(dm[n]) * d < eps) {
497 return 0;
498 }
499 return 1;
500 }
501
502
503
504
505
506
507
508
509
510
511
512 private static void cytsys(int n, double[] dm, double[] du, double[] cr, double[] rs, double[] c) {
513
514 double temp = rs[1];
515 rs[1] = temp / dm[1];
516 double sum = cr[1] * temp;
517 for (int i = 2; i < n; i++) {
518 temp = rs[i] - du[i - 1] * temp;
519 rs[i] = temp / dm[i];
520 if (i != (n - 1)) {
521 sum = sum + cr[i] * temp;
522 }
523 }
524 temp = rs[n] - du[n - 1] * temp;
525 temp = temp - sum;
526 rs[n] = temp / dm[n];
527
528
529 c[n] = rs[n];
530 c[n - 1] = rs[n - 1] - du[n - 1] * c[n];
531 for (int i = n - 2; i >= 1; i--) {
532 c[i] = rs[i] - du[i] * c[i + 1] - cr[i] * c[n];
533 }
534 }
535
536
537
538
539 @Override
540 public int compare(String key1, String key2) {
541 String[] keys1 = key1.split(" ");
542 String[] keys2 = key2.split(" ");
543 int c1 = parseInt(keys1[2]);
544 int c2 = parseInt(keys2[2]);
545 if (c1 < c2) {
546 return -1;
547 } else if (c1 > c2) {
548 return 1;
549 }
550 return 0;
551 }
552
553
554
555
556 @Override
557 public boolean equals(Object o) {
558 if (this == o) {
559 return true;
560 }
561 if (o == null || getClass() != o.getClass()) {
562 return false;
563 }
564 TorsionTorsionType torsionTorsionType = (TorsionTorsionType) o;
565 return Arrays.equals(atomClasses, torsionTorsionType.atomClasses);
566 }
567
568
569
570
571 @Override
572 public int hashCode() {
573 return Arrays.hashCode(atomClasses);
574 }
575
576
577
578
579
580
581 public void incrementClasses(int increment) {
582 for (int i = 0; i < atomClasses.length; i++) {
583 atomClasses[i] += increment;
584 }
585 setKey(sortKey(atomClasses));
586 }
587
588
589
590
591
592
593 public void patchClasses(HashMap<AtomType, AtomType> typeMap) {
594 int count = 0;
595 for (AtomType newType : typeMap.keySet()) {
596
597 for (int atomClass : atomClasses) {
598 if (atomClass == newType.atomClass) {
599 count++;
600 }
601 }
602 }
603 if (count > 0 && count < atomClasses.length) {
604 for (AtomType newType : typeMap.keySet()) {
605 for (int i = 0; i < atomClasses.length; i++) {
606 if (atomClasses[i] == newType.atomClass) {
607 AtomType knownType = typeMap.get(newType);
608 atomClasses[i] = knownType.atomClass;
609 }
610 }
611 }
612 setKey(sortKey(atomClasses));
613 }
614 }
615
616
617
618
619
620
621 @Override
622 public String toString() {
623 StringBuilder tortorBuffer = new StringBuilder("tortors");
624 for (int i : atomClasses) {
625 tortorBuffer.append(format(" %5d", i));
626 }
627 tortorBuffer.append(format(" %2d %2d", gridPoints[0], gridPoints[1]));
628 for (int i = 0; i < energy.length; i++) {
629 int nxi = i % nx;
630 int nyi = i / ny;
631 tortorBuffer.append(format(" \\\n % 6.1f % 6.1f % 8.5f", tx[nxi], ty[nyi], energy[i]));
632 }
633 return tortorBuffer.toString();
634 }
635
636
637
638
639
640
641
642
643
644 public static Element getXMLForce(Document doc, ForceField forceField) {
645 Map<String, TorsionTorsionType> types = forceField.getTorsionTorsionTypes();
646 if (!types.values().isEmpty()) {
647 Element node = doc.createElement("AmoebaTorsionTorsionForce");
648 int i = 0;
649 for (TorsionTorsionType torsionTorsionType : types.values()) {
650 torsionTorsionType.toXML(doc, node, i);
651 i++;
652 }
653 return node;
654 }
655 return null;
656 }
657
658
659
660
661
662
663
664
665 public void toXML(Document doc, Element torTorNode, int label) {
666 Element tortors = doc.createElement("TorsionTorsion");
667 Element ttGrid = doc.createElement("TorsionTorsionGrid");
668
669
670 tortors.setAttribute("class1", format("%d", atomClasses[0]));
671 tortors.setAttribute("class2", format("%d", atomClasses[1]));
672 tortors.setAttribute("class3", format("%d", atomClasses[2]));
673 tortors.setAttribute("class4", format("%d", atomClasses[3]));
674 tortors.setAttribute("class5", format("%d", atomClasses[4]));
675 tortors.setAttribute("nx", format("%d", gridPoints[0]));
676 tortors.setAttribute("ny", format("%d", gridPoints[1]));
677
678
679 ttGrid.setAttribute("nx", format("%d", gridPoints[0]));
680 ttGrid.setAttribute("ny", format("%d", gridPoints[1]));
681 for (int i = 0; i < energy.length; i++) {
682 int nxi = i % nx;
683 int nyi = i / ny;
684
685
686 Element grid = doc.createElement("Grid");
687 grid.setAttribute("angle1", format("%f", tx[nxi]));
688 grid.setAttribute("angle2", format("%f", ty[nyi]));
689 grid.setAttribute("f", format("%f", energy[i] * KCAL_TO_KJ));
690 ttGrid.appendChild(grid);
691 }
692
693 tortors.setAttribute("grid", String.valueOf(label));
694 ttGrid.setAttribute("grid", String.valueOf(label));
695 torTorNode.appendChild(tortors);
696 torTorNode.appendChild(ttGrid);
697 }
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715 private void nspline(int n, double[] x0, double[] y0, double y21, double y2n, double[] s1,
716 double[] s2, double[] h, double[] g, double[] dy, double[] dla, double[] dmu) {
717
718
719 for (int i = 0; i < n; i++) {
720 h[i] = x0[i + 1] - x0[i];
721 dy[i] = (y0[i + 1] - y0[i]) / h[i];
722 }
723
724
725 for (int i = 1; i < n; i++) {
726 dla[i] = h[i] / (h[i] + h[i - 1]);
727 dmu[i] = 1.0 - dla[i];
728 g[i] = 3.0 * (dla[i] * dy[i - 1] + dmu[i] * dy[i]);
729 }
730
731
732 dla[n] = 1.0;
733 dla[0] = 0.0;
734 dmu[n] = 0.0;
735 dmu[0] = 1.0;
736 g[0] = 3.0 * dy[0] - 0.5 * h[0] * y21;
737 g[n] = 3.0 * dy[n - 1] + 0.5 * h[n - 1] * y2n;
738
739
740 dmu[0] = 0.5 * dmu[0];
741 g[0] = 0.5 * g[0];
742 for (int i = 1; i <= n; i++) {
743 double t = 2.0 - dmu[i - 1] * dla[i];
744 dmu[i] = dmu[i] / t;
745 g[i] = (g[i] - g[i - 1] * dla[i]) / t;
746 }
747
748 for (int i = n - 1; i >= 0; i--) {
749 g[i] = g[i] - dmu[i] * g[i + 1];
750 }
751
752
753 arraycopy(g, 0, s1, 0, n + 1);
754
755
756 s2[0] = y21;
757 s2[n] = y2n;
758 for (int i = 1; i < n; i++) {
759 s2[i] =
760 6.0 * (y0[i + 1] - y0[i]) / (h[i] * h[i]) - 4.0 * s1[i] / h[i] - 2.0 * s1[i + 1] / h[i];
761 }
762 }
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778 private void cspline(int n, double[] xn, double[] fn, double[] b, double[] c, double[] d,
779 double[] h, double[] du, double[] dm, double[] rc, double[] rs) {
780 double eps = 0.000001;
781 if (abs(fn[n] - fn[0]) > eps) {
782 logger.severe("TORTOR values are not periodic.");
783 }
784
785
786 double mean = 0.5 * (fn[0] + fn[n]);
787 fn[0] = mean;
788 fn[n] = mean;
789
790
791 for (int i = 0; i < n; i++) {
792 h[i] = xn[i + 1] - xn[i];
793 }
794 h[n] = h[0];
795
796 if (n - 1 >= 0) {
797 arraycopy(h, 1, du, 1, n - 1);
798 }
799
800 du[n] = h[0];
801 for (int i = 1; i <= n; i++) {
802 dm[i] = 2.0 * (h[i - 1] + h[i]);
803 }
804
805
806 double temp1 = (fn[1] - fn[0]) / h[0];
807 for (int i = 1; i < n; i++) {
808 double temp2 = (fn[i + 1] - fn[i]) / h[i];
809 rs[i] = 3.0 * (temp2 - temp1);
810 temp1 = temp2;
811 }
812 rs[n] = 3.0 * ((fn[1] - fn[0]) / h[0] - temp1);
813
814
815 if (cytsy(n, dm, du, rc, rs, c) != 1) {
816 return;
817 }
818
819
820 c[0] = c[n];
821 for (int i = 0; i < n; i++) {
822 b[i] = (fn[i + 1] - fn[i]) / h[i] - h[i] / 3.0 * (c[i + 1] + 2.0 * c[i]);
823 d[i] = (c[i + 1] - c[i]) / (3.0 * h[i]);
824 }
825 b[n] = (fn[1] - fn[n]) / h[n] - h[n] / 3.0 * (c[1] + 2.0 * c[n]);
826 }
827 }