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.ANGLE;
52 import static ffx.potential.parameters.ForceField.ForceFieldType.ANGLEP;
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.LocalGeometryFunctionalForm;
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.lang.String.valueOf;
62 import static java.lang.System.arraycopy;
63 import static org.apache.commons.math3.util.FastMath.PI;
64 import static org.apache.commons.math3.util.FastMath.pow;
65
66
67
68
69
70
71
72 @FFXProperty(name = "angle", clazz = String.class, propertyGroup = PotentialFunctionParameter, description = """
73 [3 integers and 4 reals]
74 Provides the values for a single bond angle bending parameter.
75 The integer modifiers give the atom class numbers for the three kinds of atoms involved in the angle which is to be defined.
76 The real number modifiers give the force constant value for the angle and up to three ideal bond angles in degrees.
77 In most cases only one ideal bond angle is given, and that value is used for all occurrences of the specified bond angle.
78 If all three ideal angles are given, the values apply when the central atom of the angle is attached to 0, 1 or 2 additional hydrogen atoms, respectively.
79 This "hydrogen environment" option is provided to implement the corresponding feature of the AMOEBA force field.
80 The default units for the force constant are kcal/mole/radian^2, but this can be controlled via the angleunit keyword.
81 """)
82 @FFXProperty(name = "anglep", clazz = String.class, propertyGroup = PotentialFunctionParameter, description = """
83 [3 integers and 3 reals]
84 Provides the values for a single projected in-plane bond angle bending parameter.
85 The integer modifiers give the atom class numbers for the three kinds of atoms involved in the angle which is to be defined.
86 The real number modifiers give the force constant value for the angle and up to two ideal bond angles in degrees.
87 In most cases only one ideal bond angle is given, and that value is used for all occurrences of the specified bond angle.
88 If all two ideal angles are given, the values apply when the central atom of the angle is attached to 0 or 1 additional hydrogen atoms, respectively.
89 This "hydrogen environment" option is provided to implement the corresponding feature of the AMOEBA force field.
90 The default units for the force constant are kcal/mole/radian^2, but this can be controlled via the angleunit keyword.
91 """)
92 public final class AngleType extends BaseType implements Comparator<String> {
93
94
95
96
97 public static final double DEFAULT_ANGLE_UNIT = pow(PI / 180.0, 2.0);
98
99
100
101 public static final double DEFAULT_ANGLE_CUBIC = 0.0;
102
103
104
105 public static final double DEFAULT_ANGLE_QUARTIC = 0.0;
106
107
108
109 public static final double DEFAULT_ANGLE_PENTIC = 0.0;
110
111
112
113 public static final double DEFAULT_ANGLE_SEXTIC = 0.0;
114
115
116
117
118 @FFXProperty(name = "angleunit", propertyGroup = EnergyUnitConversion, defaultValue = "(Pi/180)^2", description = """
119 Sets the scale factor needed to convert the energy value computed by the bond angle bending potential into units of kcal/mole.
120 The correct value is force field dependent and typically provided in the header of the master force field parameter file.
121 """)
122 public double angleUnit = DEFAULT_ANGLE_UNIT;
123
124
125
126
127 @FFXProperty(name = "angle-cubic", propertyGroup = LocalGeometryFunctionalForm, defaultValue = "0.0", description = """
128 Sets the value of the cubic term in the Taylor series expansion form of the bond angle bending potential energy.
129 The real number modifier gives the value of the coefficient as a multiple of the quadratic coefficient.
130 This term multiplied by the angle bending energy unit conversion factor, the force constant,
131 and the cube of the deviation of the bond angle from its ideal value gives the cubic contribution to the angle bending energy.
132 The default value in the absence of the angle-cubic keyword is zero; i.e., the cubic angle bending term is omitted.
133 """)
134 public double cubic = DEFAULT_ANGLE_CUBIC;
135
136
137
138
139 @FFXProperty(name = "angle-quartic", propertyGroup = LocalGeometryFunctionalForm, defaultValue = "0.0", description = """
140 Sets the value of the quartic term in the Taylor series expansion form of the bond angle bending potential energy.
141 The real number modifier gives the value of the coefficient as a multiple of the quadratic coefficient.
142 This term multiplied by the angle bending energy unit conversion factor, the force constant,
143 and the forth power of the deviation of the bond angle from its ideal value gives the quartic contribution to the angle bending energy.
144 The default value in the absence of the angle-quartic keyword is zero; i.e., the quartic angle bending term is omitted.
145 """)
146 public double quartic = DEFAULT_ANGLE_QUARTIC;
147
148
149
150
151 @FFXProperty(name = "angle-pentic", propertyGroup = LocalGeometryFunctionalForm, defaultValue = "0.0", description = """
152 Sets the value of the fifth power term in the Taylor series expansion form of the bond angle bending potential energy.
153 The real number modifier gives the value of the coefficient as a multiple of the quadratic coefficient.
154 This term multiplied by the angle bending energy unit conversion factor, the force constant,
155 and the fifth power of the deviation of the bond angle from its ideal value gives the pentic contribution to the angle bending energy.
156 The default value in the absence of the angle-pentic keyword is zero; i.e., the pentic angle bending term is omitted.
157 """)
158 public double pentic = DEFAULT_ANGLE_PENTIC;
159
160
161
162
163 @FFXProperty(name = "angle-sextic", propertyGroup = LocalGeometryFunctionalForm, defaultValue = "0.0", description = """
164 Sets the value of the sixth power term in the Taylor series expansion form of the bond angle bending potential energy.
165 The real number modifier gives the value of the coefficient as a multiple of the quadratic coefficient.
166 This term multiplied by the angle bending energy unit conversion factor, the force constant,
167 and the sixth power of the deviation of the bond angle from its ideal value gives the sextic contribution to the angle bending energy.
168 The default value in the absence of the angle-sextic keyword is zero; i.e., the sextic angle bending term is omitted.
169 """)
170 public double sextic = DEFAULT_ANGLE_SEXTIC;
171
172
173
174
175 private static final Logger logger = Logger.getLogger(AngleType.class.getName());
176
177
178
179 public final int[] atomClasses;
180
181
182
183 public final double forceConstant;
184
185
186
187
188 public final double[] angle;
189
190
191
192 public final AngleMode angleMode;
193
194
195
196 public AngleFunction angleFunction;
197
198
199
200
201
202
203
204
205 public AngleType(int[] atomClasses, double forceConstant, double[] angle) {
206 this(atomClasses, forceConstant, angle, AngleMode.NORMAL);
207 }
208
209
210
211
212
213
214
215
216
217 public AngleType(int[] atomClasses, double forceConstant, double[] angle, AngleMode angleMode) {
218 this(atomClasses, forceConstant, angle, angleMode, AngleFunction.SEXTIC);
219 }
220
221
222
223
224
225
226
227
228
229
230 public AngleType(int[] atomClasses, double forceConstant, double[] angle, AngleMode angleMode,
231 AngleFunction angleFunction) {
232 super(ANGLE, sortKey(atomClasses));
233 this.atomClasses = atomClasses;
234 this.forceConstant = forceConstant;
235 this.angle = angle;
236 this.angleMode = angleMode;
237 this.angleFunction = angleFunction;
238 if (angleMode == AngleMode.IN_PLANE) {
239 forceFieldType = ANGLEP;
240 }
241 }
242
243
244
245
246
247
248
249
250
251 public static AngleType average(AngleType angleType1, AngleType angleType2, int[] atomClasses) {
252 if (angleType1 == null || angleType2 == null || atomClasses == null) {
253 return null;
254 }
255 AngleMode angleMode = angleType1.angleMode;
256 if (angleMode != angleType2.angleMode) {
257 return null;
258 }
259 AngleFunction angleFunction = angleType1.angleFunction;
260 if (angleFunction != angleType2.angleFunction) {
261 return null;
262 }
263 int len = angleType1.angle.length;
264 if (len != angleType2.angle.length) {
265 return null;
266 }
267 double forceConstant = (angleType1.forceConstant + angleType2.forceConstant) / 2.0;
268 double[] angle = new double[len];
269 for (int i = 0; i < len; i++) {
270 angle[i] = (angleType1.angle[i] + angleType2.angle[i]) / 2.0;
271 }
272
273 return new AngleType(atomClasses, forceConstant, angle, angleMode, angleFunction);
274 }
275
276
277
278
279
280
281
282
283 public static AngleType parse(String input, String[] tokens) {
284 if (tokens.length < 6) {
285 logger.log(Level.WARNING, "Invalid ANGLE type:\n{0}", input);
286 } else {
287 int[] atomClasses = new int[3];
288 double forceConstant;
289 int angles;
290 double[] bondAngle;
291 try {
292 atomClasses[0] = parseInt(tokens[1]);
293 atomClasses[1] = parseInt(tokens[2]);
294 atomClasses[2] = parseInt(tokens[3]);
295 forceConstant = parseDouble(tokens[4]);
296 angles = tokens.length - 5;
297 bondAngle = new double[angles];
298 for (int i = 0; i < angles; i++) {
299 bondAngle[i] = parseDouble(tokens[5 + i]);
300 }
301 } catch (NumberFormatException e) {
302 String message = "Exception parsing ANGLE type:\n" + input + "\n";
303 logger.log(Level.SEVERE, message, e);
304 return null;
305 }
306 double[] newBondAngle = new double[angles];
307 arraycopy(bondAngle, 0, newBondAngle, 0, angles);
308 return new AngleType(atomClasses, forceConstant, newBondAngle);
309 }
310 return null;
311 }
312
313
314
315
316
317
318
319
320 public static AngleType parseInPlane(String input, String[] tokens) {
321 if (tokens.length < 6) {
322 logger.log(Level.WARNING, "Invalid ANGLEP type:\n{0}", input);
323 } else {
324 int[] atomClasses = new int[3];
325 double forceConstant;
326 int angles;
327 double[] bondAngle;
328 try {
329 atomClasses[0] = parseInt(tokens[1]);
330 atomClasses[1] = parseInt(tokens[2]);
331 atomClasses[2] = parseInt(tokens[3]);
332 forceConstant = parseDouble(tokens[4]);
333 angles = tokens.length - 5;
334 bondAngle = new double[angles];
335 for (int i = 0; i < angles; i++) {
336 bondAngle[i] = parseDouble(tokens[5 + i]);
337 }
338 } catch (NumberFormatException e) {
339 String message = "Exception parsing ANGLEP type:\n" + input + "\n";
340 logger.log(Level.SEVERE, message, e);
341 return null;
342 }
343 double[] newBondAngle = new double[angles];
344 arraycopy(bondAngle, 0, newBondAngle, 0, angles);
345 return new AngleType(atomClasses, forceConstant, newBondAngle, AngleMode.IN_PLANE);
346 }
347 return null;
348 }
349
350
351
352
353
354
355
356 public static String sortKey(int[] c) {
357 if (c == null || c.length != 3) {
358 return null;
359 }
360 if (c[0] > c[2]) {
361 int temp = c[0];
362 c[0] = c[2];
363 c[2] = temp;
364 }
365 return c[0] + " " + c[1] + " " + c[2];
366 }
367
368
369
370
371 @Override
372 public int compare(String key1, String key2) {
373 String[] keys1 = key1.split(" ");
374 String[] keys2 = key2.split(" ");
375 int[] c1 = new int[3];
376 int[] c2 = new int[3];
377 for (int i = 0; i < 3; i++) {
378 c1[i] = Integer.parseInt(keys1[i]);
379 c2[i] = Integer.parseInt(keys2[i]);
380 }
381
382 if (c1[1] < c2[1]) {
383 return -1;
384 } else if (c1[1] > c2[1]) {
385 return 1;
386 } else if (c1[0] < c2[0]) {
387 return -1;
388 } else if (c1[0] > c2[0]) {
389 return 1;
390 } else if (c1[2] < c2[2]) {
391 return -1;
392 } else if (c1[2] > c2[2]) {
393 return 1;
394 }
395
396 return 0;
397 }
398
399
400
401
402 @Override
403 public boolean equals(Object o) {
404 if (this == o) {
405 return true;
406 }
407 if (o == null || getClass() != o.getClass()) {
408 return false;
409 }
410 AngleType angleType = (AngleType) o;
411 return Arrays.equals(atomClasses, angleType.atomClasses);
412 }
413
414
415
416
417 @Override
418 public int hashCode() {
419 return Arrays.hashCode(atomClasses);
420 }
421
422
423
424
425
426
427 public void incrementClasses(int increment) {
428 for (int i = 0; i < atomClasses.length; i++) {
429 atomClasses[i] += increment;
430 }
431 setKey(sortKey(atomClasses));
432 }
433
434
435
436
437
438
439
440 public AngleType patchClasses(HashMap<AtomType, AtomType> typeMap) {
441 int count = 0;
442 int len = atomClasses.length;
443
444
445 for (AtomType newType : typeMap.keySet()) {
446 for (int atomClass : atomClasses) {
447 if (atomClass == newType.atomClass) {
448 count++;
449 }
450 }
451 }
452
453
454 if (count == 1 || count == 2) {
455 int[] newClasses = Arrays.copyOf(atomClasses, len);
456 for (AtomType newType : typeMap.keySet()) {
457 for (int i = 0; i < len; i++) {
458 if (atomClasses[i] == newType.atomClass) {
459 AtomType knownType = typeMap.get(newType);
460 newClasses[i] = knownType.atomClass;
461 }
462 }
463 }
464 return new AngleType(newClasses, forceConstant, angle, angleMode, angleFunction);
465 }
466
467 return null;
468 }
469
470
471
472
473
474
475 public void setAngleFunction(AngleFunction angleFunction) {
476 this.angleFunction = angleFunction;
477 }
478
479
480
481
482
483
484 @Override
485 public String toString() {
486 StringBuilder angleString;
487 if (angleMode == AngleMode.NORMAL) {
488 angleString = new StringBuilder(
489 format("angle %5d %5d %5d %6.2f", atomClasses[0], atomClasses[1], atomClasses[2],
490 forceConstant));
491 } else {
492 angleString = new StringBuilder(
493 format("anglep %5d %5d %5d %6.2f", atomClasses[0], atomClasses[1], atomClasses[2],
494 forceConstant));
495 }
496 for (double eq : angle) {
497 angleString.append(format(" %6.2f", eq));
498 }
499 return angleString.toString();
500 }
501
502
503
504
505
506
507
508
509 public static Element getXMLForce(Document doc, ForceField forceField) {
510 Map<String, AngleType> angMap = forceField.getAngleTypes();
511 angMap.putAll(forceField.getAnglepTypes());
512 if (!angMap.values().isEmpty()) {
513 Element node = doc.createElement("AmoebaAngleForce");
514 node.setAttribute("angle-cubic", valueOf(forceField.getDouble("angle-cubic", DEFAULT_ANGLE_CUBIC)));
515 node.setAttribute("angle-quartic", valueOf(forceField.getDouble("angle-quartic", DEFAULT_ANGLE_QUARTIC)));
516 node.setAttribute("angle-pentic", valueOf(forceField.getDouble("angle-pentic", DEFAULT_ANGLE_PENTIC)));
517 node.setAttribute("angle-sextic", valueOf(forceField.getDouble("angle-sextic", DEFAULT_ANGLE_SEXTIC)));
518 for (AngleType angleType : angMap.values()) {
519 node.appendChild(angleType.toXML(doc));
520 }
521 return node;
522 }
523 return null;
524 }
525
526
527
528
529
530
531
532 public Element toXML(Document doc) {
533 Element node = doc.createElement("Angle");
534 node.setAttribute("class1", format("%d", atomClasses[0]));
535 node.setAttribute("class2", format("%d", atomClasses[1]));
536 node.setAttribute("class3", format("%d", atomClasses[2]));
537
538 node.setAttribute("k", format("%f", forceConstant * KCAL_TO_KJ / (DEGREES_PER_RADIAN * DEGREES_PER_RADIAN)));
539 int i = 1;
540 for (double eq : angle) {
541 node.setAttribute(format("angle%d", i), format("%f", eq));
542 i++;
543 }
544 if (angleMode == AngleMode.NORMAL) {
545 node.setAttribute("inPlane", "False");
546 } else {
547 node.setAttribute("inPlane", "True");
548 }
549 return node;
550 }
551
552
553
554
555 public enum AngleFunction {
556 HARMONIC, SEXTIC
557 }
558
559
560
561
562 public enum AngleMode {
563 NORMAL, IN_PLANE
564 }
565 }