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.STRBND;
52 import static ffx.utilities.Constants.ANG_TO_NM;
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.lang.String.valueOf;
61 import static java.util.Arrays.copyOf;
62 import static org.apache.commons.math3.util.FastMath.PI;
63
64
65
66
67
68
69
70 @FFXProperty(name = "strbnd", clazz = String.class, propertyGroup = PotentialFunctionParameter, description = """
71 [3 integers and 2 reals]
72 Provides the values for a single stretch-bend cross term potential parameter.
73 The integer modifiers give the atom class numbers for the three kinds of atoms involved in the angle which is to be defined.
74 The real number modifiers give the force constant values for the first bond (first two atom classes) with the angle, and the second bond with the angle, respectively.
75 The default units for the stretch-bend force constant are kcal/mole/Ang-radian, but this can be controlled via the strbndunit keyword.
76 """)
77 public final class StretchBendType extends BaseType implements Comparator<String> {
78
79
80
81
82 public static final double DEFAULT_STRBND_UNIT = PI / 180.0;
83
84 @FFXProperty(name = "strbndunit", propertyGroup = EnergyUnitConversion, defaultValue = "(Pi/180)", description = """
85 Sets the scale factor needed to convert the energy value computed by the bond stretching-angle bending cross
86 term potential into units of kcal/mole. The correct value is force field dependent and typically provided
87 in the header of the master force field parameter file.
88 """)
89 public double strbndunit = DEFAULT_STRBND_UNIT;
90
91 private static final Logger logger = Logger.getLogger(StretchBendType.class.getName());
92
93
94
95 public final int[] atomClasses;
96
97
98
99 public final double[] forceConstants;
100
101
102
103
104
105
106
107 public StretchBendType(int[] atomClasses, double[] forceConstants) {
108 super(STRBND, sortKey(copyOf(atomClasses, 3)));
109
110 if (atomClasses[0] > atomClasses[2]) {
111 int temp = atomClasses[0];
112 double f = forceConstants[0];
113 atomClasses[0] = atomClasses[2];
114 forceConstants[0] = forceConstants[1];
115 atomClasses[2] = temp;
116 forceConstants[1] = f;
117 }
118 this.atomClasses = atomClasses;
119 this.forceConstants = forceConstants;
120 }
121
122
123
124
125
126
127
128
129
130 public static StretchBendType average(StretchBendType stretchBendType1,
131 StretchBendType stretchBendType2, int[] atomClasses) {
132 if (stretchBendType1 == null || stretchBendType2 == null || atomClasses == null) {
133 return null;
134 }
135 int len = stretchBendType1.forceConstants.length;
136 if (len != stretchBendType2.forceConstants.length) {
137 return null;
138 }
139 double[] forceConstants = new double[len];
140 for (int i = 0; i < len; i++) {
141 forceConstants[i] =
142 (stretchBendType1.forceConstants[i] + stretchBendType2.forceConstants[i]) / 2.0;
143 }
144 return new StretchBendType(atomClasses, forceConstants);
145 }
146
147
148
149
150
151
152
153
154 public static StretchBendType parse(String input, String[] tokens) {
155 if (tokens.length < 6) {
156 logger.log(Level.WARNING, "Invalid STRBND type:\n{0}", input);
157 } else {
158 try {
159 int[] atomClasses = new int[3];
160 atomClasses[0] = parseInt(tokens[1]);
161 atomClasses[1] = parseInt(tokens[2]);
162 atomClasses[2] = parseInt(tokens[3]);
163 double[] forceConstants = new double[2];
164 forceConstants[0] = parseDouble(tokens[4]);
165 forceConstants[1] = parseDouble(tokens[5]);
166 return new StretchBendType(atomClasses, forceConstants);
167 } catch (NumberFormatException e) {
168 String message = "Exception parsing STRBND type:\n" + input + "\n";
169 logger.log(Level.SEVERE, message, e);
170 }
171 }
172 return null;
173 }
174
175
176
177
178
179
180
181 public static String sortKey(int[] c) {
182 if (c == null || c.length != 3) {
183 return null;
184 }
185 if (c[0] > c[2]) {
186 int temp = c[0];
187 c[0] = c[2];
188 c[2] = temp;
189 }
190 return c[0] + " " + c[1] + " " + c[2];
191 }
192
193
194
195
196 @Override
197 public int compare(String key1, String key2) {
198 String[] keys1 = key1.split(" ");
199 String[] keys2 = key2.split(" ");
200 int[] c1 = new int[3];
201 int[] c2 = new int[3];
202 for (int i = 0; i < 3; i++) {
203 c1[i] = parseInt(keys1[i]);
204 c2[i] = parseInt(keys2[i]);
205 }
206 if (c1[1] < c2[1]) {
207 return -1;
208 } else if (c1[1] > c2[1]) {
209 return 1;
210 } else if (c1[0] < c2[0]) {
211 return -1;
212 } else if (c1[0] > c2[0]) {
213 return 1;
214 } else if (c1[2] < c2[2]) {
215 return -1;
216 } else if (c1[2] > c2[2]) {
217 return 1;
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 StretchBendType stretchBendType = (StretchBendType) o;
234 return Arrays.equals(atomClasses, stretchBendType.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 StretchBendType patchClasses(HashMap<AtomType, AtomType> typeMap) {
264 int count = 0;
265 int len = atomClasses.length;
266
267
268 for (AtomType newType : typeMap.keySet()) {
269 for (int atomClass : atomClasses) {
270 if (atomClass == newType.atomClass) {
271 count++;
272 }
273 }
274 }
275
276
277 if (count == 1 || count == 2) {
278 int[] newClasses = Arrays.copyOf(atomClasses, len);
279 for (AtomType newType : typeMap.keySet()) {
280 for (int i = 0; i < len; i++) {
281 if (atomClasses[i] == newType.atomClass) {
282 AtomType knownType = typeMap.get(newType);
283 newClasses[i] = knownType.atomClass;
284 }
285 }
286 }
287
288 return new StretchBendType(newClasses, forceConstants);
289 }
290 return null;
291 }
292
293
294
295
296
297
298 @Override
299 public String toString() {
300 return String.format("strbnd %5d %5d %5d %6.2f %6.2f", atomClasses[0], atomClasses[1],
301 atomClasses[2], forceConstants[0], forceConstants[1]);
302 }
303
304
305
306
307
308
309
310
311 public static Element getXMLForce(Document doc, ForceField forceField) {
312 Map<String, StretchBendType> types = forceField.getStretchBendTypes();
313 if (!types.values().isEmpty()) {
314 Element node = doc.createElement("AmoebaStretchBendForce");
315 node.setAttribute("stretchBendUnit", valueOf(forceField.getDouble("strbndunit", DEFAULT_STRBND_UNIT) * DEGREES_PER_RADIAN));
316 for (StretchBendType stretchBendType : types.values()) {
317 node.appendChild(stretchBendType.toXML(doc));
318 }
319 return node;
320 }
321 return null;
322 }
323
324
325
326
327 public Element toXML(Document doc) {
328 Element node = doc.createElement("StretchBend");
329 node.setAttribute("class1", format("%d", atomClasses[0]));
330 node.setAttribute("class2", format("%d", atomClasses[1]));
331 node.setAttribute("class3", format("%d", atomClasses[2]));
332
333 node.setAttribute("k1", format("%f", forceConstants[0] * KCAL_TO_KJ / (ANG_TO_NM * DEGREES_PER_RADIAN)));
334 node.setAttribute("k2", format("%f", forceConstants[1] * KCAL_TO_KJ / (ANG_TO_NM * DEGREES_PER_RADIAN)));
335 return node;
336 }
337 }