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.util.Arrays;
45 import java.util.Comparator;
46 import java.util.HashMap;
47 import java.util.Map;
48 import java.util.logging.Level;
49 import java.util.logging.Logger;
50
51 import static ffx.potential.parameters.ForceField.ForceFieldType.IMPROPER;
52 import static ffx.potential.parameters.ForceField.ForceFieldType.TORSION;
53 import static ffx.utilities.Constants.DEGREES_PER_RADIAN;
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.util.Arrays.copyOf;
61 import static org.apache.commons.math3.util.FastMath.cos;
62 import static org.apache.commons.math3.util.FastMath.sin;
63 import static org.apache.commons.math3.util.FastMath.toRadians;
64
65
66
67
68
69
70
71 @FFXProperty(name = "improper", clazz = String.class, propertyGroup = PotentialFunctionParameter, description = """
72 [4 integers and 2 reals]"
73 Provides the values for a single CHARMM-style improper dihedral angle parameter.
74 The integer modifiers give the atom class numbers for the four kinds of atoms involved in the torsion which is to be defined.
75 The real number modifiers give the force constant value for the deviation from the target improper torsional angle, and the target value for the torsional angle, respectively.
76 The default units for the improper force constant are kcal/mole/radian^2, but this can be controlled via the impropunit keyword.
77 """)
78 @FFXProperty(name = "torsion", clazz = String.class, propertyGroup = PotentialFunctionParameter, description = """
79 [4 integers and up to 6 real/real/integer triples]
80 Provides the values for a single torsional angle parameter.
81 The first four integer modifiers give the atom class numbers for the atoms involved in the torsional angle to be defined.
82 Each of the remaining triples of real/real/integer modifiers give the amplitude,
83 phase offset in degrees and periodicity of a particular torsional function term, respectively.
84 Periodicities through 6-fold are allowed for torsional parameters.
85 """)
86 public final class TorsionType extends BaseType implements Comparator<String> {
87
88 private static final Logger logger = Logger.getLogger(TorsionType.class.getName());
89
90
91
92 public final int[] atomClasses;
93
94
95
96 public final int terms;
97
98
99
100 public final double[] amplitude;
101
102
103
104 public final double[] phase;
105
106
107
108 public final double[] cosine;
109
110
111
112 public final double[] sine;
113
114
115
116 public final int[] periodicity;
117
118
119
120 private final TorsionMode torsionMode;
121
122 public static final double DEFAULT_TORSION_UNIT = 1.0;
123
124
125
126 @FFXProperty(name = "torsionunit", propertyGroup = EnergyUnitConversion, defaultValue = "1.0", description = """
127 Sets the scale factor needed to convert the energy value computed by the torsional angle potential into units of kcal/mole.
128 The correct value is force field dependent and typically provided in the header of the master force field parameter file.
129 """)
130 public double torsionUnit = DEFAULT_TORSION_UNIT;
131
132
133
134
135
136
137
138
139
140 public TorsionType(int[] atomClasses, double[] amplitude, double[] phase, int[] periodicity) {
141 this(atomClasses, amplitude, phase, periodicity, TorsionMode.NORMAL);
142 }
143
144
145
146
147
148
149
150
151
152
153 public TorsionType(int[] atomClasses, double[] amplitude, double[] phase, int[] periodicity,
154 TorsionMode torsionMode) {
155 super(TORSION, sortKey(atomClasses));
156 this.atomClasses = atomClasses;
157 int max = 1;
158 for (int i1 : periodicity) {
159 if (i1 > max) {
160 max = i1;
161 }
162 }
163 terms = max;
164 this.amplitude = new double[terms];
165 this.phase = new double[terms];
166 this.periodicity = new int[terms];
167 for (int i = 0; i < terms; i++) {
168 this.periodicity[i] = i + 1;
169 }
170 for (int i = 0; i < amplitude.length; i++) {
171 int j = periodicity[i] - 1;
172 if (j >= 0 && j < terms) {
173 this.amplitude[j] = amplitude[i];
174 this.phase[j] = phase[i];
175 }
176 }
177
178 cosine = new double[terms];
179 sine = new double[terms];
180 for (int i = 0; i < terms; i++) {
181 double angle = toRadians(this.phase[i]);
182 cosine[i] = cos(angle);
183 sine[i] = sin(angle);
184 }
185
186 this.torsionMode = torsionMode;
187 if (torsionMode == TorsionMode.IMPROPER) {
188 forceFieldType = IMPROPER;
189 }
190 }
191
192
193
194
195
196
197
198
199
200 public static TorsionType average(TorsionType torsionType1, TorsionType torsionType2,
201 int[] atomClasses) {
202 if (torsionType1 == null || torsionType2 == null || atomClasses == null) {
203 return null;
204 }
205 int len = torsionType1.amplitude.length;
206 if (len != torsionType2.amplitude.length) {
207 return null;
208 }
209 double[] amplitude = new double[len];
210 double[] phase = new double[len];
211 int[] periodicity = new int[len];
212 for (int i = 0; i < len; i++) {
213 amplitude[i] = (torsionType1.amplitude[i] + torsionType2.amplitude[i]) / 2.0;
214 phase[i] = (torsionType1.phase[i] + torsionType2.phase[i]) / 2.0;
215 periodicity[i] = (torsionType1.periodicity[i] + torsionType2.periodicity[i]) / 2;
216 }
217
218 return new TorsionType(atomClasses, amplitude, phase, periodicity);
219 }
220
221
222
223
224
225
226
227
228 public static TorsionType parse(String input, String[] tokens) {
229 if (tokens.length < 5) {
230 logger.log(Level.WARNING, "Invalid TORSION type:\n{0}", input);
231 } else {
232 try {
233 int[] atomClasses = new int[4];
234 atomClasses[0] = parseInt(tokens[1]);
235 atomClasses[1] = parseInt(tokens[2]);
236 atomClasses[2] = parseInt(tokens[3]);
237 atomClasses[3] = parseInt(tokens[4]);
238 int terms = (tokens.length - 5) / 3;
239 double[] amplitude = new double[terms];
240 double[] phase = new double[terms];
241 int[] periodicity = new int[terms];
242 int index = 5;
243 for (int i = 0; i < terms; i++) {
244 amplitude[i] = parseDouble(tokens[index++]);
245 phase[i] = parseDouble(tokens[index++]);
246 periodicity[i] = parseInt(tokens[index++]);
247 }
248 return new TorsionType(atomClasses, amplitude, phase, periodicity);
249 } catch (NumberFormatException e) {
250 String message = "NumberFormatException parsing TORSION type:\n" + input + "\n";
251 logger.log(Level.SEVERE, message, e);
252 } catch (Exception e) {
253 String message = "Exception parsing TORSION type:\n" + input + "\n";
254 logger.log(Level.SEVERE, message, e);
255 }
256 }
257 return null;
258 }
259
260
261
262
263
264
265
266
267 public static TorsionType parseImproper(String input, String[] tokens) {
268 if (tokens.length < 5) {
269 logger.log(Level.WARNING, "Invalid IMPROPER type:\n{0}", input);
270 } else {
271 try {
272 int[] atomClasses = new int[4];
273 atomClasses[0] = parseInt(tokens[1]);
274 atomClasses[1] = parseInt(tokens[2]);
275 atomClasses[2] = parseInt(tokens[3]);
276 atomClasses[3] = parseInt(tokens[4]);
277 double[] amplitude = new double[1];
278 double[] phase = new double[1];
279 int[] periodicity = new int[1];
280 int index = 5;
281 amplitude[0] = parseDouble(tokens[index++]);
282 phase[0] = parseDouble(tokens[index]);
283 periodicity[0] = 1;
284 return new TorsionType(atomClasses, amplitude, phase, periodicity, TorsionMode.IMPROPER);
285 } catch (NumberFormatException e) {
286 String message = "Exception parsing IMPROPER type:\n" + input + "\n";
287 logger.log(Level.SEVERE, message, e);
288 }
289 }
290 return null;
291 }
292
293
294
295
296
297
298
299
300 public static String sortKey(int[] c) {
301 if (c == null || c.length != 4) {
302 return null;
303 }
304 if (c[1] < c[2]) {
305
306 } else if (c[2] < c[1]) {
307
308 int temp = c[0];
309 c[0] = c[3];
310 c[3] = temp;
311 temp = c[1];
312 c[1] = c[2];
313 c[2] = temp;
314 } else if (c[1] == c[2]) {
315 if (c[0] > c[3]) {
316
317 int temp = c[0];
318 c[0] = c[3];
319 c[3] = temp;
320 }
321 } else if (c[0] <= c[3]) {
322
323 } else {
324
325 int temp = c[0];
326 c[0] = c[3];
327 c[3] = temp;
328 temp = c[1];
329 c[1] = c[2];
330 c[2] = temp;
331 }
332
333 return c[0] + " " + c[1] + " " + c[2] + " " + c[3];
334 }
335
336
337
338
339
340
341 @Override
342 public int compare(String s1, String s2) {
343 String[] keys1 = s1.split(" ");
344 String[] keys2 = s2.split(" ");
345 int[] c1 = new int[4];
346 int[] c2 = new int[4];
347
348 for (int i = 0; i < 4; i++) {
349 c1[i] = parseInt(keys1[i]);
350 c2[i] = parseInt(keys2[i]);
351 }
352
353 if (c1[1] < c2[1]) {
354 return -1;
355 } else if (c1[1] > c2[1]) {
356 return 1;
357 } else if (c1[2] < c2[2]) {
358 return -1;
359 } else if (c1[2] > c2[2]) {
360 return 1;
361 } else if (c1[0] < c2[0]) {
362 return -1;
363 } else if (c1[0] > c2[0]) {
364 return 1;
365 } else if (c1[3] < c2[3]) {
366 return -1;
367 } else if (c1[3] > c2[3]) {
368 return 1;
369 }
370
371 return 0;
372 }
373
374
375
376
377
378
379
380
381 @Override
382 public boolean equals(Object o) {
383 if (this == o) {
384 return true;
385 }
386 if (o == null || getClass() != o.getClass()) {
387 return false;
388 }
389 TorsionType torsionType = (TorsionType) o;
390 return Arrays.equals(atomClasses, torsionType.atomClasses);
391 }
392
393
394
395
396
397
398
399
400 @Override
401 public int hashCode() {
402 return Arrays.hashCode(atomClasses);
403 }
404
405
406
407
408
409
410 public void incrementClasses(int increment) {
411 for (int i = 0; i < atomClasses.length; i++) {
412 if (atomClasses[i] != 0) {
413 atomClasses[i] += increment;
414 }
415 }
416 setKey(sortKey(atomClasses));
417 }
418
419
420
421
422
423
424
425 public TorsionType patchClasses(HashMap<AtomType, AtomType> typeMap) {
426 int count = 0;
427 int len = atomClasses.length;
428
429
430 for (AtomType newType : typeMap.keySet()) {
431 for (int atomClass : atomClasses) {
432 if (atomClass == newType.atomClass) {
433 count++;
434 }
435 }
436 }
437
438
439 if (count == 1 || count == 2 || count == 3) {
440 int[] newClasses = copyOf(atomClasses, len);
441 for (AtomType newType : typeMap.keySet()) {
442 for (int i = 0; i < len; i++) {
443 if (atomClasses[i] == newType.atomClass) {
444 AtomType knownType = typeMap.get(newType);
445 newClasses[i] = knownType.atomClass;
446 }
447 }
448 }
449 return new TorsionType(newClasses, amplitude, phase, periodicity);
450 }
451 return null;
452 }
453
454
455
456
457
458
459
460
461 @Override
462 public String toString() {
463 StringBuilder torsionBuffer;
464 if (torsionMode == TorsionMode.IMPROPER) {
465 torsionBuffer = new StringBuilder("improper");
466 } else {
467 torsionBuffer = new StringBuilder("torsion");
468 }
469 for (int i : atomClasses) {
470 torsionBuffer.append(format(" %5d", i));
471 }
472
473 boolean nonZero = false;
474 for (double v : amplitude) {
475 if (v != 0.0) {
476 nonZero = true;
477 break;
478 }
479 }
480
481 for (int i = 0; i < amplitude.length; i++) {
482 if (amplitude[i] == 0.0 && (i > 0 || nonZero)) {
483 continue;
484 }
485 if (torsionMode == TorsionMode.NORMAL) {
486 torsionBuffer.append(format(" %7.3f %7.3f %1d", amplitude[i], phase[i], periodicity[i]));
487 } else {
488 torsionBuffer.append(format(" %7.3f %7.3f", amplitude[i], phase[i]));
489 }
490 }
491 return torsionBuffer.toString();
492 }
493
494
495
496
497
498
499
500
501
502 public static Element getXMLForce(Document doc, ForceField forceField) {
503 Map<String, TorsionType> types = forceField.getTorsionTypes();
504 if (!types.values().isEmpty()) {
505 Element node = doc.createElement("PeriodicTorsionForce");
506 double torsionUnit = forceField.getDouble("torsionunit", DEFAULT_TORSION_UNIT);
507 for (TorsionType torsionType : types.values()) {
508 node.appendChild(torsionType.toXML(doc, torsionUnit));
509 }
510 return node;
511 }
512 return null;
513 }
514
515
516
517
518
519
520
521
522 public Element toXML(Document doc, Double torsUnit) {
523 Element node = doc.createElement("Proper");
524
525 node.setAttribute("class1", format("%d", atomClasses[0]));
526 node.setAttribute("class2", format("%d", atomClasses[1]));
527 node.setAttribute("class3", format("%d", atomClasses[2]));
528 node.setAttribute("class4", format("%d", atomClasses[3]));
529 for (int i = 0; i < amplitude.length; i++) {
530
531 node.setAttribute(format("k%d", i + 1), format("%f", amplitude[i] * KCAL_TO_KJ * torsUnit));
532
533 node.setAttribute(format("phase%d", i + 1), format("%f", phase[i] / DEGREES_PER_RADIAN));
534 node.setAttribute(format("periodicity%d", i + 1), format("%d", periodicity[i]));
535 }
536 return node;
537 }
538
539
540
541
542 public enum TorsionMode {
543 NORMAL, IMPROPER
544 }
545 }