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.ANGTORS;
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
62
63
64
65
66
67
68 @FFXProperty(name = "angtors", clazz = String.class, propertyGroup = PotentialFunctionParameter, description = """
69 [4 integers and 6 reals]
70 Provides the values for a single bond angle bending-torsional angle parameter.
71 The integer modifiers give the atom class numbers for the four kinds of atoms involved in the torsion and its contained angles.
72 The real number modifiers give the force constant values for both angles coupled with 1-, 2- and 3-fold torsional terms.
73 The default units for the force constants are kcal/mole/radian, but this can be controlled via the angtorunit keyword.
74 """)
75 public final class AngleTorsionType extends BaseType implements Comparator<String> {
76
77 public static final double DEFAULT_ANGTOR_UNIT = 1.0 / DEGREES_PER_RADIAN;
78
79
80
81
82 @FFXProperty(name = "angtorunit", propertyGroup = EnergyUnitConversion, defaultValue = "Pi/180", description = """
83 Sets the scale factor needed to convert the energy value computed by the angle bending-torsional angle
84 cross term into units of kcal/mole. The correct value is force field dependent and typically provided in the
85 header of the master force field parameter file.
86 """)
87 public double angtorunit = DEFAULT_ANGTOR_UNIT;
88
89
90
91
92 private static final Logger logger = Logger.getLogger(AngleTorsionType.class.getName());
93
94
95
96 public final int[] atomClasses;
97
98
99
100 public final double[] forceConstants;
101
102
103
104
105
106
107
108 public AngleTorsionType(int[] atomClasses, double[] forceConstants) {
109 super(ANGTORS, sortKey(atomClasses));
110 this.atomClasses = atomClasses;
111 this.forceConstants = forceConstants;
112 }
113
114
115
116
117
118
119
120
121
122 public static AngleTorsionType average(@Nullable AngleTorsionType angleTorsionType1,
123 @Nullable AngleTorsionType angleTorsionType2,
124 @Nullable int[] atomClasses) {
125 if (angleTorsionType1 == null || angleTorsionType2 == null || atomClasses == null) {
126 return null;
127 }
128 int len = angleTorsionType1.forceConstants.length;
129 if (len != angleTorsionType2.forceConstants.length) {
130 return null;
131 }
132 double[] forceConstants = new double[len];
133 for (int i = 0; i < len; i++) {
134 forceConstants[i] =
135 (angleTorsionType1.forceConstants[i] + angleTorsionType2.forceConstants[i]) / 2.0;
136 }
137 return new AngleTorsionType(atomClasses, forceConstants);
138 }
139
140
141
142
143
144
145
146
147 public static AngleTorsionType parse(String input, String[] tokens) {
148 if (tokens.length < 10) {
149 logger.log(Level.WARNING, "Invalid ANGTORS type:\n{0}", input);
150 } else {
151 try {
152 int[] atomClasses = new int[4];
153 atomClasses[0] = parseInt(tokens[1]);
154 atomClasses[1] = parseInt(tokens[2]);
155 atomClasses[2] = parseInt(tokens[3]);
156 atomClasses[3] = parseInt(tokens[4]);
157 double[] constants = new double[6];
158 constants[0] = parseDouble(tokens[5]);
159 constants[1] = parseDouble(tokens[6]);
160 constants[2] = parseDouble(tokens[7]);
161 constants[3] = parseDouble(tokens[8]);
162 constants[4] = parseDouble(tokens[9]);
163 constants[5] = parseDouble(tokens[10]);
164 return new AngleTorsionType(atomClasses, constants);
165 } catch (NumberFormatException e) {
166 String message = "Exception parsing ANGTORS type:\n" + input + "\n";
167 logger.log(Level.SEVERE, message, e);
168 }
169 }
170 return null;
171 }
172
173
174
175
176
177
178
179
180 public static String sortKey(int[] c) {
181 return c[0] + " " + c[1] + " " + c[2] + " " + c[3];
182 }
183
184
185
186
187
188
189 @Override
190 public int compare(String s1, String s2) {
191 String[] keys1 = s1.split(" ");
192 String[] keys2 = s2.split(" ");
193 int[] c1 = new int[4];
194 int[] c2 = new int[4];
195
196 for (int i = 0; i < 4; i++) {
197 c1[i] = parseInt(keys1[i]);
198 c2[i] = parseInt(keys2[i]);
199 }
200
201 if (c1[1] < c2[1]) {
202 return -1;
203 } else if (c1[1] > c2[1]) {
204 return 1;
205 } else if (c1[2] < c2[2]) {
206 return -1;
207 } else if (c1[2] > c2[2]) {
208 return 1;
209 } else if (c1[0] < c2[0]) {
210 return -1;
211 } else if (c1[0] > c2[0]) {
212 return 1;
213 } else if (c1[3] < c2[3]) {
214 return -1;
215 } else if (c1[3] > c2[3]) {
216 return 1;
217 }
218
219 return 0;
220 }
221
222
223
224
225 @Override
226 public boolean equals(Object o) {
227 if (this == o) {
228 return true;
229 }
230 if (o == null || getClass() != o.getClass()) {
231 return false;
232 }
233 AngleTorsionType angleTorsionType = (AngleTorsionType) o;
234 return Arrays.equals(atomClasses, angleTorsionType.atomClasses);
235 }
236
237
238
239
240 @Override
241 public int hashCode() {
242 return Arrays.hashCode(atomClasses);
243 }
244
245
246
247
248
249
250 public void incrementClasses(int increment) {
251 for (int i = 0; i < atomClasses.length; i++) {
252 atomClasses[i] += increment;
253 }
254 setKey(sortKey(atomClasses));
255 }
256
257
258
259
260
261
262
263 public AngleTorsionType patchClasses(HashMap<AtomType, AtomType> typeMap) {
264 int count = 0;
265 int len = atomClasses.length;
266
267
268 for (AtomType newType : typeMap.keySet()) {
269
270 for (int atomClass : atomClasses) {
271 if (atomClass == newType.atomClass) {
272 count++;
273 }
274 }
275 }
276
277
278 if (count == 1 || count == 2) {
279 int[] newClasses = copyOf(atomClasses, len);
280 for (AtomType newType : typeMap.keySet()) {
281 for (int i = 0; i < len; i++) {
282 if (atomClasses[i] == newType.atomClass) {
283 AtomType knownType = typeMap.get(newType);
284 newClasses[i] = knownType.atomClass;
285 }
286 }
287 }
288 return new AngleTorsionType(newClasses, forceConstants);
289 }
290 return null;
291 }
292
293
294
295
296
297
298 @Override
299 public String toString() {
300 return format("angtors %5d %5d %5d %5d %6.3f %6.3f %6.3f %6.3f %6.3f %6.3f",
301 atomClasses[0], atomClasses[1], atomClasses[2], atomClasses[3], forceConstants[0],
302 forceConstants[1], forceConstants[2], forceConstants[3], forceConstants[4],
303 forceConstants[5]);
304 }
305
306
307
308
309
310
311
312
313 public static Element getXMLForce(Document doc, ForceField forceField) {
314 Map<String, AngleTorsionType> types = forceField.getAngleTorsionTypes();
315 if (!types.values().isEmpty()) {
316 Element node = doc.createElement("AmoebaAngleTorsionForce");
317 for (AngleTorsionType angleTorsionType : types.values()) {
318 node.appendChild(angleTorsionType.toXML(doc));
319 }
320 return node;
321 }
322 return null;
323 }
324
325
326
327
328
329
330
331 public Element toXML(Document doc) {
332 Element node = doc.createElement("Torsion");
333 node.setAttribute("class1", format("%d", atomClasses[0]));
334 node.setAttribute("class2", format("%d", atomClasses[1]));
335 node.setAttribute("class3", format("%d", atomClasses[2]));
336 node.setAttribute("class4", format("%d", atomClasses[3]));
337 node.setAttribute("v11", format("%.17f", forceConstants[0] * KCAL_TO_KJ));
338 node.setAttribute("v12", format("%.17f", forceConstants[1] * KCAL_TO_KJ));
339 node.setAttribute("v13", format("%.17f", forceConstants[2] * KCAL_TO_KJ));
340 node.setAttribute("v21", format("%.17f", forceConstants[3] * KCAL_TO_KJ));
341 node.setAttribute("v22", format("%.17f", forceConstants[4] * KCAL_TO_KJ));
342 node.setAttribute("v23", format("%.17f", forceConstants[5] * KCAL_TO_KJ));
343 return node;
344 }
345 }