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